| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * System Control and Management Interface (SCMI) Message Protocol Quirks | |
| * | |
| * Copyright (C) 2025 ARM Ltd. | |
| */ | |
| /** | |
| * DOC: Theory of operation | |
| * | |
| * A framework to define SCMI quirks and their activation conditions based on | |
| * existing static_keys kernel facilities. | |
| * | |
| * Quirks are named and their activation conditions defined using the macro | |
| * DEFINE_SCMI_QUIRK() in this file. | |
| * | |
| * After a quirk is defined, a corresponding entry must also be added to the | |
| * global @scmi_quirks_table in this file using __DECLARE_SCMI_QUIRK_ENTRY(). | |
| * | |
| * Additionally a corresponding quirk declaration must be added also to the | |
| * quirk.h file using DECLARE_SCMI_QUIRK(). | |
| * | |
| * The needed quirk code-snippet itself will be defined local to the SCMI code | |
| * that is meant to fix and will be associated to the previously defined quirk | |
| * and related activation conditions using the macro SCMI_QUIRK(). | |
| * | |
| * At runtime, during the SCMI stack probe sequence, once the SCMI Server had | |
| * advertised the running platform Vendor, SubVendor and Implementation Version | |
| * data, all the defined quirks matching the activation conditions will be | |
| * enabled. | |
| * | |
| * Example | |
| * | |
| * quirk.c | |
| * ------- | |
| * DEFINE_SCMI_QUIRK(fix_me, "vendor", "subvend", "0x12000-0x30000", | |
| * "someone,plat_A", "another,plat_b", "vend,sku"); | |
| * | |
| * static struct scmi_quirk *scmi_quirks_table[] = { | |
| * ... | |
| * __DECLARE_SCMI_QUIRK_ENTRY(fix_me), | |
| * NULL | |
| * }; | |
| * | |
| * quirk.h | |
| * ------- | |
| * DECLARE_SCMI_QUIRK(fix_me); | |
| * | |
| * <somewhere_in_the_scmi_stack.c> | |
| * ------------------------------ | |
| * | |
| * #define QUIRK_CODE_SNIPPET_FIX_ME() \ | |
| * ({ \ | |
| * if (p->condition) \ | |
| * a_ptr->calculated_val = 123; \ | |
| * }) | |
| * | |
| * | |
| * int some_function_to_fix(int param, struct something *p) | |
| * { | |
| * struct some_strut *a_ptr; | |
| * | |
| * a_ptr = some_load_func(p); | |
| * SCMI_QUIRK(fix_me, QUIRK_CODE_SNIPPET_FIX_ME); | |
| * some_more_func(a_ptr); | |
| * ... | |
| * | |
| * return 0; | |
| * } | |
| * | |
| */ | |
| #include <linux/ctype.h> | |
| #include <linux/cleanup.h> | |
| #include <linux/device.h> | |
| #include <linux/export.h> | |
| #include <linux/hashtable.h> | |
| #include <linux/kstrtox.h> | |
| #include <linux/of.h> | |
| #include <linux/slab.h> | |
| #include <linux/static_key.h> | |
| #include <linux/string.h> | |
| #include <linux/stringhash.h> | |
| #include <linux/types.h> | |
| #include "quirks.h" | |
| #define SCMI_QUIRKS_HT_SZ 4 | |
| struct scmi_quirk { | |
| bool enabled; | |
| const char *name; | |
| const char *vendor; | |
| const char *sub_vendor_id; | |
| const char *impl_ver_range; | |
| u32 start_range; | |
| u32 end_range; | |
| struct static_key_false *key; | |
| struct hlist_node hash; | |
| unsigned int hkey; | |
| const char *const compats[]; | |
| }; | |
| #define __DEFINE_SCMI_QUIRK_ENTRY(_qn, _ven, _sub, _impl, ...) \ | |
| static struct scmi_quirk scmi_quirk_entry_ ## _qn = { \ | |
| .name = __stringify(quirk_ ## _qn), \ | |
| .vendor = _ven, \ | |
| .sub_vendor_id = _sub, \ | |
| .impl_ver_range = _impl, \ | |
| .key = &(scmi_quirk_ ## _qn), \ | |
| .compats = { __VA_ARGS__ __VA_OPT__(,) NULL }, \ | |
| } | |
| #define __DECLARE_SCMI_QUIRK_ENTRY(_qn) (&(scmi_quirk_entry_ ## _qn)) | |
| /* | |
| * Define a quirk by name and provide the matching tokens where: | |
| * | |
| * _qn: A string which will be used to build the quirk and the global | |
| * static_key names. | |
| * _ven : SCMI Vendor ID string match, NULL means any. | |
| * _sub : SCMI SubVendor ID string match, NULL means any. | |
| * _impl : SCMI Implementation Version string match, NULL means any. | |
| * This string can be used to express version ranges which will be | |
| * interpreted as follows: | |
| * | |
| * NULL [0, 0xFFFFFFFF] | |
| * "X" [X, X] | |
| * "X-" [X, 0xFFFFFFFF] | |
| * "-X" [0, X] | |
| * "X-Y" [X, Y] | |
| * | |
| * with X <= Y and <v> in [X, Y] meaning X <= <v> <= Y | |
| * | |
| * ... : An optional variadic macros argument used to provide a comma-separated | |
| * list of compatible strings matches; when no variadic argument is | |
| * provided, ANY compatible will match this quirk. | |
| * | |
| * This implicitly define also a properly named global static-key that | |
| * will be used to dynamically enable the quirk at initialization time. | |
| * | |
| * Note that it is possible to associate multiple quirks to the same | |
| * matching pattern, if your firmware quality is really astounding :P | |
| * | |
| * Example: | |
| * | |
| * Compatibles list NOT provided, so ANY compatible will match: | |
| * | |
| * DEFINE_SCMI_QUIRK(my_new_issue, "Vend", "SVend", "0x12000-0x30000"); | |
| * | |
| * | |
| * A few compatibles provided to match against: | |
| * | |
| * DEFINE_SCMI_QUIRK(my_new_issue, "Vend", "SVend", "0x12000-0x30000", | |
| * "xvend,plat_a", "xvend,plat_b", "xvend,sku_name"); | |
| */ | |
| #define DEFINE_SCMI_QUIRK(_qn, _ven, _sub, _impl, ...) \ | |
| DEFINE_STATIC_KEY_FALSE(scmi_quirk_ ## _qn); \ | |
| __DEFINE_SCMI_QUIRK_ENTRY(_qn, _ven, _sub, _impl, ##__VA_ARGS__) | |
| /* | |
| * Same as DEFINE_SCMI_QUIRK but EXPORTED: this is meant to address quirks | |
| * that possibly reside in code that is included in loadable kernel modules | |
| * that needs to be able to access the global static keys at runtime to | |
| * determine if enabled or not. (see SCMI_QUIRK to understand usage) | |
| */ | |
| #define DEFINE_SCMI_QUIRK_EXPORTED(_qn, _ven, _sub, _impl, ...) \ | |
| DEFINE_STATIC_KEY_FALSE(scmi_quirk_ ## _qn); \ | |
| EXPORT_SYMBOL_GPL(scmi_quirk_ ## _qn); \ | |
| __DEFINE_SCMI_QUIRK_ENTRY(_qn, _ven, _sub, _impl, ##__VA_ARGS__) | |
| /* Global Quirks Definitions */ | |
| DEFINE_SCMI_QUIRK(clock_rates_triplet_out_of_spec, NULL, NULL, NULL); | |
| DEFINE_SCMI_QUIRK(perf_level_get_fc_force, "Qualcomm", NULL, "0x20000-"); | |
| /* | |
| * Quirks Pointers Array | |
| * | |
| * This is filled at compile-time with the list of pointers to all the currently | |
| * defined quirks descriptors. | |
| */ | |
| static struct scmi_quirk *scmi_quirks_table[] = { | |
| __DECLARE_SCMI_QUIRK_ENTRY(clock_rates_triplet_out_of_spec), | |
| __DECLARE_SCMI_QUIRK_ENTRY(perf_level_get_fc_force), | |
| NULL | |
| }; | |
| /* | |
| * Quirks HashTable | |
| * | |
| * A run-time populated hashtable containing all the defined quirks descriptors | |
| * hashed by matching pattern. | |
| */ | |
| static DEFINE_READ_MOSTLY_HASHTABLE(scmi_quirks_ht, SCMI_QUIRKS_HT_SZ); | |
| static unsigned int scmi_quirk_signature(const char *vend, const char *sub_vend) | |
| { | |
| char *signature, *p; | |
| unsigned int hash32; | |
| unsigned long hash = 0; | |
| /* vendor_id/sub_vendor_id guaranteed <= SCMI_SHORT_NAME_MAX_SIZE */ | |
| signature = kasprintf(GFP_KERNEL, "|%s|%s|", vend ?: "", sub_vend ?: ""); | |
| if (!signature) | |
| return 0; | |
| pr_debug("SCMI Quirk Signature >>>%s<<<\n", signature); | |
| p = signature; | |
| while (*p) | |
| hash = partial_name_hash(tolower(*p++), hash); | |
| hash32 = end_name_hash(hash); | |
| kfree(signature); | |
| return hash32; | |
| } | |
| static int scmi_quirk_range_parse(struct scmi_quirk *quirk) | |
| { | |
| const char *last, *first __free(kfree) = NULL; | |
| int ret = 0; | |
| size_t len; | |
| char *sep; | |
| quirk->start_range = 0; | |
| quirk->end_range = 0xFFFFFFFF; | |
| len = quirk->impl_ver_range ? strlen(quirk->impl_ver_range) : 0; | |
| if (!len) | |
| return 0; | |
| first = kmemdup(quirk->impl_ver_range, len + 1, GFP_KERNEL); | |
| if (!first) | |
| return -ENOMEM; | |
| last = first + len - 1; | |
| sep = strchr(first, '-'); | |
| if (sep) | |
| *sep = '\0'; | |
| if (sep != first) /* X OR X- OR X-y */ { | |
| ret = kstrtouint(first, 0, &quirk->start_range); | |
| if (ret) | |
| return ret; | |
| } | |
| if (!sep) | |
| quirk->end_range = quirk->start_range; | |
| else if (sep != last) /* -X OR x-Y */ | |
| ret = kstrtouint(sep + 1, 0, &quirk->end_range); | |
| if (quirk->start_range > quirk->end_range) | |
| return -EINVAL; | |
| return ret; | |
| } | |
| void scmi_quirks_initialize(void) | |
| { | |
| struct scmi_quirk *quirk; | |
| for (unsigned int i = 0; (quirk = scmi_quirks_table[i]); i++) { | |
| int ret; | |
| ret = scmi_quirk_range_parse(quirk); | |
| if (ret) { | |
| pr_err("SCMI skip QUIRK [%s] - BAD RANGE - |%s|\n", | |
| quirk->name, quirk->impl_ver_range); | |
| continue; | |
| } | |
| quirk->hkey = scmi_quirk_signature(quirk->vendor, | |
| quirk->sub_vendor_id); | |
| hash_add(scmi_quirks_ht, &quirk->hash, quirk->hkey); | |
| pr_debug("Registered SCMI QUIRK [%s] -- %p - Key [0x%08X] - %s/%s/[0x%08X-0x%08X]\n", | |
| quirk->name, quirk, quirk->hkey, | |
| quirk->vendor, quirk->sub_vendor_id, | |
| quirk->start_range, quirk->end_range); | |
| } | |
| pr_debug("SCMI Quirks initialized\n"); | |
| } | |
| void scmi_quirks_enable(struct device *dev, const char *vend, | |
| const char *subv, const u32 impl) | |
| { | |
| for (int i = 3; i >= 0; i--) { | |
| struct scmi_quirk *quirk; | |
| unsigned int hkey; | |
| hkey = scmi_quirk_signature(i > 1 ? vend : NULL, | |
| i > 2 ? subv : NULL); | |
| /* | |
| * Note that there could be multiple matches so we | |
| * will enable multiple quirk part of a hash collision | |
| * domain...BUT we cannot assume that ALL quirks on the | |
| * same collision domain are a full match. | |
| */ | |
| hash_for_each_possible(scmi_quirks_ht, quirk, hash, hkey) { | |
| if (quirk->enabled || quirk->hkey != hkey || | |
| impl < quirk->start_range || | |
| impl > quirk->end_range) | |
| continue; | |
| if (quirk->compats[0] && | |
| !of_machine_compatible_match(quirk->compats)) | |
| continue; | |
| dev_info(dev, "Enabling SCMI Quirk [%s]\n", | |
| quirk->name); | |
| dev_dbg(dev, | |
| "Quirk matched on: %s/%s/%s/[0x%08X-0x%08X]\n", | |
| quirk->compats[0], quirk->vendor, | |
| quirk->sub_vendor_id, | |
| quirk->start_range, quirk->end_range); | |
| static_branch_enable(quirk->key); | |
| quirk->enabled = true; | |
| } | |
| } | |
| } |