// SPDX-License-Identifier: GPL-2.0
/*
 * Functions corresponding to password object type attributes under BIOS Password Object GUID for
 * use with dell-wmi-sysman
 *
 *  Copyright (c) 2020 Dell Inc.
 */

#include "dell-wmi-sysman.h"

enum po_properties {IS_PASS_SET = 1, MIN_PASS_LEN, MAX_PASS_LEN};

get_instance_id(po);

static ssize_t is_enabled_show(struct kobject *kobj, struct kobj_attribute *attr,
					  char *buf)
{
	int instance_id = get_po_instance_id(kobj);
	union acpi_object *obj;
	ssize_t ret;

	if (instance_id < 0)
		return instance_id;

	/* need to use specific instance_id and guid combination to get right data */
	obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID);
	if (!obj)
		return -EIO;
	if (obj->package.elements[IS_PASS_SET].type != ACPI_TYPE_INTEGER) {
		kfree(obj);
		return -EINVAL;
	}
	ret = snprintf(buf, PAGE_SIZE, "%lld\n", obj->package.elements[IS_PASS_SET].integer.value);
	kfree(obj);
	return ret;
}

static struct kobj_attribute po_is_pass_set = __ATTR_RO(is_enabled);

static ssize_t current_password_store(struct kobject *kobj,
				      struct kobj_attribute *attr,
				      const char *buf, size_t count)
{
	char *target = NULL;
	int length;

	length = strlen(buf);
	if (buf[length-1] == '\n')
		length--;

	/* firmware does verifiation of min/max password length,
	 * hence only check for not exceeding MAX_BUFF here.
	 */
	if (length >= MAX_BUFF)
		return -EINVAL;

	if (strcmp(kobj->name, "Admin") == 0)
		target = wmi_priv.current_admin_password;
	else if (strcmp(kobj->name, "System") == 0)
		target = wmi_priv.current_system_password;
	if (!target)
		return -EIO;
	memcpy(target, buf, length);
	target[length] = '\0';

	return count;
}

static struct kobj_attribute po_current_password = __ATTR_WO(current_password);

static ssize_t new_password_store(struct kobject *kobj,
				  struct kobj_attribute *attr,
				  const char *buf, size_t count)
{
	char *p, *buf_cp;
	int ret;

	buf_cp = kstrdup(buf, GFP_KERNEL);
	if (!buf_cp)
		return -ENOMEM;
	p = memchr(buf_cp, '\n', count);

	if (p != NULL)
		*p = '\0';
	if (strlen(buf_cp) > MAX_BUFF) {
		ret = -EINVAL;
		goto out;
	}

	ret = set_new_password(kobj->name, buf_cp);

out:
	kfree(buf_cp);
	return ret ? ret : count;
}

static struct kobj_attribute po_new_password = __ATTR_WO(new_password);

attribute_n_property_show(min_password_length, po);
static struct kobj_attribute po_min_pass_length = __ATTR_RO(min_password_length);

attribute_n_property_show(max_password_length, po);
static struct kobj_attribute po_max_pass_length = __ATTR_RO(max_password_length);

static ssize_t mechanism_show(struct kobject *kobj, struct kobj_attribute *attr,
			 char *buf)
{
	return sprintf(buf, "password\n");
}

static struct kobj_attribute po_mechanism = __ATTR_RO(mechanism);

static ssize_t role_show(struct kobject *kobj, struct kobj_attribute *attr,
			 char *buf)
{
	if (strcmp(kobj->name, "Admin") == 0)
		return sprintf(buf, "bios-admin\n");
	else if (strcmp(kobj->name, "System") == 0)
		return sprintf(buf, "power-on\n");
	return -EIO;
}

static struct kobj_attribute po_role = __ATTR_RO(role);

static struct attribute *po_attrs[] = {
	&po_is_pass_set.attr,
	&po_min_pass_length.attr,
	&po_max_pass_length.attr,
	&po_current_password.attr,
	&po_new_password.attr,
	&po_role.attr,
	&po_mechanism.attr,
	NULL,
};

static const struct attribute_group po_attr_group = {
	.attrs = po_attrs,
};

int alloc_po_data(void)
{
	int ret = 0;

	wmi_priv.po_instances_count = get_instance_count(DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID);
	wmi_priv.po_data = kcalloc(wmi_priv.po_instances_count, sizeof(struct po_data), GFP_KERNEL);
	if (!wmi_priv.po_data) {
		wmi_priv.po_instances_count = 0;
		ret = -ENOMEM;
	}
	return ret;
}

/**
 * populate_po_data() - Populate all properties of an instance under password object attribute
 * @po_obj: ACPI object with password object data
 * @instance_id: The instance to enumerate
 * @attr_name_kobj: The parent kernel object
 */
int populate_po_data(union acpi_object *po_obj, int instance_id, struct kobject *attr_name_kobj)
{
	wmi_priv.po_data[instance_id].attr_name_kobj = attr_name_kobj;
	if (check_property_type(po, ATTR_NAME, ACPI_TYPE_STRING))
		return -EINVAL;
	strlcpy_attr(wmi_priv.po_data[instance_id].attribute_name,
		     po_obj[ATTR_NAME].string.pointer);
	if (check_property_type(po, MIN_PASS_LEN, ACPI_TYPE_INTEGER))
		return -EINVAL;
	wmi_priv.po_data[instance_id].min_password_length =
		(uintptr_t)po_obj[MIN_PASS_LEN].string.pointer;
	if (check_property_type(po, MAX_PASS_LEN, ACPI_TYPE_INTEGER))
		return -EINVAL;
	wmi_priv.po_data[instance_id].max_password_length =
		(uintptr_t) po_obj[MAX_PASS_LEN].string.pointer;

	return sysfs_create_group(attr_name_kobj, &po_attr_group);
}

/**
 * exit_po_attributes() - Clear all attribute data
 *
 * Clears all data allocated for this group of attributes
 */
void exit_po_attributes(void)
{
	int instance_id;

	for (instance_id = 0; instance_id < wmi_priv.po_instances_count; instance_id++) {
		if (wmi_priv.po_data[instance_id].attr_name_kobj)
			sysfs_remove_group(wmi_priv.po_data[instance_id].attr_name_kobj,
								&po_attr_group);
	}
	wmi_priv.po_instances_count = 0;

	kfree(wmi_priv.po_data);
	wmi_priv.po_data = NULL;
}