* Re: [PATCH net-next v6 1/2] net: ti: icssg-prueth: Add Frame Preemption MAC Merge support
From: Maxime Chevallier @ 2026-05-25 19:18 UTC (permalink / raw)
To: Meghana Malladi, liuhangbin, h-mittal1, haokexin, vadim.fedorenko,
devnexen, horms, jacob.e.keller, arnd, afd, basharath, parvathi,
vladimir.oltean, rogerq, danishanwar, pabeni, kuba, edumazet,
davem, andrew+netdev
Cc: linux-arm-kernel, netdev, linux-kernel, srk, Vignesh Raghavendra
In-Reply-To: <20260525182700.3135858-2-m-malladi@ti.com>
Hi Meghana,
On 5/25/26 20:26, Meghana Malladi wrote:
> From: MD Danish Anwar <danishanwar@ti.com>
>
> Introduce QoS infrastructure for Frame Preemption (FPE) support in
> the ICSSG Ethernet driver.
>
> prueth_qos_iet tracks FPE enable/active state and verify state machine
> status via firmware-reported enum icssg_ietfpe_verify_states.
> icssg_config_ietfpe() configures IET FPE in firmware, triggers
> verify state machine based on ethtool MAC Merge parameters.
> Polls firmware verify status up to 3 times with verify_time_ms intervals
> and driver handles timeout by logging error and returning.
> In case of any failure during configuration for enable/disable,
> IET FPE falls back to disabled state.
>
> For MQPRIO qdisc support all queues are express by default later
> gets override by user-provided preemptible_tcs bitmask via tc qdisc mask
> Preempt mask configuration: Maps traffic classes to queue express/preemptible
> state and applied only when FPE is active (Tx enabled)
>
> Verify state machine re-triggers on link up/down events based on
> fpe_enabled and fpe_active flags, and for memory protection, fpe_lock
> serializes all FPE state mutations, preventing races between ethtool
> config, qdisc setup, and link events
>
> Signed-off-by: MD Danish Anwar <danishanwar@ti.com>
> Signed-off-by: Meghana Malladi <m-malladi@ti.com>
There's quite a lot of checkpatch output for this patch, I'll let you
browse through it :)
Also keep in mind that we still recommend 80 chars on net.
Besides that, I have a few comments, see below :)
> ---
>
> v6-v5:
> - Balance fpe_lock mutex lifecycle on all error paths
> - Remove deadcode inside icssg_iet_set_preempt_mask()
> - Add fallback label inside icssg_config_ietfpe() to fix
> stale state machine handling.
> - Replace netdev_err with netdev_info and netdev_dbg wherever
> applicable
> - Remove EXPORT_SYMBOL_GPL for icssg_config_ietfpe()
> - Remove redundant code inside icssg_qos_init()
> - qdisc deletion path clears per-queue map as well.
> - Protect all read/writes to p_mqprio with a mutex
> All the above changes address the comments raised by sashiko
>
> drivers/net/ethernet/ti/Makefile | 3 +-
> drivers/net/ethernet/ti/icssg/icssg_common.c | 1 +
> drivers/net/ethernet/ti/icssg/icssg_config.h | 9 -
> drivers/net/ethernet/ti/icssg/icssg_prueth.c | 6 +
> drivers/net/ethernet/ti/icssg/icssg_prueth.h | 2 +
> drivers/net/ethernet/ti/icssg/icssg_qos.c | 269 +++++++++++++++++++
> drivers/net/ethernet/ti/icssg/icssg_qos.h | 66 +++++
> 7 files changed, 346 insertions(+), 10 deletions(-)
> create mode 100644 drivers/net/ethernet/ti/icssg/icssg_qos.c
> create mode 100644 drivers/net/ethernet/ti/icssg/icssg_qos.h
>
> diff --git a/drivers/net/ethernet/ti/Makefile b/drivers/net/ethernet/ti/Makefile
> index f4276c9a77620..d19bcd25c9d07 100644
> --- a/drivers/net/ethernet/ti/Makefile
> +++ b/drivers/net/ethernet/ti/Makefile
> @@ -46,6 +46,7 @@ icssg-y := icssg/icssg_common.o \
> icssg/icssg_config.o \
> icssg/icssg_mii_cfg.o \
> icssg/icssg_stats.o \
> - icssg/icssg_ethtool.o
> + icssg/icssg_ethtool.o \
> + icssg/icssg_qos.o
>
> obj-$(CONFIG_TI_ICSS_IEP) += icssg/icss_iep.o
> diff --git a/drivers/net/ethernet/ti/icssg/icssg_common.c b/drivers/net/ethernet/ti/icssg/icssg_common.c
> index a28a608f9bf4b..c3ee97e96cd50 100644
> --- a/drivers/net/ethernet/ti/icssg/icssg_common.c
> +++ b/drivers/net/ethernet/ti/icssg/icssg_common.c
> @@ -1724,6 +1724,7 @@ void prueth_netdev_exit(struct prueth *prueth,
>
> netif_napi_del(&emac->napi_rx);
>
> + mutex_destroy(&emac->qos.iet.fpe_lock);
> pruss_release_mem_region(prueth->pruss, &emac->dram);
> free_netdev(emac->ndev);
> prueth->emac[mac] = NULL;
> diff --git a/drivers/net/ethernet/ti/icssg/icssg_config.h b/drivers/net/ethernet/ti/icssg/icssg_config.h
> index 60d69744ffae2..1ac202f855ed4 100644
> --- a/drivers/net/ethernet/ti/icssg/icssg_config.h
> +++ b/drivers/net/ethernet/ti/icssg/icssg_config.h
> @@ -323,13 +323,4 @@ struct prueth_fdb_slot {
> u8 fid;
> u8 fid_c2;
> } __packed;
> -
> -enum icssg_ietfpe_verify_states {
> - ICSSG_IETFPE_STATE_UNKNOWN = 0,
> - ICSSG_IETFPE_STATE_INITIAL,
> - ICSSG_IETFPE_STATE_VERIFYING,
> - ICSSG_IETFPE_STATE_SUCCEEDED,
> - ICSSG_IETFPE_STATE_FAILED,
> - ICSSG_IETFPE_STATE_DISABLED
> -};
> #endif /* __NET_TI_ICSSG_CONFIG_H */
> diff --git a/drivers/net/ethernet/ti/icssg/icssg_prueth.c b/drivers/net/ethernet/ti/icssg/icssg_prueth.c
> index 591be5c8056b4..39f379df923bf 100644
> --- a/drivers/net/ethernet/ti/icssg/icssg_prueth.c
> +++ b/drivers/net/ethernet/ti/icssg/icssg_prueth.c
> @@ -392,6 +392,8 @@ static void emac_adjust_link(struct net_device *ndev)
> } else {
> icssg_set_port_state(emac, ICSSG_EMAC_PORT_DISABLE);
> }
> +
> + icssg_qos_link_state_update(ndev);
> }
>
> if (emac->link) {
> @@ -1652,6 +1654,7 @@ static const struct net_device_ops emac_netdev_ops = {
> .ndo_hwtstamp_get = icssg_ndo_get_ts_config,
> .ndo_hwtstamp_set = icssg_ndo_set_ts_config,
> .ndo_xsk_wakeup = prueth_xsk_wakeup,
> + .ndo_setup_tc = icssg_qos_ndo_setup_tc,
> };
>
> static int prueth_netdev_init(struct prueth *prueth,
> @@ -1686,6 +1689,8 @@ static int prueth_netdev_init(struct prueth *prueth,
>
> INIT_DELAYED_WORK(&emac->stats_work, icssg_stats_work_handler);
>
> + icssg_qos_init(ndev);
> +
> ret = pruss_request_mem_region(prueth->pruss,
> port == PRUETH_PORT_MII0 ?
> PRUSS_MEM_DRAM0 : PRUSS_MEM_DRAM1,
> @@ -1793,6 +1798,7 @@ static int prueth_netdev_init(struct prueth *prueth,
> free:
> pruss_release_mem_region(prueth->pruss, &emac->dram);
> free_ndev:
> + mutex_destroy(&emac->qos.iet.fpe_lock);
> emac->ndev = NULL;
> prueth->emac[mac] = NULL;
> free_netdev(ndev);
> diff --git a/drivers/net/ethernet/ti/icssg/icssg_prueth.h b/drivers/net/ethernet/ti/icssg/icssg_prueth.h
> index df93d15c5b786..85f7017d2c8e7 100644
> --- a/drivers/net/ethernet/ti/icssg/icssg_prueth.h
> +++ b/drivers/net/ethernet/ti/icssg/icssg_prueth.h
> @@ -44,6 +44,7 @@
> #include "icssg_config.h"
> #include "icss_iep.h"
> #include "icssg_switch_map.h"
> +#include "icssg_qos.h"
>
> #define PRUETH_MAX_MTU (2000 - ETH_HLEN - ETH_FCS_LEN)
> #define PRUETH_MIN_PKT_SIZE (VLAN_ETH_ZLEN)
> @@ -254,6 +255,7 @@ struct prueth_emac {
> struct bpf_prog *xdp_prog;
> struct xdp_attachment_info xdpi;
> int xsk_qid;
> + struct prueth_qos qos;
> };
>
> /* The buf includes headroom compatible with both skb and xdpf */
> diff --git a/drivers/net/ethernet/ti/icssg/icssg_qos.c b/drivers/net/ethernet/ti/icssg/icssg_qos.c
> new file mode 100644
> index 0000000000000..2781abf39e9bb
> --- /dev/null
> +++ b/drivers/net/ethernet/ti/icssg/icssg_qos.c
> @@ -0,0 +1,269 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Texas Instruments ICSSG PRUETH QoS submodule
> + * Copyright (C) 2023 Texas Instruments Incorporated - http://www.ti.com/
> + */
> +
> +#include "icssg_prueth.h"
> +#include "icssg_switch_map.h"
> +
> +static void icssg_iet_set_preempt_mask(struct prueth_emac *emac)
> +{
> + void __iomem *config = emac->dram.va + ICSSG_CONFIG_OFFSET;
> + struct prueth_qos_mqprio *p_mqprio = &emac->qos.mqprio;
> + struct tc_mqprio_qopt *qopt = &p_mqprio->mqprio.qopt;
> + struct prueth_qos_iet *iet = &emac->qos.iet;
> + int prempt_mask = 0, i;
> + u8 tc, num_tc;
> +
> + if (!iet->preemptible_tcs)
> + goto reset_hw;
> +
> + if (iet->fpe_active) {
> + /* Configure the queues based on the preemptible tc map set by the user */
> + num_tc = p_mqprio->mqprio.qopt.num_tc;
> + for (tc = 0; tc < num_tc; tc++) {
> + /* check if the tc is preemptive or not */
> + if (iet->preemptible_tcs & BIT(tc)) {
> + for (i = qopt->offset[tc]; i < qopt->offset[tc] + qopt->count[tc]; i++) {
> + /* Set all the queues in this tc as preemptive queues */
> + writeb(BIT(4), config + EXPRESS_PRE_EMPTIVE_Q_MAP + i);
> + }
> + } else {
> + /* Set all the queues in this tc as express queues */
> + for (i = qopt->offset[tc]; i < qopt->offset[tc] + qopt->count[tc]; i++) {
> + writeb(0, config + EXPRESS_PRE_EMPTIVE_Q_MAP + i);
> + prempt_mask |= BIT(i);
> + }
> + }
> + netdev_set_tc_queue(emac->ndev, tc, qopt->count[tc], qopt->offset[tc]);
> + }
> + writeb(prempt_mask, config + EXPRESS_PRE_EMPTIVE_Q_MASK);
> + return;
> + }
> +
> +reset_hw:
> + /* Reset to default: all queues as express */
> + for (i = 0; i < ICSSG_MAX_TC_QUEUES; i++)
> + writeb(0, config + EXPRESS_PRE_EMPTIVE_Q_MAP + i);
> + writeb(ICSSG_EXPRESS_Q_MASK_ALL, config + EXPRESS_PRE_EMPTIVE_Q_MASK);
> +}
> +
> +static int icssg_iet_verify_wait(struct prueth_emac *emac)
> +{
> + void __iomem *config = emac->dram.va + ICSSG_CONFIG_OFFSET;
> + struct prueth_qos_iet *iet = &emac->qos.iet;
> + int try = 3;
> +
> + do {
> + msleep(iet->verify_time_ms);
> + iet->verify_status = readb(config + PRE_EMPTION_VERIFY_STATUS);
> + if (iet->verify_status == ICSSG_IETFPE_STATE_SUCCEEDED)
> + return 0;
> + } while (--try > 0);
You can replace that whole loop with a readb_poll_timeout, to avoid
open-coding it.
> +
> + netdev_err(emac->ndev, "MAC Verify timeout\n");
> + return -ETIMEDOUT;
> +}
> +
> +/* Direct synchronous configuration of IET FPE.
> + * Caller must hold iet->fpe_lock.
> + */
> +int icssg_config_ietfpe(struct net_device *ndev, bool enable)
> +{
> + struct prueth_emac *emac = netdev_priv(ndev);
> + void __iomem *config = emac->dram.va + ICSSG_CONFIG_OFFSET;
> + struct prueth_qos_iet *iet = &emac->qos.iet;
> + int ret;
> + u8 val;
> +
> + lockdep_assert_held(&iet->fpe_lock);
> +
> + if (!netif_running(ndev)) {
> + netdev_dbg(ndev, "cannot change IET/FPE state when interface is down\n");
> + return 0;
> + }
> +
> + /* Update FPE Tx enable bit (PRE_EMPTION_ENABLE_TX) if
> + * fpe_enabled is set to enable MM in Tx direction
> + */
> + writeb(enable ? 1 : 0, config + PRE_EMPTION_ENABLE_TX);
> +
> + /* If FPE is to be enabled, first configure MAC Verify state
> + * machine in firmware as firmware kicks the Verify process
> + * as soon as ICSSG_EMAC_PORT_PREMPT_TX_ENABLE command is
> + * received.
> + */
> + if (enable && iet->mac_verify_configure) {
> + writeb(1, config + PRE_EMPTION_ENABLE_VERIFY);
> + writew(iet->tx_min_frag_size + ETH_FCS_LEN,
> + config + PRE_EMPTION_ADD_FRAG_SIZE_LOCAL);
> + writel(iet->verify_time_ms, config + PRE_EMPTION_VERIFY_TIME);
> + } else {
> + writeb(0, config + PRE_EMPTION_ENABLE_VERIFY);
> + iet->verify_status = ICSSG_IETFPE_STATE_DISABLED;
> + }
> +
> + /* Send command to enable FPE Tx side. Rx is always enabled */
> + ret = icssg_set_port_state(emac,
> + enable ? ICSSG_EMAC_PORT_PREMPT_TX_ENABLE :
> + ICSSG_EMAC_PORT_PREMPT_TX_DISABLE);
> + if (ret) {
> + netdev_err(ndev, "TX preempt %s command failed\n",
> + str_enable_disable(enable));
> + goto fallback;
> + }
> +
> + if (enable && iet->mac_verify_configure) {
> + ret = icssg_iet_verify_wait(emac);
> + if (ret) {
> + netdev_err(ndev, "MAC Verification failed with timeout\n");
> + goto disable_tx;
> + }
> + } else if (enable) {
> + /* Give firmware some time to update PRE_EMPTION_ACTIVE_TX state */
> + usleep_range(100, 200);
> + }
> +
> + if (enable) {
> + val = readb(config + PRE_EMPTION_ACTIVE_TX);
> + if (val != 1) {
> + netdev_err(ndev,
> + "Firmware fails to activate IET/FPE\n");
> + ret = -EIO;
> + goto disable_tx;
> + }
> + iet->fpe_active = true;
> + } else {
> + iet->fpe_active = false;
> + }
> +
> + icssg_iet_set_preempt_mask(emac);
> + netdev_info(ndev, "IET FPE %s successfully\n",
> + str_enable_disable(iet->fpe_active));
> + return ret;
> +
> +disable_tx:
> + icssg_set_port_state(emac, ICSSG_EMAC_PORT_PREMPT_TX_DISABLE);
> +fallback:
> + writeb(0, config + PRE_EMPTION_ENABLE_TX);
> + writeb(0, config + PRE_EMPTION_ENABLE_VERIFY);
> + iet->verify_status = ICSSG_IETFPE_STATE_DISABLED;
> + iet->fpe_active = false;
> + return ret;
> +}
> +
> +void icssg_qos_init(struct net_device *ndev)
> +{
> + struct prueth_emac *emac = netdev_priv(ndev);
> + struct prueth_qos_iet *iet = &emac->qos.iet;
> +
> + mutex_init(&iet->fpe_lock);
> + /* Set default values to prevent garbage values during .get_mm() */
> + mutex_lock(&iet->fpe_lock);
> + iet->verify_time_ms = ICSSG_IET_MAX_VERIFY_TIME;
> + iet->tx_min_frag_size = ETH_ZLEN;
> + mutex_unlock(&iet->fpe_lock);
> +}
> +EXPORT_SYMBOL_GPL(icssg_qos_init);
> +
> +static int icssg_iet_change_preemptible_tcs(struct prueth_emac *emac)
> +{
> + struct prueth_qos_iet *iet = &emac->qos.iet;
> + int ret;
> +
> + mutex_lock(&iet->fpe_lock);
> + ret = icssg_config_ietfpe(emac->ndev, iet->fpe_enabled);
> + mutex_unlock(&iet->fpe_lock);
> +
> + return ret;
> +}
> +
> +static int emac_tc_query_caps(struct net_device *ndev, void *type_data)
> +{
> + struct tc_query_caps_base *base = type_data;
> +
> + switch (base->type) {
> + case TC_SETUP_QDISC_MQPRIO: {
> + struct tc_mqprio_caps *caps = base->caps;
> +
> + caps->validate_queue_counts = true;
> + return 0;
> + }
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +
> +static int emac_tc_setup_mqprio(struct net_device *ndev, void *type_data)
> +{
> + struct prueth_emac *emac = netdev_priv(ndev);
> + struct prueth_qos_mqprio *p_mqprio = &emac->qos.mqprio;
> + struct tc_mqprio_qopt_offload *mqprio = type_data;
> + struct prueth_qos_iet *iet = &emac->qos.iet;
> + int ret;
> +
> + /* Validate parameters */
> + if (mqprio->qopt.num_tc > ICSSG_MAX_TC_QUEUES) {
> + netdev_err(ndev, "Number of traffic classes (%u) exceeds hardware limit\n",
> + mqprio->qopt.num_tc);
> + return -EINVAL;
> + }
> +
> + if (mqprio->flags & TC_MQPRIO_F_SHAPER) {
> + netdev_err(ndev, "traffic shaping is not supported\n");
> + return -EINVAL;
Maybe -EOPNOTSUPP ?
> + }
> +
> + if (mqprio->flags & (TC_MQPRIO_F_MIN_RATE | TC_MQPRIO_F_MAX_RATE)) {
> + netdev_err(ndev, "per-queue rate limiting is not supported\n");
> + return -EINVAL;
Same
> + }
> +
> + if (!mqprio->qopt.num_tc) {
> + netdev_reset_tc(ndev);
> + } else {
> + netdev_set_num_tc(ndev, mqprio->qopt.num_tc);
> + }
> +
> + mutex_lock(&iet->fpe_lock);
> + if (!mqprio->qopt.num_tc) {
> + iet->preemptible_tcs = 0;
> + } else {
> + memcpy(&p_mqprio->mqprio, mqprio, sizeof(*mqprio));
> + iet->preemptible_tcs = mqprio->preemptible_tcs;
> + }
> + mutex_unlock(&iet->fpe_lock);
> +
> + netdev_dbg(ndev, "dev->num_tc %u dev->real_num_tx_queues %u\n",
> + ndev->num_tc, ndev->real_num_tx_queues);
> +
> + ret = icssg_iet_change_preemptible_tcs(emac);
> + return ret;
Simplify this with :
return icssg_iet_change_preemptible_tcs(emac);
> +}
> +
> +int icssg_qos_ndo_setup_tc(struct net_device *ndev, enum tc_setup_type type,
> + void *type_data)
> +{
> + switch (type) {
> + case TC_QUERY_CAPS:
> + return emac_tc_query_caps(ndev, type_data);
> + case TC_SETUP_QDISC_MQPRIO:
> + return emac_tc_setup_mqprio(ndev, type_data);
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +EXPORT_SYMBOL_GPL(icssg_qos_ndo_setup_tc);
> +
> +void icssg_qos_link_state_update(struct net_device *ndev)
> +{
> + struct prueth_emac *emac = netdev_priv(ndev);
> + struct prueth_qos_iet *iet = &emac->qos.iet;
> + int ret;
> +
> + ret = icssg_iet_change_preemptible_tcs(emac);
> + if (ret)
> + netdev_dbg(ndev, "IET FPE %s failed\n",
> + str_enable_disable(iet->fpe_active));
> +}
> +EXPORT_SYMBOL_GPL(icssg_qos_link_state_update);
> diff --git a/drivers/net/ethernet/ti/icssg/icssg_qos.h b/drivers/net/ethernet/ti/icssg/icssg_qos.h
> new file mode 100644
> index 0000000000000..9355e96bbcda8
> --- /dev/null
> +++ b/drivers/net/ethernet/ti/icssg/icssg_qos.h
> @@ -0,0 +1,66 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/* Copyright (C) 2023 Texas Instruments Incorporated - http://www.ti.com/
> + */
> +
> +#ifndef __NET_TI_ICSSG_QOS_H
> +#define __NET_TI_ICSSG_QOS_H
> +
> +#include <linux/atomic.h>
> +#include <linux/netdevice.h>
> +#include <net/pkt_sched.h>
> +
> +#define ICSSG_MAX_TC_QUEUES 8
> +#define ICSSG_EXPRESS_Q_MASK_ALL 0xFF
> +#define ICSSG_IET_MAX_VERIFY_TIME 128
> +#define ICSSG_IET_MIN_VERIFY_TIME 1
> +
> +/**
> + * enum icssg_ietfpe_verify_states - status of MAC Merge Verify returned by firmware
> + * @ICSSG_IETFPE_STATE_UNKNOWN:
> + * verification status is unknown
> + * @ICSSG_IETFPE_STATE_INITIAL:
> + * Firmware returns this if verify state diagram is idle
> + * @ICSSG_IETFPE_STATE_VERIFYING:
> + * Firmware returns this if verification is ongoing
> + * @ICSSG_IETFPE_STATE_SUCCEEDED:
> + * Firmware returns this if verify state diagram completes verification
> + * @ICSSG_IETFPE_STATE_FAILED:
> + * Firmware returns this if verify state diagram fails during verification
> + * @ICSSG_IETFPE_STATE_DISABLED:
> + * verification is disabled by the driver
> + */
> +enum icssg_ietfpe_verify_states {
> + ICSSG_IETFPE_STATE_UNKNOWN = 0,
> + ICSSG_IETFPE_STATE_INITIAL,
> + ICSSG_IETFPE_STATE_VERIFYING,
> + ICSSG_IETFPE_STATE_SUCCEEDED,
> + ICSSG_IETFPE_STATE_FAILED,
> + ICSSG_IETFPE_STATE_DISABLED
> +};
> +
> +struct prueth_qos_mqprio {
> + struct tc_mqprio_qopt_offload mqprio;
> +};
> +
> +struct prueth_qos_iet {
> + bool fpe_enabled;
> + bool mac_verify_configure;
> + u32 tx_min_frag_size;
> + u32 verify_time_ms;
> + bool fpe_active;
> + enum icssg_ietfpe_verify_states verify_status;
> + struct mutex fpe_lock;
Checkpatch already says it, but you need to document what this
mutex is protecting.
> + u8 preemptible_tcs;
> +};
> +
> +struct prueth_qos {
> + struct prueth_qos_iet iet;
> + struct prueth_qos_mqprio mqprio;
> +};
> +
> +void icssg_qos_init(struct net_device *ndev);
> +void icssg_qos_link_state_update(struct net_device *ndev);
> +int icssg_qos_ndo_setup_tc(struct net_device *ndev, enum tc_setup_type type,
> + void *type_data);
> +int icssg_config_ietfpe(struct net_device *ndev, bool enable);
> +#endif /* __NET_TI_ICSSG_QOS_H */
Thanks,
Maxime
^ permalink raw reply
* Re: [PATCH net-next v6 2/2] net: ti: icssg-prueth: Add ethtool ops for Frame Preemption MAC Merge
From: Maxime Chevallier @ 2026-05-25 19:35 UTC (permalink / raw)
To: Meghana Malladi, liuhangbin, h-mittal1, haokexin, vadim.fedorenko,
devnexen, horms, jacob.e.keller, arnd, afd, basharath, parvathi,
vladimir.oltean, rogerq, danishanwar, pabeni, kuba, edumazet,
davem, andrew+netdev
Cc: linux-arm-kernel, netdev, linux-kernel, srk, Vignesh Raghavendra
In-Reply-To: <20260525182700.3135858-3-m-malladi@ti.com>
Hi,
On 5/25/26 20:27, Meghana Malladi wrote:
> From: MD Danish Anwar <danishanwar@ti.com>
>
> Add driver support for viewing and changing the MAC Merge sublayer
> parameters via ethtool ops: .set_mm(), .get_mm() and .get_mm_stats().
>
> The minimum size of non-final mPacket fragments supported by the firmware
> without leading errors is 64 Bytes (including FCS). Verify time bounded to
> 1-128 ms per 802.3-2018 clause 30.14.1.6. Add a check to ensure
> user passed tx_min_frag_size argument via ethtool, honors this.
> Add pa stats registers to check statistics for preemption, which can be
> dumped using ethtool ops.
>
> Fix emac_get_stat_by_name() to return u64 instead of int and return 0 on
> error instead of -EINVAL. This prevents invalid stat lookups from corrupting
> output stats with signed error codes cast to u64. Error conditions are still
> logged via netdev_err().
>
> Signed-off-by: MD Danish Anwar <danishanwar@ti.com>
> Signed-off-by: Meghana Malladi <m-malladi@ti.com>
> ---
>
> v6-v5:
> - Initialize tx_min_frag_size and verify_time with valid values
> to avoid returning corrupted values during get_mm()
> - Fix rx_min_frag_size to ETH_ZLEN excluding ETH_FCS_LEN
> - Fix return codes for emac_set_mm()
> All the above changes address the comments raised by sashiko
>
> drivers/net/ethernet/ti/icssg/icssg_ethtool.c | 106 ++++++++++++++++++
> drivers/net/ethernet/ti/icssg/icssg_prueth.h | 7 +-
> drivers/net/ethernet/ti/icssg/icssg_qos.h | 46 ++++++++
> drivers/net/ethernet/ti/icssg/icssg_stats.c | 4 +-
> drivers/net/ethernet/ti/icssg/icssg_stats.h | 7 +-
> .../net/ethernet/ti/icssg/icssg_switch_map.h | 5 +
> 6 files changed, 168 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/net/ethernet/ti/icssg/icssg_ethtool.c b/drivers/net/ethernet/ti/icssg/icssg_ethtool.c
> index b715af21d23ac..ee940051644d6 100644
> --- a/drivers/net/ethernet/ti/icssg/icssg_ethtool.c
> +++ b/drivers/net/ethernet/ti/icssg/icssg_ethtool.c
> @@ -294,6 +294,109 @@ static int emac_set_per_queue_coalesce(struct net_device *ndev, u32 queue,
> return 0;
> }
>
> +static int emac_get_mm(struct net_device *ndev, struct ethtool_mm_state *state)
> +{
> + struct prueth_emac *emac = netdev_priv(ndev);
> + struct prueth_qos_iet *iet = &emac->qos.iet;
> + enum icssg_ietfpe_verify_states verify_status;
> +
> + if (emac->is_sr1)
> + return -EOPNOTSUPP;
> +
> + mutex_lock(&iet->fpe_lock);
> + state->tx_enabled = iet->fpe_enabled;
> + state->tx_min_frag_size = iet->tx_min_frag_size;
> + state->tx_active = iet->fpe_active;
> + state->verify_enabled = iet->mac_verify_configure;
> + state->verify_time = iet->verify_time_ms;
> + verify_status = iet->verify_status;
> + mutex_unlock(&iet->fpe_lock);
> +
> + state->rx_min_frag_size = ETH_ZLEN;
> + state->pmac_enabled = true;
> +
> + switch (verify_status) {
> + case ICSSG_IETFPE_STATE_DISABLED:
> + state->verify_status = ETHTOOL_MM_VERIFY_STATUS_DISABLED;
> + break;
> + case ICSSG_IETFPE_STATE_INITIAL:
> + state->verify_status = ETHTOOL_MM_VERIFY_STATUS_INITIAL;
> + break;
> + case ICSSG_IETFPE_STATE_VERIFYING:
> + state->verify_status = ETHTOOL_MM_VERIFY_STATUS_VERIFYING;
> + break;
> + case ICSSG_IETFPE_STATE_SUCCEEDED:
> + state->verify_status = ETHTOOL_MM_VERIFY_STATUS_SUCCEEDED;
> + break;
> + case ICSSG_IETFPE_STATE_FAILED:
> + state->verify_status = ETHTOOL_MM_VERIFY_STATUS_FAILED;
> + break;
> + default:
> + state->verify_status = ETHTOOL_MM_VERIFY_STATUS_UNKNOWN;
> + break;
> + }
> +
> + /* 802.3-2018 clause 30.14.1.6, says that the aMACMergeVerifyTime
> + * variable has a range between 1 and 128 ms inclusive. Limit to that.
> + */
> + state->max_verify_time = ETHTOOL_MM_MAX_VERIFY_TIME_MS;
> +
> + return 0;
> +}
> +
> +static int emac_set_mm(struct net_device *ndev, struct ethtool_mm_cfg *cfg,
> + struct netlink_ext_ack *extack)
> +{
> + struct prueth_emac *emac = netdev_priv(ndev);
> + struct prueth_qos_iet *iet = &emac->qos.iet;
> + int err;
> +
> + if (emac->is_sr1)
> + return -EOPNOTSUPP;
> +
> + if (!cfg->pmac_enabled) {
> + NL_SET_ERR_MSG_MOD(extack, "preemptible MAC is always enabled");
> + return -EOPNOTSUPP;
> + }
> +
> + err = icssg_qos_validate_tx_min_frag_size(cfg->tx_min_frag_size, extack);
> + if (err)
> + return err;
> +
> + err = icssg_qos_validate_verify_time(cfg->verify_time, extack);
> + if (err)
> + return err;
The ethnl code already validates the verify_time :
https://elixir.bootlin.com/linux/v7.0.9/source/net/ethtool/mm.c#L153
> +
> + mutex_lock(&iet->fpe_lock);
> + iet->verify_time_ms = cfg->verify_time;
> + iet->tx_min_frag_size = cfg->tx_min_frag_size;
> + iet->fpe_enabled = cfg->tx_enabled;
> + iet->mac_verify_configure = cfg->verify_enabled;
> + err = icssg_config_ietfpe(ndev, cfg->tx_enabled);
> + mutex_unlock(&iet->fpe_lock);
> +
> + return err;
> +}
> +
> +static void emac_get_mm_stats(struct net_device *ndev,
> + struct ethtool_mm_stats *s)
> +{
> + struct prueth_emac *emac = netdev_priv(ndev);
> +
> + if (emac->is_sr1)
> + return;
> +
> + if (!emac->prueth->pa_stats)
> + return;
> +
> + /* MACMergeHoldCount stats is not tracked by the firmware */
> + s->MACMergeFrameAssOkCount = emac_get_stat_by_name(emac, "FW_PREEMPT_ASSEMBLY_OK");
> + s->MACMergeFrameAssErrorCount = emac_get_stat_by_name(emac, "FW_PREEMPT_ASSEMBLY_ERR");
> + s->MACMergeFragCountRx = emac_get_stat_by_name(emac, "FW_PREEMPT_FRAG_CNT_RX");
> + s->MACMergeFragCountTx = emac_get_stat_by_name(emac, "FW_PREEMPT_FRAG_CNT_TX");
> + s->MACMergeFrameSmdErrorCount = emac_get_stat_by_name(emac, "FW_PREEMPT_BAD_FRAG");
> +}
> +
> const struct ethtool_ops icssg_ethtool_ops = {
> .get_drvinfo = emac_get_drvinfo,
> .get_msglevel = emac_get_msglevel,
> @@ -317,5 +420,8 @@ const struct ethtool_ops icssg_ethtool_ops = {
> .set_eee = emac_set_eee,
> .nway_reset = emac_nway_reset,
> .get_rmon_stats = emac_get_rmon_stats,
> + .get_mm = emac_get_mm,
> + .set_mm = emac_set_mm,
> + .get_mm_stats = emac_get_mm_stats,
> };
> EXPORT_SYMBOL_GPL(icssg_ethtool_ops);
> diff --git a/drivers/net/ethernet/ti/icssg/icssg_prueth.h b/drivers/net/ethernet/ti/icssg/icssg_prueth.h
> index 85f7017d2c8e7..61320c252bec2 100644
> --- a/drivers/net/ethernet/ti/icssg/icssg_prueth.h
> +++ b/drivers/net/ethernet/ti/icssg/icssg_prueth.h
> @@ -45,6 +45,7 @@
> #include "icss_iep.h"
> #include "icssg_switch_map.h"
> #include "icssg_qos.h"
> +#include "icssg_stats.h"
>
> #define PRUETH_MAX_MTU (2000 - ETH_HLEN - ETH_FCS_LEN)
> #define PRUETH_MIN_PKT_SIZE (VLAN_ETH_ZLEN)
> @@ -58,8 +59,8 @@
>
> #define ICSSG_MAX_RFLOWS 8 /* per slice */
>
> -#define ICSSG_NUM_PA_STATS 32
> -#define ICSSG_NUM_MIIG_STATS 60
> +#define ICSSG_NUM_PA_STATS ARRAY_SIZE(icssg_all_pa_stats)
> +#define ICSSG_NUM_MIIG_STATS ARRAY_SIZE(icssg_all_miig_stats)
> /* Number of ICSSG related stats */
> #define ICSSG_NUM_STATS (ICSSG_NUM_MIIG_STATS + ICSSG_NUM_PA_STATS)
> #define ICSSG_NUM_STANDARD_STATS 31
> @@ -460,7 +461,7 @@ int emac_fdb_flow_id_updated(struct prueth_emac *emac);
>
> void icssg_stats_work_handler(struct work_struct *work);
> void emac_update_hardware_stats(struct prueth_emac *emac);
> -int emac_get_stat_by_name(struct prueth_emac *emac, char *stat_name);
> +u64 emac_get_stat_by_name(struct prueth_emac *emac, char *stat_name);
>
> /* Common functions */
> void prueth_cleanup_rx_chns(struct prueth_emac *emac,
> diff --git a/drivers/net/ethernet/ti/icssg/icssg_qos.h b/drivers/net/ethernet/ti/icssg/icssg_qos.h
> index 9355e96bbcda8..87ca031afcaa4 100644
> --- a/drivers/net/ethernet/ti/icssg/icssg_qos.h
> +++ b/drivers/net/ethernet/ti/icssg/icssg_qos.h
> @@ -13,6 +13,7 @@
> #define ICSSG_EXPRESS_Q_MASK_ALL 0xFF
> #define ICSSG_IET_MAX_VERIFY_TIME 128
> #define ICSSG_IET_MIN_VERIFY_TIME 1
> +#define ICSSG_IET_MAX_TX_MIN_FRAG_SIZE 252
>
> /**
> * enum icssg_ietfpe_verify_states - status of MAC Merge Verify returned by firmware
> @@ -63,4 +64,49 @@ void icssg_qos_link_state_update(struct net_device *ndev);
> int icssg_qos_ndo_setup_tc(struct net_device *ndev, enum tc_setup_type type,
> void *type_data);
> int icssg_config_ietfpe(struct net_device *ndev, bool enable);
> +static inline int icssg_qos_validate_tx_min_frag_size(u32 min_frag_size,
> + struct netlink_ext_ack *extack)
> +{
> + /* Firmware takes min_frag_size including FCS length.
> + * The firmware requires the fragment size (including FCS) to be
> + * a multiple of 64 bytes. Since 64 bytes = ETH_ZLEN + ETH_FCS_LEN,
> + * valid user-facing values are: 60, 124, 188, 252.
> + */
> +
> + if (min_frag_size < ETH_ZLEN) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "tx_min_frag_size must be at least 60 bytes");
> + return -EINVAL;
> + }
> +
> + if (min_frag_size > ICSSG_IET_MAX_TX_MIN_FRAG_SIZE) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "tx_min_frag_size must not exceed 252 bytes");
> + return -EINVAL;
> + }
> +
> + if ((min_frag_size + ETH_FCS_LEN) % (ETH_ZLEN + ETH_FCS_LEN)) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "tx_min_frag_size must be a multiple of 64 bytes minus 4");
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
Is there any reason this helper is in a header file instead
of being directly in icssg_ethtool.c ?
> +
> +static inline int icssg_qos_validate_verify_time(u32 verify_time_ms,
> + struct netlink_ext_ack *extack)
> +{
> + /* 802.3-2018 clause 30.14.1.6: aMACMergeVerifyTime must be
> + * between 1 and 128 ms inclusive
> + */
> + if (verify_time_ms < ICSSG_IET_MIN_VERIFY_TIME ||
> + verify_time_ms > ICSSG_IET_MAX_VERIFY_TIME) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "verify_time must be between 1 and 128 ms");
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
And this shouldn't be needed as the ethnl mm code already validates
these boundaries, as they are straight from the standard.
> #endif /* __NET_TI_ICSSG_QOS_H */
> diff --git a/drivers/net/ethernet/ti/icssg/icssg_stats.c b/drivers/net/ethernet/ti/icssg/icssg_stats.c
> index 7159baa0155cf..cfdb6f5dc5da1 100644
> --- a/drivers/net/ethernet/ti/icssg/icssg_stats.c
> +++ b/drivers/net/ethernet/ti/icssg/icssg_stats.c
> @@ -74,7 +74,7 @@ void icssg_stats_work_handler(struct work_struct *work)
> }
> EXPORT_SYMBOL_GPL(icssg_stats_work_handler);
>
> -int emac_get_stat_by_name(struct prueth_emac *emac, char *stat_name)
> +u64 emac_get_stat_by_name(struct prueth_emac *emac, char *stat_name)
> {
> int i;
>
> @@ -91,5 +91,5 @@ int emac_get_stat_by_name(struct prueth_emac *emac, char *stat_name)
> }
>
> netdev_err(emac->ndev, "Invalid stats %s\n", stat_name);
> - return -EINVAL;
> + return 0;
> }
> diff --git a/drivers/net/ethernet/ti/icssg/icssg_stats.h b/drivers/net/ethernet/ti/icssg/icssg_stats.h
> index 5ec0b38e0c67d..8073deac35c3e 100644
> --- a/drivers/net/ethernet/ti/icssg/icssg_stats.h
> +++ b/drivers/net/ethernet/ti/icssg/icssg_stats.h
> @@ -8,8 +8,6 @@
> #ifndef __NET_TI_ICSSG_STATS_H
> #define __NET_TI_ICSSG_STATS_H
>
> -#include "icssg_prueth.h"
> -
> #define STATS_TIME_LIMIT_1G_MS 25000 /* 25 seconds @ 1G */
>
> struct miig_stats_regs {
> @@ -189,6 +187,11 @@ static const struct icssg_pa_stats icssg_all_pa_stats[] = {
> ICSSG_PA_STATS(FW_INF_DROP_PRIOTAGGED),
> ICSSG_PA_STATS(FW_INF_DROP_NOTAG),
> ICSSG_PA_STATS(FW_INF_DROP_NOTMEMBER),
> + ICSSG_PA_STATS(FW_PREEMPT_BAD_FRAG),
> + ICSSG_PA_STATS(FW_PREEMPT_ASSEMBLY_ERR),
> + ICSSG_PA_STATS(FW_PREEMPT_FRAG_CNT_TX),
> + ICSSG_PA_STATS(FW_PREEMPT_ASSEMBLY_OK),
> + ICSSG_PA_STATS(FW_PREEMPT_FRAG_CNT_RX),
> ICSSG_PA_STATS(FW_RX_EOF_SHORT_FRMERR),
> ICSSG_PA_STATS(FW_RX_B0_DROP_EARLY_EOF),
> ICSSG_PA_STATS(FW_TX_JUMBO_FRM_CUTOFF),
> diff --git a/drivers/net/ethernet/ti/icssg/icssg_switch_map.h b/drivers/net/ethernet/ti/icssg/icssg_switch_map.h
> index 7e053b8af3ece..855fd4ed0b3f6 100644
> --- a/drivers/net/ethernet/ti/icssg/icssg_switch_map.h
> +++ b/drivers/net/ethernet/ti/icssg/icssg_switch_map.h
> @@ -256,6 +256,11 @@
> #define FW_INF_DROP_PRIOTAGGED 0x0148
> #define FW_INF_DROP_NOTAG 0x0150
> #define FW_INF_DROP_NOTMEMBER 0x0158
> +#define FW_PREEMPT_BAD_FRAG 0x0160
> +#define FW_PREEMPT_ASSEMBLY_ERR 0x0168
> +#define FW_PREEMPT_FRAG_CNT_TX 0x0170
> +#define FW_PREEMPT_ASSEMBLY_OK 0x0178
> +#define FW_PREEMPT_FRAG_CNT_RX 0x0180
> #define FW_RX_EOF_SHORT_FRMERR 0x0188
> #define FW_RX_B0_DROP_EARLY_EOF 0x0190
> #define FW_TX_JUMBO_FRM_CUTOFF 0x0198
Maxime
^ permalink raw reply
* Re: [PATCH net-next v6 1/2] net: ti: icssg-prueth: Add Frame Preemption MAC Merge support
From: Andrew Lunn @ 2026-05-25 19:46 UTC (permalink / raw)
To: Maxime Chevallier
Cc: Meghana Malladi, liuhangbin, h-mittal1, haokexin, vadim.fedorenko,
devnexen, horms, jacob.e.keller, arnd, afd, basharath, parvathi,
vladimir.oltean, rogerq, danishanwar, pabeni, kuba, edumazet,
davem, andrew+netdev, linux-arm-kernel, netdev, linux-kernel, srk,
Vignesh Raghavendra
In-Reply-To: <de0255c8-d751-4220-b0ab-88aef3a15f9a@bootlin.com>
> > +++ b/drivers/net/ethernet/ti/icssg/icssg_prueth.h
> > @@ -44,6 +44,7 @@
> > #include "icssg_config.h"
> > #include "icss_iep.h"
> > #include "icssg_switch_map.h"
> > +#include "icssg_qos.h"
> > #define PRUETH_MAX_MTU (2000 - ETH_HLEN - ETH_FCS_LEN)
Sorry for hijacking another message, but the indentation looks odd
here.
Andrew
^ permalink raw reply
* Re: [PATCH v4 2/5] PCI/ATS: Validate STU for VFs in pci_prepare_ats()
From: Nicolin Chen @ 2026-05-25 19:46 UTC (permalink / raw)
To: Pranjal Shrivastava
Cc: iommu, linux-pci, linux-arm-kernel, linux-kernel, Joerg Roedel,
Will Deacon, Bjorn Helgaas, David Woodhouse, Lu Baolu,
Robin Murphy, Suravee Suthikulpanit, Jason Gunthorpe,
David Matlack, Samiullah Khawaja, Daniel Mentz, Pasha Tatashin,
Mostafa Saleh, Jason Gunthorpe
In-Reply-To: <20260525184347.4059549-3-praan@google.com>
On Mon, May 25, 2026 at 06:43:44PM +0000, Pranjal Shrivastava wrote:
> While every PCI Function that implements ATS has an independent ATS
> Extended Capability structure with a Read/Write Smallest Translation
> Unit (STU) field, the kernel manages SR-IOV ATS by requiring the IOMMU
> driver to configure the STU on the Physical Function (PF) before any
> any Virtual Functions (VFs) are created.
>
> Currently, pci_prepare_ats() bails out early for VFs, assuming that the
> PF has already been correctly prepared. However, this creates a potential
> mismatch if a VF is subsequently prepared with a different page shift.
>
> Update pci_prepare_ats() to validate that the requested page shift (ps)
> matches the STU already configured in the associated PF. This ensures
> early detection of incompatible configurations and maintains the kernel's
> policy of consistent STU sizing across all functions associated with a
> given SMMU.
>
> Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
> Reviewed-by: Samiullah Khawaja <skhawaja@google.com>
> Signed-off-by: Pranjal Shrivastava <praan@google.com>
Reviewed-by: Nicolin Chen <nicolinc@nvidia.com>
Nit:
> @@ -73,8 +73,13 @@ int pci_prepare_ats(struct pci_dev *dev, int ps)
> if (ps < PCI_ATS_MIN_STU)
> return -EINVAL;
>
> - if (dev->is_virtfn)
> + if (dev->is_virtfn) {
> + struct pci_dev *pdev = pci_physfn(dev);
> +
> + if (pdev->ats_stu != ps)
> + return -EINVAL;
> return 0;
> + }
pdev doesn't seem useful. Dropping it reads better to me:
if (pci_physfn(dev)->ats_stu != ps)
return -EINVAL;
Nicolin
^ permalink raw reply
* Re: [PATCH] dt-bindings: clock: renesas: div6: Use ZT/ZTR trace clock in R-Mobile APE6 example
From: Conor Dooley @ 2026-05-25 19:47 UTC (permalink / raw)
To: Marek Vasut
Cc: linux-arm-kernel, Brian Masney, Conor Dooley, Geert Uytterhoeven,
Krzysztof Kozlowski, Michael Turquette, Rob Herring, Stephen Boyd,
devicetree, linux-clk, linux-kernel, linux-renesas-soc
In-Reply-To: <20260523192622.56605-1-marek.vasut+renesas@mailbox.org>
[-- Attachment #1: Type: text/plain, Size: 570 bytes --]
On Sat, May 23, 2026 at 09:25:50PM +0200, Marek Vasut wrote:
> Since commit 2abdc3dcf978 ("dt-bindings: clock: renesas,cpg-clocks:
> Document ZT/ZTR trace clock on R-Mobile APE6"), the APE6 clock node
> expects two additional "clock-output-names" entries, "zt" and "ztr".
> Update the example accordingly.
>
> Fixes: 2abdc3dcf978 ("dt-bindings: clock: renesas,cpg-clocks: Document ZT/ZTR trace clock on R-Mobile APE6")
> Signed-off-by: Marek Vasut <marek.vasut+renesas@mailbox.org>
Acked-by: Conor Dooley <conor.dooley@microchip.com>
pw-bot: not-applicable
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* Re: [PATCHSET v4 sched_ext/for-7.2] bpf/arena: Direct kernel-side access
From: Tejun Heo @ 2026-05-25 19:54 UTC (permalink / raw)
To: David Vernet, Andrea Righi, Changwoo Min, Alexei Starovoitov,
Andrii Nakryiko, Daniel Borkmann, Martin KaFai Lau,
Kumar Kartikeya Dwivedi
Cc: Peter Zijlstra, Catalin Marinas, Will Deacon, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, Dave Hansen, Andrew Morton,
David Hildenbrand, Mike Rapoport, Emil Tsalapatis, sched-ext, bpf,
x86, linux-arm-kernel, linux-mm, linux-kernel, Tejun Heo
In-Reply-To: <20260522172219.1423324-1-tj@kernel.org>
Hello,
Patches 1-5 went through bpf-next and are pulled into sched_ext/for-7.2.
Applied 6-8 to for-7.2.
Thanks.
--
tejun
^ permalink raw reply
* Re: [PATCH v4 3/5] iommu/arm-smmu-v3: Fix ATS state tracking
From: Nicolin Chen @ 2026-05-25 20:05 UTC (permalink / raw)
To: Pranjal Shrivastava
Cc: iommu, linux-pci, linux-arm-kernel, linux-kernel, Joerg Roedel,
Will Deacon, Bjorn Helgaas, David Woodhouse, Lu Baolu,
Robin Murphy, Suravee Suthikulpanit, Jason Gunthorpe,
David Matlack, Samiullah Khawaja, Daniel Mentz, Pasha Tatashin,
Mostafa Saleh
In-Reply-To: <20260525184347.4059549-4-praan@google.com>
On Mon, May 25, 2026 at 06:43:45PM +0000, Pranjal Shrivastava wrote:
> @@ -3065,8 +3065,14 @@ static void arm_smmu_enable_ats(struct arm_smmu_master *master)
> * ATC invalidation of PASID 0 causes the entire ATC to be flushed.
> */
> arm_smmu_atc_inv_master(master, IOMMU_NO_PASID);
> - if (pci_enable_ats(pdev, stu))
> - dev_err(master->dev, "Failed to enable ATS (STU %zu)\n", stu);
> +
> + /*
> + * Any failure at this point is a kernel bug. pci_ats_supported()
> + * and pci_prepare_ats() have already verified the hardware capability
> + * and programmed the STU. Thus, pci_enable_ats() should not fail here.
Nits:
- WARN usually indicates a kernel bug already.
- pci_prepare_ats() covers pci_ats_supported().
/*
* As pci_prepare_ats() have already verified the hardware capability
* and programmed the STE, pci_enable_ats() should not fail here.
*/
> @@ -4264,9 +4270,16 @@ static struct iommu_device *arm_smmu_probe_device(struct device *dev)
> master->stall_enabled = true;
>
> if (dev_is_pci(dev)) {
> - unsigned int stu = __ffs(smmu->pgsize_bitmap);
> + struct pci_dev *pdev = to_pci_dev(dev);
>
> - pci_prepare_ats(to_pci_dev(dev), stu);
> + if (pci_ats_supported(pdev)) {
> + unsigned int stu = __ffs(smmu->pgsize_bitmap);
> + int ret;
> +
> + ret = pci_prepare_ats(pdev, stu);
> + if (ret)
> + return ERR_PTR(ret);
> + }
Again, pci_prepare_ats() covers pci_ats_supported(). So, the check
is redundant. Instead, it should check arm_smmu_ats_supported().
By the way, this would conflict into my series:
https://lore.kernel.org/linux-iommu/18bb6f421b3be891caa8f1fb50f3a4d56b52d5be.1779392420.git.nicolinc@nvidia.com/
It would be nicer to have an arm_smmu_master_prepare_ats() so that
both series would have a common ground; mine would be just adding
some additional lines if your series goes in first.
Thanks
Nicolin
^ permalink raw reply
* Re: [PATCH v9 00/23] perf python: Modernize and extend Python API (Phase 1)
From: Arnaldo Carvalho de Melo @ 2026-05-25 20:14 UTC (permalink / raw)
To: Ian Rogers
Cc: namhyung, adrian.hunter, alice.mei.rogers, dapeng1.mi,
james.clark, leo.yan, linux-arm-kernel, linux-kernel,
linux-perf-users, mingo, peterz, tmricht
In-Reply-To: <20260522220435.2378363-1-irogers@google.com>
On Fri, May 22, 2026 at 03:04:11PM -0700, Ian Rogers wrote:
> The perf script command has long supported running Python and Perl scripts by
> embedding libpython and libperl. This approach has several drawbacks:
> - overhead by creating Python dictionaries for every event (whether used or
> not),
> - complex build dependencies on specific Python/Perl versions,
> - complications with threading due to perf being the interpreter,
> - no clear way to run standalone scripts like ilist.py.
>
> This series takes a different approach with some initial implementation posted
> as an RFC last October:
> https://lore.kernel.org/linux-perf-users/20251029053413.355154-1-irogers@google.com/
> with the motivation coming up on the mailing list earlier:
> https://lore.kernel.org/lkml/CAP-5=fWDqE8SYfOLZkg_0=4Ayx6E7O+h7uUp4NDeCFkiN4b7-w@mail.gmail.com/
>
> The ultimate goal is to remove the embedded libpython and libperl support from
> perf entirely, expanding the existing perf Python module to provide full access
> to perf data files and events, allowing scripts to be run as standalone Python
> applications.
>
> To make the review process more manageable, the original 58-patch series has
> been split. This v9 series represents "Phase 1: API & Infrastructure" (23 patches).
> It contains:
> 1. Missed explicit dependency cleanups and header sorting.
> 2. Crucial core safety infrastructure (reference counting for evlist/evsel)
> to support safe lifecycle management in garbage-collected Python.
> 3. The core Python API extensions (session wrappers, perf_data wrappers,
> sample accessors, stubs, and LiveSession helper).
>
> The subsequent "Phase 2" series will contain the actual porting of all
> existing Python/Perl scripts to the new API (which yields up to 35x speedups
> as demonstrated previously) and the final removal of embedded interpreters.
I tried applying it now and got this:
Cover: ./v9_20260522_irogers_perf_python_modernize_and_extend_python_api_phase_1.cover
Link: https://lore.kernel.org/r/20260522220435.2378363-1-irogers@google.com
Base: not specified
git am ./v9_20260522_irogers_perf_python_modernize_and_extend_python_api_phase_1.mbx
⬢ [acme@toolbx perf-tools-next2]$ git am ./v9_20260522_irogers_perf_python_modernize_and_extend_python_api_phase_1.mbx
Applying: perf arch arm: Sort includes and add missed explicit dependencies
Applying: perf arch x86: Sort includes and add missed explicit dependencies
Applying: perf tests: Sort includes and add missed explicit dependencies
Applying: perf script: Sort includes and add missed explicit dependencies
Applying: perf util: Sort includes and add missed explicit dependencies
error: patch failed: tools/perf/util/evsel.c:11
error: tools/perf/util/evsel.c: patch does not apply
Patch failed at 0005 perf util: Sort includes and add missed explicit dependencies
hint: Use 'git am --show-current-patch=diff' to see the failed patch
hint: When you have resolved this problem, run "git am --continue".
hint: If you prefer to skip this patch, run "git am --skip" instead.
hint: To restore the original branch and stop patching, run "git am --abort".
hint: Disable this message with "git config set advice.mergeConflict false"
⬢ [acme@toolbx perf-tools-next2]$ git am --abort
⬢ [acme@toolbx perf-tools-next2]$ git diff
⬢ [acme@toolbx perf-tools-next2]$
I agree with the intent and plan on reviewing and testing it.
- Arnaldo
^ permalink raw reply
* Re: [PATCH v9 00/23] perf python: Modernize and extend Python API (Phase 1)
From: Arnaldo Carvalho de Melo @ 2026-05-25 20:17 UTC (permalink / raw)
To: Ian Rogers
Cc: namhyung, adrian.hunter, alice.mei.rogers, dapeng1.mi,
james.clark, leo.yan, linux-arm-kernel, linux-kernel,
linux-perf-users, mingo, peterz, tmricht
In-Reply-To: <ahStntuCibNk-IjO@x1>
On Mon, May 25, 2026 at 05:14:25PM -0300, Arnaldo Carvalho de Melo wrote:
> On Fri, May 22, 2026 at 03:04:11PM -0700, Ian Rogers wrote:
> > This series takes a different approach with some initial implementation posted
> > as an RFC last October:
> > https://lore.kernel.org/linux-perf-users/20251029053413.355154-1-irogers@google.com/
> > with the motivation coming up on the mailing list earlier:
> > https://lore.kernel.org/lkml/CAP-5=fWDqE8SYfOLZkg_0=4Ayx6E7O+h7uUp4NDeCFkiN4b7-w@mail.gmail.com/
> > The ultimate goal is to remove the embedded libpython and libperl support from
> > perf entirely, expanding the existing perf Python module to provide full access
> > to perf data files and events, allowing scripts to be run as standalone Python
> > applications.
> I tried applying it now and got this:
> Cover: ./v9_20260522_irogers_perf_python_modernize_and_extend_python_api_phase_1.cover
> Link: https://lore.kernel.org/r/20260522220435.2378363-1-irogers@google.com
> Base: not specified
> git am ./v9_20260522_irogers_perf_python_modernize_and_extend_python_api_phase_1.mbx
> ⬢ [acme@toolbx perf-tools-next2]$ git am ./v9_20260522_irogers_perf_python_modernize_and_extend_python_api_phase_1.mbx
> Applying: perf arch arm: Sort includes and add missed explicit dependencies
> Applying: perf arch x86: Sort includes and add missed explicit dependencies
> Applying: perf tests: Sort includes and add missed explicit dependencies
> Applying: perf script: Sort includes and add missed explicit dependencies
> Applying: perf util: Sort includes and add missed explicit dependencies
> error: patch failed: tools/perf/util/evsel.c:11
> error: tools/perf/util/evsel.c: patch does not apply
> Patch failed at 0005 perf util: Sort includes and add missed explicit dependencies
> hint: Use 'git am --show-current-patch=diff' to see the failed patch
> hint: When you have resolved this problem, run "git am --continue".
> hint: If you prefer to skip this patch, run "git am --skip" instead.
> hint: To restore the original branch and stop patching, run "git am --abort".
> hint: Disable this message with "git config set advice.mergeConflict false"
> ⬢ [acme@toolbx perf-tools-next2]$ git am --abort
> ⬢ [acme@toolbx perf-tools-next2]$ git diff
> ⬢ [acme@toolbx perf-tools-next2]$
> I agree with the intent and plan on reviewing and testing it.
What I have now is at tmp.perf-tools-next if you wish to rebase from
there,
Regards,
- Arnaldo
^ permalink raw reply
* Re: [PATCH v4 1/5] PCI/ATS: Ensure pci_ats_supported() is PF-aware for VFs
From: Nicolin Chen @ 2026-05-25 19:42 UTC (permalink / raw)
To: Pranjal Shrivastava
Cc: iommu, linux-pci, linux-arm-kernel, linux-kernel, Joerg Roedel,
Will Deacon, Bjorn Helgaas, David Woodhouse, Lu Baolu,
Robin Murphy, Suravee Suthikulpanit, Jason Gunthorpe,
David Matlack, Samiullah Khawaja, Daniel Mentz, Pasha Tatashin,
Mostafa Saleh, Jason Gunthorpe
In-Reply-To: <20260525184347.4059549-2-praan@google.com>
On Mon, May 25, 2026 at 06:43:43PM +0000, Pranjal Shrivastava wrote:
> Update pci_ats_supported() to additionally check the associated PF's
> status when called on a VF. This ensures that PF-level quirks and
> untrusted status are correctly propagated to VFs, providing a robust
> support check that aligns with the kernel's PF-centric ATS configuration
> model and is immune to the timing of VF-specific fixups.
>
> Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
> Reviewed-by: Samiullah Khawaja <skhawaja@google.com>
> Signed-off-by: Pranjal Shrivastava <praan@google.com>
Reviewed-by: Nicolin Chen <nicolinc@nvidia.com>
^ permalink raw reply
* Re: [PATCH v4 3/5] iommu/arm-smmu-v3: Fix ATS state tracking
From: Pranjal Shrivastava @ 2026-05-25 20:30 UTC (permalink / raw)
To: Nicolin Chen
Cc: iommu, linux-pci, linux-arm-kernel, linux-kernel, Joerg Roedel,
Will Deacon, Bjorn Helgaas, David Woodhouse, Lu Baolu,
Robin Murphy, Suravee Suthikulpanit, Jason Gunthorpe,
David Matlack, Samiullah Khawaja, Daniel Mentz, Pasha Tatashin,
Mostafa Saleh
In-Reply-To: <ahSrkOv+4QZWrcJq@Asurada-Nvidia>
On Mon, May 25, 2026 at 01:05:36PM -0700, Nicolin Chen wrote:
> On Mon, May 25, 2026 at 06:43:45PM +0000, Pranjal Shrivastava wrote:
> > @@ -3065,8 +3065,14 @@ static void arm_smmu_enable_ats(struct arm_smmu_master *master)
> > * ATC invalidation of PASID 0 causes the entire ATC to be flushed.
> > */
> > arm_smmu_atc_inv_master(master, IOMMU_NO_PASID);
> > - if (pci_enable_ats(pdev, stu))
> > - dev_err(master->dev, "Failed to enable ATS (STU %zu)\n", stu);
> > +
> > + /*
> > + * Any failure at this point is a kernel bug. pci_ats_supported()
> > + * and pci_prepare_ats() have already verified the hardware capability
> > + * and programmed the STU. Thus, pci_enable_ats() should not fail here.
>
> Nits:
> - WARN usually indicates a kernel bug already.
> - pci_prepare_ats() covers pci_ats_supported().
Ack. For the pci_ats_supported.. since we're failing probe if
pci_prepare_ats() fails, the idea was to call prepare only if ATS was
supported [1] i.e. not penalise non-ATS callers of pci_prepare_ats. But
I agree.. maybe a better way would be to factor our pci_ats_supported().
>
> /*
> * As pci_prepare_ats() have already verified the hardware capability
> * and programmed the STE, pci_enable_ats() should not fail here.
> */
>
> > @@ -4264,9 +4270,16 @@ static struct iommu_device *arm_smmu_probe_device(struct device *dev)
> > master->stall_enabled = true;
> >
> > if (dev_is_pci(dev)) {
> > - unsigned int stu = __ffs(smmu->pgsize_bitmap);
> > + struct pci_dev *pdev = to_pci_dev(dev);
> >
> > - pci_prepare_ats(to_pci_dev(dev), stu);
> > + if (pci_ats_supported(pdev)) {
> > + unsigned int stu = __ffs(smmu->pgsize_bitmap);
> > + int ret;
> > +
> > + ret = pci_prepare_ats(pdev, stu);
> > + if (ret)
> > + return ERR_PTR(ret);
> > + }
>
> Again, pci_prepare_ats() covers pci_ats_supported(). So, the check
> is redundant. Instead, it should check arm_smmu_ats_supported().
>
Ack, I'll add a check with arm_smmu_ats_supported
> By the way, this would conflict into my series:
> https://lore.kernel.org/linux-iommu/18bb6f421b3be891caa8f1fb50f3a4d56b52d5be.1779392420.git.nicolinc@nvidia.com/
>
> It would be nicer to have an arm_smmu_master_prepare_ats() so that
> both series would have a common ground; mine would be just adding
> some additional lines if your series goes in first.
Ahh yes, I was thinking about the conflict to (I'm yet to review the
series though). That makes sense, we could add
arm_smmu_master_prepare_ats() for clarity
Thanks,
Praan
[1] https://lore.kernel.org/all/20260519145947.GK7702@ziepe.ca/
^ permalink raw reply
* Re: [PATCH v4 3/5] iommu/arm-smmu-v3: Fix ATS state tracking
From: Nicolin Chen @ 2026-05-25 20:40 UTC (permalink / raw)
To: Pranjal Shrivastava
Cc: iommu, linux-pci, linux-arm-kernel, linux-kernel, Joerg Roedel,
Will Deacon, Bjorn Helgaas, David Woodhouse, Lu Baolu,
Robin Murphy, Suravee Suthikulpanit, Jason Gunthorpe,
David Matlack, Samiullah Khawaja, Daniel Mentz, Pasha Tatashin,
Mostafa Saleh
In-Reply-To: <ahSxWoWd5hFvKE_R@google.com>
On Mon, May 25, 2026 at 08:30:18PM +0000, Pranjal Shrivastava wrote:
> On Mon, May 25, 2026 at 01:05:36PM -0700, Nicolin Chen wrote:
> > On Mon, May 25, 2026 at 06:43:45PM +0000, Pranjal Shrivastava wrote:
> > - pci_prepare_ats() covers pci_ats_supported().
>
> the idea was to call prepare only if ATS was
> supported [1] i.e. not penalise non-ATS callers of pci_prepare_ats. But
Yea. I realized that it is slightly different after I replied.
Nicolin
^ permalink raw reply
* [PATCH v2 0/9] firmware: arm_scmi: Refactoring and enablement of ACPI PCC transport
From: Sudeep Holla @ 2026-05-25 20:42 UTC (permalink / raw)
To: arm-scmi, linux-arm-kernel; +Cc: Sudeep Holla, Cristian Marussi
The SCMI can be utilized in systems using either the FDT or ACPI specification.
While FDT-based systems can natively use SCMI, ACPI-based systems often
need to abstract the functionality provided by SCMI under ASL methods.
So far, there has been no need to support SCMI natively on ACPI systems.
However, with the addition of a few new protocols such as Powercap and Telemetry,
which lack abstractions in the ACPI specification, there is now a need to
run SCMI natively for those use cases. ACPI for System Control and Management
Interface document[1] provides a mechanism which allows ACPI-based systems to
discover whether a platform supports SCMI and to natively use SCMI for
selected/limited subset of SCMI protocols.
This patch series introduces ACPI PCC transport support for the Arm SCMI
framework, alongside several foundational refactors and enhancements to
achieve firmware-node neutrality between Device Tree (DT) and ACPI systems.
The key changes include:
1. ACPI/DT abstraction and fwnode transition
Converted the core SCMI code to use `fwnode_handle` instead of DT-specific
structures, ensuring seamless operation across both ACPI and DT
environments. All property lookups, child enumeration, and device
association paths have been updated accordingly.
2. Unified transport registration for ACPI and DT
Extended the SCMI transport driver macros to support ACPI match tables,
enabling transports to probe using ACPI device IDs while maintaining
backward compatibility with DT-only systems.
3. Protocol device initialization and refactoring
Refactored the protocol device creation and validation logic into a new
helper for improved readability and maintainability. Enhanced the
initialization logic to handle ACPI-based SCMI devices without explicit
child fwnodes.
4. Introduction of ACPI PCC transport
Added a new SCMI transport driver leveraging ACPI PCCT (Platform
Communications Channel Table) subspaces via the Linux PCC mailbox
framework. This enables SCMI communication over PCC on ACPI-based
platforms.
Collectively, these changes lay the groundwork for robust SCMI operation on
ACPI platforms, achieving near parity with DT systems where applicable,
while enabling the new PCC transport path for firmware communication.
Note: SCMI will not be run natively for all existing protocols other
than Powercap and Telemetry, as they must continue to use ACPI abstractions.
[1] https://developer.arm.com/documentation/111115/latest
-->8
Device (SCM0) {
Name(_HID, "ARML0001")
Name (_UID, 0)
Name (_DSD, Package () {
ToUUID("84a2d1c6-86b6-4199-8dac-9c17449d5e03"), // SCMI Properties UUID
Package () {
Package(2) {
"arm-arml0001-transport-pcc", // Key
Package () { // Value
1, // Revision
3, // Count
Package(){4, 0, 1}, // PCCT Idx, TransportUID, A2P Common
Package(){7, 1, 0}, // PCCT Idx, TransportUID, A2P Exclusive
Package(){9, 2, 2} // PCCT Idx, TransportUID, P2A Exclusive
} // Value
},
Package(2) {
"arm-arml0001-protocol-pcap", // Key
Package () { // Value
1, // Revision
Package() { // Protocol Exclusive Transport Package
// Empty; A2P Common
},
Package() {} // Protocol Association Package
} // Value
},
Package(2) {
"arm-arml0001-protocol-telemetry", // Key
Package () { // Value
1, // Revision
Package() { // Protocol Exclusive Transport Package
Package (2) {1, 0}, // UID=1; Flags=0; A2P Exclusive
Package (2) {2, 0} // UID=2; Flags=0; P2A Exclusive
},
Package() {} // Protocol Association Package
} // Value
},
} // Device Properties
}) // _DSD
} // SCM0
Signed-off-by: Sudeep Holla <sudeep.holla@kernel.org>
---
Changes in v2:
- Fixed SCMI fwnode lifetime handling by taking a reference when
assigning protocol device fwnodes and releasing it from the device
release path.
- Reworked ACPI PCC _DSD parsing to validate package types,
revisions, counts, UID ranges, flags, duplicate UIDs, and channel
mappings.
- Parse the SCMI ACPI _DSD UUID package directly as we moved to SCMI
specific UUID from the generic device properties UUID and reject
malformed or unsupported protocol association data.
- Tightened PCC channel selection so each protocol gets at most one Tx
and one Rx channel, with exactly one common A2P channel and at most
one common P2A channel.
- Only set the PCC interrupt-enable flag when the mailbox channel has a
txdone IRQ.
- Added the SCMI Telemetry protocol ID(Temporary to get the build
working, must come from Cristian's Telemetry support patched)
- Link to v1: https://patch.msgid.link/20251017-acpi_scmi_pcc-v1-0-0adbab7709d9@arm.com
---
Sudeep Holla (9):
firmware: arm_scmi: Fix OF node reference handling
firmware: arm_scmi: Set fwnode for the generated SCMI platform device
firmware: arm_scmi: Extend transport driver macro to support ACPI
firmware: arm_scmi: Convert OF-only paths to generic fwnode in SCMI core
firmware: arm_scmi: Fall back to ACPI HID when "compatible" is absent
firmware: arm_scmi: Pass protocol ID to chan_available() transport callback
firmware: arm_scmi: Refactor protocol device creation logic
firmware: arm_scmi: transport: Add ACPI PCC transport
firmware: arm_scmi: Initialise all protocol devices and transport channels
drivers/firmware/arm_scmi/bus.c | 36 +-
drivers/firmware/arm_scmi/common.h | 36 +-
drivers/firmware/arm_scmi/driver.c | 183 ++++----
drivers/firmware/arm_scmi/transports/Kconfig | 12 +
drivers/firmware/arm_scmi/transports/Makefile | 2 +
drivers/firmware/arm_scmi/transports/mailbox.c | 7 +-
drivers/firmware/arm_scmi/transports/optee.c | 7 +-
drivers/firmware/arm_scmi/transports/pcc.c | 593 +++++++++++++++++++++++++
drivers/firmware/arm_scmi/transports/smc.c | 12 +-
drivers/firmware/arm_scmi/transports/virtio.c | 3 +-
include/linux/scmi_protocol.h | 1 +
11 files changed, 785 insertions(+), 107 deletions(-)
---
base-commit: e7ae89a0c97ce2b68b0983cd01eda67cf373517d
change-id: 20251017-acpi_scmi_pcc-e44e1233eae3
--
Regards,
Sudeep
^ permalink raw reply
* [PATCH v2 3/9] firmware: arm_scmi: Extend transport driver macro to support ACPI
From: Sudeep Holla @ 2026-05-25 20:42 UTC (permalink / raw)
To: arm-scmi, linux-arm-kernel; +Cc: Sudeep Holla, Cristian Marussi
In-Reply-To: <20260525-acpi_scmi_pcc-v2-0-4f38938d08d8@arm.com>
Extend the SCMI transport driver helper to support ACPI-based
systems. Introduce an internal helper macro that accepts both OF
and ACPI match tables, and expose two wrappers:
- DEFINE_SCMI_TRANSPORT_DRIVER(...) for DT/OF transports
- DEFINE_SCMI_ACPI_TRANSPORT_DRIVER(...) for ACPI transports
For ACPI, the generated platform_driver now sets .acpi_match_table
via ACPI_PTR(), and the spawned platform_device inherits the
parent device's ACPI companion so subsequent fwnode lookups see
the correct firmware node.
This keeps existing DT users unchanged while allowing transports
to be probed using struct acpi_device_id tables on ACPI platforms.
Signed-off-by: Sudeep Holla <sudeep.holla@kernel.org>
---
drivers/firmware/arm_scmi/common.h | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h
index cc469370e989..aea3685da824 100644
--- a/drivers/firmware/arm_scmi/common.h
+++ b/drivers/firmware/arm_scmi/common.h
@@ -9,6 +9,7 @@
#ifndef _SCMI_COMMON_H
#define _SCMI_COMMON_H
+#include <linux/acpi.h>
#include <linux/bitfield.h>
#include <linux/completion.h>
#include <linux/device.h>
@@ -477,7 +478,8 @@ struct scmi_transport {
struct scmi_transport_core_operations **core_ops;
};
-#define DEFINE_SCMI_TRANSPORT_DRIVER(__tag, __drv, __desc, __match, __core_ops)\
+#define __DEFINE_SCMI_TRANSPORT_DRIVER(__tag, __drv, __desc, __of_match, \
+ __acpi_match, __core_ops) \
static void __tag##_dev_free(void *data) \
{ \
struct platform_device *spdev = data; \
@@ -498,6 +500,7 @@ static int __tag##_probe(struct platform_device *pdev) \
\
device_set_of_node_from_dev(&spdev->dev, dev); \
device_set_node(&spdev->dev, dev_fwnode(dev)); \
+ ACPI_COMPANION_SET(&spdev->dev, ACPI_COMPANION(dev)); \
\
strans.supplier = dev; \
memcpy(&strans.desc, &(__desc), sizeof(strans.desc)); \
@@ -522,11 +525,18 @@ err: \
static struct platform_driver __drv = { \
.driver = { \
.name = #__tag "_transport", \
- .of_match_table = __match, \
+ .of_match_table = __of_match, \
+ .acpi_match_table = ACPI_PTR(__acpi_match), \
}, \
.probe = __tag##_probe, \
}
+#define DEFINE_SCMI_TRANSPORT_DRIVER(__tag, __drv, __desc, __match, __core_ops)\
+ __DEFINE_SCMI_TRANSPORT_DRIVER(__tag, __drv, __desc, __match, NULL, __core_ops)
+
+#define DEFINE_SCMI_ACPI_TRANSPORT_DRIVER(__tag, __drv, __desc, __match, __core_ops)\
+ __DEFINE_SCMI_TRANSPORT_DRIVER(__tag, __drv, __desc, NULL, __match, __core_ops)
+
void scmi_notification_instance_data_set(const struct scmi_handle *handle,
void *priv);
void *scmi_notification_instance_data_get(const struct scmi_handle *handle);
--
2.43.0
^ permalink raw reply related
* [PATCH v2 6/9] firmware: arm_scmi: Pass protocol ID to chan_available() transport callback
From: Sudeep Holla @ 2026-05-25 20:42 UTC (permalink / raw)
To: arm-scmi, linux-arm-kernel; +Cc: Sudeep Holla, Cristian Marussi
In-Reply-To: <20260525-acpi_scmi_pcc-v2-0-4f38938d08d8@arm.com>
Extend the SCMI transport_ops chan_available() callback to include the
protocol ID (prot_id) as an argument. This allows transports to determine
channel availability based on the specific protocol being used, improving
flexibility in platforms that share transport channels across multiple
protocols. This will be useful when ACPI PCC transport gets added.
Updated all existing users and definitions of chan_available() in
SCMI core and transport drivers (mailbox, optee, etc.) accordingly.
No functional change.
Signed-off-by: Sudeep Holla <sudeep.holla@kernel.org>
---
drivers/firmware/arm_scmi/common.h | 3 ++-
drivers/firmware/arm_scmi/driver.c | 2 +-
drivers/firmware/arm_scmi/transports/mailbox.c | 3 ++-
drivers/firmware/arm_scmi/transports/optee.c | 3 ++-
drivers/firmware/arm_scmi/transports/smc.c | 3 ++-
drivers/firmware/arm_scmi/transports/virtio.c | 3 ++-
6 files changed, 11 insertions(+), 6 deletions(-)
diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h
index 192c7d697f2e..347e0da0d9cc 100644
--- a/drivers/firmware/arm_scmi/common.h
+++ b/drivers/firmware/arm_scmi/common.h
@@ -204,7 +204,8 @@ struct scmi_chan_info {
* @poll_done: Callback to poll transfer status
*/
struct scmi_transport_ops {
- bool (*chan_available)(struct fwnode_handle *fwnode, int idx);
+ bool (*chan_available)(struct fwnode_handle *fwnode, int prot_id,
+ int idx);
int (*chan_setup)(struct scmi_chan_info *cinfo, struct device *dev,
bool tx);
int (*chan_free)(int id, void *p, void *data);
diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
index 04a0d8202504..02f14167c918 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -2723,7 +2723,7 @@ static int scmi_chan_setup(struct scmi_info *info, struct fwnode_handle *fwnode,
idx = tx ? 0 : 1;
idr = tx ? &info->tx_idr : &info->rx_idr;
- if (!info->desc->ops->chan_available(fwnode, idx)) {
+ if (!info->desc->ops->chan_available(fwnode, prot_id, idx)) {
cinfo = idr_find(idr, SCMI_PROTOCOL_BASE);
if (unlikely(!cinfo)) /* Possible only if platform has no Rx */
return -EINVAL;
diff --git a/drivers/firmware/arm_scmi/transports/mailbox.c b/drivers/firmware/arm_scmi/transports/mailbox.c
index 280623e576f8..86b35a1e7b0e 100644
--- a/drivers/firmware/arm_scmi/transports/mailbox.c
+++ b/drivers/firmware/arm_scmi/transports/mailbox.c
@@ -77,7 +77,8 @@ static void rx_callback(struct mbox_client *cl, void *m)
core->shmem->read_header(smbox->shmem), NULL);
}
-static bool mailbox_chan_available(struct fwnode_handle *fwnode, int idx)
+static bool
+mailbox_chan_available(struct fwnode_handle *fwnode, int prot_id, int idx)
{
int num_mb;
struct device_node *of_node = to_of_node(fwnode);
diff --git a/drivers/firmware/arm_scmi/transports/optee.c b/drivers/firmware/arm_scmi/transports/optee.c
index be2630982c72..e89bd281fe72 100644
--- a/drivers/firmware/arm_scmi/transports/optee.c
+++ b/drivers/firmware/arm_scmi/transports/optee.c
@@ -312,7 +312,8 @@ static int invoke_process_msg_channel(struct scmi_optee_channel *channel, size_t
return 0;
}
-static bool scmi_optee_chan_available(struct fwnode_handle *fwnode, int idx)
+static bool
+scmi_optee_chan_available(struct fwnode_handle *fwnode, int prot_id, int idx)
{
u32 channel_id;
struct device_node *of_node = to_of_node(fwnode);
diff --git a/drivers/firmware/arm_scmi/transports/smc.c b/drivers/firmware/arm_scmi/transports/smc.c
index 5ea32f8c562c..9fd9830f69ba 100644
--- a/drivers/firmware/arm_scmi/transports/smc.c
+++ b/drivers/firmware/arm_scmi/transports/smc.c
@@ -84,7 +84,8 @@ static irqreturn_t smc_msg_done_isr(int irq, void *data)
return IRQ_HANDLED;
}
-static bool smc_chan_available(struct fwnode_handle *fwnode, int idx)
+static bool
+smc_chan_available(struct fwnode_handle *fwnode, int prot_id, int idx)
{
struct device_node *of_node = to_of_node(fwnode);
struct device_node *np __free(device_node) = NULL;
diff --git a/drivers/firmware/arm_scmi/transports/virtio.c b/drivers/firmware/arm_scmi/transports/virtio.c
index cb749ac7ab46..7137165dc204 100644
--- a/drivers/firmware/arm_scmi/transports/virtio.c
+++ b/drivers/firmware/arm_scmi/transports/virtio.c
@@ -373,7 +373,8 @@ static unsigned int virtio_get_max_msg(struct scmi_chan_info *base_cinfo)
return vioch->max_msg;
}
-static bool virtio_chan_available(struct fwnode_handle *fwnode, int idx)
+static bool
+virtio_chan_available(struct fwnode_handle *fwnode, int prot_id, int idx)
{
struct scmi_vio_channel *channels, *vioch = NULL;
--
2.43.0
^ permalink raw reply related
* [PATCH v2 2/9] firmware: arm_scmi: Set fwnode for the generated SCMI platform device
From: Sudeep Holla @ 2026-05-25 20:42 UTC (permalink / raw)
To: arm-scmi, linux-arm-kernel; +Cc: Sudeep Holla, Cristian Marussi
In-Reply-To: <20260525-acpi_scmi_pcc-v2-0-4f38938d08d8@arm.com>
Add a call to device_set_node() in the SCMI probe helper to associate
generated SCMI platform device with the firmware node of its supplier
transport device.
This complements device_set_of_node_from_dev() and ensures that
firmware node information is propagated correctly for both Device Tree
and non-DT (e.g. ACPI) based systems.
Signed-off-by: Sudeep Holla <sudeep.holla@kernel.org>
---
drivers/firmware/arm_scmi/common.h | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h
index 7c9617d080a0..cc469370e989 100644
--- a/drivers/firmware/arm_scmi/common.h
+++ b/drivers/firmware/arm_scmi/common.h
@@ -497,6 +497,7 @@ static int __tag##_probe(struct platform_device *pdev) \
return -ENOMEM; \
\
device_set_of_node_from_dev(&spdev->dev, dev); \
+ device_set_node(&spdev->dev, dev_fwnode(dev)); \
\
strans.supplier = dev; \
memcpy(&strans.desc, &(__desc), sizeof(strans.desc)); \
--
2.43.0
^ permalink raw reply related
* [PATCH v2 1/9] firmware: arm_scmi: Fix OF node reference handling
From: Sudeep Holla @ 2026-05-25 20:42 UTC (permalink / raw)
To: arm-scmi, linux-arm-kernel; +Cc: Sudeep Holla, Cristian Marussi
In-Reply-To: <20260525-acpi_scmi_pcc-v2-0-4f38938d08d8@arm.com>
SCMI devices store the DT node in dev.of_node through
device_set_node(), but that helper only assigns the fwnode and
of_node pointers without taking an OF node reference.
Take a reference when assigning the node and release it from the
SCMI device release path. With the device owning that reference,
remove the separate channel-side get/put pair from the core driver.
Signed-off-by: Sudeep Holla <sudeep.holla@kernel.org>
---
drivers/firmware/arm_scmi/bus.c | 3 ++-
drivers/firmware/arm_scmi/driver.c | 4 ----
2 files changed, 2 insertions(+), 5 deletions(-)
diff --git a/drivers/firmware/arm_scmi/bus.c b/drivers/firmware/arm_scmi/bus.c
index 793be9eabaed..7de45533b4c7 100644
--- a/drivers/firmware/arm_scmi/bus.c
+++ b/drivers/firmware/arm_scmi/bus.c
@@ -395,6 +395,7 @@ static void scmi_device_release(struct device *dev)
{
struct scmi_device *scmi_dev = to_scmi_dev(dev);
+ of_node_put(scmi_dev->dev.of_node);
kfree_const(scmi_dev->name);
kfree(scmi_dev);
}
@@ -465,7 +466,7 @@ __scmi_device_create(struct device_node *np, struct device *parent,
scmi_dev->id = id;
scmi_dev->protocol_id = protocol;
scmi_dev->dev.parent = parent;
- device_set_node(&scmi_dev->dev, of_fwnode_handle(np));
+ device_set_node(&scmi_dev->dev, of_fwnode_handle(of_node_get(np)));
scmi_dev->dev.bus = &scmi_bus_type;
scmi_dev->dev.release = scmi_device_release;
dev_set_name(&scmi_dev->dev, "scmi_dev.%d", id);
diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
index f167194f7cf6..e04cb55e866a 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -2748,13 +2748,11 @@ static int scmi_chan_setup(struct scmi_info *info, struct device_node *of_node,
devm_kfree(info->dev, cinfo);
return -EINVAL;
}
- of_node_get(of_node);
cinfo->id = prot_id;
cinfo->dev = &tdev->dev;
ret = info->desc->ops->chan_setup(cinfo, info->dev, tx);
if (ret) {
- of_node_put(of_node);
scmi_device_destroy(info->dev, prot_id, name);
devm_kfree(info->dev, cinfo);
return ret;
@@ -2777,7 +2775,6 @@ static int scmi_chan_setup(struct scmi_info *info, struct device_node *of_node,
"unable to allocate SCMI idr slot err %d\n", ret);
/* Destroy channel and device only if created by this call. */
if (tdev) {
- of_node_put(of_node);
scmi_device_destroy(info->dev, prot_id, name);
devm_kfree(info->dev, cinfo);
}
@@ -2862,7 +2859,6 @@ static int scmi_chan_destroy(int id, void *p, void *idr)
struct scmi_info *info = handle_to_scmi_info(cinfo->handle);
struct scmi_device *sdev = to_scmi_dev(cinfo->dev);
- of_node_put(cinfo->dev->of_node);
scmi_device_destroy(info->dev, id, sdev->name);
cinfo->dev = NULL;
}
--
2.43.0
^ permalink raw reply related
* [PATCH v2 4/9] firmware: arm_scmi: Convert OF-only paths to generic fwnode in SCMI core
From: Sudeep Holla @ 2026-05-25 20:42 UTC (permalink / raw)
To: arm-scmi, linux-arm-kernel; +Cc: Sudeep Holla, Cristian Marussi
In-Reply-To: <20260525-acpi_scmi_pcc-v2-0-4f38938d08d8@arm.com>
Switch SCMI core plumbing from struct device_node * to struct
fwnode_handle * so the core can describe SCMI instances and protocols using
firmware nodes rather than OF nodes directly.
This change:
- Replaces core OF property lookups with fwnode_property_*() helpers.
- Switches child enumeration to
fwnode_for_each_available_child_node_scoped().
- Plumbs fwnode through the SCMI device creation and channel setup paths.
- Updates transport ->chan_available() callbacks to take a fwnode.
- Stores per-protocol child fwnodes in info->active_protocols so the core
can later locate the descriptor for a given protocol ID.
- Updates mailbox/optee/smc/virtio transports to accept fwnodes and map
back to OF nodes where their existing parsing remains DT-specific.
DT-only transports such as mailbox, OP-TEE and SMC still parse DT
properties by mapping the fwnode back to an OF node. On non-DT systems
these transports report no channel available.
This is a mechanical step towards firmware-node neutrality and prepares the
SCMI core for non-DT transports, such as an ACPI/PCC transport. DT users
continue to work unchanged; no non-DT transport is enabled by this patch.
Signed-off-by: Sudeep Holla <sudeep.holla@kernel.org>
---
drivers/firmware/arm_scmi/bus.c | 37 ++++-----
drivers/firmware/arm_scmi/common.h | 9 +-
drivers/firmware/arm_scmi/driver.c | 110 +++++++++++++------------
drivers/firmware/arm_scmi/transports/mailbox.c | 6 +-
drivers/firmware/arm_scmi/transports/optee.c | 6 +-
drivers/firmware/arm_scmi/transports/smc.c | 11 ++-
drivers/firmware/arm_scmi/transports/virtio.c | 2 +-
7 files changed, 98 insertions(+), 83 deletions(-)
diff --git a/drivers/firmware/arm_scmi/bus.c b/drivers/firmware/arm_scmi/bus.c
index 7de45533b4c7..f939347e84c4 100644
--- a/drivers/firmware/arm_scmi/bus.c
+++ b/drivers/firmware/arm_scmi/bus.c
@@ -10,7 +10,7 @@
#include <linux/atomic.h>
#include <linux/types.h>
#include <linux/module.h>
-#include <linux/of.h>
+#include <linux/property.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/device.h>
@@ -395,17 +395,16 @@ static void scmi_device_release(struct device *dev)
{
struct scmi_device *scmi_dev = to_scmi_dev(dev);
- of_node_put(scmi_dev->dev.of_node);
+ fwnode_handle_put(dev_fwnode(dev));
kfree_const(scmi_dev->name);
kfree(scmi_dev);
}
static void __scmi_device_destroy(struct scmi_device *scmi_dev)
{
- pr_debug("(%pOF) Destroying SCMI device '%s' for protocol 0x%x (%s)\n",
- scmi_dev->dev.parent->of_node,
- dev_name(&scmi_dev->dev), scmi_dev->protocol_id,
- scmi_dev->name);
+ pr_debug("(%pfwf) Destroying SCMI device '%s' for protocol 0x%x (%s)\n",
+ dev_fwnode(&scmi_dev->dev), dev_name(&scmi_dev->dev),
+ scmi_dev->protocol_id, scmi_dev->name);
if (scmi_dev->protocol_id == SCMI_PROTOCOL_SYSTEM)
atomic_set(&scmi_syspower_registered, 0);
@@ -415,7 +414,7 @@ static void __scmi_device_destroy(struct scmi_device *scmi_dev)
}
static struct scmi_device *
-__scmi_device_create(struct device_node *np, struct device *parent,
+__scmi_device_create(struct fwnode_handle *fwnode, struct device *parent,
int protocol, const char *name)
{
int id, retval;
@@ -425,7 +424,7 @@ __scmi_device_create(struct device_node *np, struct device *parent,
* If the same protocol/name device already exist under the same parent
* (i.e. SCMI instance) just return the existent device.
* This avoids any race between the SCMI driver, creating devices for
- * each DT defined protocol at probe time, and the concurrent
+ * each fwnode defined protocol at probe time, and the concurrent
* registration of SCMI drivers.
*/
scmi_dev = scmi_child_dev_find(parent, protocol, name);
@@ -466,7 +465,7 @@ __scmi_device_create(struct device_node *np, struct device *parent,
scmi_dev->id = id;
scmi_dev->protocol_id = protocol;
scmi_dev->dev.parent = parent;
- device_set_node(&scmi_dev->dev, of_fwnode_handle(of_node_get(np)));
+ device_set_node(&scmi_dev->dev, fwnode_handle_get(fwnode));
scmi_dev->dev.bus = &scmi_bus_type;
scmi_dev->dev.release = scmi_device_release;
dev_set_name(&scmi_dev->dev, "scmi_dev.%d", id);
@@ -475,8 +474,8 @@ __scmi_device_create(struct device_node *np, struct device *parent,
if (retval)
goto put_dev;
- pr_debug("(%pOF) Created SCMI device '%s' for protocol 0x%x (%s)\n",
- parent->of_node, dev_name(&scmi_dev->dev), protocol, name);
+ pr_debug("(%pfwf) Created SCMI device '%s' - protocol 0x%x (%s)\n",
+ fwnode, dev_name(&scmi_dev->dev), protocol, name);
return scmi_dev;
put_dev:
@@ -486,15 +485,15 @@ __scmi_device_create(struct device_node *np, struct device *parent,
}
static struct scmi_device *
-_scmi_device_create(struct device_node *np, struct device *parent,
+_scmi_device_create(struct fwnode_handle *fwnode, struct device *parent,
int protocol, const char *name)
{
struct scmi_device *sdev;
- sdev = __scmi_device_create(np, parent, protocol, name);
+ sdev = __scmi_device_create(fwnode, parent, protocol, name);
if (!sdev)
- pr_err("(%pOF) Failed to create device for protocol 0x%x (%s)\n",
- parent->of_node, protocol, name);
+ pr_err("(%pfwf) Failed to create device - protocol 0x%x (%s)\n",
+ fwnode, protocol, name);
return sdev;
}
@@ -502,7 +501,7 @@ _scmi_device_create(struct device_node *np, struct device *parent,
/**
* scmi_device_create - A method to create one or more SCMI devices
*
- * @np: A reference to the device node to use for the new device(s)
+ * @fwnode: A reference to the device node to use for the new device(s)
* @parent: The parent device to use identifying a specific SCMI instance
* @protocol: The SCMI protocol to be associated with this device
* @name: The requested-name of the device to be created; this is optional
@@ -522,7 +521,7 @@ _scmi_device_create(struct device_node *np, struct device *parent,
* could have been potentially created for a whole protocol, unless no
* device was found to have been requested for that specific protocol.
*/
-struct scmi_device *scmi_device_create(struct device_node *np,
+struct scmi_device *scmi_device_create(struct fwnode_handle *fwnode,
struct device *parent, int protocol,
const char *name)
{
@@ -531,7 +530,7 @@ struct scmi_device *scmi_device_create(struct device_node *np,
struct scmi_device *scmi_dev = NULL;
if (name)
- return _scmi_device_create(np, parent, protocol, name);
+ return _scmi_device_create(fwnode, parent, protocol, name);
mutex_lock(&scmi_requested_devices_mtx);
phead = idr_find(&scmi_requested_devices, protocol);
@@ -545,7 +544,7 @@ struct scmi_device *scmi_device_create(struct device_node *np,
list_for_each_entry(rdev, phead, node) {
struct scmi_device *sdev;
- sdev = _scmi_device_create(np, parent,
+ sdev = _scmi_device_create(fwnode, parent,
rdev->id_table->protocol_id,
rdev->id_table->name);
if (sdev)
diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h
index aea3685da824..192c7d697f2e 100644
--- a/drivers/firmware/arm_scmi/common.h
+++ b/drivers/firmware/arm_scmi/common.h
@@ -150,7 +150,7 @@ extern const struct bus_type scmi_bus_type;
#define SCMI_BUS_NOTIFY_DEVICE_UNREQUEST 1
extern struct blocking_notifier_head scmi_requested_devices_nh;
-struct scmi_device *scmi_device_create(struct device_node *np,
+struct scmi_device *scmi_device_create(struct fwnode_handle *fwnode,
struct device *parent, int protocol,
const char *name);
void scmi_device_destroy(struct device *parent, int protocol, const char *name);
@@ -204,7 +204,7 @@ struct scmi_chan_info {
* @poll_done: Callback to poll transfer status
*/
struct scmi_transport_ops {
- bool (*chan_available)(struct device_node *of_node, int idx);
+ bool (*chan_available)(struct fwnode_handle *fwnode, int idx);
int (*chan_setup)(struct scmi_chan_info *cinfo, struct device *dev,
bool tx);
int (*chan_free)(int id, void *p, void *data);
@@ -230,7 +230,7 @@ struct scmi_transport_ops {
* be pending simultaneously in the system. May be overridden by the
* get_max_msg op.
* @max_msg_size: Maximum size of data payload per message that can be handled.
- * @atomic_threshold: Optional system wide DT-configured threshold, expressed
+ * @atomic_threshold: Optional system wide fwnode-configured threshold, expressed
* in microseconds, for atomic operations.
* Only SCMI synchronous commands reported by the platform
* to have an execution latency lesser-equal to the threshold
@@ -238,7 +238,8 @@ struct scmi_transport_ops {
* decision is finally left up to the SCMI drivers.
* @no_completion_irq: Flag to indicate that this transport has no completion
* interrupt and has to be polled. This is similar to the
- * force_polling below, except this is set via DT property.
+ * force_polling below, except this is set via fwnode
+ * property.
* @force_polling: Flag to force this whole transport to use SCMI core polling
* mechanism instead of completion interrupts even if available.
* @sync_cmds_completed_on_ret: Flag to indicate that the transport assures
diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
index e04cb55e866a..75dda6d5e8b1 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -29,7 +29,7 @@
#include <linux/hashtable.h>
#include <linux/list.h>
#include <linux/module.h>
-#include <linux/of.h>
+#include <linux/property.h>
#include <linux/platform_device.h>
#include <linux/processor.h>
#include <linux/refcount.h>
@@ -100,7 +100,7 @@ struct scmi_xfers_info {
* initialization code to identify this instance.
*
* Each protocol is initialized independently once for each SCMI platform in
- * which is defined by DT and implemented by the SCMI server fw.
+ * which is defined by fwnode and implemented by the SCMI server fw.
*/
struct scmi_protocol_instance {
const struct scmi_handle *handle;
@@ -135,8 +135,9 @@ struct scmi_protocol_instance {
* @protocols_imp: List of protocols implemented, currently maximum of
* scmi_revision_info.num_protocols elements allocated by the
* base protocol
- * @active_protocols: IDR storing device_nodes for protocols actually defined
- * in the DT and confirmed as implemented by fw.
+ * @active_protocols: IDR storing fwnodes for protocols actually defined
+ * in the firmware description and confirmed as implemented
+ * by fw.
* @notify_priv: Pointer to private data structure specific to notifications.
* @node: List head
* @users: Number of users of this instance
@@ -414,19 +415,19 @@ EXPORT_SYMBOL_GPL(scmi_protocol_unregister);
* scmi_create_protocol_devices - Create devices for all pending requests for
* this SCMI instance.
*
- * @np: The device node describing the protocol
+ * @fwnode: The firmware node describing the protocol
* @info: The SCMI instance descriptor
* @prot_id: The protocol ID
* @name: The optional name of the device to be created: if not provided this
* call will lead to the creation of all the devices currently requested
* for the specified protocol.
*/
-static void scmi_create_protocol_devices(struct device_node *np,
+static void scmi_create_protocol_devices(struct fwnode_handle *fwnode,
struct scmi_info *info,
int prot_id, const char *name)
{
mutex_lock(&info->devreq_mtx);
- scmi_device_create(np, info->dev, prot_id, name);
+ scmi_device_create(fwnode, info->dev, prot_id, name);
mutex_unlock(&info->devreq_mtx);
}
@@ -750,9 +751,10 @@ struct scmi_xfer *scmi_xfer_raw_get(const struct scmi_handle *handle)
* @protocol_id: Identifier of the protocol
*
* Note that in a regular SCMI stack, usually, a protocol has to be defined in
- * the DT to have an associated channel and be usable; but in Raw mode any
- * protocol in range is allowed, re-using the Base channel, so as to enable
- * fuzzing on any protocol without the need of a fully compiled DT.
+ * the firmware description to have an associated channel and be usable; but in
+ * Raw mode any protocol in range is allowed, re-using the Base channel, so as
+ * to enable fuzzing on any protocol without the need of a fully compiled
+ * firmware description.
*
* Return: A reference to the channel to use, or an ERR_PTR
*/
@@ -766,7 +768,7 @@ scmi_xfer_raw_channel_get(const struct scmi_handle *handle, u8 protocol_id)
if (!cinfo) {
if (protocol_id == SCMI_PROTOCOL_BASE)
return ERR_PTR(-EINVAL);
- /* Use Base channel for protocols not defined for DT */
+ /* Use Base channel for protocols not defined for fwnode */
cinfo = idr_find(&info->tx_idr, SCMI_PROTOCOL_BASE);
if (!cinfo)
return ERR_PTR(-EINVAL);
@@ -2708,7 +2710,7 @@ static int scmi_xfer_info_init(struct scmi_info *sinfo)
return ret;
}
-static int scmi_chan_setup(struct scmi_info *info, struct device_node *of_node,
+static int scmi_chan_setup(struct scmi_info *info, struct fwnode_handle *fwnode,
int prot_id, bool tx)
{
int ret, idx;
@@ -2721,7 +2723,7 @@ static int scmi_chan_setup(struct scmi_info *info, struct device_node *of_node,
idx = tx ? 0 : 1;
idr = tx ? &info->tx_idr : &info->rx_idr;
- if (!info->desc->ops->chan_available(of_node, idx)) {
+ if (!info->desc->ops->chan_available(fwnode, idx)) {
cinfo = idr_find(idr, SCMI_PROTOCOL_BASE);
if (unlikely(!cinfo)) /* Possible only if platform has no Rx */
return -EINVAL;
@@ -2741,7 +2743,7 @@ static int scmi_chan_setup(struct scmi_info *info, struct device_node *of_node,
snprintf(name, 32, "__scmi_transport_device_%s_%02X",
idx ? "rx" : "tx", prot_id);
/* Create a uniquely named, dedicated transport device for this chan */
- tdev = scmi_device_create(of_node, info->dev, prot_id, name);
+ tdev = scmi_device_create(fwnode, info->dev, prot_id, name);
if (!tdev) {
dev_err(info->dev,
"failed to create transport device (%s)\n", name);
@@ -2786,14 +2788,14 @@ static int scmi_chan_setup(struct scmi_info *info, struct device_node *of_node,
}
static inline int
-scmi_txrx_setup(struct scmi_info *info, struct device_node *of_node,
+scmi_txrx_setup(struct scmi_info *info, struct fwnode_handle *fwnode,
int prot_id)
{
- int ret = scmi_chan_setup(info, of_node, prot_id, true);
+ int ret = scmi_chan_setup(info, fwnode, prot_id, true);
if (!ret) {
/* Rx is optional, report only memory errors */
- ret = scmi_chan_setup(info, of_node, prot_id, false);
+ ret = scmi_chan_setup(info, fwnode, prot_id, false);
if (ret && ret != -ENOMEM)
ret = 0;
}
@@ -2810,15 +2812,15 @@ scmi_txrx_setup(struct scmi_info *info, struct device_node *of_node,
*
* @info: The SCMI instance descriptor.
*
- * Initialize all the channels found described in the DT against the underlying
- * configured transport using custom defined dedicated devices instead of
- * borrowing devices from the SCMI drivers; this way channels are initialized
+ * Initialize all the channels found described in the fwnode against the
+ * underlying configured transport using custom defined dedicated devices instead
+ * of borrowing devices from the SCMI drivers; this way channels are initialized
* upfront during core SCMI stack probing and are no more coupled with SCMI
* devices used by SCMI drivers.
*
* Note that, even though a pair of TX/RX channels is associated to each
- * protocol defined in the DT, a distinct freshly initialized channel is
- * created only if the DT node for the protocol at hand describes a dedicated
+ * protocol defined in the fwnode, a distinct freshly initialized channel is
+ * created only if the fwnode for the protocol at hand describes a dedicated
* channel: in all the other cases the common BASE protocol channel is reused.
*
* Return: 0 on Success
@@ -2826,17 +2828,17 @@ scmi_txrx_setup(struct scmi_info *info, struct device_node *of_node,
static int scmi_channels_setup(struct scmi_info *info)
{
int ret;
- struct device_node *top_np = info->dev->of_node;
+ struct fwnode_handle *fwnode = dev_fwnode(info->dev);
/* Initialize a common generic channel at first */
- ret = scmi_txrx_setup(info, top_np, SCMI_PROTOCOL_BASE);
+ ret = scmi_txrx_setup(info, fwnode, SCMI_PROTOCOL_BASE);
if (ret)
return ret;
- for_each_available_child_of_node_scoped(top_np, child) {
+ fwnode_for_each_available_child_node_scoped(fwnode, child) {
u32 prot_id;
- if (of_property_read_u32(child, "reg", &prot_id))
+ if (fwnode_property_read_u32(child, "reg", &prot_id))
continue;
if (!FIELD_FIT(MSG_PROTOCOL_ID_MASK, prot_id))
@@ -2919,12 +2921,12 @@ static int scmi_bus_notifier(struct notifier_block *nb,
static int scmi_device_request_notifier(struct notifier_block *nb,
unsigned long action, void *data)
{
- struct device_node *np;
+ struct fwnode_handle *fwnode;
struct scmi_device_id *id_table = data;
struct scmi_info *info = req_nb_to_scmi_info(nb);
- np = idr_find(&info->active_protocols, id_table->protocol_id);
- if (!np)
+ fwnode = idr_find(&info->active_protocols, id_table->protocol_id);
+ if (!fwnode)
return NOTIFY_DONE;
dev_dbg(info->dev, "%sRequested device (%s) for protocol 0x%x\n",
@@ -2933,7 +2935,7 @@ static int scmi_device_request_notifier(struct notifier_block *nb,
switch (action) {
case SCMI_BUS_NOTIFY_DEVICE_REQUEST:
- scmi_create_protocol_devices(np, info, id_table->protocol_id,
+ scmi_create_protocol_devices(fwnode, info, id_table->protocol_id,
id_table->name);
break;
case SCMI_BUS_NOTIFY_DEVICE_UNREQUEST:
@@ -3020,13 +3022,14 @@ static struct scmi_debug_info *scmi_debugfs_common_setup(struct scmi_info *info)
if (!dbg)
return NULL;
- dbg->name = kstrdup(of_node_full_name(info->dev->of_node), GFP_KERNEL);
+ dbg->name = kstrdup(fwnode_get_name(dev_fwnode(info->dev)), GFP_KERNEL);
if (!dbg->name) {
devm_kfree(info->dev, dbg);
return NULL;
}
- of_property_read_string(info->dev->of_node, "compatible", &c_ptr);
+ fwnode_property_read_string(dev_fwnode(info->dev), "compatible",
+ &c_ptr);
dbg->type = kstrdup(c_ptr, GFP_KERNEL);
if (!dbg->type) {
kfree(dbg->name);
@@ -3132,23 +3135,23 @@ static const struct scmi_desc *scmi_transport_setup(struct device *dev)
dev_info(dev, "Using %s\n", dev_driver_string(trans->supplier));
- ret = of_property_read_u32(dev->of_node, "arm,max-rx-timeout-ms",
- &trans->desc.max_rx_timeout_ms);
+ ret = fwnode_property_read_u32(dev_fwnode(dev), "arm,max-rx-timeout-ms",
+ &trans->desc.max_rx_timeout_ms);
if (ret && ret != -EINVAL)
- dev_err(dev, "Malformed arm,max-rx-timeout-ms DT property.\n");
+ dev_err(dev, "Malformed arm,max-rx-timeout-ms property.\n");
- ret = of_property_read_u32(dev->of_node, "arm,max-msg-size",
- &trans->desc.max_msg_size);
+ ret = fwnode_property_read_u32(dev_fwnode(dev), "arm,max-msg-size",
+ &trans->desc.max_msg_size);
if (ret && ret != -EINVAL)
- dev_err(dev, "Malformed arm,max-msg-size DT property.\n");
+ dev_err(dev, "Malformed arm,max-msg-size property.\n");
- ret = of_property_read_u32(dev->of_node, "arm,max-msg",
- &trans->desc.max_msg);
+ ret = fwnode_property_read_u32(dev_fwnode(dev), "arm,max-msg",
+ &trans->desc.max_msg);
if (ret && ret != -EINVAL)
- dev_err(dev, "Malformed arm,max-msg DT property.\n");
+ dev_err(dev, "Malformed arm,max-msg property.\n");
- trans->desc.no_completion_irq = of_property_read_bool(dev->of_node,
- "arm,no-completion-irq");
+ trans->desc.no_completion_irq =
+ fwnode_property_read_bool(dev_fwnode(dev), "arm,no-completion-irq");
dev_info(dev,
"SCMI max-rx-timeout: %dms / max-msg-size: %dbytes / max-msg: %d\n",
@@ -3156,8 +3159,8 @@ static const struct scmi_desc *scmi_transport_setup(struct device *dev)
trans->desc.max_msg);
/* System wide atomic threshold for atomic ops .. if any */
- if (!of_property_read_u32(dev->of_node, "atomic-threshold-us",
- &trans->desc.atomic_threshold))
+ if (!fwnode_property_read_u32(dev_fwnode(dev), "atomic-threshold-us",
+ &trans->desc.atomic_threshold))
dev_info(dev,
"SCMI System wide atomic threshold set to %u us\n",
trans->desc.atomic_threshold);
@@ -3186,7 +3189,6 @@ static int scmi_probe(struct platform_device *pdev)
struct scmi_info *info;
bool coex = IS_ENABLED(CONFIG_ARM_SCMI_RAW_MODE_SUPPORT_COEX);
struct device *dev = &pdev->dev;
- struct device_node *child, *np = dev->of_node;
desc = scmi_transport_setup(dev);
if (!desc) {
@@ -3225,7 +3227,7 @@ static int scmi_probe(struct platform_device *pdev)
handle->devm_protocol_put = scmi_devm_protocol_put;
handle->is_transport_atomic = scmi_is_transport_atomic;
- /* Setup all channels described in the DT at first */
+ /* Setup all channels described in the fwnode at first */
ret = scmi_channels_setup(info);
if (ret) {
err_str = "failed to setup channels\n";
@@ -3300,10 +3302,10 @@ static int scmi_probe(struct platform_device *pdev)
scmi_enable_matching_quirks(info);
- for_each_available_child_of_node(np, child) {
+ fwnode_for_each_available_child_node_scoped(dev_fwnode(dev), child) {
u32 prot_id;
- if (of_property_read_u32(child, "reg", &prot_id))
+ if (fwnode_property_read_u32(child, "reg", &prot_id))
continue;
if (!FIELD_FIT(MSG_PROTOCOL_ID_MASK, prot_id))
@@ -3316,8 +3318,8 @@ static int scmi_probe(struct platform_device *pdev)
}
/*
- * Save this valid DT protocol descriptor amongst
- * @active_protocols for this SCMI instance/
+ * Save this valid fwnode protocol descriptor amongst
+ * @active_protocols for this SCMI instance.
*/
ret = idr_alloc(&info->active_protocols, child,
prot_id, prot_id + 1, GFP_KERNEL);
@@ -3327,7 +3329,7 @@ static int scmi_probe(struct platform_device *pdev)
continue;
}
- of_node_get(child);
+ fwnode_handle_get(child);
scmi_create_protocol_devices(child, info, prot_id, NULL);
}
@@ -3355,7 +3357,7 @@ static void scmi_remove(struct platform_device *pdev)
{
int id;
struct scmi_info *info = platform_get_drvdata(pdev);
- struct device_node *child;
+ struct fwnode_handle *child;
if (IS_ENABLED(CONFIG_ARM_SCMI_RAW_MODE_SUPPORT))
scmi_raw_mode_cleanup(info->raw);
@@ -3374,7 +3376,7 @@ static void scmi_remove(struct platform_device *pdev)
mutex_unlock(&info->protocols_mtx);
idr_for_each_entry(&info->active_protocols, child, id)
- of_node_put(child);
+ fwnode_handle_put(child);
idr_destroy(&info->active_protocols);
blocking_notifier_chain_unregister(&scmi_requested_devices_nh,
diff --git a/drivers/firmware/arm_scmi/transports/mailbox.c b/drivers/firmware/arm_scmi/transports/mailbox.c
index ae0f67e6cc45..280623e576f8 100644
--- a/drivers/firmware/arm_scmi/transports/mailbox.c
+++ b/drivers/firmware/arm_scmi/transports/mailbox.c
@@ -77,9 +77,13 @@ static void rx_callback(struct mbox_client *cl, void *m)
core->shmem->read_header(smbox->shmem), NULL);
}
-static bool mailbox_chan_available(struct device_node *of_node, int idx)
+static bool mailbox_chan_available(struct fwnode_handle *fwnode, int idx)
{
int num_mb;
+ struct device_node *of_node = to_of_node(fwnode);
+
+ if (!of_node)
+ return false;
/*
* Just check if bidirrectional channels are involved, and check the
diff --git a/drivers/firmware/arm_scmi/transports/optee.c b/drivers/firmware/arm_scmi/transports/optee.c
index 07ae18d5279d..be2630982c72 100644
--- a/drivers/firmware/arm_scmi/transports/optee.c
+++ b/drivers/firmware/arm_scmi/transports/optee.c
@@ -312,9 +312,13 @@ static int invoke_process_msg_channel(struct scmi_optee_channel *channel, size_t
return 0;
}
-static bool scmi_optee_chan_available(struct device_node *of_node, int idx)
+static bool scmi_optee_chan_available(struct fwnode_handle *fwnode, int idx)
{
u32 channel_id;
+ struct device_node *of_node = to_of_node(fwnode);
+
+ if (!of_node)
+ return false;
return !of_property_read_u32_index(of_node, "linaro,optee-channel-id",
idx, &channel_id);
diff --git a/drivers/firmware/arm_scmi/transports/smc.c b/drivers/firmware/arm_scmi/transports/smc.c
index 21abb571e4f2..5ea32f8c562c 100644
--- a/drivers/firmware/arm_scmi/transports/smc.c
+++ b/drivers/firmware/arm_scmi/transports/smc.c
@@ -84,10 +84,15 @@ static irqreturn_t smc_msg_done_isr(int irq, void *data)
return IRQ_HANDLED;
}
-static bool smc_chan_available(struct device_node *of_node, int idx)
+static bool smc_chan_available(struct fwnode_handle *fwnode, int idx)
{
- struct device_node *np __free(device_node) =
- of_parse_phandle(of_node, "shmem", 0);
+ struct device_node *of_node = to_of_node(fwnode);
+ struct device_node *np __free(device_node) = NULL;
+
+ if (!of_node)
+ return false;
+
+ np = of_parse_phandle(of_node, "shmem", 0);
if (!np)
return false;
diff --git a/drivers/firmware/arm_scmi/transports/virtio.c b/drivers/firmware/arm_scmi/transports/virtio.c
index 326c4a93e44b..cb749ac7ab46 100644
--- a/drivers/firmware/arm_scmi/transports/virtio.c
+++ b/drivers/firmware/arm_scmi/transports/virtio.c
@@ -373,7 +373,7 @@ static unsigned int virtio_get_max_msg(struct scmi_chan_info *base_cinfo)
return vioch->max_msg;
}
-static bool virtio_chan_available(struct device_node *of_node, int idx)
+static bool virtio_chan_available(struct fwnode_handle *fwnode, int idx)
{
struct scmi_vio_channel *channels, *vioch = NULL;
--
2.43.0
^ permalink raw reply related
* [PATCH v2 7/9] firmware: arm_scmi: Refactor protocol device creation logic
From: Sudeep Holla @ 2026-05-25 20:42 UTC (permalink / raw)
To: arm-scmi, linux-arm-kernel; +Cc: Sudeep Holla, Cristian Marussi
In-Reply-To: <20260525-acpi_scmi_pcc-v2-0-4f38938d08d8@arm.com>
Refactor the protocol validation and device creation logic in
scmi_probe() into a new helper function, scmi_device_check_create(),
to improve readability and reduce code duplication.
The new helper consolidates checks for protocol ID range, implementation
availability, and duplicate activation, before invoking
scmi_create_protocol_devices(). This refactor simplifies the SCMI probe
path while preserving existing behavior.
No functional changes intended. This refactoring is required to enable
ACPI PCC transport.
Signed-off-by: Sudeep Holla <sudeep.holla@kernel.org>
---
drivers/firmware/arm_scmi/driver.c | 56 ++++++++++++++++++++++----------------
1 file changed, 33 insertions(+), 23 deletions(-)
diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
index 02f14167c918..47f13409dfeb 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -3181,6 +3181,38 @@ static void scmi_enable_matching_quirks(struct scmi_info *info)
rev->sub_vendor_id, rev->impl_ver);
}
+static void scmi_device_check_create(struct fwnode_handle *fwnode, int prot_id,
+ struct scmi_info *info)
+{
+ int ret;
+ struct device *dev = info->dev;
+ struct scmi_handle *handle = &info->handle;
+
+ if (!FIELD_FIT(MSG_PROTOCOL_ID_MASK, prot_id))
+ dev_err(dev, "Out of range protocol %d\n", prot_id);
+
+ if (!scmi_is_protocol_implemented(handle, prot_id)) {
+ dev_err(dev, "SCMI protocol %d not implemented\n",
+ prot_id);
+ return;
+ }
+
+ /*
+ * Save this valid fwnode protocol descriptor amongst
+ * @active_protocols for this SCMI instance.
+ */
+ ret = idr_alloc(&info->active_protocols, fwnode,
+ prot_id, prot_id + 1, GFP_KERNEL);
+ if (ret != prot_id) {
+ dev_err(dev, "SCMI protocol %d already activated. Skip\n",
+ prot_id);
+ return;
+ }
+
+ fwnode_handle_get(fwnode);
+ scmi_create_protocol_devices(fwnode, info, prot_id, NULL);
+}
+
static int scmi_probe(struct platform_device *pdev)
{
int ret;
@@ -3309,29 +3341,7 @@ static int scmi_probe(struct platform_device *pdev)
if (fwnode_property_read_u32(child, "reg", &prot_id))
continue;
- if (!FIELD_FIT(MSG_PROTOCOL_ID_MASK, prot_id))
- dev_err(dev, "Out of range protocol %d\n", prot_id);
-
- if (!scmi_is_protocol_implemented(handle, prot_id)) {
- dev_err(dev, "SCMI protocol %d not implemented\n",
- prot_id);
- continue;
- }
-
- /*
- * Save this valid fwnode protocol descriptor amongst
- * @active_protocols for this SCMI instance.
- */
- ret = idr_alloc(&info->active_protocols, child,
- prot_id, prot_id + 1, GFP_KERNEL);
- if (ret != prot_id) {
- dev_err(dev, "SCMI protocol %d already activated. Skip\n",
- prot_id);
- continue;
- }
-
- fwnode_handle_get(child);
- scmi_create_protocol_devices(child, info, prot_id, NULL);
+ scmi_device_check_create(child, prot_id, info);
}
return 0;
--
2.43.0
^ permalink raw reply related
* [PATCH v2 9/9] firmware: arm_scmi: Initialise all protocol devices and transport channels
From: Sudeep Holla @ 2026-05-25 20:42 UTC (permalink / raw)
To: arm-scmi, linux-arm-kernel; +Cc: Sudeep Holla, Cristian Marussi
In-Reply-To: <20260525-acpi_scmi_pcc-v2-0-4f38938d08d8@arm.com>
Unlike Device Tree, the ACPI SCMI namespace device does not provide
child fwnodes to represent each protocol. Initialise protocol
channels and devices for the ACPI SCMI protocol entries known to
the core, and let the transport channel-availability checks and
SCMI protocol implementation checks decide which ones are usable
on the platform.
Signed-off-by: Sudeep Holla <sudeep.holla@kernel.org>
---
drivers/firmware/arm_scmi/driver.c | 24 ++++++++++++++++++++++--
1 file changed, 22 insertions(+), 2 deletions(-)
diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
index 47f13409dfeb..9e25346a7eae 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -2827,7 +2827,7 @@ scmi_txrx_setup(struct scmi_info *info, struct fwnode_handle *fwnode,
*/
static int scmi_channels_setup(struct scmi_info *info)
{
- int ret;
+ int ret, idx;
struct fwnode_handle *fwnode = dev_fwnode(info->dev);
/* Initialize a common generic channel at first */
@@ -2850,6 +2850,17 @@ static int scmi_channels_setup(struct scmi_info *info)
return ret;
}
+ if (acpi_disabled)
+ return 0;
+
+ for (idx = 1; idx < ARRAY_SIZE(scmi_dsd_info_list); idx++) {
+ int prot_id = scmi_dsd_info_list[idx].protocol_id;
+
+ ret = scmi_txrx_setup(info, fwnode, prot_id);
+ if (ret)
+ return ret;
+ }
+
return 0;
}
@@ -3215,7 +3226,7 @@ static void scmi_device_check_create(struct fwnode_handle *fwnode, int prot_id,
static int scmi_probe(struct platform_device *pdev)
{
- int ret;
+ int ret, idx;
char *err_str = "probe failure\n";
struct scmi_handle *handle;
const struct scmi_desc *desc;
@@ -3344,6 +3355,15 @@ static int scmi_probe(struct platform_device *pdev)
scmi_device_check_create(child, prot_id, info);
}
+ if (acpi_disabled)
+ return 0;
+
+ for (idx = 1; idx < ARRAY_SIZE(scmi_dsd_info_list); idx++) {
+ int prot_id = scmi_dsd_info_list[idx].protocol_id;
+
+ scmi_device_check_create(dev_fwnode(dev), prot_id, info);
+ }
+
return 0;
notification_exit:
--
2.43.0
^ permalink raw reply related
* [PATCH v2 5/9] firmware: arm_scmi: Fall back to ACPI HID when "compatible" is absent
From: Sudeep Holla @ 2026-05-25 20:42 UTC (permalink / raw)
To: arm-scmi, linux-arm-kernel; +Cc: Sudeep Holla, Cristian Marussi
In-Reply-To: <20260525-acpi_scmi_pcc-v2-0-4f38938d08d8@arm.com>
scmi_debugfs_common_setup() uses the "compatible" property to
populate the debugfs transport type string. ACPI-described SCMI
devices do not provide that DT property, so the string remains
NULL and debugfs setup falls through the allocation failure path.
Check the property lookup result and use the ACPI HID as the
fallback transport type for ACPI-described systems.
Signed-off-by: Sudeep Holla <sudeep.holla@kernel.org>
---
drivers/firmware/arm_scmi/driver.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
index 75dda6d5e8b1..04a0d8202504 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -3028,8 +3028,9 @@ static struct scmi_debug_info *scmi_debugfs_common_setup(struct scmi_info *info)
return NULL;
}
- fwnode_property_read_string(dev_fwnode(info->dev), "compatible",
- &c_ptr);
+ if (fwnode_property_read_string(dev_fwnode(info->dev), "compatible",
+ &c_ptr))
+ c_ptr = acpi_device_hid(ACPI_COMPANION(info->dev)) ?: "unknown";
dbg->type = kstrdup(c_ptr, GFP_KERNEL);
if (!dbg->type) {
kfree(dbg->name);
--
2.43.0
^ permalink raw reply related
* [PATCH v2 8/9] firmware: arm_scmi: transport: Add ACPI PCC transport
From: Sudeep Holla @ 2026-05-25 20:42 UTC (permalink / raw)
To: arm-scmi, linux-arm-kernel; +Cc: Sudeep Holla, Cristian Marussi
In-Reply-To: <20260525-acpi_scmi_pcc-v2-0-4f38938d08d8@arm.com>
Introduce a new SCMI transport that uses ACPI PCCT (PCC) subspaces via
the Linux PCC mailbox layer. The driver parses ACPI _DSD data to map
protocols to PCC subspace UIDs, supports shared TX/RX channels, and
optionally sets up a P2A channel for notifications.
Key points:
- new CONFIG_ARM_SCMI_TRANSPORT_PCC option
- integration with SCMI core via scmi_desc and transport ops
- response/notification fetch from PCC shared memory header/payload
- 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 <sudeep.holla@kernel.org>
---
drivers/firmware/arm_scmi/common.h | 11 +
drivers/firmware/arm_scmi/transports/Kconfig | 12 +
drivers/firmware/arm_scmi/transports/Makefile | 2 +
drivers/firmware/arm_scmi/transports/pcc.c | 593 ++++++++++++++++++++++++++
include/linux/scmi_protocol.h | 1 +
5 files changed, 619 insertions(+)
diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h
index 347e0da0d9cc..e4b2e1d3e7e7 100644
--- a/drivers/firmware/arm_scmi/common.h
+++ b/drivers/firmware/arm_scmi/common.h
@@ -465,6 +465,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 struct scmi_dsd_info scmi_dsd_info_list[] = {
+ { SCMI_PROTOCOL_BASE, "arm-arml0001-transport-pcc"},
+ { SCMI_PROTOCOL_POWERCAP, "arm-arml0001-protocol-pcap"},
+ { SCMI_PROTOCOL_TELEMETRY, "arm-arml0001-protocol-telemetry"},
+};
+
/**
* struct scmi_transport - A structure representing a configured transport
*
diff --git a/drivers/firmware/arm_scmi/transports/Kconfig b/drivers/firmware/arm_scmi/transports/Kconfig
index 57eccf316e26..4936078f279f 100644
--- a/drivers/firmware/arm_scmi/transports/Kconfig
+++ b/drivers/firmware/arm_scmi/transports/Kconfig
@@ -77,6 +77,18 @@ 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
+ 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.
+
config ARM_SCMI_TRANSPORT_VIRTIO
tristate "SCMI transport based on VirtIO"
depends on VIRTIO
diff --git a/drivers/firmware/arm_scmi/transports/Makefile b/drivers/firmware/arm_scmi/transports/Makefile
index 3ba3d3bee151..e7c4b8de6251 100644
--- a/drivers/firmware/arm_scmi/transports/Makefile
+++ b/drivers/firmware/arm_scmi/transports/Makefile
@@ -7,6 +7,8 @@ scmi_transport_mailbox-objs := mailbox.o
obj-$(CONFIG_ARM_SCMI_TRANSPORT_MAILBOX) += scmi_transport_mailbox.o
scmi_transport_optee-objs := optee.o
obj-$(CONFIG_ARM_SCMI_TRANSPORT_OPTEE) += scmi_transport_optee.o
+scmi_transport_pcc-objs := pcc.o
+obj-$(CONFIG_ARM_SCMI_TRANSPORT_PCC) += scmi_transport_pcc.o
scmi_transport_virtio-objs := virtio.o
obj-$(CONFIG_ARM_SCMI_TRANSPORT_VIRTIO) += scmi_transport_virtio.o
diff --git a/drivers/firmware/arm_scmi/transports/pcc.c b/drivers/firmware/arm_scmi/transports/pcc.c
new file mode 100644
index 000000000000..aaebd1530370
--- /dev/null
+++ b/drivers/firmware/arm_scmi/transports/pcc.c
@@ -0,0 +1,593 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * System Control and Management Interface (SCMI) Message ACPI PCC
+ * Transport Driver
+ *
+ * This transport uses ACPI PCC (PCCT Type 3/4) subspaces via the Linux
+ * PCC mailbox controller to exchange SCMI messages over the standard
+ * SCMI Shared Memory Transport (SMT) layout.
+ *
+ * PCC subspace selection is conveyed via ACPI SCMI namespace device.
+ *
+ * Copyright (C) 2025
+ */
+
+#include <linux/acpi.h>
+#include <linux/err.h>
+#include <linux/device.h>
+#include <linux/hashtable.h>
+#include <linux/io.h>
+#include <linux/limits.h>
+#include <linux/mailbox_client.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <acpi/pcc.h>
+
+#include "../common.h"
+
+#define SCMI_SHMEM_FLAG_INTR_ENABLED BIT(0)
+#define SCMI_TRANSPORT_PACKAGE_MAX_VERSION (1)
+#define SCMI_PROTOCOL_PACKAGE_MAX_VERSION (1)
+#define SCMI_TRANSPORT_SHARED_CHANNEL BIT_ULL(0)
+#define SCMI_TRANSPORT_P2A_CHANNEL BIT_ULL(1)
+#define SCMI_TRANSPORT_FLAGS_MASK (SCMI_TRANSPORT_SHARED_CHANNEL | \
+ SCMI_TRANSPORT_P2A_CHANNEL)
+
+/*
+ * 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[];
+};
+
+/**
+ * struct scmi_pcc - Structure representing a SCMI mailbox transport
+ *
+ * @cl: Mailbox Client
+ * @pchan: Transmit/Receive PCC/mailbox channel
+ * @cinfo: SCMI channel info
+ * @shmem: Transmit/Receive shared memory area
+ */
+struct scmi_pcc {
+ struct mbox_client cl;
+ struct pcc_mbox_chan *pchan;
+ struct scmi_chan_info *cinfo;
+};
+
+struct pcc_transport {
+ u32 uid;
+ u32 pcc_ss_id;
+ u32 protocol_id;
+ u32 flags;
+ struct hlist_node hnode;
+};
+
+/* Not all MAX_PCC_SUBSPACES will be used for SCMI, keeping it at 16 for now */
+static DECLARE_HASHTABLE(pcc_id_hash, ilog2(MAX_PCC_SUBSPACES / 8));
+
+#define client_to_scmi_pcc(c) container_of(c, struct scmi_pcc, cl)
+
+static struct scmi_transport_core_operations *core;
+
+static void acpi_scmi_clear_transport_hash(void)
+{
+ struct pcc_transport *p;
+ struct hlist_node *tmp;
+ int idx;
+
+ hash_for_each_safe(pcc_id_hash, idx, tmp, p, hnode) {
+ hash_del(&p->hnode);
+ kfree(p);
+ }
+}
+
+static int acpi_scmi_dsd_parse_transport_package(const union acpi_object *obj)
+{
+ unsigned int revision, pkg_cnt;
+ unsigned int common_a2p = 0, common_p2a = 0;
+ int idx;
+
+ if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count < 2)
+ return -EINVAL;
+ if (obj->package.elements[0].type != ACPI_TYPE_INTEGER ||
+ obj->package.elements[1].type != ACPI_TYPE_INTEGER)
+ return -EINVAL;
+
+ revision = obj->package.elements[0].integer.value;
+ pkg_cnt = obj->package.elements[1].integer.value;
+
+ 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++) {
+ union acpi_object *pack = &obj->package.elements[idx + 2];
+ struct pcc_transport *p, *tmp;
+ u64 flags;
+ u32 uid;
+
+ if (pack->type != ACPI_TYPE_PACKAGE || pack->package.count != 3) {
+ pr_info("Invalid transport properties pkg %d\n", idx);
+ return -EINVAL;
+ }
+ if (pack->package.elements[0].type != ACPI_TYPE_INTEGER ||
+ pack->package.elements[1].type != ACPI_TYPE_INTEGER ||
+ pack->package.elements[2].type != ACPI_TYPE_INTEGER)
+ return -EINVAL;
+ if (pack->package.elements[0].integer.value > U32_MAX ||
+ pack->package.elements[1].integer.value > U32_MAX)
+ return -EINVAL;
+
+ flags = pack->package.elements[2].integer.value;
+ if (flags & ~SCMI_TRANSPORT_FLAGS_MASK)
+ return -EINVAL;
+
+ uid = pack->package.elements[1].integer.value;
+ hash_for_each_possible(pcc_id_hash, 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 = pack->package.elements[0].integer.value;
+ 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(pcc_id_hash, &p->hnode, uid);
+ }
+
+ if (common_a2p != 1 || common_p2a > 1)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int acpi_scmi_dsd_parse_protocol_subpackage(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;
+
+ if (pkg_cnt > 2) {
+ pr_warn("Only 2 channels: one Tx and one Rx needed\n");
+ return -EINVAL;
+ }
+
+ for (idx = 0; idx < pkg_cnt; idx++) {
+ union acpi_object *pack = &obj->package.elements[idx];
+ u64 flags;
+
+ if (pack->type != ACPI_TYPE_PACKAGE || pack->package.count != 2)
+ return -EINVAL;
+ if (pack->package.elements[0].type != ACPI_TYPE_INTEGER ||
+ pack->package.elements[1].type != ACPI_TYPE_INTEGER)
+ return -EINVAL;
+ if (pack->package.elements[0].integer.value > U32_MAX)
+ return -EINVAL;
+
+ flags = pack->package.elements[1].integer.value;
+ if (flags)
+ return -EINVAL;
+
+ uid = pack->package.elements[0].integer.value;
+ found = false;
+ hash_for_each_possible(pcc_id_hash, 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->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;
+ if (!found)
+ return -ENOENT;
+ }
+
+ return ret;
+}
+
+static int
+acpi_scmi_dsd_parse_protocol_package(const union acpi_object *obj, int prot_id)
+{
+ unsigned int revision;
+ union acpi_object *pack;
+ int ret;
+
+ if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count != 3)
+ return -EINVAL;
+ if (obj->package.elements[0].type != ACPI_TYPE_INTEGER)
+ return -EINVAL;
+
+ revision = obj->package.elements[0].integer.value;
+ pack = &obj->package.elements[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 */
+ if (pack->package.count != 0) {
+ ret = acpi_scmi_dsd_parse_protocol_subpackage(pack, prot_id);
+ if (ret)
+ return ret;
+ }
+
+ pack = &obj->package.elements[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;
+}
+
+/* ACPI SCMI _DSD UUID: "84a2d1c6-86b6-4199-8dac-9c17449d5e03" */
+static const guid_t acpi_scmi_uuid = GUID_INIT(0x84a2d1c6, 0x86b6, 0x4199,
+ 0x8d, 0xac, 0x9c, 0x17,
+ 0x44, 0x9d, 0x5e, 0x03);
+
+static int acpi_scmi_lookup_protocol_id(const char *name)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(scmi_dsd_info_list); i++) {
+ if (!strcmp(name, scmi_dsd_info_list[i].property_name))
+ return scmi_dsd_info_list[i].protocol_id;
+ }
+ return -ENOENT;
+}
+
+static int acpi_scmi_parse_properties(const union acpi_object *properties)
+{
+ int i;
+
+ if (properties->type != ACPI_TYPE_PACKAGE)
+ return -EINVAL;
+
+ for (i = 0; i < properties->package.count; i++) {
+ const union acpi_object *property;
+ const union acpi_object *k, *v;
+ int prot_id, ret;
+
+ property = &properties->package.elements[i];
+ if (property->type != ACPI_TYPE_PACKAGE ||
+ property->package.count != 2)
+ return -EINVAL;
+
+ k = &property->package.elements[0];
+ v = &property->package.elements[1];
+
+ if (k->type != ACPI_TYPE_STRING || !k->string.pointer)
+ return -EINVAL;
+ if (v->type != ACPI_TYPE_PACKAGE)
+ return -EINVAL;
+
+ prot_id = acpi_scmi_lookup_protocol_id(k->string.pointer);
+ if (prot_id < 0)
+ return -EINVAL;
+
+ /*
+ * We validate structure inside the dedicated parsing functions.
+ * They return proper errors and avoid double-validation.
+ */
+ if (prot_id == SCMI_PROTOCOL_BASE)
+ ret = acpi_scmi_dsd_parse_transport_package(v);
+ else
+ ret = acpi_scmi_dsd_parse_protocol_package(v, prot_id);
+
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+static int acpi_scmi_parse_dsd(struct acpi_device *adev)
+{
+ struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *desc;
+ acpi_status status;
+ int i, ret = -ENOENT;
+
+ 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)
+ goto out_free_inval;
+
+ /* Look for the device properties GUID. */
+ for (i = 0; i < desc->package.count; i += 2) {
+ 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(properties);
+ goto out_free;
+ }
+
+out_free:
+ ACPI_FREE(buf.pointer);
+ return ret;
+out_free_inval:
+ ret = -EINVAL;
+ goto out_free;
+}
+
+static int acpi_scmi_namespace_device_parse(struct fwnode_handle *fwnode)
+{
+ struct acpi_device *adev = to_acpi_device_node(fwnode);
+ int ret;
+
+ acpi_scmi_clear_transport_hash();
+ hash_init(pcc_id_hash);
+
+ ret = acpi_scmi_parse_dsd(adev);
+ if (ret)
+ acpi_scmi_clear_transport_hash();
+
+ return ret;
+}
+
+static int pcc_get_ss_id(u32 prot_id, bool tx)
+{
+ struct pcc_transport *p;
+ int idx;
+
+ hash_for_each(pcc_id_hash, 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 bool
+pcc_chan_available(struct fwnode_handle *fwnode, int prot_id, int idx)
+{
+ /*
+ * Parse the ACPI SCMI namespace device on each Tx-channel lookup and
+ * reuse the resulting mapping for the matching Rx lookup. Defer full
+ * validation until pcc_chan_setup() for simplicity.
+ */
+ if (!idx && acpi_scmi_namespace_device_parse(fwnode))
+ return false;
+
+ if (pcc_get_ss_id(prot_id, idx ? false : true) < 0)
+ return false;
+
+ return true;
+}
+
+static void tx_prepare(struct mbox_client *cl, void *m)
+{
+ struct scmi_pcc *smbox = client_to_scmi_pcc(cl);
+ struct pcc_shared_mem __iomem *shmem = smbox->pchan->shmem;
+ struct scmi_xfer *xfer = m;
+
+ /*
+ * PCC take cares not to call tx_prepare until last transmit is done
+ * Mark channel busy + clear error, request platform IRQ if available
+ */
+ if (smbox->pchan->mchan->mbox->txdone_irq)
+ iowrite32(SCMI_SHMEM_FLAG_INTR_ENABLED, &shmem->header.flags);
+ iowrite32(sizeof(shmem->header.command) + xfer->tx.len,
+ &shmem->header.length);
+ iowrite32(pack_scmi_header(&xfer->hdr), &shmem->header.command);
+ if (xfer->tx.buf)
+ memcpy_toio(shmem->msg_payload, xfer->tx.buf, xfer->tx.len);
+}
+
+static void rx_callback(struct mbox_client *cl, void *m)
+{
+ struct scmi_pcc *smbox = client_to_scmi_pcc(cl);
+ struct pcc_shared_mem __iomem *shmem = smbox->pchan->shmem;
+
+ core->rx_callback(smbox->cinfo, ioread32(&shmem->header.command), NULL);
+}
+
+static int pcc_chan_setup(struct scmi_chan_info *cinfo, struct device *dev,
+ bool tx)
+{
+ const char *desc = tx ? "Tx" : "Rx";
+ struct device *cdev = cinfo->dev;
+ struct scmi_pcc *smbox;
+ int ret, ss_id;
+ struct mbox_client *cl;
+
+ smbox = devm_kzalloc(dev, sizeof(*smbox), GFP_KERNEL);
+ if (!smbox)
+ return -ENOMEM;
+
+ cl = &smbox->cl;
+ cl->dev = cdev;
+ cl->tx_prepare = tx ? tx_prepare : NULL;
+ cl->rx_callback = rx_callback;
+ cl->tx_block = false;
+
+ ss_id = pcc_get_ss_id(cinfo->id, tx);
+ if (ss_id < 0)
+ return ss_id;
+
+ smbox->pchan = pcc_mbox_request_channel(cl, ss_id);
+ if (IS_ERR(smbox->pchan)) {
+ ret = PTR_ERR(smbox->pchan);
+ if (ret != -EPROBE_DEFER)
+ dev_err(cdev,
+ "failed to request SCMI %s mailbox\n", desc);
+ return ret;
+ }
+
+ cinfo->transport_info = smbox;
+ smbox->cinfo = cinfo;
+
+ return 0;
+}
+
+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)) {
+ pcc_mbox_free_channel(smbox->pchan);
+ cinfo->transport_info = NULL;
+ smbox->pchan = NULL;
+ smbox->cinfo = NULL;
+ }
+
+ return 0;
+}
+
+static int
+pcc_send_message(struct scmi_chan_info *cinfo, struct scmi_xfer *xfer)
+{
+ struct scmi_pcc *smbox = cinfo->transport_info;
+ int ret;
+
+ /*
+ * The mailbox layer has its own queue. However the mailbox queue
+ * confuses the per message SCMI timeouts since the clock starts when
+ * the message is submitted into the mailbox queue. So when multiple
+ * messages are queued up the clock starts on all messages instead of
+ * only the one inflight.
+ */
+ ret = mbox_send_message(smbox->pchan->mchan, xfer);
+ /* mbox_send_message returns non-negative value on success */
+ if (ret < 0)
+ return ret;
+
+ 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.. */
+ memcpy_fromio(xfer->rx.buf, shmem->msg_payload + 4, xfer->rx.len);
+}
+
+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 */
+ xfer->rx.len = min_t(size_t, max_len, len > 4 ? len - 4 : 0);
+
+ /* Take a copy to the rx buffer.. */
+ 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 */
+ .max_msg = 20, /* Limited by MBOX_TX_QUEUE_LEN */
+ .max_msg_size = SCMI_SHMEM_MAX_PAYLOAD_SIZE,
+};
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id scmi_acpi_ids[] = {
+ { "ARML0001", 0 },
+ { }
+};
+
+MODULE_DEVICE_TABLE(acpi, scmi_acpi_ids);
+#endif
+
+DEFINE_SCMI_ACPI_TRANSPORT_DRIVER(scmi_pcc, scmi_pcc_driver,
+ scmi_pcc_desc, scmi_acpi_ids, core);
+module_platform_driver(scmi_pcc_driver);
+
+MODULE_AUTHOR("Sudeep Holla <sudeep.holla@kernel.org>");
+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 aafaac1496b0..d4ab8d918ee9 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -926,6 +926,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 {
--
2.43.0
^ permalink raw reply related
* Re: [PATCH v6 0/4] Introduce Allwinner H616 PWM controller
From: Andre Przywara @ 2026-05-25 21:15 UTC (permalink / raw)
To: Richard Genoud
Cc: Uwe Kleine-König, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Philipp Zabel, Paul Kocialkowski, Thomas Petazzoni, John Stultz,
Joao Schim, bigunclemax, linux-pwm, devicetree, linux-arm-kernel,
linux-sunxi, linux-kernel
In-Reply-To: <20260416134037.3160537-1-richard.genoud@bootlin.com>
On Thu, 16 Apr 2026 15:40:33 +0200
Richard Genoud <richard.genoud@bootlin.com> wrote:
Hi Richard,
> Allwinner H616 PWM controller is quite different from the A10 one.
>
> It can drive 6 PWM channels, and like for the A10, each channel has a
> bypass that permits to output a clock, bypassing the PWM logic, when
> enabled.
>
> But, the channels are paired 2 by 2, sharing a first set of
> MUX/prescaler/gate.
> Then, for each channel, there's another prescaler (that will be bypassed
> if the bypass is enabled for this channel).
>
> It looks like that:
> _____ ______ ________
> OSC24M --->| | | | | |
> APB1 ----->| Mux |--->| Gate |--->| /div_m |-----> PWM_clock_src_xy
> |_____| |______| |________|
> ________
> | |
> +->| /div_k |---> PWM_clock_x
> | |________|
> | ______
> | | |
> +-->| Gate |----> PWM_bypass_clock_x
> | |______|
> PWM_clock_src_xy -----+ ________
> | | |
> +->| /div_k |---> PWM_clock_y
> | |________|
> | ______
> | | |
> +-->| Gate |----> PWM_bypass_clock_y
> |______|
>
> Where xy can be 0/1, 2/3, 4/5
>
> PWM_clock_x/y serve for the PWM purpose.
> PWM_bypass_clock_x/y serve for the clock-provider purpose.
> The common clock framework has been used to manage those clocks.
>
> This PWM driver serves as a clock-provider for PWM_bypass_clocks.
> This is needed for example by the embedded AC300 PHY which clock comes
> from PMW5 pin (PB12).
>
> Usually, to get a clock from a PWM driver, we use the pwm-clock driver
> so that the PWM driver doesn't need to be a clk-provider itself.
> While this works in most cases, here it just doesn't.
> That's because the pwm-clock request a period from the PWM driver,
> without any clue that it actually wants a clock at a specific frequency,
> and not a PWM signal with duty cycle capability.
> So, the PWM driver doesn't know if it can use the bypass or not, it
> doesn't even have the real accurate frequency information (23809524 Hz
> instead of 24MHz) because PWM drivers only deal with periods.
>
> With pwm-clock, we loose a precious information along the way (that we
> actually want a clock and not a PWM signal).
> That's ok with simple PWM drivers that don't have multiple input clocks,
> but in this case, without this information, we can't know for sure which
> clock to use.
> And here, for instance, if we ask for a 24MHz clock, pwm-clock will
> requests 42ns (assigned-clocks doesn't help for that matter). The logic
> is to select the highest clock (100MHz) with no prescaler and a duty
> cycle value of 2/4 => we have 25MHz instead of 24MHz.
Didn't we discuss this choice of "highest clock" before? I dimly
remember asking whether we cannot use a least-error approach, so
considering all clocks and choosing the one which matches the target
best?
> And that's a perfectly fine choice for a PMW, because we still can
> change the duty cycle in the range [0-4]/4.
> But obviously for a clock, we don't care about the duty cycle, but more
> about the clock accuracy.
Thanks for your research into this and summarising this up! I see your
point, and always found the choice to use nanoseconds in PWM somewhat
problematic, especially when looking at the rounding errors.
And while turning the PWM into a clock provider looks like a clever
solution, this is somewhat arbitrary - why do we have this for those
SoCs and not the other (older) ones? The 24 MHz != 1/42ns problem is
the same there.
What also bugs me a bit is also that this is actually a decision
affecting the generic hardware devicetree description of the system,
but its rooted in a Linux implementation detail (ns resolution periods).
So before we consider going this route:
- Can we change the internal interface in Linux, maybe introducing
some special interface from pwm-clock to the pwm drivers, to convey
frequencies directly, instead of period lengths?
- Can we add an optional interface for pwm drivers in general, to use
a frequency / duty-cycle pair to describe a PWM setup? I would naively
think those to be some kind of natural PWM parameters.
- Can we at least add a picosecond precision interface? That doesn't
solve the rounding issue for those number not only divisible by
2 or 5 (like 24), but at least it seems to mitigate it:
24 MHz => 41666 ps => 24.000384 MHz
I see that having a clock provider seems like a more sustainable and
fitting choice, but as mentioned it's something that affects the DT
description, so I don't want to change that lightly.
Cheers,
Andre
> And actually, this PWM is really a PWM AND a real clock when the bypass
> is set.
>
> This series is based onto v7.0
>
> NB: checkpatch is not happy with patch 2, but it's a false positive.
> It doesn't detect that PWM_XY_SRC_MUX/GATE/DIV are structures, but as
> it's more readable like that, I prefer keeping it that way.
>
> Changes since v5:
> - remove trailing junk after commit message in patch 4
> - remove Tested-by when it doesn't make sense.
> (sorry for the noise)
>
> Changes since v4:
> - Fix a bug on bypass for channels greater than 1
> - add colons to clarify 2 debug messages
> - switch from H616 to sun8i prefix (in code, filename, module name)
> - fix consistency issues in macro parameters
> - rename some macros with a confusing naming
> - rebase on v7.0
>
> Changes since v3:
> - gather Acked-by/Tested-by
> - fix cast from pointer to integer of different size (kernel test robot
> with arc platform)
> - add devm_action for clk_hw_unregister_composite as suggested by Philipp
> - remove now unused pwm_remove as suggested by Philipp
>
> Changes since v2:
> - use U32_MAX instead of defining UINT32_MAX
> - add a comment on U32_MAX usage in clk_round_rate()
> - change clk_table_div_m (use macros)
> - fix formatting (double space, superfluous comma, extra line feed)
> - fix the parent clock order
> - simplify code by using scoped_guard()
> - add missing const in to_h616_pwm_chip() and rename to
> h616_pwm_from_chip()
> - add/remove missing/superfluous error messages
> - rename cnt->period_ticks, duty_cnt->duty_ticks
> - fix PWM_PERIOD_MAX
> - add .remove() callback
> - fix DIV_ROUND_CLOSEST_ULL->DIV_ROUND_UP_ULL
> - add H616_ prefix
> - protect _reg in macros
> - switch to waveforms instead of apply/get_state
> - shrink struct h616_pwm_channel
> - rebase on v6.19-rc4
>
> Changes since v1:
> - rebase onto v6.19-rc1
> - add missing headers
> - remove MODULE_ALIAS (suggested by Krzysztof)
> - use sun4i-pwm binding instead of creating a new one (suggested by Krzysztof)
> - retrieve the parent clocks from the devicetree
> - switch num_parents to unsigned int
>
> Richard Genoud (4):
> dt-bindings: pwm: allwinner: add h616 pwm compatible
> pwm: sun8i: Add H616 PWM support
> arm64: dts: allwinner: h616: add PWM controller
> MAINTAINERS: Add entry on Allwinner sun8i/H616 PWM driver
>
> .../bindings/pwm/allwinner,sun4i-a10-pwm.yaml | 19 +-
> MAINTAINERS | 5 +
> .../arm64/boot/dts/allwinner/sun50i-h616.dtsi | 47 +
> drivers/pwm/Kconfig | 12 +
> drivers/pwm/Makefile | 1 +
> drivers/pwm/pwm-sun8i.c | 938 ++++++++++++++++++
> 6 files changed, 1021 insertions(+), 1 deletion(-)
> create mode 100644 drivers/pwm/pwm-sun8i.c
>
>
> base-commit: 028ef9c96e96197026887c0f092424679298aae8
>
^ permalink raw reply
* Re: [PATCH] pinctrl: sunxi: Implement function_is_gpio
From: Andre Przywara @ 2026-05-25 21:38 UTC (permalink / raw)
To: Paul Kocialkowski
Cc: linux-gpio, linux-arm-kernel, linux-sunxi, linux-kernel,
Linus Walleij, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland
In-Reply-To: <20260517171405.3697469-1-paulk@sys-base.io>
On Sun, 17 May 2026 19:14:05 +0200
Paul Kocialkowski <paulk@sys-base.io> wrote:
Hi Paul,
> The function_is_gpio pinmux op allows the core to find out whether a
> GPIO can be safely requested from a pinctrl property and requested as a
> GPIO at the same time.
>
> This is especially useful to request a GPIO with a particular drive
> strength, which would otherwise not be possible.
That looks a easy enough solution, but:
> Signed-off-by: Paul Kocialkowski <paulk@sys-base.io>
> ---
> drivers/pinctrl/sunxi/pinctrl-sunxi.c | 12 ++++++++++++
> 1 file changed, 12 insertions(+)
>
> diff --git a/drivers/pinctrl/sunxi/pinctrl-sunxi.c b/drivers/pinctrl/sunxi/pinctrl-sunxi.c
> index d3042e0c9712..6162f2d86723 100644
> --- a/drivers/pinctrl/sunxi/pinctrl-sunxi.c
> +++ b/drivers/pinctrl/sunxi/pinctrl-sunxi.c
> @@ -821,6 +821,17 @@ static int sunxi_pmx_get_func_groups(struct pinctrl_dev *pctldev,
> return 0;
> }
>
> +static bool sunxi_pmx_function_is_gpio(struct pinctrl_dev *pctldev,
> + unsigned function)
> +{
> + struct sunxi_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
> +
> + if (!strncmp(pctl->functions[function].name, "gpio", 4))
I wonder if this condition is a bit too relaxed? There could be
some (theoretical) function just starting with gpio, but not being mux
0 or 1. So should we check for gpio_in or gpio_out, explicitly? Or at
least use (strcmp(name, "gpio_", 5)? Or maybe even better for the mux
value directly? Is "function" an indicator of this, or does this rely
on the two GPIO functions being always listed first, at least so far?
And what about the IRQ function? Isn't that some GPIO as well, or does
that not count for the purpose of the function_is_gpio() callback?
Cheers,
Andre
> + return true;
> +
> + return false;
> +}
> +
> static void sunxi_pmx_set(struct pinctrl_dev *pctldev,
> unsigned pin,
> u8 config)
> @@ -952,6 +963,7 @@ static const struct pinmux_ops sunxi_pmx_ops = {
> .get_functions_count = sunxi_pmx_get_funcs_cnt,
> .get_function_name = sunxi_pmx_get_func_name,
> .get_function_groups = sunxi_pmx_get_func_groups,
> + .function_is_gpio = sunxi_pmx_function_is_gpio,
> .set_mux = sunxi_pmx_set_mux,
> .gpio_set_direction = sunxi_pmx_gpio_set_direction,
> .request = sunxi_pmx_request,
^ permalink raw reply
* Re: [PATCH v9 00/23] perf python: Modernize and extend Python API (Phase 1)
From: Ian Rogers @ 2026-05-25 22:39 UTC (permalink / raw)
To: Arnaldo Carvalho de Melo
Cc: namhyung, adrian.hunter, alice.mei.rogers, dapeng1.mi,
james.clark, leo.yan, linux-arm-kernel, linux-kernel,
linux-perf-users, mingo, peterz, tmricht
In-Reply-To: <ahSuZCJCQa4tE5aN@x1>
On Mon, May 25, 2026 at 1:17 PM Arnaldo Carvalho de Melo
<acme@kernel.org> wrote:
>
> On Mon, May 25, 2026 at 05:14:25PM -0300, Arnaldo Carvalho de Melo wrote:
> > On Fri, May 22, 2026 at 03:04:11PM -0700, Ian Rogers wrote:
> > > This series takes a different approach with some initial implementation posted
> > > as an RFC last October:
> > > https://lore.kernel.org/linux-perf-users/20251029053413.355154-1-irogers@google.com/
> > > with the motivation coming up on the mailing list earlier:
> > > https://lore.kernel.org/lkml/CAP-5=fWDqE8SYfOLZkg_0=4Ayx6E7O+h7uUp4NDeCFkiN4b7-w@mail.gmail.com/
>
> > > The ultimate goal is to remove the embedded libpython and libperl support from
> > > perf entirely, expanding the existing perf Python module to provide full access
> > > to perf data files and events, allowing scripts to be run as standalone Python
> > > applications.
>
> > I tried applying it now and got this:
>
> > Cover: ./v9_20260522_irogers_perf_python_modernize_and_extend_python_api_phase_1.cover
> > Link: https://lore.kernel.org/r/20260522220435.2378363-1-irogers@google.com
> > Base: not specified
> > git am ./v9_20260522_irogers_perf_python_modernize_and_extend_python_api_phase_1.mbx
> > ⬢ [acme@toolbx perf-tools-next2]$ git am ./v9_20260522_irogers_perf_python_modernize_and_extend_python_api_phase_1.mbx
> > Applying: perf arch arm: Sort includes and add missed explicit dependencies
> > Applying: perf arch x86: Sort includes and add missed explicit dependencies
> > Applying: perf tests: Sort includes and add missed explicit dependencies
> > Applying: perf script: Sort includes and add missed explicit dependencies
> > Applying: perf util: Sort includes and add missed explicit dependencies
> > error: patch failed: tools/perf/util/evsel.c:11
> > error: tools/perf/util/evsel.c: patch does not apply
> > Patch failed at 0005 perf util: Sort includes and add missed explicit dependencies
> > hint: Use 'git am --show-current-patch=diff' to see the failed patch
> > hint: When you have resolved this problem, run "git am --continue".
> > hint: If you prefer to skip this patch, run "git am --skip" instead.
> > hint: To restore the original branch and stop patching, run "git am --abort".
> > hint: Disable this message with "git config set advice.mergeConflict false"
> > ⬢ [acme@toolbx perf-tools-next2]$ git am --abort
> > ⬢ [acme@toolbx perf-tools-next2]$ git diff
> > ⬢ [acme@toolbx perf-tools-next2]$
>
> > I agree with the intent and plan on reviewing and testing it.
>
> What I have now is at tmp.perf-tools-next if you wish to rebase from
> there,
Thanks Arnaldo, I'll do that.
Ian
> Regards,
>
> - Arnaldo
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox