Unverified Commit 2b62e666 authored by Richard Fitzgerald's avatar Richard Fitzgerald Committed by Mark Brown
Browse files

ASoC: cs-amp-lib: Add function to write calibration to UEFI



Add cs_amp_set_efi_calibration_data() to write an amp calibration
blob to UEFI calibration variable.

The UEFI variable will be updated or created as necessary.

- If a Vendor-specific variable exists it will be updated,
  else if the Cirrus variable exists it will be update
  else the Cirrus variable will be created.

Some collateral changes are required:

- cs_amp_convert_efi_status() now specifically handles
  EFI_WRITE_PROTECTED error.

- cs_amp_get_cal_efi_buffer() can optionally return the name,
  guid and attr of the variable it found.

- cs_amp_get_cal_efi_buffer() will update the 'size' field of
  the returned data blob if it is zero. The BIOS could have
  pre-allocated the UEFI variable as zero-filled

Signed-off-by: default avatarRichard Fitzgerald <rf@opensource.cirrus.com>
Reviewed-by: default avatarTakashi Iwai <tiwai@suse.de>
Link: https://patch.msgid.link/20251021105022.1013685-9-rf@opensource.cirrus.com


Signed-off-by: default avatarMark Brown <broonie@kernel.org>
parent 959400ca
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -55,6 +55,8 @@ int cs_amp_write_ambient_temp(struct cs_dsp *dsp,
			      u32 temp);
int cs_amp_get_efi_calibration_data(struct device *dev, u64 target_uid, int amp_index,
				    struct cirrus_amp_cal_data *out_data);
int cs_amp_set_efi_calibration_data(struct device *dev, int amp_index, int num_amps,
				    const struct cirrus_amp_cal_data *in_data);
int cs_amp_get_vendor_spkid(struct device *dev);
struct dentry *cs_amp_create_debugfs(struct device *dev);

+186 −4
Original line number Diff line number Diff line
@@ -13,6 +13,7 @@
#include <linux/firmware/cirrus/cs_dsp.h>
#include <linux/math64.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/overflow.h>
#include <linux/slab.h>
#include <linux/timekeeping.h>
@@ -49,9 +50,16 @@ static const struct cs_amp_lib_cal_efivar {
	},
};

#define CS_AMP_CAL_DEFAULT_EFI_ATTR			\
		(EFI_VARIABLE_NON_VOLATILE |		\
		 EFI_VARIABLE_BOOTSERVICE_ACCESS |	\
		 EFI_VARIABLE_RUNTIME_ACCESS)

/* Offset from Unix time to Windows time (100ns since 1 Jan 1601) */
#define UNIX_TIME_TO_WINDOWS_TIME_OFFSET	116444736000000000ULL

static DEFINE_MUTEX(cs_amp_efi_cal_write_lock);

static u64 cs_amp_time_now_in_windows_time(void)
{
	u64 time_in_100ns = div_u64(ktime_get_real_ns(), 100);
@@ -263,6 +271,20 @@ static efi_status_t cs_amp_get_efi_variable(efi_char16_t *name,
	return EFI_NOT_FOUND;
}

static efi_status_t cs_amp_set_efi_variable(efi_char16_t *name,
					    efi_guid_t *guid,
					    u32 attr,
					    unsigned long size,
					    void *buf)
{
	KUNIT_STATIC_STUB_REDIRECT(cs_amp_set_efi_variable, name, guid, attr, size, buf);

	if (!efi_rt_services_supported(EFI_RT_SUPPORTED_SET_VARIABLE))
		return EFI_NOT_FOUND;

	return efi.set_variable(name, guid, attr, size, buf);
}

static int cs_amp_convert_efi_status(efi_status_t status)
{
	switch (status) {
@@ -272,6 +294,7 @@ static int cs_amp_convert_efi_status(efi_status_t status)
		return -ENOENT;
	case EFI_BUFFER_TOO_SMALL:
		return -EFBIG;
	case EFI_WRITE_PROTECTED:
	case EFI_UNSUPPORTED:
	case EFI_ACCESS_DENIED:
	case EFI_SECURITY_VIOLATION:
@@ -281,7 +304,10 @@ static int cs_amp_convert_efi_status(efi_status_t status)
	}
}

static struct cirrus_amp_efi_data *cs_amp_get_cal_efi_buffer(struct device *dev)
static struct cirrus_amp_efi_data *cs_amp_get_cal_efi_buffer(struct device *dev,
							     efi_char16_t **name,
							     efi_guid_t **guid,
							     u32 *attr)
{
	struct cirrus_amp_efi_data *efi_data;
	unsigned long data_size = 0;
@@ -293,7 +319,7 @@ static struct cirrus_amp_efi_data *cs_amp_get_cal_efi_buffer(struct device *dev)
	for (i = 0; i < ARRAY_SIZE(cs_amp_lib_cal_efivars); i++) {
		status = cs_amp_get_efi_variable(cs_amp_lib_cal_efivars[i].name,
						 cs_amp_lib_cal_efivars[i].guid,
						 NULL, &data_size, NULL);
						 attr, &data_size, NULL);
		if (status == EFI_BUFFER_TOO_SMALL)
			break;
	}
@@ -301,6 +327,12 @@ static struct cirrus_amp_efi_data *cs_amp_get_cal_efi_buffer(struct device *dev)
	if (status != EFI_BUFFER_TOO_SMALL)
		return ERR_PTR(-ENOENT);

	if (name)
		*name = cs_amp_lib_cal_efivars[i].name;

	if (guid)
		*guid = cs_amp_lib_cal_efivars[i].guid;

	if (data_size < sizeof(*efi_data)) {
		dev_err(dev, "EFI cal variable truncated\n");
		return ERR_PTR(-EOVERFLOW);
@@ -313,7 +345,7 @@ static struct cirrus_amp_efi_data *cs_amp_get_cal_efi_buffer(struct device *dev)

	status = cs_amp_get_efi_variable(cs_amp_lib_cal_efivars[i].name,
					 cs_amp_lib_cal_efivars[i].guid,
					 NULL, &data_size, data);
					 attr, &data_size, data);
	if (status != EFI_SUCCESS) {
		ret = -EINVAL;
		goto err;
@@ -329,6 +361,10 @@ static struct cirrus_amp_efi_data *cs_amp_get_cal_efi_buffer(struct device *dev)
		goto err;
	}

	/* This could be zero-filled space pre-allocated by the BIOS */
	if (efi_data->size == 0)
		efi_data->size = data_size;

	return efi_data;

err:
@@ -338,6 +374,20 @@ static struct cirrus_amp_efi_data *cs_amp_get_cal_efi_buffer(struct device *dev)
	return ERR_PTR(ret);
}

static int cs_amp_set_cal_efi_buffer(struct device *dev,
				     efi_char16_t *name,
				     efi_guid_t *guid,
				     u32 attr,
				     struct cirrus_amp_efi_data *data)
{
	efi_status_t status;

	status = cs_amp_set_efi_variable(name, guid, attr,
					 struct_size(data, data, data->count), data);

	return cs_amp_convert_efi_status(status);
}

static int _cs_amp_get_efi_calibration_data(struct device *dev, u64 target_uid, int amp_index,
					    struct cirrus_amp_cal_data *out_data)
{
@@ -345,7 +395,7 @@ static int _cs_amp_get_efi_calibration_data(struct device *dev, u64 target_uid,
	struct cirrus_amp_cal_data *cal = NULL;
	int i, ret;

	efi_data = cs_amp_get_cal_efi_buffer(dev);
	efi_data = cs_amp_get_cal_efi_buffer(dev, NULL, NULL, NULL);
	if (IS_ERR(efi_data))
		return PTR_ERR(efi_data);

@@ -397,6 +447,98 @@ static int _cs_amp_get_efi_calibration_data(struct device *dev, u64 target_uid,
	return ret;
}

static int _cs_amp_set_efi_calibration_data(struct device *dev, int amp_index, int num_amps,
					    const struct cirrus_amp_cal_data *in_data)
{
	u64 cal_target = cs_amp_cal_target_u64(in_data);
	unsigned long num_entries;
	struct cirrus_amp_efi_data *data __free(kfree) = NULL;
	efi_char16_t *name = CIRRUS_LOGIC_CALIBRATION_EFI_NAME;
	efi_guid_t *guid = &CIRRUS_LOGIC_CALIBRATION_EFI_GUID;
	u32 attr = CS_AMP_CAL_DEFAULT_EFI_ATTR;
	int i, ret;

	if (cal_target == 0)
		return -EINVAL;

	data = cs_amp_get_cal_efi_buffer(dev, &name, &guid, &attr);
	ret = PTR_ERR_OR_ZERO(data);
	if (ret == -ENOENT) {
		data = NULL;
		goto alloc_new;
	} else if (ret) {
		return ret;
	}

	/*
	 * If the EFI variable is just zero-filled reserved space the count
	 * must be set.
	 */
	if (data->count == 0)
		data->count = (data->size - sizeof(data)) / sizeof(data->data[0]);

	if (amp_index < 0) {
		/* Is there already a slot for this target? */
		for (amp_index = 0; amp_index < data->count; amp_index++) {
			if (cs_amp_cal_target_u64(&data->data[amp_index]) == cal_target)
				break;
		}

		/* Else find an empty slot */
		if (amp_index >= data->count) {
			for (amp_index = 0; amp_index < data->count; amp_index++) {
				if ((data->data[amp_index].calTime[0] == 0) &&
				    (data->data[amp_index].calTime[1] == 0))
					break;
			}
		}
	} else {
		/*
		 * If the index is forced there could be another active
		 * slot with the same calTarget. So deduplicate.
		 */
		for (i = 0; i < data->count; i++) {
			if (i == amp_index)
				continue;

			if ((data->data[i].calTime[0] == 0) && (data->data[i].calTime[1] == 0))
				continue;

			if (cs_amp_cal_target_u64(&data->data[i]) == cal_target)
				memset(data->data[i].calTime, 0, sizeof(data->data[i].calTime));
		}
	}

alloc_new:
	if (amp_index < 0)
		amp_index = 0;

	num_entries = max(num_amps, amp_index + 1);
	if (!data || (data->count < num_entries)) {
		struct cirrus_amp_efi_data *old_data __free(kfree) = no_free_ptr(data);
		unsigned int new_data_size = struct_size(data, data, num_entries);

		data = kzalloc(new_data_size, GFP_KERNEL);
		if (!data)
			return -ENOMEM;

		if (old_data)
			memcpy(data, old_data, struct_size(old_data, data, old_data->count));

		data->count = num_entries;
		data->size = new_data_size;
	}

	data->data[amp_index] = *in_data;
	ret = cs_amp_set_cal_efi_buffer(dev, name, guid, attr, data);
	if (ret) {
		dev_err(dev, "Failed writing calibration to EFI: %d\n", ret);
		return ret;
	}

	return 0;
}

/**
 * cs_amp_get_efi_calibration_data - get an entry from calibration data in EFI.
 * @dev:	struct device of the caller.
@@ -443,6 +585,46 @@ int cs_amp_get_efi_calibration_data(struct device *dev, u64 target_uid, int amp_
}
EXPORT_SYMBOL_NS_GPL(cs_amp_get_efi_calibration_data, "SND_SOC_CS_AMP_LIB");

/**
 * cs_amp_set_efi_calibration_data - write a calibration data entry to EFI.
 * @dev:	struct device of the caller.
 * @amp_index:	Entry index to use, or -1 to use any available slot.
 * @num_amps:	Maximum number of amps to reserve slots for, or -1 to ignore.
 * @in_data:	struct cirrus_amp_cal_data entry to be written to EFI.
 *
 * If a Vendor-specific variable exists it will be updated,
 * else if the Cirrus variable exists it will be updated
 * else the Cirrus variable will be created.
 *
 * If amp_index >= 0 the data will be placed in this entry of the calibration
 * data array, overwriting what was in that entry. Any other entries with the
 * same calTarget will be marked empty.
 *
 * If amp_index < 0 and in_data->calTarget matches any existing entry, that
 * entry will be overwritten. Else the first available free entry will be used,
 * extending the size of the EFI variable if there are no free entries.
 *
 * If num_amps > 0 the EFI variable will be sized to contain at least this
 * many calibration entries, with any new entries marked empty.
 *
 * Return: 0 if the write was successful, -EFBIG if space could not be made in
 *	   the EFI file to add the entry, -EACCES if it was not possible to
 *	   read or write the EFI variable.
 */
int cs_amp_set_efi_calibration_data(struct device *dev, int amp_index, int num_amps,
				    const struct cirrus_amp_cal_data *in_data)
{
	if (IS_ENABLED(CONFIG_EFI) || IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST)) {
		scoped_guard(mutex, &cs_amp_efi_cal_write_lock) {
			return _cs_amp_set_efi_calibration_data(dev, amp_index,
								num_amps, in_data);
		}
	}

	return -ENOENT;
}
EXPORT_SYMBOL_NS_GPL(cs_amp_set_efi_calibration_data, "SND_SOC_CS_AMP_LIB");

struct cs_amp_spkid_efi {
	efi_char16_t *name;
	efi_guid_t *guid;