During early boot, devices may perform DDR training and produce training
data that can be reused on subsequent boots to reduce initialization
time. The sahara protocol provides a command mode flow to transfer this
training data to the host, but the driver currently does not handle
command mode and drops the training payload.
Add Sahara command mode support to retrieve DDR training data from the
device. When the device enters command mode and sends CMD_READY, query
the support command list and request DDR training data using EXECUTE and
EXECUTE_DATA. Allocate receive buffers based on the reported response
size and copy the raw payload directly from the MHI DL completion
callback.
Store the captured training data in controller-scoped memory using devres,
so it remains available after sahara channel teardown. Also distinguish
raw payload completion from control packets in the DL callback, avoiding
misinterpretation of training data as protocol messages, and requeue
the RX buffer after switching back to IMAGE_TX_PENDING to allow the
boot flow to continue.
Signed-off-by: Kishore Batta <kishore.batta@oss.qualcomm.com>
---
drivers/bus/mhi/sahara/sahara.c | 328 +++++++++++++++++++++++++++++++++++++++-
1 file changed, 320 insertions(+), 8 deletions(-)
diff --git a/drivers/bus/mhi/sahara/sahara.c b/drivers/bus/mhi/sahara/sahara.c
index 0a0f578aaa47ab2c4ca0765666b392fb9936ddd5..c88f1220199ac4373d3552167870c19a0d5f23b9 100644
--- a/drivers/bus/mhi/sahara/sahara.c
+++ b/drivers/bus/mhi/sahara/sahara.c
@@ -5,11 +5,14 @@
*/
#include <linux/devcoredump.h>
+#include <linux/device.h>
+#include <linux/device/devres.h>
#include <linux/firmware.h>
#include <linux/limits.h>
#include <linux/mhi.h>
#include <linux/minmax.h>
#include <linux/mod_devicetable.h>
+#include <linux/mutex.h>
#include <linux/overflow.h>
#include <linux/sahara.h>
#include <linux/types.h>
@@ -60,8 +63,16 @@
#define SAHARA_RESET_LENGTH 0x8
#define SAHARA_MEM_DEBUG64_LENGTH 0x18
#define SAHARA_MEM_READ64_LENGTH 0x18
-
+#define SAHARA_COMMAND_READY_LENGTH 0x8
+#define SAHARA_COMMAND_EXEC_RESP_LENGTH 0x10
+#define SAHARA_COMMAND_EXECUTE_LENGTH 0xc
+#define SAHARA_COMMAND_EXEC_DATA_LENGTH 0xc
+#define SAHARA_SWITCH_MODE_LENGTH 0xc
+
+#define SAHARA_EXEC_CMD_GET_COMMAND_ID_LIST 0x8
+#define SAHARA_EXEC_CMD_GET_TRAINING_DATA 0x9
#define SAHARA_DDR_TRAINING_IMG_ID 34
+#define SAHARA_NUM_CMD_BUF SAHARA_NUM_TX_BUF
struct sahara_packet {
__le32 cmd;
@@ -97,6 +108,19 @@ struct sahara_packet {
__le64 memory_address;
__le64 memory_length;
} memory_read64;
+ struct {
+ __le32 client_command;
+ } command_execute;
+ struct {
+ __le32 client_command;
+ __le32 response_length;
+ } command_execute_resp;
+ struct {
+ __le32 client_command;
+ } command_exec_data;
+ struct {
+ __le32 mode;
+ } mode_switch;
};
};
@@ -163,6 +187,7 @@ struct sahara_context {
struct work_struct fw_work;
struct work_struct dump_work;
struct work_struct read_data_work;
+ struct work_struct cmd_work;
struct mhi_device *mhi_dev;
const char * const *image_table;
u32 table_size;
@@ -183,6 +208,24 @@ struct sahara_context {
bool is_mem_dump_mode;
bool non_streaming;
const char *fw_folder;
+ bool is_cmd_mode;
+ bool receiving_trng_data;
+ size_t trng_size;
+ size_t trng_rcvd;
+ u32 trng_nbuf;
+ char *cmd_buff[SAHARA_NUM_CMD_BUF];
+};
+
+/*
+ * Controller-scoped training data store (per MHI controller device).
+ * Stored as devres resource on mhi_dev->mhi_cntrl->mhi_dev->dev.
+ */
+struct sahara_ctrl_trng_data {
+ struct mutex lock; /* Protects data, size, copied and receiving */
+ void *data;
+ size_t size;
+ size_t copied;
+ bool receiving;
};
struct sahara_variant {
@@ -330,6 +373,48 @@ static int sahara_request_fw(struct sahara_context *context, const char *path)
return ret;
}
+static void sahara_ctrl_trng_release(struct device *dev, void *res)
+{
+ struct sahara_ctrl_trng_data *ct = res;
+
+ mutex_lock(&ct->lock);
+ kfree(ct->data);
+ ct->data = NULL;
+ ct->size = 0;
+ ct->copied = 0;
+ ct->receiving = false;
+ mutex_unlock(&ct->lock);
+}
+
+static int sahara_ctrl_trng_match(struct device *dev, void *res, void *match_data)
+{
+ /* Exactly one instance per controller */
+ return 1;
+}
+
+static struct sahara_ctrl_trng_data *sahara_ctrl_trng_get(struct device *dev)
+{
+ struct sahara_ctrl_trng_data *ct;
+
+ ct = devres_find(dev, sahara_ctrl_trng_release,
+ sahara_ctrl_trng_match, NULL);
+ if (ct)
+ return ct;
+
+ ct = devres_alloc(sahara_ctrl_trng_release, sizeof(*ct), GFP_KERNEL);
+ if (!ct)
+ return NULL;
+
+ mutex_init(&ct->lock);
+ ct->data = NULL;
+ ct->size = 0;
+ ct->copied = 0;
+ ct->receiving = false;
+
+ devres_add(dev, ct);
+ return ct;
+}
+
static int sahara_find_image(struct sahara_context *context, u32 image_id)
{
char *fw_path;
@@ -423,6 +508,11 @@ static void sahara_send_reset(struct sahara_context *context)
context->is_mem_dump_mode = false;
context->read_data_offset = 0;
context->read_data_length = 0;
+ context->is_cmd_mode = false;
+ context->receiving_trng_data = false;
+ context->trng_size = 0;
+ context->trng_rcvd = 0;
+ context->trng_nbuf = 0;
context->tx[0]->cmd = cpu_to_le32(SAHARA_RESET_CMD);
context->tx[0]->length = cpu_to_le32(SAHARA_RESET_LENGTH);
@@ -458,7 +548,8 @@ static void sahara_hello(struct sahara_context *context)
if (le32_to_cpu(context->rx->hello.mode) != SAHARA_MODE_IMAGE_TX_PENDING &&
le32_to_cpu(context->rx->hello.mode) != SAHARA_MODE_IMAGE_TX_COMPLETE &&
- le32_to_cpu(context->rx->hello.mode) != SAHARA_MODE_MEMORY_DEBUG) {
+ le32_to_cpu(context->rx->hello.mode) != SAHARA_MODE_MEMORY_DEBUG &&
+ le32_to_cpu(context->rx->hello.mode) != SAHARA_MODE_COMMAND) {
dev_err(&context->mhi_dev->dev, "Unsupported hello packet - mode %d\n",
le32_to_cpu(context->rx->hello.mode));
return;
@@ -477,6 +568,153 @@ static void sahara_hello(struct sahara_context *context)
dev_err(&context->mhi_dev->dev, "Unable to send hello response %d\n", ret);
}
+static void sahara_switch_mode_to_img_tx(struct sahara_context *context)
+{
+ int ret;
+
+ context->tx[0]->cmd = cpu_to_le32(SAHARA_SWITCH_MODE_CMD);
+ context->tx[0]->length = cpu_to_le32(SAHARA_SWITCH_MODE_LENGTH);
+ context->tx[0]->mode_switch.mode = cpu_to_le32(SAHARA_MODE_IMAGE_TX_PENDING);
+
+ ret = mhi_queue_buf(context->mhi_dev, DMA_TO_DEVICE, context->tx[0],
+ SAHARA_SWITCH_MODE_LENGTH, MHI_EOT);
+
+ if (ret)
+ dev_err(&context->mhi_dev->dev, "Unable to send mode switch %d\n", ret);
+}
+
+static void sahara_command_execute(struct sahara_context *context, u32 client_command)
+{
+ int ret;
+
+ context->tx[0]->cmd = cpu_to_le32(SAHARA_EXECUTE_CMD);
+ context->tx[0]->length = cpu_to_le32(SAHARA_COMMAND_EXECUTE_LENGTH);
+ context->tx[0]->command_execute.client_command = cpu_to_le32(client_command);
+
+ ret = mhi_queue_buf(context->mhi_dev, DMA_TO_DEVICE, context->tx[0],
+ SAHARA_COMMAND_EXECUTE_LENGTH, MHI_EOT);
+ if (ret)
+ dev_err(&context->mhi_dev->dev, "Unable to send command execute %d\n", ret);
+}
+
+static void sahara_command_execute_data(struct sahara_context *context, u32 client_command)
+{
+ int ret;
+
+ context->tx[0]->cmd = cpu_to_le32(SAHARA_EXECUTE_DATA_CMD);
+ context->tx[0]->length = cpu_to_le32(SAHARA_COMMAND_EXEC_DATA_LENGTH);
+ context->tx[0]->command_exec_data.client_command = cpu_to_le32(client_command);
+
+ ret = mhi_queue_buf(context->mhi_dev, DMA_TO_DEVICE, context->tx[0],
+ SAHARA_COMMAND_EXEC_DATA_LENGTH, MHI_EOT);
+ if (ret)
+ dev_err(&context->mhi_dev->dev, "Unable to send execute data %d\n", ret);
+}
+
+static void sahara_command_ready(struct sahara_context *context)
+{
+ if (le32_to_cpu(context->rx->length) != SAHARA_COMMAND_READY_LENGTH) {
+ dev_err(&context->mhi_dev->dev,
+ "Malformed command ready packet - length %u\n",
+ le32_to_cpu(context->rx->length));
+ return;
+ }
+
+ context->is_cmd_mode = true;
+ context->receiving_trng_data = false;
+
+ sahara_command_execute(context, SAHARA_EXEC_CMD_GET_COMMAND_ID_LIST);
+}
+
+static void sahara_command_execute_resp(struct sahara_context *context)
+{
+ struct device *dev = &context->mhi_dev->mhi_cntrl->mhi_dev->dev;
+ struct sahara_ctrl_trng_data *ct;
+ u32 client_cmd, resp_len;
+ int ret;
+ u64 remaining;
+ u32 i;
+
+ if (le32_to_cpu(context->rx->length) != SAHARA_COMMAND_EXEC_RESP_LENGTH ||
+ le32_to_cpu(context->rx->command_execute_resp.response_length) < 0) {
+ dev_err(&context->mhi_dev->dev,
+ "Malformed command execute resp packet - length %d\n",
+ le32_to_cpu(context->rx->length));
+ return;
+ }
+
+ client_cmd = le32_to_cpu(context->rx->command_execute_resp.client_command);
+ resp_len = le32_to_cpu(context->rx->command_execute_resp.response_length);
+
+ sahara_command_execute_data(context, client_cmd);
+
+ if (client_cmd == SAHARA_EXEC_CMD_GET_COMMAND_ID_LIST) {
+ sahara_command_execute(context, SAHARA_EXEC_CMD_GET_TRAINING_DATA);
+ return;
+ }
+
+ if (client_cmd != SAHARA_EXEC_CMD_GET_TRAINING_DATA)
+ return;
+
+ ct = sahara_ctrl_trng_get(dev);
+ if (!ct) {
+ context->is_cmd_mode = false;
+ sahara_switch_mode_to_img_tx(context);
+ return;
+ }
+
+ mutex_lock(&ct->lock);
+ kfree(ct->data);
+ ct->data = kzalloc(resp_len, GFP_KERNEL);
+ ct->size = resp_len;
+ ct->copied = 0;
+ ct->receiving = true;
+ mutex_unlock(&ct->lock);
+
+ if (!ct->data) {
+ context->is_cmd_mode = false;
+ sahara_switch_mode_to_img_tx(context);
+ return;
+ }
+
+ context->trng_size = resp_len;
+ context->trng_rcvd = 0;
+ context->receiving_trng_data = true;
+
+ remaining = resp_len;
+ for (i = 0; i < SAHARA_NUM_CMD_BUF && remaining; i++) {
+ size_t pkt = min_t(size_t, remaining, SAHARA_PACKET_MAX_SIZE);
+
+ ret = mhi_queue_buf(context->mhi_dev, DMA_FROM_DEVICE,
+ context->cmd_buff[i], pkt,
+ (remaining <= pkt) ? MHI_EOT : MHI_CHAIN);
+ if (ret)
+ break;
+
+ remaining -= pkt;
+ }
+
+ context->trng_nbuf = i;
+}
+
+static void sahara_command_processing(struct work_struct *work)
+{
+ struct sahara_context *context = container_of(work, struct sahara_context, cmd_work);
+ int ret;
+
+ if (le32_to_cpu(context->rx->cmd) == SAHARA_EXECUTE_RESP_CMD)
+ sahara_command_execute_resp(context);
+
+ if (!context->receiving_trng_data) {
+ ret = mhi_queue_buf(context->mhi_dev, DMA_FROM_DEVICE,
+ context->rx, SAHARA_PACKET_MAX_SIZE, MHI_EOT);
+
+ if (ret)
+ dev_err(&context->mhi_dev->dev,
+ "Unable to requeue rx buf %d\n", ret);
+ }
+}
+
static int read_data_helper(struct sahara_context *context, int buf_index)
{
enum mhi_flags mhi_flag;
@@ -703,6 +941,9 @@ static void sahara_processing(struct work_struct *work)
case SAHARA_MEM_DEBUG64_CMD:
sahara_memory_debug64(context);
break;
+ case SAHARA_CMD_READY_CMD:
+ sahara_command_ready(context);
+ break;
default:
dev_err(&context->mhi_dev->dev, "Unknown command %d\n",
le32_to_cpu(context->rx->cmd));
@@ -1003,6 +1244,20 @@ static int sahara_mhi_probe(struct mhi_device *mhi_dev, const struct mhi_device_
INIT_WORK(&context->fw_work, sahara_processing);
INIT_WORK(&context->dump_work, sahara_dump_processing);
INIT_WORK(&context->read_data_work, sahara_read_data_processing);
+ INIT_WORK(&context->cmd_work, sahara_command_processing);
+
+ for (i = 0; i < SAHARA_NUM_CMD_BUF; i++) {
+ context->cmd_buff[i] = devm_kzalloc(&mhi_dev->dev,
+ SAHARA_PACKET_MAX_SIZE, GFP_KERNEL);
+ if (!context->cmd_buff[i])
+ return -ENOMEM;
+ }
+
+ context->is_cmd_mode = false;
+ context->receiving_trng_data = false;
+ context->trng_size = 0;
+ context->trng_rcvd = 0;
+ context->trng_nbuf = 0;
context->active_image_id = SAHARA_IMAGE_ID_NONE;
dev_set_drvdata(&mhi_dev->dev, context);
@@ -1026,6 +1281,7 @@ static void sahara_mhi_remove(struct mhi_device *mhi_dev)
cancel_work_sync(&context->fw_work);
cancel_work_sync(&context->dump_work);
+ cancel_work_sync(&context->cmd_work);
vfree(context->mem_dump);
sahara_release_image(context);
mhi_unprepare_from_transfer(mhi_dev);
@@ -1042,15 +1298,71 @@ static void sahara_mhi_ul_xfer_cb(struct mhi_device *mhi_dev, struct mhi_result
static void sahara_mhi_dl_xfer_cb(struct mhi_device *mhi_dev, struct mhi_result *mhi_result)
{
struct sahara_context *context = dev_get_drvdata(&mhi_dev->dev);
+ struct sahara_ctrl_trng_data *ct;
+ struct device *dev;
+ size_t copy;
+ int ret;
+ u32 i;
+
+ if (mhi_result->transaction_status)
+ return;
+
+ /*
+ * Raw training payload completions arrive for cmd_buff[] buffers.
+ * Do not schedule cmd_work for those.
+ */
+ if (context->is_cmd_mode && context->receiving_trng_data &&
+ mhi_result->buf_addr != context->rx) {
+ dev = &context->mhi_dev->mhi_cntrl->mhi_dev->dev;
+ ct = sahara_ctrl_trng_get(dev);
+ if (!ct)
+ return;
- if (!mhi_result->transaction_status) {
- context->rx_size = mhi_result->bytes_xferd;
- if (context->is_mem_dump_mode)
- schedule_work(&context->dump_work);
- else
- schedule_work(&context->fw_work);
+ for (i = 0; i < context->trng_nbuf; i++) {
+ if (mhi_result->buf_addr == context->cmd_buff[i]) {
+ mutex_lock(&ct->lock);
+ copy = min_t(size_t, mhi_result->bytes_xferd,
+ ct->size - ct->copied);
+ memcpy((u8 *)ct->data + ct->copied,
+ mhi_result->buf_addr, copy);
+ ct->copied += copy;
+ mutex_unlock(&ct->lock);
+
+ context->trng_rcvd += copy;
+
+ if (context->trng_rcvd >= context->trng_size) {
+ mutex_lock(&ct->lock);
+ ct->receiving = false;
+ mutex_unlock(&ct->lock);
+
+ context->receiving_trng_data = false;
+ context->is_cmd_mode = false;
+
+ sahara_switch_mode_to_img_tx(context);
+ ret = mhi_queue_buf(context->mhi_dev,
+ DMA_FROM_DEVICE,
+ context->rx,
+ SAHARA_PACKET_MAX_SIZE,
+ MHI_EOT);
+ if (ret)
+ dev_err(&context->mhi_dev->dev,
+ "Unable to requeue rx buf %d\n", ret);
+ }
+ return;
+ }
+ }
+ return;
}
+ /* Normal Rx completion */
+ context->rx_size = mhi_result->bytes_xferd;
+ if (context->is_mem_dump_mode)
+ schedule_work(&context->dump_work);
+ else if (context->is_cmd_mode)
+ schedule_work(&context->cmd_work);
+ else
+ schedule_work(&context->fw_work);
+
}
static const struct mhi_device_id sahara_mhi_match_table[] = {
--
2.34.1
Hi Kishore,
kernel test robot noticed the following build warnings:
[auto build test WARNING on a0ae2a256046c0c5d3778d1a194ff2e171f16e5f]
url: https://github.com/intel-lab-lkp/linux/commits/Kishore-Batta/Add-documentation-for-Sahara-protocol/20260320-144614
base: a0ae2a256046c0c5d3778d1a194ff2e171f16e5f
patch link: https://lore.kernel.org/r/20260319-sahara_protocol_new_v2-v4-7-47ad79308762%40oss.qualcomm.com
patch subject: [PATCH v4 7/9] bus: mhi: Capture DDR training data using command mode
config: i386-randconfig-141-20260322 (https://download.01.org/0day-ci/archive/20260323/202603230107.6EzMoVPn-lkp@intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
smatch: v0.5.0-9004-gb810ac53
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202603230107.6EzMoVPn-lkp@intel.com/
New smatch warnings:
drivers/bus/mhi/sahara/sahara.c:639 sahara_command_execute_resp() warn: unsigned '(context->rx->command_execute_resp.response_length)' is never less than zero.
Old smatch warnings:
drivers/bus/mhi/sahara/sahara.c:353 sahara_select_variant() warn: this array is probably non-NULL. 'id->chan'
vim +639 drivers/bus/mhi/sahara/sahara.c
628
629 static void sahara_command_execute_resp(struct sahara_context *context)
630 {
631 struct device *dev = &context->mhi_dev->mhi_cntrl->mhi_dev->dev;
632 struct sahara_ctrl_trng_data *ct;
633 u32 client_cmd, resp_len;
634 int ret;
635 u64 remaining;
636 u32 i;
637
638 if (le32_to_cpu(context->rx->length) != SAHARA_COMMAND_EXEC_RESP_LENGTH ||
> 639 le32_to_cpu(context->rx->command_execute_resp.response_length) < 0) {
640 dev_err(&context->mhi_dev->dev,
641 "Malformed command execute resp packet - length %d\n",
642 le32_to_cpu(context->rx->length));
643 return;
644 }
645
646 client_cmd = le32_to_cpu(context->rx->command_execute_resp.client_command);
647 resp_len = le32_to_cpu(context->rx->command_execute_resp.response_length);
648
649 sahara_command_execute_data(context, client_cmd);
650
651 if (client_cmd == SAHARA_EXEC_CMD_GET_COMMAND_ID_LIST) {
652 sahara_command_execute(context, SAHARA_EXEC_CMD_GET_TRAINING_DATA);
653 return;
654 }
655
656 if (client_cmd != SAHARA_EXEC_CMD_GET_TRAINING_DATA)
657 return;
658
659 ct = sahara_ctrl_trng_get(dev);
660 if (!ct) {
661 context->is_cmd_mode = false;
662 sahara_switch_mode_to_img_tx(context);
663 return;
664 }
665
666 mutex_lock(&ct->lock);
667 kfree(ct->data);
668 ct->data = kzalloc(resp_len, GFP_KERNEL);
669 ct->size = resp_len;
670 ct->copied = 0;
671 ct->receiving = true;
672 mutex_unlock(&ct->lock);
673
674 if (!ct->data) {
675 context->is_cmd_mode = false;
676 sahara_switch_mode_to_img_tx(context);
677 return;
678 }
679
680 context->trng_size = resp_len;
681 context->trng_rcvd = 0;
682 context->receiving_trng_data = true;
683
684 remaining = resp_len;
685 for (i = 0; i < SAHARA_NUM_CMD_BUF && remaining; i++) {
686 size_t pkt = min_t(size_t, remaining, SAHARA_PACKET_MAX_SIZE);
687
688 ret = mhi_queue_buf(context->mhi_dev, DMA_FROM_DEVICE,
689 context->cmd_buff[i], pkt,
690 (remaining <= pkt) ? MHI_EOT : MHI_CHAIN);
691 if (ret)
692 break;
693
694 remaining -= pkt;
695 }
696
697 context->trng_nbuf = i;
698 }
699
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
© 2016 - 2026 Red Hat, Inc.