Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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: Richard Fitzgerald <rf@opensource.cirrus.com>
Reviewed-by: Takashi Iwai <tiwai@suse.de>
Link: https://patch.msgid.link/20251021105022.1013685-9-rf@opensource.cirrus.com
Signed-off-by: Mark Brown <broonie@kernel.org>

authored by

Richard Fitzgerald and committed by
Mark Brown
2b62e666 959400ca

+188 -4
+2
include/sound/cs-amp-lib.h
··· 55 55 u32 temp); 56 56 int cs_amp_get_efi_calibration_data(struct device *dev, u64 target_uid, int amp_index, 57 57 struct cirrus_amp_cal_data *out_data); 58 + int cs_amp_set_efi_calibration_data(struct device *dev, int amp_index, int num_amps, 59 + const struct cirrus_amp_cal_data *in_data); 58 60 int cs_amp_get_vendor_spkid(struct device *dev); 59 61 struct dentry *cs_amp_create_debugfs(struct device *dev); 60 62
+186 -4
sound/soc/codecs/cs-amp-lib.c
··· 13 13 #include <linux/firmware/cirrus/cs_dsp.h> 14 14 #include <linux/math64.h> 15 15 #include <linux/module.h> 16 + #include <linux/mutex.h> 16 17 #include <linux/overflow.h> 17 18 #include <linux/slab.h> 18 19 #include <linux/timekeeping.h> ··· 50 49 }, 51 50 }; 52 51 52 + #define CS_AMP_CAL_DEFAULT_EFI_ATTR \ 53 + (EFI_VARIABLE_NON_VOLATILE | \ 54 + EFI_VARIABLE_BOOTSERVICE_ACCESS | \ 55 + EFI_VARIABLE_RUNTIME_ACCESS) 56 + 53 57 /* Offset from Unix time to Windows time (100ns since 1 Jan 1601) */ 54 58 #define UNIX_TIME_TO_WINDOWS_TIME_OFFSET 116444736000000000ULL 59 + 60 + static DEFINE_MUTEX(cs_amp_efi_cal_write_lock); 55 61 56 62 static u64 cs_amp_time_now_in_windows_time(void) 57 63 { ··· 271 263 return EFI_NOT_FOUND; 272 264 } 273 265 266 + static efi_status_t cs_amp_set_efi_variable(efi_char16_t *name, 267 + efi_guid_t *guid, 268 + u32 attr, 269 + unsigned long size, 270 + void *buf) 271 + { 272 + KUNIT_STATIC_STUB_REDIRECT(cs_amp_set_efi_variable, name, guid, attr, size, buf); 273 + 274 + if (!efi_rt_services_supported(EFI_RT_SUPPORTED_SET_VARIABLE)) 275 + return EFI_NOT_FOUND; 276 + 277 + return efi.set_variable(name, guid, attr, size, buf); 278 + } 279 + 274 280 static int cs_amp_convert_efi_status(efi_status_t status) 275 281 { 276 282 switch (status) { ··· 294 272 return -ENOENT; 295 273 case EFI_BUFFER_TOO_SMALL: 296 274 return -EFBIG; 275 + case EFI_WRITE_PROTECTED: 297 276 case EFI_UNSUPPORTED: 298 277 case EFI_ACCESS_DENIED: 299 278 case EFI_SECURITY_VIOLATION: ··· 304 281 } 305 282 } 306 283 307 - static struct cirrus_amp_efi_data *cs_amp_get_cal_efi_buffer(struct device *dev) 284 + static struct cirrus_amp_efi_data *cs_amp_get_cal_efi_buffer(struct device *dev, 285 + efi_char16_t **name, 286 + efi_guid_t **guid, 287 + u32 *attr) 308 288 { 309 289 struct cirrus_amp_efi_data *efi_data; 310 290 unsigned long data_size = 0; ··· 319 293 for (i = 0; i < ARRAY_SIZE(cs_amp_lib_cal_efivars); i++) { 320 294 status = cs_amp_get_efi_variable(cs_amp_lib_cal_efivars[i].name, 321 295 cs_amp_lib_cal_efivars[i].guid, 322 - NULL, &data_size, NULL); 296 + attr, &data_size, NULL); 323 297 if (status == EFI_BUFFER_TOO_SMALL) 324 298 break; 325 299 } 326 300 327 301 if (status != EFI_BUFFER_TOO_SMALL) 328 302 return ERR_PTR(-ENOENT); 303 + 304 + if (name) 305 + *name = cs_amp_lib_cal_efivars[i].name; 306 + 307 + if (guid) 308 + *guid = cs_amp_lib_cal_efivars[i].guid; 329 309 330 310 if (data_size < sizeof(*efi_data)) { 331 311 dev_err(dev, "EFI cal variable truncated\n"); ··· 345 313 346 314 status = cs_amp_get_efi_variable(cs_amp_lib_cal_efivars[i].name, 347 315 cs_amp_lib_cal_efivars[i].guid, 348 - NULL, &data_size, data); 316 + attr, &data_size, data); 349 317 if (status != EFI_SUCCESS) { 350 318 ret = -EINVAL; 351 319 goto err; ··· 361 329 goto err; 362 330 } 363 331 332 + /* This could be zero-filled space pre-allocated by the BIOS */ 333 + if (efi_data->size == 0) 334 + efi_data->size = data_size; 335 + 364 336 return efi_data; 365 337 366 338 err: ··· 374 338 return ERR_PTR(ret); 375 339 } 376 340 341 + static int cs_amp_set_cal_efi_buffer(struct device *dev, 342 + efi_char16_t *name, 343 + efi_guid_t *guid, 344 + u32 attr, 345 + struct cirrus_amp_efi_data *data) 346 + { 347 + efi_status_t status; 348 + 349 + status = cs_amp_set_efi_variable(name, guid, attr, 350 + struct_size(data, data, data->count), data); 351 + 352 + return cs_amp_convert_efi_status(status); 353 + } 354 + 377 355 static int _cs_amp_get_efi_calibration_data(struct device *dev, u64 target_uid, int amp_index, 378 356 struct cirrus_amp_cal_data *out_data) 379 357 { ··· 395 345 struct cirrus_amp_cal_data *cal = NULL; 396 346 int i, ret; 397 347 398 - efi_data = cs_amp_get_cal_efi_buffer(dev); 348 + efi_data = cs_amp_get_cal_efi_buffer(dev, NULL, NULL, NULL); 399 349 if (IS_ERR(efi_data)) 400 350 return PTR_ERR(efi_data); 401 351 ··· 447 397 return ret; 448 398 } 449 399 400 + static int _cs_amp_set_efi_calibration_data(struct device *dev, int amp_index, int num_amps, 401 + const struct cirrus_amp_cal_data *in_data) 402 + { 403 + u64 cal_target = cs_amp_cal_target_u64(in_data); 404 + unsigned long num_entries; 405 + struct cirrus_amp_efi_data *data __free(kfree) = NULL; 406 + efi_char16_t *name = CIRRUS_LOGIC_CALIBRATION_EFI_NAME; 407 + efi_guid_t *guid = &CIRRUS_LOGIC_CALIBRATION_EFI_GUID; 408 + u32 attr = CS_AMP_CAL_DEFAULT_EFI_ATTR; 409 + int i, ret; 410 + 411 + if (cal_target == 0) 412 + return -EINVAL; 413 + 414 + data = cs_amp_get_cal_efi_buffer(dev, &name, &guid, &attr); 415 + ret = PTR_ERR_OR_ZERO(data); 416 + if (ret == -ENOENT) { 417 + data = NULL; 418 + goto alloc_new; 419 + } else if (ret) { 420 + return ret; 421 + } 422 + 423 + /* 424 + * If the EFI variable is just zero-filled reserved space the count 425 + * must be set. 426 + */ 427 + if (data->count == 0) 428 + data->count = (data->size - sizeof(data)) / sizeof(data->data[0]); 429 + 430 + if (amp_index < 0) { 431 + /* Is there already a slot for this target? */ 432 + for (amp_index = 0; amp_index < data->count; amp_index++) { 433 + if (cs_amp_cal_target_u64(&data->data[amp_index]) == cal_target) 434 + break; 435 + } 436 + 437 + /* Else find an empty slot */ 438 + if (amp_index >= data->count) { 439 + for (amp_index = 0; amp_index < data->count; amp_index++) { 440 + if ((data->data[amp_index].calTime[0] == 0) && 441 + (data->data[amp_index].calTime[1] == 0)) 442 + break; 443 + } 444 + } 445 + } else { 446 + /* 447 + * If the index is forced there could be another active 448 + * slot with the same calTarget. So deduplicate. 449 + */ 450 + for (i = 0; i < data->count; i++) { 451 + if (i == amp_index) 452 + continue; 453 + 454 + if ((data->data[i].calTime[0] == 0) && (data->data[i].calTime[1] == 0)) 455 + continue; 456 + 457 + if (cs_amp_cal_target_u64(&data->data[i]) == cal_target) 458 + memset(data->data[i].calTime, 0, sizeof(data->data[i].calTime)); 459 + } 460 + } 461 + 462 + alloc_new: 463 + if (amp_index < 0) 464 + amp_index = 0; 465 + 466 + num_entries = max(num_amps, amp_index + 1); 467 + if (!data || (data->count < num_entries)) { 468 + struct cirrus_amp_efi_data *old_data __free(kfree) = no_free_ptr(data); 469 + unsigned int new_data_size = struct_size(data, data, num_entries); 470 + 471 + data = kzalloc(new_data_size, GFP_KERNEL); 472 + if (!data) 473 + return -ENOMEM; 474 + 475 + if (old_data) 476 + memcpy(data, old_data, struct_size(old_data, data, old_data->count)); 477 + 478 + data->count = num_entries; 479 + data->size = new_data_size; 480 + } 481 + 482 + data->data[amp_index] = *in_data; 483 + ret = cs_amp_set_cal_efi_buffer(dev, name, guid, attr, data); 484 + if (ret) { 485 + dev_err(dev, "Failed writing calibration to EFI: %d\n", ret); 486 + return ret; 487 + } 488 + 489 + return 0; 490 + } 491 + 450 492 /** 451 493 * cs_amp_get_efi_calibration_data - get an entry from calibration data in EFI. 452 494 * @dev: struct device of the caller. ··· 584 442 return -ENOENT; 585 443 } 586 444 EXPORT_SYMBOL_NS_GPL(cs_amp_get_efi_calibration_data, "SND_SOC_CS_AMP_LIB"); 445 + 446 + /** 447 + * cs_amp_set_efi_calibration_data - write a calibration data entry to EFI. 448 + * @dev: struct device of the caller. 449 + * @amp_index: Entry index to use, or -1 to use any available slot. 450 + * @num_amps: Maximum number of amps to reserve slots for, or -1 to ignore. 451 + * @in_data: struct cirrus_amp_cal_data entry to be written to EFI. 452 + * 453 + * If a Vendor-specific variable exists it will be updated, 454 + * else if the Cirrus variable exists it will be updated 455 + * else the Cirrus variable will be created. 456 + * 457 + * If amp_index >= 0 the data will be placed in this entry of the calibration 458 + * data array, overwriting what was in that entry. Any other entries with the 459 + * same calTarget will be marked empty. 460 + * 461 + * If amp_index < 0 and in_data->calTarget matches any existing entry, that 462 + * entry will be overwritten. Else the first available free entry will be used, 463 + * extending the size of the EFI variable if there are no free entries. 464 + * 465 + * If num_amps > 0 the EFI variable will be sized to contain at least this 466 + * many calibration entries, with any new entries marked empty. 467 + * 468 + * Return: 0 if the write was successful, -EFBIG if space could not be made in 469 + * the EFI file to add the entry, -EACCES if it was not possible to 470 + * read or write the EFI variable. 471 + */ 472 + int cs_amp_set_efi_calibration_data(struct device *dev, int amp_index, int num_amps, 473 + const struct cirrus_amp_cal_data *in_data) 474 + { 475 + if (IS_ENABLED(CONFIG_EFI) || IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST)) { 476 + scoped_guard(mutex, &cs_amp_efi_cal_write_lock) { 477 + return _cs_amp_set_efi_calibration_data(dev, amp_index, 478 + num_amps, in_data); 479 + } 480 + } 481 + 482 + return -ENOENT; 483 + } 484 + EXPORT_SYMBOL_NS_GPL(cs_amp_set_efi_calibration_data, "SND_SOC_CS_AMP_LIB"); 587 485 588 486 struct cs_amp_spkid_efi { 589 487 efi_char16_t *name;