firmware: arm_scmi: Drop handle on protocol bind failures

The SCMI bus notifier acquires an SCMI handle when the driver core emits
BUS_NOTIFY_BIND_DRIVER, before invoking the protocol driver probe
callback. The protocol probe path only checks whether sdev->handle is
set.

If device_link_add() fails after the handle has been acquired, the
protocol device can still bind with a valid handle but without the
dependency link to the SCMI parent. A concurrent parent unbind can then
miss the child and tear down the SCMI instance while the child still
holds a handle into it.

If the protocol driver probe later fails, for example with
-EPROBE_DEFER, the driver core emits BUS_NOTIFY_DRIVER_NOT_BOUND rather
than BUS_NOTIFY_UNBOUND_DRIVER. The SCMI notifier only released the
handle on BUS_NOTIFY_UNBOUND_DRIVER, so each failed protocol-device bind
leaked the SCMI instance users refcount and left sdev->handle set after
the failed probe.

Make the link helper report failure and drop the acquired handle if the
link cannot be created. Also handle BUS_NOTIFY_DRIVER_NOT_BOUND in the
same cleanup path used for unbind so failed probes balance the earlier
BUS_NOTIFY_BIND_DRIVER acquisition.

Fixes: 971fc0665f ("firmware: arm_scmi: Move handle get/set helpers")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Link: https://patch.msgid.link/20260714-scmi_core_fixes-v6-10-3afe499d46e3@kernel.org
Signed-off-by: Sudeep Holla <sudeep.holla@kernel.org>
This commit is contained in:
Sudeep Holla 2026-07-14 13:56:29 +01:00
parent e6a0e7a49d
commit e3a5c30d23

View File

@ -2629,21 +2629,31 @@ static int scmi_handle_put(const struct scmi_handle *handle)
return 0;
}
static void scmi_device_link_add(struct device *consumer,
static bool scmi_device_link_add(struct device *consumer,
struct device *supplier)
{
struct device_link *link;
link = device_link_add(consumer, supplier, DL_FLAG_AUTOREMOVE_CONSUMER);
WARN_ON(!link);
return !WARN_ON(!link);
}
static void scmi_clear_handle(struct scmi_device *scmi_dev)
{
if (!scmi_dev->handle)
return;
scmi_handle_put(scmi_dev->handle);
scmi_dev->handle = NULL;
}
static void scmi_set_handle(struct scmi_device *scmi_dev)
{
scmi_dev->handle = scmi_handle_get(&scmi_dev->dev);
if (scmi_dev->handle)
scmi_device_link_add(&scmi_dev->dev, scmi_dev->handle->dev);
if (scmi_dev->handle &&
!scmi_device_link_add(&scmi_dev->dev, scmi_dev->handle->dev))
scmi_clear_handle(scmi_dev);
}
static int __scmi_xfer_info_init(struct scmi_info *sinfo,
@ -2927,6 +2937,7 @@ static int scmi_bus_notifier(struct notifier_block *nb,
{
struct scmi_info *info = bus_nb_to_scmi_info(nb);
struct scmi_device *sdev = to_scmi_dev(data);
const char *status;
/* Skip devices of different SCMI instances */
if (sdev->dev.parent != info->dev)
@ -2936,18 +2947,22 @@ static int scmi_bus_notifier(struct notifier_block *nb,
case BUS_NOTIFY_BIND_DRIVER:
/* setup handle now as the transport is ready */
scmi_set_handle(sdev);
status = "about to be BOUND.";
break;
case BUS_NOTIFY_DRIVER_NOT_BOUND:
scmi_clear_handle(sdev);
status = "NOT BOUND.";
break;
case BUS_NOTIFY_UNBOUND_DRIVER:
scmi_handle_put(sdev->handle);
sdev->handle = NULL;
scmi_clear_handle(sdev);
status = "UNBOUND.";
break;
default:
return NOTIFY_DONE;
}
dev_dbg(info->dev, "Device %s (%s) is now %s\n", dev_name(&sdev->dev),
sdev->name, action == BUS_NOTIFY_BIND_DRIVER ?
"about to be BOUND." : "UNBOUND.");
sdev->name, status);
return NOTIFY_OK;
}