[PATCH v2] firmware: stratix10-rsu: Add synchronous fallback for async SVC operations

From: tze . yee . ng

Date: Fri Jul 10 2026 - 03:04:56 EST


From: Mahesh Rao <mahesh.rao@xxxxxxxxxx>

The RSU driver was migrated to the Stratix10 asynchronous service
framework, which assumes async client registration always succeeds. On
platforms where the async controller is unavailable or RSU async support
is not present, probe fails or sysfs operations cannot reach firmware.

Detect async capability at probe via stratix10_svc_add_async_client() and
fall back to the legacy synchronous rsu_send_msg() path when registration
fails. Track capability in priv->async and branch accordingly for:

- Initial RSU status and retry counter during probe (retry is bundled in
async status; issue COMMAND_RSU_RETRY separately in sync mode)
- notify sysfs store (notify, status, and retry)
- SPT table retrieval (COMMAND_RSU_GET_SPT_TABLE async vs mbox
COMMAND_MBOX_SEND_CMD synchronous)

Restore synchronous callbacks (rsu_status_callback, rsu_retry_callback,
rsu_get_spt_callback) and wire COMMAND_MBOX_SEND_CMD payload handling in
__rsu_send_msg_locked.

Improve probe and remove cleanup: return on intermediate probe failures,
remove the async client when registered, and free the SPT response buffer
on sync-path allocation errors.

Suggested-by: Anders Hedlund <anders.hedlund@xxxxxxxxxxxxx>
Signed-off-by: Mahesh Rao <mahesh.rao@xxxxxxxxxx>
Signed-off-by: Asyraaf Azhar <mohamad.asyraaf.azhar@xxxxxxxxxx>
Signed-off-by: Tze Yee Ng <tze.yee.ng@xxxxxxxxxx>
---
Changes in v2:
- The functional change is unchanged from v1.
- Rebased onto dinguyen/socfpga_firmware_for_v7.3 for v7.3 integration,
instead of the v1 base. That tree already includes the QSPI device-info
series (COMMAND_RSU_GET_DEVICE_INFO probe path and sysfs) and the probe
teardown refactor (__rsu_send_msg_locked() / rsu_send_msg() split, goto
remove_async_client / goto free_channel labels).
- Wire COMMAND_MBOX_SEND_CMD payload handling in __rsu_send_msg_locked()
instead of rsu_send_msg(), matching the rebased locking model
(lockdep_assert_held(&priv->lock)).
- Add kernel-doc for priv->get_spt_response_buf in struct
stratix10_rsu_priv.
---
drivers/firmware/stratix10-rsu.c | 227 ++++++++++++++++++++++++++++---
1 file changed, 207 insertions(+), 20 deletions(-)

diff --git a/drivers/firmware/stratix10-rsu.c b/drivers/firmware/stratix10-rsu.c
index d887c74b9821..b360f752d191 100644
--- a/drivers/firmware/stratix10-rsu.c
+++ b/drivers/firmware/stratix10-rsu.c
@@ -18,6 +18,10 @@
#include <linux/string.h>
#include <linux/sysfs.h>

+#define RSU_STATE_MASK GENMASK_ULL(31, 0)
+#define RSU_VERSION_MASK GENMASK_ULL(63, 32)
+#define RSU_ERROR_LOCATION_MASK GENMASK_ULL(31, 0)
+#define RSU_ERROR_DETAIL_MASK GENMASK_ULL(63, 32)
/*
* INTEL_SIP_SMC_RSU_GET_DEVICE_INFO packs each flash word as:
* [63:32] erase_size, [31:0] size (see stratix10-smc.h).
@@ -44,6 +48,7 @@

#define RSU_RETRY_SLEEP_MS (1U)
#define RSU_ASYNC_MSG_RETRY (3U)
+#define RSU_GET_SPT_CMD 0x5A
#define RSU_GET_SPT_RESP_LEN (4 * sizeof(unsigned int))

struct flash_device_info {
@@ -73,6 +78,7 @@ typedef void (*rsu_callback)(struct stratix10_svc_client *client,
* @client: active service client
* @completion: state for callback completion
* @lock: a mutex to protect callback completion state
+ * @async: supports async operations
* @status.current_image: address of image currently running in flash
* @status.fail_image: address of failed image in flash
* @status.version: the interface version number of RSU firmware
@@ -93,12 +99,14 @@ typedef void (*rsu_callback)(struct stratix10_svc_client *client,
* @max_retry: the preset max retry value
* @spt0_address: address of spt0
* @spt1_address: address of spt1
+ * @get_spt_response_buf: response from sdm for get_spt command
*/
struct stratix10_rsu_priv {
struct stratix10_svc_chan *chan;
struct stratix10_svc_client client;
struct completion completion;
struct mutex lock;
+ bool async;
struct {
unsigned long current_image;
unsigned long fail_image;
@@ -129,6 +137,8 @@ struct stratix10_rsu_priv {

unsigned long spt0_address;
unsigned long spt1_address;
+
+ unsigned int *get_spt_response_buf;
};

/**
@@ -148,6 +158,45 @@ static void rsu_device_info_invalidate(struct stratix10_rsu_priv *priv)
typedef void (*rsu_async_callback)(struct device *dev,
struct stratix10_rsu_priv *priv, struct stratix10_svc_cb_data *data);

+/**
+ * rsu_status_callback() - Status callback from Intel Service Layer
+ * @client: pointer to service client
+ * @data: pointer to callback data structure
+ *
+ * Callback from Intel service layer for RSU status request. Status is
+ * only updated after a system reboot, so a get updated status call is
+ * made during driver probe.
+ */
+static void rsu_status_callback(struct stratix10_svc_client *client,
+ struct stratix10_svc_cb_data *data)
+{
+ struct stratix10_rsu_priv *priv = client->priv;
+ struct arm_smccc_res *res = (struct arm_smccc_res *)data->kaddr1;
+
+ if (data->status == BIT(SVC_STATUS_OK)) {
+ priv->status.version = FIELD_GET(RSU_VERSION_MASK,
+ res->a2);
+ priv->status.state = FIELD_GET(RSU_STATE_MASK, res->a2);
+ priv->status.fail_image = res->a1;
+ priv->status.current_image = res->a0;
+ priv->status.error_location =
+ FIELD_GET(RSU_ERROR_LOCATION_MASK, res->a3);
+ priv->status.error_details =
+ FIELD_GET(RSU_ERROR_DETAIL_MASK, res->a3);
+ } else {
+ dev_err(client->dev, "COMMAND_RSU_STATUS returned 0x%lX\n",
+ res->a0);
+ priv->status.version = 0;
+ priv->status.state = 0;
+ priv->status.fail_image = 0;
+ priv->status.current_image = 0;
+ priv->status.error_location = 0;
+ priv->status.error_details = 0;
+ }
+
+ complete(&priv->completion);
+}
+
/**
* rsu_async_status_callback() - Status callback from rsu_async_send()
* @dev: pointer to device object
@@ -192,6 +241,32 @@ static void rsu_command_callback(struct stratix10_svc_client *client,
complete(&priv->completion);
}

+/**
+ * rsu_retry_callback() - Callback from Intel service layer for getting
+ * the current image's retry counter from the firmware
+ * @client: pointer to client
+ * @data: pointer to callback data structure
+ *
+ * Callback from Intel service layer for retry counter, which is used by
+ * user to know how many times the images is still allowed to reload
+ * itself before giving up and starting RSU fail-over flow.
+ */
+static void rsu_retry_callback(struct stratix10_svc_client *client,
+ struct stratix10_svc_cb_data *data)
+{
+ struct stratix10_rsu_priv *priv = client->priv;
+ unsigned int *counter = (unsigned int *)data->kaddr1;
+
+ if (data->status == BIT(SVC_STATUS_OK))
+ priv->retry_counter = *counter;
+ else if (data->status == BIT(SVC_STATUS_NO_SUPPORT))
+ dev_warn(client->dev, "Secure FW doesn't support retry\n");
+ else
+ dev_err(client->dev, "Failed to get retry counter %lu\n",
+ BIT(data->status));
+
+ complete(&priv->completion);
+}

/**
* rsu_max_retry_callback() - Callback from Intel service layer for getting
@@ -337,6 +412,38 @@ static void rsu_async_get_spt_table_callback(struct device *dev,
priv->spt1_address = *((unsigned long *)data->kaddr2);
}

+static void rsu_get_spt_callback(struct stratix10_svc_client *client,
+ struct stratix10_svc_cb_data *data)
+{
+ struct stratix10_rsu_priv *priv = client->priv;
+ unsigned long *mbox_err = (unsigned long *)data->kaddr1;
+ unsigned long *resp_len = (unsigned long *)data->kaddr2;
+
+ if (data->status != BIT(SVC_STATUS_OK) || (*mbox_err) ||
+ (*resp_len != RSU_GET_SPT_RESP_LEN))
+ goto error;
+
+ priv->spt0_address = priv->get_spt_response_buf[0];
+ priv->spt0_address <<= 32;
+ priv->spt0_address |= priv->get_spt_response_buf[1];
+ priv->spt1_address = priv->get_spt_response_buf[2];
+ priv->spt1_address <<= 32;
+ priv->spt1_address |= priv->get_spt_response_buf[3];
+
+ goto complete;
+
+error:
+ dev_err(priv->client.dev,
+ "failed to get SPTs (status=%#x, mbox_err=%lu, resp_len=%lu)\n",
+ data->status, mbox_err ? *mbox_err : 0,
+ resp_len ? *resp_len : 0);
+
+complete:
+ stratix10_svc_free_memory(priv->chan, priv->get_spt_response_buf);
+ priv->get_spt_response_buf = NULL;
+ complete(&priv->completion);
+}
+
/**
* __rsu_send_msg_locked() - send a message to Intel service layer
* @priv: pointer to rsu private data
@@ -353,7 +460,7 @@ static int __rsu_send_msg_locked(struct stratix10_rsu_priv *priv,
unsigned long arg,
rsu_callback callback)
{
- struct stratix10_svc_client_msg msg;
+ struct stratix10_svc_client_msg msg = {0};
int ret;

lockdep_assert_held(&priv->lock);
@@ -365,6 +472,14 @@ static int __rsu_send_msg_locked(struct stratix10_rsu_priv *priv,
if (arg)
msg.arg[0] = arg;

+ if (command == COMMAND_MBOX_SEND_CMD) {
+ msg.arg[1] = 0;
+ msg.payload = NULL;
+ msg.payload_length = 0;
+ msg.payload_output = priv->get_spt_response_buf;
+ msg.payload_length_output = RSU_GET_SPT_RESP_LEN;
+ }
+
ret = stratix10_svc_send(priv->chan, &msg);
if (ret < 0)
goto status_done;
@@ -489,6 +604,8 @@ static int rsu_send_async_msg(struct device *dev, struct stratix10_rsu_priv *pri

if (status && !handle) {
dev_err(dev, "Failed to send async message\n");
+ if (msg.payload_output)
+ stratix10_svc_free_memory(priv->chan, msg.payload_output);
return -ETIMEDOUT;
}

@@ -528,6 +645,8 @@ static int rsu_send_async_msg(struct device *dev, struct stratix10_rsu_priv *pri
}

status_done:
+ if (msg.payload_output)
+ stratix10_svc_free_memory(priv->chan, msg.payload_output);
stratix10_svc_async_done(priv->chan, handle);
return ret;
}
@@ -775,15 +894,30 @@ static ssize_t notify_store(struct device *dev,
if (ret)
return ret;

- ret = rsu_send_async_msg(dev, priv, COMMAND_RSU_NOTIFY, status, NULL);
+ if (priv->async)
+ ret = rsu_send_async_msg(dev, priv, COMMAND_RSU_NOTIFY, status, NULL);
+ else
+ ret = rsu_send_msg(priv, COMMAND_RSU_NOTIFY, status, rsu_command_callback);
if (ret) {
dev_err(dev, "Error, RSU notify returned %i\n", ret);
return ret;
}

/* to get the updated state */
- ret = rsu_send_async_msg(dev, priv, COMMAND_RSU_STATUS, 0,
- rsu_async_status_callback);
+ if (priv->async) {
+ ret = rsu_send_async_msg(dev, priv, COMMAND_RSU_STATUS, 0,
+ rsu_async_status_callback);
+ } else {
+ ret = rsu_send_msg(priv, COMMAND_RSU_STATUS, 0,
+ rsu_status_callback);
+ /*
+ * In async mode COMMAND_RSU_RETRY is part of COMMAND_RSU_STATUS;
+ * issue it separately only in synchronous mode.
+ */
+ if (!ret)
+ ret = rsu_send_msg(priv, COMMAND_RSU_RETRY, 0,
+ rsu_retry_callback);
+ }
if (ret) {
dev_err(dev, "Error, getting RSU status %i\n", ret);
return ret;
@@ -974,7 +1108,11 @@ static int stratix10_rsu_probe(struct platform_device *pdev)
priv->dcmf_status.dcmf1 = INVALID_DCMF_STATUS;
priv->dcmf_status.dcmf2 = INVALID_DCMF_STATUS;
priv->dcmf_status.dcmf3 = INVALID_DCMF_STATUS;
- /* spt0/1_address and status fields default to 0 from kzalloc */
+ priv->max_retry = INVALID_RETRY_COUNTER;
+ priv->spt0_address = INVALID_SPT_ADDRESS;
+ priv->spt1_address = INVALID_SPT_ADDRESS;
+ priv->get_spt_response_buf = NULL;
+ priv->async = false;
rsu_device_info_invalidate(priv);

mutex_init(&priv->lock);
@@ -989,19 +1127,39 @@ static int stratix10_rsu_probe(struct platform_device *pdev)
}

ret = stratix10_svc_add_async_client(priv->chan, false);
- if (ret) {
- dev_err(dev, "failed to add async client\n");
- goto free_channel;
+ if (ret < 0) {
+ dev_dbg(dev, "Async operations not supported, fallback to non-async mode\n");
+ priv->async = false;
+ } else {
+ priv->async = true;
}

platform_set_drvdata(pdev, priv);

/* get the initial state from firmware */
- ret = rsu_send_async_msg(dev, priv, COMMAND_RSU_STATUS, 0,
- rsu_async_status_callback);
+ if (priv->async)
+ ret = rsu_send_async_msg(dev, priv, COMMAND_RSU_STATUS, 0,
+ rsu_async_status_callback);
+ else
+ ret = rsu_send_msg(priv, COMMAND_RSU_STATUS, 0,
+ rsu_status_callback);
if (ret) {
dev_err(dev, "Error, getting RSU status %i\n", ret);
- goto remove_async_client;
+ if (priv->async)
+ goto remove_async_client;
+ goto free_channel;
+ }
+
+ /*
+ * In async mode COMMAND_RSU_RETRY is part of COMMAND_RSU_STATUS;
+ * issue it separately only in synchronous mode.
+ */
+ if (!priv->async) {
+ ret = rsu_send_msg(priv, COMMAND_RSU_RETRY, 0, rsu_retry_callback);
+ if (ret) {
+ dev_err(dev, "Error, getting RSU retry %i\n", ret);
+ goto free_channel;
+ }
}

/* get DCMF version from firmware */
@@ -1009,21 +1167,27 @@ static int stratix10_rsu_probe(struct platform_device *pdev)
rsu_dcmf_version_callback);
if (ret) {
dev_err(dev, "Error, getting DCMF version %i\n", ret);
- goto remove_async_client;
+ if (priv->async)
+ goto remove_async_client;
+ goto free_channel;
}

ret = rsu_send_msg(priv, COMMAND_RSU_DCMF_STATUS, 0,
rsu_dcmf_status_callback);
if (ret) {
dev_err(dev, "Error, getting DCMF status %i\n", ret);
- goto remove_async_client;
+ if (priv->async)
+ goto remove_async_client;
+ goto free_channel;
}

ret = rsu_send_msg(priv, COMMAND_RSU_MAX_RETRY, 0,
rsu_max_retry_callback);
if (ret) {
dev_err(dev, "Error, getting RSU max retry %i\n", ret);
- goto remove_async_client;
+ if (priv->async)
+ goto remove_async_client;
+ goto free_channel;
}

/* get QSPI device info from firmware */
@@ -1031,16 +1195,36 @@ static int stratix10_rsu_probe(struct platform_device *pdev)
rsu_get_device_info_callback);
if (ret) {
dev_err(dev, "Error, getting QSPI Device Info %i\n", ret);
- stratix10_svc_remove_async_client(priv->chan);
- stratix10_svc_free_channel(priv->chan);
- return ret;
+ if (priv->async)
+ goto remove_async_client;
+ goto free_channel;
}

- ret = rsu_send_async_msg(dev, priv, COMMAND_RSU_GET_SPT_TABLE, 0,
- rsu_async_get_spt_table_callback);
+ if (priv->async) {
+ ret = rsu_send_async_msg(dev, priv, COMMAND_RSU_GET_SPT_TABLE,
+ 0, rsu_async_get_spt_table_callback);
+ } else {
+ priv->get_spt_response_buf =
+ stratix10_svc_allocate_memory(priv->chan, RSU_GET_SPT_RESP_LEN);
+ if (IS_ERR(priv->get_spt_response_buf)) {
+ ret = PTR_ERR(priv->get_spt_response_buf);
+ priv->get_spt_response_buf = NULL;
+ dev_err(dev, "failed to allocate get spt buffer\n");
+ } else {
+ ret = rsu_send_msg(priv, COMMAND_MBOX_SEND_CMD,
+ RSU_GET_SPT_CMD, rsu_get_spt_callback);
+ }
+ }
if (ret) {
dev_err(dev, "Error, getting SPT table %i\n", ret);
- goto remove_async_client;
+ if (priv->async) {
+ goto remove_async_client;
+ } else if (!IS_ERR_OR_NULL(priv->get_spt_response_buf)) {
+ stratix10_svc_free_memory(priv->chan,
+ priv->get_spt_response_buf);
+ priv->get_spt_response_buf = NULL;
+ }
+ goto free_channel;
}

return 0;
@@ -1056,6 +1240,9 @@ static void stratix10_rsu_remove(struct platform_device *pdev)
{
struct stratix10_rsu_priv *priv = platform_get_drvdata(pdev);

+ if (priv->async)
+ stratix10_svc_remove_async_client(priv->chan);
+
stratix10_svc_free_channel(priv->chan);
}

--
2.43.7