// 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 " , " 0 x12000 - 0 x30000 " ,
* " 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 , 0 xFFFFFFFF ]
* " X " [ X , X ]
* " X - " [ X , 0 xFFFFFFFF ]
* " - 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 " , " 0 x12000 - 0 x30000 " ) ;
*
*
* A few compatibles provided to match against :
*
* DEFINE_SCMI_QUIRK ( my_new_issue , " Vend " , " SVend " , " 0 x12000 - 0 x30000 " ,
* " 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;
size_t len;
char *sep;
int ret;
quirk->start_range = 0 ;
quirk->end_range = 0 xFFFFFFFF;
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 */
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 ;
}
}
}
Messung V0.5 in Prozent C=97 H=93 G=94
¤ Dauer der Verarbeitung: 0.12 Sekunden
(vorverarbeitet am 2026-09-30)
¤
*© Formatika GbR, Deutschland