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(ID 8) and request DDR training data(ID 9) using
EXECUTE and EXECUTE_DATA as defined by protocol. 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 <[email protected]>
---
 drivers/bus/mhi/host/clients/sahara/sahara.c | 326 ++++++++++++++++++++++++++-
 1 file changed, 319 insertions(+), 7 deletions(-)

diff --git a/drivers/bus/mhi/host/clients/sahara/sahara.c 
b/drivers/bus/mhi/host/clients/sahara/sahara.c
index 
b5ca6353540dc3815db6539e7424afdb749fd3f6..07bc743aa061dd2fa85638067d494562152474e3
 100644
--- a/drivers/bus/mhi/host/clients/sahara/sahara.c
+++ b/drivers/bus/mhi/host/clients/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/types.h>
 #include <linux/vmalloc.h>
@@ -59,7 +62,15 @@
 #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;
@@ -95,6 +106,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;
        };
 };
 
@@ -161,6 +185,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;
@@ -181,6 +206,24 @@ struct sahara_context {
        bool                            is_mem_dump_mode;
        bool                            non_streaming;
        const char                      *fw_folder;
+       bool                            is_cmd_mode;
+       bool                            receiving_training_data;
+       size_t                          training_size;
+       size_t                          training_rcvd;
+       u32                             training_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_cntrl_training_data {
+       struct mutex lock;      /* Protects data, size, copied and receiving */
+       void *data;
+       size_t size;
+       size_t copied;
+       bool receiving;
 };
 
 static bool is_streaming(struct sahara_context *context)
@@ -188,6 +231,48 @@ static bool is_streaming(struct sahara_context *context)
        return !context->non_streaming;
 }
 
+static void sahara_cntrl_training_release(struct device *dev, void *res)
+{
+       struct sahara_cntrl_training_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_cntrl_training_match(struct device *dev, void *res, void 
*match_data)
+{
+       /* Exactly one instance per controller */
+       return 1;
+}
+
+static struct sahara_cntrl_training_data *sahara_cntrl_training_get(struct 
device *dev)
+{
+       struct sahara_cntrl_training_data *ct;
+
+       ct = devres_find(dev, sahara_cntrl_training_release,
+                        sahara_cntrl_training_match, NULL);
+       if (ct)
+               return ct;
+
+       ct = devres_alloc(sahara_cntrl_training_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;
@@ -282,6 +367,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_training_data = false;
+       context->training_size = 0;
+       context->training_rcvd = 0;
+       context->training_nbuf = 0;
 
        context->tx[0]->cmd = cpu_to_le32(SAHARA_RESET_CMD);
        context->tx[0]->length = cpu_to_le32(SAHARA_RESET_LENGTH);
@@ -317,7 +407,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;
@@ -336,6 +427,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_training_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_cntrl_training_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_cntrl_training_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->training_size = resp_len;
+       context->training_rcvd = 0;
+       context->receiving_training_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->training_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_training_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;
@@ -562,6 +800,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));
@@ -862,6 +1103,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_training_data = false;
+       context->training_size = 0;
+       context->training_rcvd = 0;
+       context->training_nbuf = 0;
 
        context->active_image_id = SAHARA_IMAGE_ID_NONE;
        dev_set_drvdata(&mhi_dev->dev, context);
@@ -885,6 +1140,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);
@@ -901,15 +1157,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_cntrl_training_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_training_data &&
+           mhi_result->buf_addr != context->rx) {
+               dev = &context->mhi_dev->mhi_cntrl->mhi_dev->dev;
+               ct = sahara_cntrl_training_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->training_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->training_rcvd += copy;
+
+                               if (context->training_rcvd >= 
context->training_size) {
+                                       mutex_lock(&ct->lock);
+                                       ct->receiving = false;
+                                       mutex_unlock(&ct->lock);
+
+                                       context->receiving_training_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

Reply via email to