// 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); * * * ------------------------------ * * #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 #include #include #include #include #include #include #include #include #include #include #include "quirks.h" #define SCMI_QUIRKS_HT_SZ 4 struct scmi_quirk { bool enabled; const char *name; char *vendor; char *sub_vendor_id; 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 in [X, Y] meaning X <= <= 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 = quirk->impl_ver_range; size_t len; char *sep; int ret; quirk->start_range = 0; quirk->end_range = 0xFFFFFFFF; len = quirk->impl_ver_range ? strlen(quirk->impl_ver_range) : 0; if (!len) return 0; last = first + len - 1; sep = strchr(quirk->impl_ver_range, '-'); if (sep) *sep = '\0'; if (sep == first) /* -X */ ret = kstrtouint(first + 1, 0, &quirk->end_range); else /* 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-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; int i; for (i = 0, quirk = scmi_quirks_table[0]; quirk; i++, quirk = scmi_quirks_table[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; } } }