Re: [PATCH v3 7/9] firmware: arm_scmi: Add ACPI PCC transport

Jonathan Cameron <[email protected]>
Newsgroups org.infradead.lists.linux-arm-kernel,org.kernel.vger.arm-scmi
Organization Qualcomm
Message-ID <[email protected]>
On Thu, 13 Aug 2026 12:33:02 +0100
Sudeep Holla <[email protected]> wrote:

> Introduce a new SCMI transport that uses ACPI PCCT (PCC) subspaces via
> the Linux PCC mailbox layer. Parse ACPI _DSD data to map protocol
> associations to PCC transport UIDs. Support common and
> protocol-exclusive A2P channels, plus optional common or
> protocol-exclusive P2A channels for notifications.
> 
> Key points:
> - new CONFIG_ARM_SCMI_TRANSPORT_PCC option
> - integration with SCMI core via scmi_desc and transport ops
> - response and notification fetch from PCC shared memory
> - ACPI device matching and registration via the ACPI transport macro
> 
> This enables SCMI to be exercised over PCC on ACPI platforms.
> 
> Signed-off-by: Sudeep Holla <[email protected]>
Hi Sudeep

This is quite dense and ACPI parsing code is always 'interesting'
Anyhow some comments inline

Jonathan

> ---
>  drivers/firmware/arm_scmi/common.h            |  11 +
>  drivers/firmware/arm_scmi/transports/Kconfig  |  13 +
>  drivers/firmware/arm_scmi/transports/Makefile |   2 +
>  drivers/firmware/arm_scmi/transports/pcc.c    | 791 ++++++++++++++++++++++++++
>  include/linux/scmi_protocol.h                 |   1 +
>  5 files changed, 818 insertions(+)
> 
> diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h
> index 1ab4543e0f4a..3a49ea40aea5 100644
> --- a/drivers/firmware/arm_scmi/common.h
> +++ b/drivers/firmware/arm_scmi/common.h
> @@ -468,6 +468,17 @@ struct scmi_transport_core_operations {
>  	const struct scmi_message_operations *msg;
>  };
>  
> +struct scmi_dsd_info {
> +	u32 protocol_id;
> +	const char *const property_name;
> +};
> +
> +static const struct scmi_dsd_info scmi_dsd_info_list[] __maybe_unused = {
> +	{ SCMI_PROTOCOL_BASE, "arm-arml0001-transport-pcc"},

For symmetry needs a space before }

> +	{ SCMI_PROTOCOL_POWERCAP, "arm-arml0001-protocol-pcap"},
> +	{ SCMI_PROTOCOL_TELEMETRY, "arm-arml0001-protocol-telemetry"},

I guess it is trivial but I'd have been tempted to add the transport first
then follow up with the new protocol as a separate patch.

> +};
> +
>  /**
>   * struct scmi_transport_handle  - Transport instance handle
>   * @supplier_get: A helper to retrieve the device descriptor, identifying the
> diff --git a/drivers/firmware/arm_scmi/transports/Kconfig b/drivers/firmware/arm_scmi/transports/Kconfig
> index 57eccf316e26..1054165576b3 100644
> --- a/drivers/firmware/arm_scmi/transports/Kconfig
> +++ b/drivers/firmware/arm_scmi/transports/Kconfig
> @@ -77,6 +77,19 @@ config ARM_SCMI_TRANSPORT_OPTEE
>  	  This driver can also be built as a module. If so, the module
>  	  will be called scmi_transport_optee.
>  
> +config ARM_SCMI_TRANSPORT_PCC
> +	tristate "SCMI transport based on ACPI PCC"
> +	depends on PCC
> +	select ARM_SCMI_HAVE_TRANSPORT
> +	default y
We almost never do default y except when papering over new symbols for things

that were always built before.  Why is it appropriate here?

> +	help
> +	  Enable ACPI PCC mailbox based transport for SCMI.
> +
> +	  If you want the ARM SCMI PROTOCOL stack to include support for a
> +	  transport based on mailboxes, answer Y.
> +	  This driver can also be built as a module. If so, the module
> +	  will be called scmi_transport_pcc.
> diff --git a/drivers/firmware/arm_scmi/transports/pcc.c b/drivers/firmware/arm_scmi/transports/pcc.c
> new file mode 100644
> index 000000000000..337d551e3ad8
> --- /dev/null
> +++ b/drivers/firmware/arm_scmi/transports/pcc.c

> +/*
> + * SCMI specification requires all parameters, message headers, return
> + * arguments or any protocol data to be expressed in little endian
> + * format only.
> + */
> +struct pcc_shared_mem {
> +	struct acpi_pcct_ext_pcc_shared_memory header;
> +	u8 msg_payload[];

Can we do __counted_by header.length?
I'm not sure if that works or not.

> +};


...

> +
> +static int
> +acpi_scmi_dsd_parse_transport_package(struct pcc_transport_map *map,
> +				      const union acpi_object *obj)
> +{
> +	const union acpi_object *elems;
> +	u32 revision, pkg_cnt;
> +	unsigned int common_a2p = 0, common_p2a = 0;
> +	int idx;
> +
> +	if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count < 2 ||
> +	    acpi_scmi_pkg_u32(obj, 0, &revision) ||
> +	    acpi_scmi_pkg_u32(obj, 1, &pkg_cnt))
> +		return -EINVAL;
> +	if (revision != SCMI_TRANSPORT_PACKAGE_MAX_VERSION)
> +		return -EINVAL;
> +	if (obj->package.count != pkg_cnt + 2)
> +		return -EINVAL;
> +
> +	for (idx = 0; idx < pkg_cnt; idx++) {

	for (int idx = 0; ...

> +		union acpi_object *pack = &obj->package.elements[idx + 2];
> +		struct pcc_transport *p, *tmp;
> +		u32 pcc_ss_id, uid;
> +		u64 flags;
> +
> +		elems = acpi_scmi_pkg_elements(pack, 3);
> +		if (!elems) {
> +			pr_info("Invalid transport properties pkg %d\n", idx);
> +			return -EINVAL;
> +		}
> +		if (acpi_scmi_pkg_u32(pack, 0, &pcc_ss_id) ||
> +		    acpi_scmi_pkg_u32(pack, 1, &uid) ||
> +		    acpi_scmi_pkg_u64(pack, 2, &flags))
> +			return -EINVAL;
> +		if (flags & ~SCMI_TRANSPORT_FLAGS_MASK)
> +			return -EINVAL;
> +
> +		hash_for_each_possible(map->table, tmp, hnode, uid) {
> +			if (tmp->uid == uid) {
> +				pr_info("Duplicate UID %d\n", uid);
> +				return -EEXIST;
> +			}
> +		}
> +
> +		p = kzalloc(sizeof(*p), GFP_KERNEL);
> +		if (!p)
> +			return -ENOMEM;
> +
> +		p->uid = uid;
> +		p->pcc_ss_id = pcc_ss_id;
> +		p->flags = flags;
> +		if (p->flags & SCMI_TRANSPORT_SHARED_CHANNEL) {
> +			p->protocol_id = SCMI_PROTOCOL_BASE;
> +			if (p->flags & SCMI_TRANSPORT_P2A_CHANNEL)
> +				common_p2a++;
> +			else
> +				common_a2p++;
> +		}
> +
> +		hash_add(map->table, &p->hnode, uid);
> +	}
> +
> +	if (common_a2p != 1 || common_p2a > 1)
> +		return -EINVAL;

If you are just going to fail on larger counts, why not do it earlier
as you do in some of the other similar functions when a repeat is seen?
If they need to be in the hash table anyway add a comment.

> +
> +	return 0;
> +}
> +
> +static int
> +acpi_scmi_dsd_parse_protocol_subpackage(struct pcc_transport_map *map,
> +					const union acpi_object *obj,
> +					int prot_id)
> +{
> +	bool found, tx_found = false, rx_found = false;
> +	u32 uid;
> +	int idx, ret = 0;
> +	struct pcc_transport *p;
> +	unsigned int pkg_cnt = obj->package.count;

Not sure if you've standardized on an ordering I can't spot for declarations.
If not pick one for the whole file.

> +
> +	if (pkg_cnt > 2) {
> +		pr_warn("Only 2 channels: one Tx and one Rx needed\n");
Not sure that's helpful.  "%u channels found, only 2 needed ... 


> +		return -EINVAL;
> +	}
> +

	for (u32 idx = 0; ...

> +	for (idx = 0; idx < pkg_cnt; idx++) {
> +		union acpi_object *pack = &obj->package.elements[idx];
> +		u64 flags;
> +
> +		if (!acpi_scmi_pkg_elements(pack, 2) ||

figure out how to avoid those magic 2s.

> +		    acpi_scmi_pkg_u32(pack, 0, &uid) ||
> +		    acpi_scmi_pkg_u64(pack, 1, &flags))
> +			return -EINVAL;
> +		if (flags)
> +			return -EINVAL;
> +
> +		found = false;
> +		hash_for_each_possible(map->table, p, hnode, uid) {
> +			if (p->uid != uid)
> +				continue;
> +
> +			found = true;
> +			if (p->flags & SCMI_TRANSPORT_SHARED_CHANNEL) {
> +				pr_info("Invalid! %d channel is shared\n",
> +					p->pcc_ss_id);
> +				ret = -EINVAL;
> +				break;
> +			}
> +			if (p->protocol_id && p->protocol_id != prot_id)
> +				return -EINVAL;
> +
> +			if (p->flags & SCMI_TRANSPORT_P2A_CHANNEL) {
> +				if (rx_found)
> +					return -EINVAL;
> +				rx_found = true;
> +			} else {
> +				if (tx_found)
> +					return -EINVAL;
> +				tx_found = true;
> +			}
> +			p->protocol_id = prot_id;
> +			break;
> +		}
> +
> +		if (ret)
> +			return ret;
Might as well return above.  You do in some paths already.

> +		if (!found)
> +			return -ENOENT;
> +	}
> +
> +	return ret;
Can you get here with ret != 0?
	return 0 probably as this is the normal exit path.

> +}
> +
> +static int
> +acpi_scmi_dsd_parse_protocol_package(struct pcc_transport_map *map,
> +				     const union acpi_object *obj, int prot_id)
> +{
> +	const union acpi_object *elems;
> +	const union acpi_object *pack;
> +	u32 revision;
> +	int ret;
> +
> +	elems = acpi_scmi_pkg_elements(obj, 3);
> +	if (!elems || acpi_scmi_pkg_u32(obj, 0, &revision))
> +		return -EINVAL;
> +
> +	pack = &elems[1];
> +
> +	if (revision != SCMI_PROTOCOL_PACKAGE_MAX_VERSION)
> +		return -EINVAL;
> +
> +	if (pack->type != ACPI_TYPE_PACKAGE) {
> +		pr_info("Invalid protocol transport package\n");
> +		return -EINVAL;
> +	}
> +
> +	/* Empty protocol specific transport package allowed */

For a statement like that I'd kind of expect a spec reference.

> +	if (pack->package.count != 0) {
> +		ret = acpi_scmi_dsd_parse_protocol_subpackage(map, pack, prot_id);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	pack = &elems[2];
> +	if (pack->type != ACPI_TYPE_PACKAGE) {
> +		pr_info("Invalid protocol transport association package\n");
> +		return -EINVAL;
> +	}
> +
> +	if (pack->package.count != 0) {
> +		pr_info("Non-empty association package not supported\n");
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}

> +
> +static int acpi_scmi_parse_properties(struct pcc_transport_map *map,
> +				      const union acpi_object *properties)
> +{
> +	bool transport_found = false;
> +	int i;
> +
> +	if (properties->type != ACPI_TYPE_PACKAGE)
> +		return -EINVAL;
> +
> +	for (i = 0; i < properties->package.count; i++) {
> +		const union acpi_object *v;
> +		const char *name;
> +		int prot_id, ret;
> +
> +		ret = acpi_scmi_property(properties, i, &name, &v);
> +		if (ret)
> +			return ret;
> +
> +		prot_id = acpi_scmi_lookup_protocol_id(name);
> +		if (prot_id < 0)
> +			continue;
> +		if (prot_id != SCMI_PROTOCOL_BASE)
> +			continue;
> +		if (v->type != ACPI_TYPE_PACKAGE)
> +			return -EINVAL;
> +		if (transport_found)
> +			return -EEXIST;
> +
> +		ret = acpi_scmi_dsd_parse_transport_package(map, v);
> +		if (ret)
> +			return ret;
> +		transport_found = true;
> +	}
> +
> +	if (!transport_found)
> +		return -ENOENT;

This double loop needs a few more comments.  Why do we need to handle
the base protocol completely first? 

Maybe can factor it out to a helper that takes bool unique, bool base?
then we just get 2 calls to that.

> +
> +	for (i = 0; i < properties->package.count; i++) {
> +		const union acpi_object *v;
> +		const char *name;
> +		int prot_id, ret;
> +
> +		ret = acpi_scmi_property(properties, i, &name, &v);
> +		if (ret)
> +			return ret;
> +
> +		prot_id = acpi_scmi_lookup_protocol_id(name);

> +		if (prot_id < 0 || prot_id == SCMI_PROTOCOL_BASE)
> +			continue;
> +		if (v->type != ACPI_TYPE_PACKAGE)
> +			return -EINVAL;
> +
> +		ret = acpi_scmi_dsd_parse_protocol_package(map, v, prot_id);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int acpi_scmi_namespace_fwnode_parse(struct fwnode_handle *fwnode,
> +					    struct pcc_transport_map *map)
> +{
> +	struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
> +	struct acpi_device *adev = to_acpi_device_node(fwnode);
> +	union acpi_object *desc;
> +	acpi_status status;
> +	int i, ret = -ENOENT;

ret is always overwritten I think.

> +
> +	if (!adev->handle)
> +		return -EINVAL;
> +
> +	status = acpi_evaluate_object_typed(adev->handle, "_DSD", NULL, &buf,
> +					    ACPI_TYPE_PACKAGE);
> +	if (ACPI_FAILURE(status))
> +		return -EINVAL;
> +
> +	desc = buf.pointer;
> +	if (desc->package.count % 2)

		ret = -EINVAL;
		goto out_free;
	}

> +		goto out_free_inval;
> +
> +	/* Look for the device properties GUID. */
> +	for (i = 0; i < desc->package.count; i += 2) {
	for (int i = 0; i < ...

acceptable in kernel these days and keeps scope tight.
Any way to justify that 2 as sizeof of something?  If not
maybe a define is appropriate.
(applies above as well.)

> +		const union acpi_object *guid;
> +		const union acpi_object *properties;
> +
> +		guid = &desc->package.elements[i];
> +		properties = &desc->package.elements[i + 1];
> +
> +		/*
> +		 * The first element must be a GUID and the second one must be
> +		 * a package.
> +		 */
> +		if (guid->type != ACPI_TYPE_BUFFER ||
> +		    guid->buffer.length != UUID_SIZE ||
> +		    properties->type != ACPI_TYPE_PACKAGE)
> +			continue;
> +
> +		if (!guid_equal((guid_t *)guid->buffer.pointer,
> +				&acpi_scmi_uuid))
> +			continue;
> +
> +		ret = acpi_scmi_parse_properties(map, properties);
> +		goto out_free;

		break maybe if this doesn't get more complex in later
patches.

> +	}
> +
> +out_free:
> +	ACPI_FREE(buf.pointer);
> +	return ret;
> +out_free_inval:
> +	ret = -EINVAL;
> +	goto out_free;

Two different error paths and one that folds back is not a nice to
read code structure.  Particularly as second one only sets a return
value.  Just set that at the callers.

> +}


> +static
> +struct pcc_transport_map *pcc_transport_map_get(struct fwnode_handle *fwnode)
> +{
> +	struct pcc_transport_map *map;
> +	int ret;
> +
> +	map = pcc_transport_map_find(fwnode);
> +	if (map)
> +		return map;
> +
> +	map = kzalloc_obj(*map, GFP_KERNEL);
> +	if (!map)
> +		return ERR_PTR(-ENOMEM);
> +
> +	hash_init(map->table);
> +	ret = acpi_scmi_namespace_fwnode_parse(fwnode, map);
> +	if (ret)
> +		goto err_free_map;
> +
> +	ret = pcc_transport_map_validate(map);
> +	if (ret)
> +		goto err_free_map;
> +
> +	map->fwnode = fwnode_handle_get(fwnode);
> +	list_add_tail(&map->node, &pcc_transport_maps);
> +
> +	return map;
> +
> +err_free_map:
> +	acpi_scmi_destroy_transport_map(map);

Personally I'd prefer seeing each step being unwound only when necessary.
So break it out here as as series of labels.


> +	return ERR_PTR(ret);
> +}
> +
> +static int pcc_lookup_ss_id(struct pcc_transport_map *map, u32 prot_id, bool tx)
> +{
> +	struct pcc_transport *p;
> +	int idx;
> +
> +	hash_for_each(map->table, idx, p, hnode) {
> +		if (p->protocol_id != prot_id)
> +			continue;
> +
> +		if ((!tx && (p->flags & SCMI_TRANSPORT_P2A_CHANNEL)) ||
> +		    (tx && !(p->flags & SCMI_TRANSPORT_P2A_CHANNEL)))
> +			return p->pcc_ss_id;
> +	}
> +
> +	return -ENOENT;
> +}
> +
> +static int pcc_get_ss_id(struct fwnode_handle *fwnode, u32 prot_id, bool tx)
> +{
> +	struct pcc_transport_map *map;
> +	int ret;
> +
> +	if (!fwnode)
> +		return -EINVAL;
> +
> +	mutex_lock(&pcc_transport_maps_lock);
	
	guard(mutex)(&pcc_transport_maps_lock);

> +	map = pcc_transport_map_get(fwnode);
> +	if (IS_ERR(map))
> +		ret = PTR_ERR(map);
		return PTR_ERR(map)

	return pcc_lookup_ss_id(map, prot_id, tx);

> +	else
> +		ret = pcc_lookup_ss_id(map, prot_id, tx);
> +	mutex_unlock(&pcc_transport_maps_lock);
> +
> +	return ret;
> +}

> +
> +static int pcc_chan_free(int id, void *p, void *data)
> +{
> +	struct scmi_chan_info *cinfo = p;
> +	struct scmi_pcc *smbox = cinfo->transport_info;
> +
> +	if (smbox && !IS_ERR(smbox->pchan)) {

Maybe an early exit is neater?

	if (!smbox || IS_ERR(smbox->pchan)
		return 0;

> +		pcc_mbox_free_channel(smbox->pchan);
> +		cinfo->transport_info = NULL;
> +		smbox->pchan = NULL;
> +		smbox->cinfo = NULL;
> +	}
> +
> +	return 0;
> +}

> +static void pcc_fetch_response(struct scmi_chan_info *cinfo,
> +			       struct scmi_xfer *xfer)
> +{
> +	struct scmi_pcc *smbox = cinfo->transport_info;
> +	struct pcc_shared_mem __iomem *shmem = smbox->pchan->shmem;
> +	size_t len = ioread32(&shmem->header.length);
> +
> +	xfer->hdr.status = ioread32(shmem->msg_payload);
> +	/* Skip the length of header and status in shmem area i.e 8 bytes */
> +	xfer->rx.len = min_t(size_t, xfer->rx.len, len > 8 ? len - 8 : 0);
> +
> +	/* Take a copy to the rx buffer.. */

As below - that bit is obvious.

> +	memcpy_fromio(xfer->rx.buf, shmem->msg_payload + 4, xfer->rx.len);

So you compute the length skipping 8 but then copy 4 in.  That needs an explanatory
comment if correct.

> +}
> +
> +static void pcc_fetch_notification(struct scmi_chan_info *cinfo, size_t max_len,
> +				   struct scmi_xfer *xfer)
> +{
> +	struct scmi_pcc *smbox = cinfo->transport_info;
> +	struct pcc_shared_mem __iomem *shmem = smbox->pchan->shmem;
> +	size_t len = ioread32(&shmem->header.length);
> +
> +	/* Skip only the length of header in shmem area i.e 4 bytes */

Ideally get that header size from a define rather than magic 4.

> +	xfer->rx.len = min_t(size_t, max_len, len > 4 ? len - 4 : 0);

min() preferred unless we are hitting one of the weird corner cases (don't think so)

> +
> +	/* Take a copy to the rx buffer.. */

Kind of obvious - maybe say why if that is useful, or drop the comment.

> +	memcpy_fromio(xfer->rx.buf, shmem->msg_payload, xfer->rx.len);
> +}
> +
> +static const struct scmi_transport_ops scmi_pcc_ops = {
> +	.chan_available = pcc_chan_available,
> +	.chan_setup = pcc_chan_setup,
> +	.chan_free = pcc_chan_free,
> +	.send_message = pcc_send_message,
> +	.fetch_response = pcc_fetch_response,
> +	.fetch_notification = pcc_fetch_notification,
> +};
> +
> +static struct scmi_desc scmi_pcc_desc = {
> +	.ops = &scmi_pcc_ops,
> +	.max_rx_timeout_ms = 30,	/* We may increase this if required */

That's always true - so what does the comment bring us?

> +	.max_msg = 20,		/* Limited by MBOX_TX_QUEUE_LEN */

If this is relevant to this driver, why can't see see it via a suitable header?
Feels to me like this is in the wrong place or needs a query interface.

> +	.max_msg_size = SCMI_SHMEM_MAX_PAYLOAD_SIZE - 12,
> +};
> +
> +static const struct acpi_device_id scmi_acpi_ids[] = {
> +	{ "ARML0001", 0 },

Uwe is driving an effort to make these all named initializers. 
+ Don't set anything you don't use as it makes refactors a pain.
Uwe has also been deleting those throughout the kernel!

> +	{ }
> +};
> +
> +MODULE_DEVICE_TABLE(acpi, scmi_acpi_ids);
> +
> +DEFINE_SCMI_ACPI_TRANSPORT_DRIVER(scmi_pcc, scmi_pcc_driver,
> +				  scmi_pcc_desc, scmi_acpi_ids, core);
> +
> +static int __init scmi_pcc_init(void)
> +{
> +	return platform_driver_register(&scmi_pcc_driver);
> +}
> +
> +static void __exit scmi_pcc_exit(void)
> +{
> +	platform_driver_unregister(&scmi_pcc_driver);
> +
> +	mutex_lock(&pcc_transport_maps_lock);

I'd move the locking into acpi_scmi_clear_transport_maps()

I'm not immediately understanding why, when all setup in this
driver is associated with the registered driver, this bit
of tear down can't be done as part of the driver remove.


> +	acpi_scmi_clear_transport_maps();
> +	mutex_unlock(&pcc_transport_maps_lock);
> +}
> +module_init(scmi_pcc_init);
> +module_exit(scmi_pcc_exit);
> +
> +MODULE_AUTHOR("Sudeep Holla <[email protected]>");
> +MODULE_DESCRIPTION("SCMI ACPI PCC Transport driver");
> +MODULE_LICENSE("GPL");
> diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
> index 5ab73b1ab9aa..02cf04543151 100644
> --- a/include/linux/scmi_protocol.h
> +++ b/include/linux/scmi_protocol.h
> @@ -930,6 +930,7 @@ enum scmi_std_protocol {
>  	SCMI_PROTOCOL_VOLTAGE = 0x17,
>  	SCMI_PROTOCOL_POWERCAP = 0x18,
>  	SCMI_PROTOCOL_PINCTRL = 0x19,
> +	SCMI_PROTOCOL_TELEMETRY = 0x1B,
>  };
>  
>  enum scmi_system_events {
>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.