Due to electrical and mechanical constraints in certain platform designs there may be likely interference of relatively high-powered harmonics of the (G-)DDR memory clocks with local radio module frequency bands used by Wifi 6/6e/7. To mitigate this, AMD has introduced a mechanism that devices can use to notify active use of particular frequencies so that other devices can make relative internal adjustments as necessary to avoid this resonance. Signed-off-by: Evan Quan <evan.quan@xxxxxxx> --- drivers/acpi/Kconfig | 17 ++ drivers/acpi/Makefile | 2 + drivers/acpi/amd_wbrf.c | 414 ++++++++++++++++++++++++++++++++++ include/linux/acpi_amd_wbrf.h | 140 ++++++++++++ 4 files changed, 573 insertions(+) create mode 100644 drivers/acpi/amd_wbrf.c create mode 100644 include/linux/acpi_amd_wbrf.h diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig index 00dd309b6682..a092ea72d152 100644 --- a/drivers/acpi/Kconfig +++ b/drivers/acpi/Kconfig @@ -594,6 +594,23 @@ config ACPI_PRMT substantially increase computational overhead related to the initialization of some server systems. +config WBRF_AMD_ACPI + bool "ACPI based WBRF mechanism introduced by AMD" + depends on ACPI + default n + help + Wifi band RFI mitigation mechanism allows multiple drivers from + different domains to notify the frequencies in use so that hardware + can be reconfigured to avoid harmonic conflicts. + + AMD has introduced an ACPI based mechanism to support WBRF for some + platforms with AMD dGPU and WLAN. This needs support from BIOS equipped + with necessary AML implementations and dGPU firmwares. + + Before enabling this ACPI based mechanism, it is suggested to confirm + with the hardware designer/provider first whether your platform + equipped with necessary BIOS and firmwares. + endif # ACPI config X86_PM_TIMER diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile index 3fc5a0d54f6e..9185d16e4495 100644 --- a/drivers/acpi/Makefile +++ b/drivers/acpi/Makefile @@ -133,3 +133,5 @@ obj-$(CONFIG_ARM64) += arm64/ obj-$(CONFIG_ACPI_VIOT) += viot.o obj-$(CONFIG_RISCV) += riscv/ + +obj-$(CONFIG_WBRF_AMD_ACPI) += amd_wbrf.o diff --git a/drivers/acpi/amd_wbrf.c b/drivers/acpi/amd_wbrf.c new file mode 100644 index 000000000000..98663b2bfd54 --- /dev/null +++ b/drivers/acpi/amd_wbrf.c @@ -0,0 +1,414 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Wifi Band Exclusion Interface (AMD ACPI Implementation) + * Copyright (C) 2023 Advanced Micro Devices + * + */ + +#include <linux/acpi.h> +#include <linux/acpi_amd_wbrf.h> + +#define ACPI_AMD_WBRF_METHOD "\\WBRF" + +/* + * Functions bit vector for WBRF method + * + * Bit 0: Supported for any functions other than function 0. + * Bit 1: Function 1 (Add / Remove frequency) is supported. + * Bit 2: Function 2 (Get frequency list) is supported. + */ +#define WBRF_ENABLED 0x0 +#define WBRF_RECORD 0x1 +#define WBRF_RETRIEVE 0x2 + +/* record actions */ +#define WBRF_RECORD_ADD 0x0 +#define WBRF_RECORD_REMOVE 0x1 + +#define WBRF_REVISION 0x1 + +/* + * The data structure used for WBRF_RETRIEVE is not naturally aligned. + * And unfortunately the design has been settled down. + */ +struct amd_wbrf_ranges_out { + u32 num_of_ranges; + struct exclusion_range band_list[MAX_NUM_OF_WBRF_RANGES]; +} __packed; + +static const guid_t wifi_acpi_dsm_guid = + GUID_INIT(0x7b7656cf, 0xdc3d, 0x4c1c, + 0x83, 0xe9, 0x66, 0xe7, 0x21, 0xde, 0x30, 0x70); + +static BLOCKING_NOTIFIER_HEAD(wbrf_chain_head); + +static int wbrf_dsm(struct acpi_device *adev, + u8 fn, + union acpi_object *argv4) +{ + union acpi_object *obj; + int rc; + + obj = acpi_evaluate_dsm(adev->handle, &wifi_acpi_dsm_guid, + WBRF_REVISION, fn, argv4); + if (!obj) + return -ENXIO; + + switch (obj->type) { + case ACPI_TYPE_INTEGER: + rc = obj->integer.value ? -EINVAL : 0; + break; + default: + rc = -EOPNOTSUPP; + } + + ACPI_FREE(obj); + + return rc; +} + +static int wbrf_record(struct acpi_device *adev, uint8_t action, + struct wbrf_ranges_in_out *in) +{ + union acpi_object argv4; + union acpi_object *tmp; + u32 num_of_ranges = 0; + u32 num_of_elements; + u32 arg_idx = 0; + u32 loop_idx; + int ret; + + if (!in) + return -EINVAL; + + for (loop_idx = 0; loop_idx < ARRAY_SIZE(in->band_list); + loop_idx++) + if (in->band_list[loop_idx].start && + in->band_list[loop_idx].end) + num_of_ranges++; + + /* + * The valid entry counter does not match with this told. + * Something must went wrong. + */ + if (num_of_ranges != in->num_of_ranges) + return -EINVAL; + + /* + * Every input frequency band comes with two end points(start/end) + * and each is accounted as an element. Meanwhile the range count + * and action type are accounted as an element each. + * So, the total element count = 2 * num_of_ranges + 1 + 1. + */ + num_of_elements = 2 * num_of_ranges + 1 + 1; + + tmp = kcalloc(num_of_elements, sizeof(*tmp), GFP_KERNEL); + if (!tmp) + return -ENOMEM; + + argv4.package.type = ACPI_TYPE_PACKAGE; + argv4.package.count = num_of_elements; + argv4.package.elements = tmp; + + tmp[arg_idx].integer.type = ACPI_TYPE_INTEGER; + tmp[arg_idx++].integer.value = num_of_ranges; + tmp[arg_idx].integer.type = ACPI_TYPE_INTEGER; + tmp[arg_idx++].integer.value = action; + + for (loop_idx = 0; loop_idx < ARRAY_SIZE(in->band_list); + loop_idx++) { + if (!in->band_list[loop_idx].start || + !in->band_list[loop_idx].end) + continue; + + tmp[arg_idx].integer.type = ACPI_TYPE_INTEGER; + tmp[arg_idx++].integer.value = in->band_list[loop_idx].start; + tmp[arg_idx].integer.type = ACPI_TYPE_INTEGER; + tmp[arg_idx++].integer.value = in->band_list[loop_idx].end; + } + + ret = wbrf_dsm(adev, WBRF_RECORD, &argv4); + + kfree(tmp); + + return ret; +} + +/** + * acpi_amd_wbrf_add_exclusion - broadcast the frequency band the device + * is using + * + * @dev: device pointer + * @in: input structure containing the frequency band the device is using + * + * Broadcast to other consumers the frequency band the device starts + * to use. Underneath the surface the information is cached into an + * internal buffer first. Then a notification is sent to all those + * registered consumers. So then they can retrieve that buffer to + * know the latest active frequency bands. The benifit with such design + * is for those consumers which have not been registered yet, they can + * still have a chance to retrieve such information later. + */ +int acpi_amd_wbrf_add_exclusion(struct device *dev, + struct wbrf_ranges_in_out *in) +{ + struct acpi_device *adev = ACPI_COMPANION(dev); + int ret; + + if (!adev) + return -ENODEV; + + ret = wbrf_record(adev, WBRF_RECORD_ADD, in); + if (ret) + return ret; + + blocking_notifier_call_chain(&wbrf_chain_head, + WBRF_CHANGED, + NULL); + + return 0; +} +EXPORT_SYMBOL_GPL(acpi_amd_wbrf_add_exclusion); + +/** + * acpi_amd_wbrf_remove_exclusion - broadcast the frequency band the device + * is no longer using + * + * @dev: device pointer + * @in: input structure containing the frequency band which is not used + * by the device any more + * + * Broadcast to other consumers the frequency band the device stops + * to use. The stored information paired with this will be dropped + * from the internal buffer. And then a notification is sent to + * all registered consumers. + */ +int acpi_amd_wbrf_remove_exclusion(struct device *dev, + struct wbrf_ranges_in_out *in) +{ + struct acpi_device *adev = ACPI_COMPANION(dev); + int ret; + + if (!adev) + return -ENODEV; + + ret = wbrf_record(adev, WBRF_RECORD_REMOVE, in); + if (ret) + return ret; + + blocking_notifier_call_chain(&wbrf_chain_head, + WBRF_CHANGED, + NULL); + + return 0; +} +EXPORT_SYMBOL_GPL(acpi_amd_wbrf_remove_exclusion); + +static bool acpi_amd_wbrf_supported_system(void) +{ + acpi_status status; + acpi_handle handle; + + status = acpi_get_handle(NULL, ACPI_AMD_WBRF_METHOD, &handle); + + return ACPI_SUCCESS(status); +} + +/** + * acpi_amd_wbrf_supported_producer - determine if the WBRF can be enabled + * for the device as a producer + * + * @dev: device pointer + * + * Determine if the platform equipped with necessary implementations to + * support WBRF for the device as a producer. + */ +bool acpi_amd_wbrf_supported_producer(struct device *dev) +{ + struct acpi_device *adev = ACPI_COMPANION(dev); + + if (!acpi_amd_wbrf_supported_system()) + return false; + + if (!adev) + return false; + + return acpi_check_dsm(adev->handle, &wifi_acpi_dsm_guid, + WBRF_REVISION, + BIT(WBRF_RECORD)); +} +EXPORT_SYMBOL_GPL(acpi_amd_wbrf_supported_producer); + +static union acpi_object * +acpi_evaluate_wbrf(acpi_handle handle, u64 rev, u64 func) +{ + acpi_status ret; + struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; + union acpi_object params[4]; + struct acpi_object_list input = { + .count = 4, + .pointer = params, + }; + + params[0].type = ACPI_TYPE_INTEGER; + params[0].integer.value = rev; + params[1].type = ACPI_TYPE_INTEGER; + params[1].integer.value = func; + params[2].type = ACPI_TYPE_PACKAGE; + params[2].package.count = 0; + params[2].package.elements = NULL; + params[3].type = ACPI_TYPE_STRING; + params[3].string.length = 0; + params[3].string.pointer = NULL; + + ret = acpi_evaluate_object(handle, "WBRF", &input, &buf); + if (ACPI_FAILURE(ret)) + return NULL; + + return buf.pointer; +} + +static bool check_acpi_wbrf(acpi_handle handle, u64 rev, u64 funcs) +{ + int i; + u64 mask = 0; + union acpi_object *obj; + + if (funcs == 0) + return false; + + obj = acpi_evaluate_wbrf(handle, rev, 0); + if (!obj) + return false; + + if (obj->type != ACPI_TYPE_BUFFER) + return false; + + /* + * Bit vector providing supported functions information. + * Each bit marks support for one specific function of the WBRF method. + */ + for (i = 0; i < obj->buffer.length && i < 8; i++) + mask |= (u64)obj->buffer.pointer[i] << i * 8; + + ACPI_FREE(obj); + + return mask & BIT(WBRF_ENABLED) && (mask & funcs) == funcs; +} + +/** + * acpi_amd_wbrf_supported_consumer - determine if the WBRF can be enabled + * for the device as a consumer + * + * @dev: device pointer + * + * Determine if the platform equipped with necessary implementations to + * support WBRF for the device as a consumer. + */ +bool acpi_amd_wbrf_supported_consumer(struct device *dev) +{ + struct acpi_device *adev = ACPI_COMPANION(dev); + + if (!acpi_amd_wbrf_supported_system()) + return false; + + if (!adev) + return false; + + return check_acpi_wbrf(adev->handle, + WBRF_REVISION, + BIT(WBRF_RETRIEVE)); +} +EXPORT_SYMBOL_GPL(acpi_amd_wbrf_supported_consumer); + +/** + * acpi_amd_wbrf_retrieve_exclusions - retrieve current active frequency + * bands + * + * @dev: device pointer + * @out: output structure containing all the active frequency bands + * + * Retrieve the current active frequency bands which were broadcasted + * by other producers. The consumer who calls this API should take + * proper actions if any of the frequency band may cause RFI with its + * own frequency band used. + */ +int acpi_amd_wbrf_retrieve_exclusions(struct device *dev, + struct wbrf_ranges_in_out *out) +{ + struct acpi_device *adev = ACPI_COMPANION(dev); + struct amd_wbrf_ranges_out acpi_out = {0}; + union acpi_object *obj; + int ret = 0; + + if (!adev) + return -ENODEV; + + obj = acpi_evaluate_wbrf(adev->handle, + WBRF_REVISION, + WBRF_RETRIEVE); + if (!obj) + return -EINVAL; + + /* + * The return buffer is with variable length and the format below: + * number_of_entries(1 DWORD): Number of entries + * start_freq of 1st entry(1 QWORD): Start frequency of the 1st entry + * end_freq of 1st entry(1 QWORD): End frequency of the 1st entry + * ... + * ... + * start_freq of the last entry(1 QWORD) + * end_freq of the last entry(1 QWORD) + * + * Thus the buffer length is determined by the number of entries. + * - For zero entry scenario, the buffer length will be 4 bytes. + * - For one entry scenario, the buffer length will be 20 bytes. + */ + if (obj->buffer.length > sizeof(acpi_out) || + obj->buffer.length < 4) { + dev_err(dev, "Wrong sized WBRT information"); + ret = -EINVAL; + goto out; + } + memcpy(&acpi_out, obj->buffer.pointer, obj->buffer.length); + + out->num_of_ranges = acpi_out.num_of_ranges; + memcpy(out->band_list, acpi_out.band_list, sizeof(acpi_out.band_list)); + +out: + ACPI_FREE(obj); + + return ret; +} +EXPORT_SYMBOL_GPL(acpi_amd_wbrf_retrieve_exclusions); + +/** + * acpi_amd_wbrf_register_notifier - register for notifications of frequency + * band update + * + * @nb: driver notifier block + * + * The consumer should register itself via this API. So that it can get + * notified timely on the frequency band updates from other producers. + */ +int acpi_amd_wbrf_register_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&wbrf_chain_head, nb); +} +EXPORT_SYMBOL_GPL(acpi_amd_wbrf_register_notifier); + +/** + * acpi_amd_wbrf_unregister_notifier - unregister for notifications of + * frequency band update + * + * @nb: driver notifier block + * + * The consumer should call this API when it is longer interested with + * the frequency band updates from other producers. Usually, this should + * be performed during driver cleanup. + */ +int acpi_amd_wbrf_unregister_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&wbrf_chain_head, nb); +} +EXPORT_SYMBOL_GPL(acpi_amd_wbrf_unregister_notifier); diff --git a/include/linux/acpi_amd_wbrf.h b/include/linux/acpi_amd_wbrf.h new file mode 100644 index 000000000000..c2363d664641 --- /dev/null +++ b/include/linux/acpi_amd_wbrf.h @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Wifi Band Exclusion Interface (AMD ACPI Implementation) + * Copyright (C) 2023 Advanced Micro Devices + * + * Due to electrical and mechanical constraints in certain platform designs + * there may be likely interference of relatively high-powered harmonics of + * the (G-)DDR memory clocks with local radio module frequency bands used + * by Wifi 6/6e/7. + * + * To mitigate this, AMD has introduced an ACPI based mechanism to support + * WBRF(Wifi Band RFI mitigation Feature) for platforms with AMD dGPU + WLAN. + * This needs support from BIOS equipped with necessary AML implementations + * and dGPU firmwares. + * + * Some general terms: + * Producer: such component who can produce high-powered radio frequency + * Consumer: such component who can adjust its in-use frequency in + * response to the radio frequencies of other components to + * mitigate the possible RFI. + * + * To make the mechanism function, those producers should notify active use + * of their particular frequencies so that other consumers can make relative + * internal adjustments as necessary to avoid this resonance. + */ + +#ifndef _ACPI_AMD_WBRF_H +#define _ACPI_AMD_WBRF_H + +#include <linux/device.h> +#include <linux/notifier.h> + +/* + * A wbrf range is defined as a frequency band with start and end + * frequency point specified(in Hz). And a vaild range should have + * its start and end frequency point filled with non-zero values. + * Meanwhile, the maximum number of wbrf ranges is limited as + * `MAX_NUM_OF_WBRF_RANGES`. + */ +#define MAX_NUM_OF_WBRF_RANGES 11 + +struct exclusion_range { + u64 start; + u64 end; +}; + +struct wbrf_ranges_in_out { + u64 num_of_ranges; + struct exclusion_range band_list[MAX_NUM_OF_WBRF_RANGES]; +}; + +/* + * The notification types for the consumers are defined as below. + * The consumers may need to take different actions in response to + * different notifications. + * WBRF_CHANGED: there was some frequency band updates. The consumers + * should retrieve the latest active frequency bands. + */ +enum wbrf_notifier_actions { + WBRF_CHANGED, +}; + +#if IS_ENABLED(CONFIG_WBRF_AMD_ACPI) +/* + * The expected flow for the producers: + * 1) During probe, call `acpi_amd_wbrf_supported_producer` to check + * if WBRF can be enabled for the device. + * 2) On using some frequency band, call `acpi_amd_wbrf_add_exclusion` + * to get other consumers properly notified. + * 3) Or on stopping using some frequency band, call + * `acpi_amd_wbrf_remove_exclusion` to get other consumers notified. + */ +bool acpi_amd_wbrf_supported_producer(struct device *dev); +int acpi_amd_wbrf_remove_exclusion(struct device *dev, + struct wbrf_ranges_in_out *in); +int acpi_amd_wbrf_add_exclusion(struct device *dev, + struct wbrf_ranges_in_out *in); + +/* + * The expected flow for the consumers: + * 1) During probe, call `acpi_amd_wbrf_supported_consumer` to check if WBRF + * can be enabled for the device. + * 2) Call `acpi_amd_wbrf_register_notifier` to register for notification + * of frequency band change(add or remove) from other producers. + * 3) Call the `acpi_amd_wbrf_retrieve_exclusions` intentionally to retrieve + * current active frequency bands considering some producers may broadcast + * such information before the consumer is up. + * 4) On receiving a notification for frequency band change, run + * `acpi_amd_wbrf_retrieve_exclusions` again to retrieve the latest + * active frequency bands. + * 5) During driver cleanup, call `acpi_amd_wbrf_unregister_notifier` to + * unregister the notifier. + */ +bool acpi_amd_wbrf_supported_consumer(struct device *dev); +int acpi_amd_wbrf_retrieve_exclusions(struct device *dev, + struct wbrf_ranges_in_out *out); +int acpi_amd_wbrf_register_notifier(struct notifier_block *nb); +int acpi_amd_wbrf_unregister_notifier(struct notifier_block *nb); +#else +static inline +bool acpi_amd_wbrf_supported_consumer(struct device *dev) +{ + return false; +} +static inline +int acpi_amd_wbrf_remove_exclusion(struct device *dev, + struct wbrf_ranges_in_out *in) +{ + return -ENODEV; +} +static inline +int acpi_amd_wbrf_add_exclusion(struct device *dev, + struct wbrf_ranges_in_out *in) +{ + return -ENODEV; +} +static inline +bool acpi_amd_wbrf_supported_producer(struct device *dev) +{ + return false; +} +static inline +int acpi_amd_wbrf_retrieve_exclusions(struct device *dev, + struct wbrf_ranges_in_out *out) +{ + return -ENODEV; +} +static inline +int acpi_amd_wbrf_register_notifier(struct notifier_block *nb) +{ + return -ENODEV; +} +static inline +int acpi_amd_wbrf_unregister_notifier(struct notifier_block *nb) +{ + return -ENODEV; +} +#endif + +#endif /* _ACPI_AMD_WBRF_H */ -- 2.34.1