mirror of
				https://github.com/torvalds/linux.git
				synced 2025-10-31 08:38:45 +02:00 
			
		
		
		
	 572ce54639
			
		
	
	
		572ce54639
		
	
	
	
	
		
			
			The quirk version range is typically a string constant and must not be
modified (e.g. as it may be stored in read-only memory). Attempting
to do so can trigger faults such as:
  |  Unable to handle kernel write to read-only memory at virtual
  |  address ffffc036d998a947
Update the range parsing so that it operates on a copy of the version
range string, and mark all the quirk strings as const to reduce the
risk of introducing similar future issues.
Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220437
Fixes: 487c407d57 ("firmware: arm_scmi: Add common framework to handle firmware quirks")
Cc: stable@vger.kernel.org	# 6.16
Cc: Cristian Marussi <cristian.marussi@arm.com>
Reported-by: Jan Palus <jpalus@fastmail.com>
Signed-off-by: Johan Hovold <johan@kernel.org>
Message-Id: <20250829132152.28218-1-johan@kernel.org>
[sudeep.holla: minor commit message rewording; switch to cleanup helpers]
Signed-off-by: Sudeep Holla <sudeep.holla@arm.com>
		
	
			
		
			
				
	
	
		
			327 lines
		
	
	
	
		
			9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			327 lines
		
	
	
	
		
			9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // 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;
 | |
| 	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;
 | |
| 
 | |
| 	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;
 | |
| 		}
 | |
| 	}
 | |
| }
 |