[PATCH] firmware: arm_scmi: Add support for multiple vendors custom protocols

Sibi Sankar quic_sibis at quicinc.com
Wed Feb 28 09:06:43 PST 2024



On 2/22/24 03:34, Cristian Marussi wrote:
> Add a mechanism to be able to tag vendor protocol modules at compile-time
> with a vendor/sub_vendor string and an implementation version and then to
> choose to load, at run-time, only those vendor protocol modules matching
> as close as possible the vendor/subvendor identification advertised by
> the SCMI platform server.
> 
> In this way, any in-tree existent vendor protocol module can be build and
> shipped by default in a single kernel image, even when using the same
> clashing protocol identification numbers, since the SCMI core will take
> care at run-time to load only the ones pertinent to the running system.
> 
> Signed-off-by: Cristian Marussi <cristian.marussi at arm.com>

Cristian,

Thanks for the patch.


> ---
> Hi all,
> 
> this is meant to address the possibility of having multiple vendors
> implementing distinct SCMI vendor protocols with possibly the same
> overlapping protocol number without the need of crafting the Kconfig
> at compile time to include only wanted protos
> (and without the need for a lottery :P)
> 
> Basic idea is simple:
> 
> - vendor protos HAS to be 'tagged' at build time with a vendor_id
> - vendor protos COULD also optionally be tagged at build time with
> sub_vendor_id and implemetation version
> 
> - at init all the build vendor protos are registerd with the SCMI core
>    using a key derived from the above tags
> 
> - at SCMI probe time the platform is identified via Base protocol as
>    usual and all the required vendor protos (i.e. IDs defined in the DT
>    as usual) are loaded after a lookup process based on the following rules:
> 
>    + protocol DB is searched using the platform/Base runtime provided tags
> 
>    	<vendor> / <sub_vendor> / <impl_ver>
> 
>      using the the following search logic (keys), first match:
> 
>       1. proto_id / <vendor_id> / <sub_vendor_id> / <impl_ver>
> 
>       2. proto_id / <vendor_id> / <sub_vendor_id> / 0
> 
>       3. proto_id / <vendor_id> / NULL / 0
> 
>    IOW, closest match, depending on how much fine grained is your
>    protocol tuned (tagged) for the platform.
> 
>    I am doubtful about the usage of <impl_ver>, and tempted to drop it
>    since we have anyway protocol version and NEGOTIATE_PROTOCOL_VERSION
>    to deal with slight difference in fw revision...
> 
> Based on sudeep/for-linux-next on top of
> 
> 	1c2c88cfcb2b ("clk: scmi: Support get/set duty_cycle operations")
> 
> Minimally tested ....
> 
>    Any feedback welcome
> 
>    Thanks,
>    Cristian
> ---
>   drivers/firmware/arm_scmi/driver.c    | 166 ++++++++++++++++++++++----
>   drivers/firmware/arm_scmi/protocols.h |   5 +
>   2 files changed, 149 insertions(+), 22 deletions(-)
> 
> diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
> index 34d77802c990..8fb2903698c9 100644
> --- a/drivers/firmware/arm_scmi/driver.c
> +++ b/drivers/firmware/arm_scmi/driver.c
> @@ -33,6 +33,7 @@
>   #include <linux/processor.h>
>   #include <linux/refcount.h>
>   #include <linux/slab.h>
> +#include <linux/xarray.h>
>   
>   #include "common.h"
>   #include "notify.h"
> @@ -44,8 +45,7 @@
>   
>   static DEFINE_IDA(scmi_id);
>   
> -static DEFINE_IDR(scmi_protocols);
> -static DEFINE_SPINLOCK(protocol_lock);
> +static DEFINE_XARRAY(scmi_protocols);
>   
>   /* List of all SCMI devices active in system */
>   static LIST_HEAD(scmi_list);
> @@ -194,11 +194,90 @@ struct scmi_info {
>   #define bus_nb_to_scmi_info(nb)	container_of(nb, struct scmi_info, bus_nb)
>   #define req_nb_to_scmi_info(nb)	container_of(nb, struct scmi_info, dev_req_nb)
>   
> -static const struct scmi_protocol *scmi_protocol_get(int protocol_id)
> +static unsigned long
> +scmi_vendor_protocol_signature(unsigned protocol_id, char *vendor_id,
> +			       char *sub_vendor_id, u32 impl_ver)
>   {
> -	const struct scmi_protocol *proto;
> +	char *signature, *p;
> +	unsigned long hash = 0;
>   
> -	proto = idr_find(&scmi_protocols, protocol_id);
> +	/* vendor_id/sub_vendor_id guaranteed <= SCMI_SHORT_NAME_MAX_SIZE */
> +	signature = kasprintf(GFP_KERNEL, "%02X|%s|%s|0x%08X", protocol_id,
> +			      vendor_id ?: "", sub_vendor_id ?: "", impl_ver);
> +	if (!signature)
> +		return 0;
> +
> +	p = signature;
> +	while (*p)
> +		hash = partial_name_hash(tolower(*p++), hash);
> +	hash = end_name_hash(hash);
> +
> +	kfree(signature);
> +
> +	return hash;
> +}
> +
> +static unsigned long
> +scmi_protocol_key_calculate(int protocol_id, char *vendor_id,
> +			    char *sub_vendor_id, u32 impl_ver)
> +{
> +	if (protocol_id < SCMI_PROTOCOL_VENDOR)
> +		return protocol_id;
> +	else
> +		return scmi_vendor_protocol_signature(protocol_id, vendor_id,
> +						      sub_vendor_id, impl_ver);
> +}
> +
> +static const struct scmi_protocol *
> +scmi_vendor_protocol_lookup(int protocol_id, char *vendor_id,
> +			    char *sub_vendor_id, u32 impl_ver)
> +{
> +	unsigned long key;
> +	struct scmi_protocol *proto = NULL;
> +
> +	/* Searching for closest match ...*/
> +	key = scmi_protocol_key_calculate(protocol_id, vendor_id,
> +					  sub_vendor_id, impl_ver);
> +	if (key)
> +		proto = xa_load(&scmi_protocols, key);
> +
> +	if (proto)
> +		return proto;
> +
> +	/* Any match on vendor/sub_vendor ? */
> +	if (impl_ver) {
> +		key = scmi_protocol_key_calculate(protocol_id, vendor_id,
> +						  sub_vendor_id, 0);
> +		if (key)
> +			proto = xa_load(&scmi_protocols, key);
> +
> +		if (proto)
> +			return proto;
> +	}
> +
> +	/* Any match on just the vendor ? */
> +	if (sub_vendor_id) {
> +		key = scmi_protocol_key_calculate(protocol_id, vendor_id,
> +						  NULL, 0);
> +		if (key)
> +			proto = xa_load(&scmi_protocols, key);
> +	}
> +
> +	return proto;
> +}
> +
> +static const struct scmi_protocol *
> +scmi_protocol_get(int protocol_id, struct scmi_revision_info *version)
> +{
> +	const struct scmi_protocol *proto = NULL;
> +
> +	if (protocol_id < SCMI_PROTOCOL_VENDOR)
> +		proto = xa_load(&scmi_protocols, protocol_id);
> +	else
> +		proto = scmi_vendor_protocol_lookup(protocol_id,
> +						    version->vendor_id,
> +						    version->sub_vendor_id,
> +						    version->impl_ver);
>   	if (!proto || !try_module_get(proto->owner)) {
>   		pr_warn("SCMI Protocol 0x%x not found!\n", protocol_id);
>   		return NULL;
> @@ -206,21 +285,47 @@ static const struct scmi_protocol *scmi_protocol_get(int protocol_id)
>   
>   	pr_debug("Found SCMI Protocol 0x%x\n", protocol_id);
>   
> +	if (protocol_id >= SCMI_PROTOCOL_VENDOR)
> +		pr_info("Loaded SCMI Vendor Protocol 0x%x - %s %s %X\n",
> +			protocol_id, proto->vendor_id ?: "",
> +			proto->sub_vendor_id ?: "", proto->impl_ver);
> +
>   	return proto;
>   }
>   
> -static void scmi_protocol_put(int protocol_id)
> +static void scmi_protocol_put(const struct scmi_protocol *proto)
>   {
> -	const struct scmi_protocol *proto;
> -
> -	proto = idr_find(&scmi_protocols, protocol_id);
>   	if (proto)
>   		module_put(proto->owner);
>   }
>   
> +static int scmi_vendor_protocol_check(const struct scmi_protocol *proto)
> +{
> +	if (!proto->vendor_id) {
> +		pr_err("missing vendor_id for protocol 0x%x\n", proto->id);
> +		return -EINVAL;
> +	}
> +
> +	if (proto->vendor_id &&

You can drop ^^, since you've already checked for it.

Reviewed-and-Tested-by: Sibi Sankar <quic_sibis at quicinc.com>

> +	    strlen(proto->vendor_id) >= SCMI_SHORT_NAME_MAX_SIZE) {
> +		pr_err("malformed vendor_id for protocol 0x%x\n", proto->id);
> +		return -EINVAL;
> +	}
> +
> +	if (proto->sub_vendor_id &&
> +	    strlen(proto->sub_vendor_id) >= SCMI_SHORT_NAME_MAX_SIZE) {
> +		pr_err("malformed sub_vendor_id for protocol 0x%x\n",
> +		       proto->id);
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
>   int scmi_protocol_register(const struct scmi_protocol *proto)
>   {
>   	int ret;
> +	unsigned long key;
>   
>   	if (!proto) {
>   		pr_err("invalid protocol\n");
> @@ -232,12 +337,23 @@ int scmi_protocol_register(const struct scmi_protocol *proto)
>   		return -EINVAL;
>   	}
>   
> -	spin_lock(&protocol_lock);
> -	ret = idr_alloc(&scmi_protocols, (void *)proto,
> -			proto->id, proto->id + 1, GFP_ATOMIC);
> -	spin_unlock(&protocol_lock);
> -	if (ret != proto->id) {
> -		pr_err("unable to allocate SCMI idr slot for 0x%x - err %d\n",
> +	if (proto->id >= SCMI_PROTOCOL_VENDOR &&
> +	    scmi_vendor_protocol_check(proto))
> +		return -EINVAL;
> +
> +	/*
> +	 * Calculate a protocol key to register this protocol with the core;
> +	 * key value 0 is considered invalid.
> +	 */
> +	key = scmi_protocol_key_calculate(proto->id, proto->vendor_id,
> +					  proto->sub_vendor_id,
> +					  proto->impl_ver);
> +	if (!key)
> +		return -EINVAL;
> +
> +	ret = xa_insert(&scmi_protocols, key, (void *)proto, GFP_KERNEL);
> +	if (ret) {
> +		pr_err("unable to allocate SCMI protocol slot for 0x%x - err %d\n",
>   		       proto->id, ret);
>   		return ret;
>   	}
> @@ -250,9 +366,15 @@ EXPORT_SYMBOL_GPL(scmi_protocol_register);
>   
>   void scmi_protocol_unregister(const struct scmi_protocol *proto)
>   {
> -	spin_lock(&protocol_lock);
> -	idr_remove(&scmi_protocols, proto->id);
> -	spin_unlock(&protocol_lock);
> +	unsigned long key;
> +
> +	key = scmi_protocol_key_calculate(proto->id, proto->vendor_id,
> +					  proto->sub_vendor_id,
> +					  proto->impl_ver);
> +	if (!key)
> +		return;
> +
> +	xa_erase(&scmi_protocols, key);
>   
>   	pr_debug("Unregistered SCMI Protocol 0x%x\n", proto->id);
>   }
> @@ -1888,7 +2010,7 @@ scmi_alloc_init_protocol_instance(struct scmi_info *info,
>   	/* Protocol specific devres group */
>   	gid = devres_open_group(handle->dev, NULL, GFP_KERNEL);
>   	if (!gid) {
> -		scmi_protocol_put(proto->id);
> +		scmi_protocol_put(pi->proto);
>   		goto out;
>   	}
>   
> @@ -1952,7 +2074,7 @@ scmi_alloc_init_protocol_instance(struct scmi_info *info,
>   
>   clean:
>   	/* Take care to put the protocol module's owner before releasing all */
> -	scmi_protocol_put(proto->id);
> +	scmi_protocol_put(proto);
>   	devres_release_group(handle->dev, gid);
>   out:
>   	return ERR_PTR(ret);
> @@ -1986,7 +2108,7 @@ scmi_get_protocol_instance(const struct scmi_handle *handle, u8 protocol_id)
>   		const struct scmi_protocol *proto;
>   
>   		/* Fails if protocol not registered on bus */
> -		proto = scmi_protocol_get(protocol_id);
> +		proto = scmi_protocol_get(protocol_id, &info->version);
>   		if (proto)
>   			pi = scmi_alloc_init_protocol_instance(info, proto);
>   		else
> @@ -2041,7 +2163,7 @@ void scmi_protocol_release(const struct scmi_handle *handle, u8 protocol_id)
>   
>   		idr_remove(&info->protocols, protocol_id);
>   
> -		scmi_protocol_put(protocol_id);
> +		scmi_protocol_put(pi->proto);
>   
>   		devres_release_group(handle->dev, gid);
>   		dev_dbg(handle->dev, "De-Initialized protocol: 0x%X\n",
> diff --git a/drivers/firmware/arm_scmi/protocols.h b/drivers/firmware/arm_scmi/protocols.h
> index 693019fff0f6..cb8dbc93584b 100644
> --- a/drivers/firmware/arm_scmi/protocols.h
> +++ b/drivers/firmware/arm_scmi/protocols.h
> @@ -29,6 +29,8 @@
>   #define PROTOCOL_REV_MAJOR(x)	((u16)(FIELD_GET(PROTOCOL_REV_MAJOR_MASK, (x))))
>   #define PROTOCOL_REV_MINOR(x)	((u16)(FIELD_GET(PROTOCOL_REV_MINOR_MASK, (x))))
>   
> +#define SCMI_PROTOCOL_VENDOR	0x80
> +
>   enum scmi_common_cmd {
>   	PROTOCOL_VERSION = 0x0,
>   	PROTOCOL_ATTRIBUTES = 0x1,
> @@ -330,6 +332,9 @@ struct scmi_protocol {
>   	const void				*ops;
>   	const struct scmi_protocol_events	*events;
>   	unsigned int				supported_version;
> +	u32					impl_ver;
> +	char					*vendor_id;
> +	char					*sub_vendor_id;
>   };
>   
>   #define DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(name, proto)	\



More information about the linux-arm-kernel mailing list