2025-04-29 15:11:07 +01:00
|
|
|
// 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/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;
|
|
|
|
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 <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 */
|
2025-04-29 15:11:08 +01:00
|
|
|
DEFINE_SCMI_QUIRK(clock_rates_triplet_out_of_spec, NULL, NULL, NULL);
|
2025-04-30 15:51:46 +02:00
|
|
|
DEFINE_SCMI_QUIRK(perf_level_get_fc_force, "Qualcomm", NULL, "0x20000-");
|
2025-04-29 15:11:07 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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[] = {
|
2025-04-29 15:11:08 +01:00
|
|
|
__DECLARE_SCMI_QUIRK_ENTRY(clock_rates_triplet_out_of_spec),
|
2025-04-30 15:51:46 +02:00
|
|
|
__DECLARE_SCMI_QUIRK_ENTRY(perf_level_get_fc_force),
|
2025-04-29 15:11:07 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|