firmware: arm_scmi: Add core raw transmission support
authorCristian Marussi <cristian.marussi@arm.com>
Wed, 18 Jan 2023 12:14:21 +0000 (12:14 +0000)
committerSudeep Holla <sudeep.holla@arm.com>
Fri, 20 Jan 2023 11:41:15 +0000 (11:41 +0000)
Add SCMI raw mode support which exposes a userspace interface to allow for
bare SCMI command injection and snooping from userspace.

Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
Tested-by: Florian Fainelli <f.fainelli@gmail.com>
Tested-by: Vincent Guittot <vincent.guittot@linaro.org>
Link: https://lore.kernel.org/r/20230118121426.492864-13-cristian.marussi@arm.com
Signed-off-by: Sudeep Holla <sudeep.holla@arm.com>
drivers/firmware/arm_scmi/Kconfig
drivers/firmware/arm_scmi/Makefile
drivers/firmware/arm_scmi/driver.c
drivers/firmware/arm_scmi/raw_mode.c [new file with mode: 0644]
drivers/firmware/arm_scmi/raw_mode.h [new file with mode: 0644]

index f36f89a..3b4f9e9 100644 (file)
@@ -30,6 +30,21 @@ config ARM_SCMI_NEED_DEBUGFS
          which needs debugfs support. When selected causess the creation
          of a common SCMI debugfs root directory.
 
+config ARM_SCMI_RAW_MODE_SUPPORT
+       bool "Enable support for SCMI Raw transmission mode"
+       depends on DEBUG_FS
+       select ARM_SCMI_NEED_DEBUGFS
+       help
+         Enable support for SCMI Raw transmission mode.
+
+         If enabled allows the direct injection and snooping of SCMI bare
+         messages through a dedicated debugfs interface.
+         It is meant to be used by SCMI compliance/testing suites.
+
+         When enabled regular SCMI drivers interactions are inhibited in
+         order to avoid unexpected interactions with the SCMI Raw message
+         flow. If unsure say N.
+
 config ARM_SCMI_HAVE_TRANSPORT
        bool
        help
index f0596c3..b31d78f 100644 (file)
@@ -3,6 +3,7 @@ scmi-bus-y = bus.o
 scmi-core-objs := $(scmi-bus-y)
 
 scmi-driver-y = driver.o notify.o
+scmi-driver-$(CONFIG_ARM_SCMI_RAW_MODE_SUPPORT) += raw_mode.o
 scmi-transport-$(CONFIG_ARM_SCMI_HAVE_SHMEM) = shmem.o
 scmi-transport-$(CONFIG_ARM_SCMI_TRANSPORT_MAILBOX) += mailbox.o
 scmi-transport-$(CONFIG_ARM_SCMI_TRANSPORT_SMC) += smc.o
index 5d5defd..2b96d9c 100644 (file)
@@ -920,7 +920,8 @@ static void scmi_handle_response(struct scmi_chan_info *cinfo,
        trace_scmi_msg_dump(info->id, cinfo->id, xfer->hdr.protocol_id,
                            xfer->hdr.id,
                            xfer->hdr.type == MSG_TYPE_DELAYED_RESP ?
-                           "DLYD" : "RESP",
+                           (!SCMI_XFER_IS_RAW(xfer) ? "DLYD" : "dlyd") :
+                           (!SCMI_XFER_IS_RAW(xfer) ? "RESP" : "resp"),
                            xfer->hdr.seq, xfer->hdr.status,
                            xfer->rx.buf, xfer->rx.len);
 
@@ -1045,7 +1046,8 @@ static int scmi_wait_for_reply(struct device *dev, const struct scmi_desc *desc,
                        /* Trace polled replies. */
                        trace_scmi_msg_dump(info->id, cinfo->id,
                                            xfer->hdr.protocol_id, xfer->hdr.id,
-                                           "RESP",
+                                           !SCMI_XFER_IS_RAW(xfer) ?
+                                           "RESP" : "resp",
                                            xfer->hdr.seq, xfer->hdr.status,
                                            xfer->rx.buf, xfer->rx.len);
                }
diff --git a/drivers/firmware/arm_scmi/raw_mode.c b/drivers/firmware/arm_scmi/raw_mode.c
new file mode 100644 (file)
index 0000000..a33ef0e
--- /dev/null
@@ -0,0 +1,1322 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * System Control and Management Interface (SCMI) Raw mode support
+ *
+ * Copyright (C) 2022 ARM Ltd.
+ */
+/**
+ * DOC: Theory of operation
+ *
+ * When enabled the SCMI Raw mode support exposes a userspace API which allows
+ * to send and receive SCMI commands, replies and notifications from a user
+ * application through injection and snooping of bare SCMI messages in binary
+ * little-endian format.
+ *
+ * Such injected SCMI transactions will then be routed through the SCMI core
+ * stack towards the SCMI backend server using whatever SCMI transport is
+ * currently configured on the system under test.
+ *
+ * It is meant to help in running any sort of SCMI backend server testing, no
+ * matter where the server is placed, as long as it is normally reachable via
+ * the transport configured on the system.
+ *
+ * It is activated by a Kernel configuration option since it is NOT meant to
+ * be used in production but only during development and in CI deployments.
+ *
+ * In order to avoid possible interferences between the SCMI Raw transactions
+ * originated from a test-suite and the normal operations of the SCMI drivers,
+ * when Raw mode is enabled, by default, all the regular SCMI drivers are
+ * inhibited.
+ *
+ * The exposed API is as follows.
+ *
+ * All SCMI Raw entries are rooted under a common top /raw debugfs top directory
+ * which in turn is rooted under the corresponding underlying  SCMI instance.
+ *
+ * /sys/kernel/debug/scmi/
+ * |-- 0
+ *     |-- atomic_threshold_us
+ *     |-- instance_name
+ *     |-- raw
+ *     |   |-- errors
+ *     |   |-- message
+ *     |   |-- message_async
+ *     |   |-- notification
+ *     |   `-- reset
+ *     `-- transport
+ *         |-- is_atomic
+ *         |-- max_msg_size
+ *         |-- max_rx_timeout_ms
+ *         |-- rx_max_msg
+ *         |-- tx_max_msg
+ *         `-- type
+ *
+ * where:
+ *
+ *  - errors: used to read back timed-out and unexpected replies
+ *  - message*: used to send sync/async commands and read back immediate and
+ *             delayed reponses (if any)
+ *  - notification: used to read any notification being emitted by the system
+ *                 (if previously enabled by the user app)
+ *  - reset: used to flush the queues of messages (of any kind) still pending
+ *          to be read; this is useful at test-suite start/stop to get
+ *          rid of any unread messages from the previous run.
+ *
+ * Note that other common general entries are available under transport/ to let
+ * the user applications properly make up their expectations in terms of
+ * timeouts and message characteristics.
+ *
+ * Each write to the message* entries causes one command request to be built
+ * and sent while the replies or delayed response are read back from those same
+ * entries one message at time (receiving an EOF at each message boundary).
+ *
+ * The user application running the test is in charge of handling timeouts
+ * on replies and properly choosing SCMI sequence numbers for the outgoing
+ * requests (using the same sequence number is supported but discouraged).
+ *
+ * Injection of multiple in-flight requests is supported as long as the user
+ * application uses properly distinct sequence numbers for concurrent requests
+ * and takes care to properly manage all the related issues about concurrency
+ * and command/reply pairing. Keep in mind that, anyway, the real level of
+ * parallelism attainable in such scenario is dependent on the characteristics
+ * of the underlying transport being used.
+ *
+ * Since the SCMI core regular stack is partially used to deliver and collect
+ * the messages, late replies arrived after timeouts and any other sort of
+ * unexpected message can be identified by the SCMI core as usual and they will
+ * be reported as messages under "errors" for later analysis.
+ */
+
+#include <linux/bitmap.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/export.h>
+#include <linux/idr.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/poll.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+
+#include "common.h"
+
+#include "raw_mode.h"
+
+#include <trace/events/scmi.h>
+
+#define SCMI_XFER_RAW_MAX_RETRIES      10
+
+/**
+ * struct scmi_raw_queue  - Generic Raw queue descriptor
+ *
+ * @free_bufs: A freelists listhead used to keep unused raw buffers
+ * @free_bufs_lock: Spinlock used to protect access to @free_bufs
+ * @msg_q: A listhead to a queue of snooped messages waiting to be read out
+ * @msg_q_lock: Spinlock used to protect access to @msg_q
+ * @wq: A waitqueue used to wait and poll on related @msg_q
+ */
+struct scmi_raw_queue {
+       struct list_head free_bufs;
+       /* Protect free_bufs[] lists */
+       spinlock_t free_bufs_lock;
+       struct list_head msg_q;
+       /* Protect msg_q[] lists */
+       spinlock_t msg_q_lock;
+       wait_queue_head_t wq;
+};
+
+/**
+ * struct scmi_raw_mode_info  - Structure holding SCMI Raw instance data
+ *
+ * @id: Sequential Raw instance ID.
+ * @handle: Pointer to SCMI entity handle to use
+ * @desc: Pointer to the transport descriptor to use
+ * @tx_max_msg: Maximum number of concurrent TX in-flight messages
+ * @q: An array of Raw queue descriptors
+ * @free_waiters: Head of freelist for unused waiters
+ * @free_mtx: A mutex to protect the waiters freelist
+ * @active_waiters: Head of list for currently active and used waiters
+ * @active_mtx: A mutex to protect the active waiters list
+ * @waiters_work: A work descriptor to be used with the workqueue machinery
+ * @wait_wq: A workqueue reference to the created workqueue
+ * @dentry: Top debugfs root dentry for SCMI Raw
+ * @gid: A group ID used for devres accounting
+ *
+ * Note that this descriptor is passed back to the core after SCMI Raw is
+ * initialized as an opaque handle to use by subsequent SCMI Raw call hooks.
+ *
+ */
+struct scmi_raw_mode_info {
+       unsigned int id;
+       const struct scmi_handle *handle;
+       const struct scmi_desc *desc;
+       int tx_max_msg;
+       struct scmi_raw_queue *q[SCMI_RAW_MAX_QUEUE];
+       struct list_head free_waiters;
+       /* Protect free_waiters list */
+       struct mutex free_mtx;
+       struct list_head active_waiters;
+       /* Protect active_waiters list */
+       struct mutex active_mtx;
+       struct work_struct waiters_work;
+       struct workqueue_struct *wait_wq;
+       struct dentry *dentry;
+       void *gid;
+};
+
+/**
+ * struct scmi_xfer_raw_waiter  - Structure to describe an xfer to be waited for
+ *
+ * @start_jiffies: The timestamp in jiffies of when this structure was queued.
+ * @cinfo: A reference to the channel to use for this transaction
+ * @xfer: A reference to the xfer to be waited for
+ * @async_response: A completion to be, optionally, used for async waits: it
+ *                 will be setup by @scmi_do_xfer_raw_start, if needed, to be
+ *                 pointed at by xfer->async_done.
+ * @node: A list node.
+ */
+struct scmi_xfer_raw_waiter {
+       unsigned long start_jiffies;
+       struct scmi_chan_info *cinfo;
+       struct scmi_xfer *xfer;
+       struct completion async_response;
+       struct list_head node;
+};
+
+/**
+ * struct scmi_raw_buffer  - Structure to hold a full SCMI message
+ *
+ * @max_len: The maximum allowed message size (header included) that can be
+ *          stored into @msg
+ * @msg: A message buffer used to collect a full message grabbed from an xfer.
+ * @node: A list node.
+ */
+struct scmi_raw_buffer {
+       size_t max_len;
+       struct scmi_msg msg;
+       struct list_head node;
+};
+
+/**
+ * struct scmi_dbg_raw_data  - Structure holding data needed by the debugfs
+ * layer
+ *
+ * @raw: A reference to the Raw instance.
+ * @tx: A message buffer used to collect TX message on write.
+ * @tx_size: The effective size of the TX message.
+ * @tx_req_size: The final expected size of the complete TX message.
+ * @rx: A message buffer to collect RX message on read.
+ * @rx_size: The effective size of the RX message.
+ */
+struct scmi_dbg_raw_data {
+       struct scmi_raw_mode_info *raw;
+       struct scmi_msg tx;
+       size_t tx_size;
+       size_t tx_req_size;
+       struct scmi_msg rx;
+       size_t rx_size;
+};
+
+static struct scmi_raw_buffer *scmi_raw_buffer_get(struct scmi_raw_queue *q)
+{
+       unsigned long flags;
+       struct scmi_raw_buffer *rb = NULL;
+       struct list_head *head = &q->free_bufs;
+
+       spin_lock_irqsave(&q->free_bufs_lock, flags);
+       if (!list_empty(head)) {
+               rb = list_first_entry(head, struct scmi_raw_buffer, node);
+               list_del_init(&rb->node);
+       }
+       spin_unlock_irqrestore(&q->free_bufs_lock, flags);
+
+       return rb;
+}
+
+static void scmi_raw_buffer_put(struct scmi_raw_queue *q,
+                               struct scmi_raw_buffer *rb)
+{
+       unsigned long flags;
+
+       /* Reset to full buffer length */
+       rb->msg.len = rb->max_len;
+
+       spin_lock_irqsave(&q->free_bufs_lock, flags);
+       list_add_tail(&rb->node, &q->free_bufs);
+       spin_unlock_irqrestore(&q->free_bufs_lock, flags);
+}
+
+static void scmi_raw_buffer_enqueue(struct scmi_raw_queue *q,
+                                   struct scmi_raw_buffer *rb)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&q->msg_q_lock, flags);
+       list_add_tail(&rb->node, &q->msg_q);
+       spin_unlock_irqrestore(&q->msg_q_lock, flags);
+
+       wake_up_interruptible(&q->wq);
+}
+
+static struct scmi_raw_buffer*
+scmi_raw_buffer_dequeue_unlocked(struct scmi_raw_queue *q)
+{
+       struct scmi_raw_buffer *rb = NULL;
+
+       if (!list_empty(&q->msg_q)) {
+               rb = list_first_entry(&q->msg_q, struct scmi_raw_buffer, node);
+               list_del_init(&rb->node);
+       }
+
+       return rb;
+}
+
+static struct scmi_raw_buffer *scmi_raw_buffer_dequeue(struct scmi_raw_queue *q)
+{
+       unsigned long flags;
+       struct scmi_raw_buffer *rb;
+
+       spin_lock_irqsave(&q->msg_q_lock, flags);
+       rb = scmi_raw_buffer_dequeue_unlocked(q);
+       spin_unlock_irqrestore(&q->msg_q_lock, flags);
+
+       return rb;
+}
+
+static void scmi_raw_buffer_queue_flush(struct scmi_raw_queue *q)
+{
+       struct scmi_raw_buffer *rb;
+
+       do {
+               rb = scmi_raw_buffer_dequeue(q);
+               if (rb)
+                       scmi_raw_buffer_put(q, rb);
+       } while (rb);
+}
+
+static struct scmi_xfer_raw_waiter *
+scmi_xfer_raw_waiter_get(struct scmi_raw_mode_info *raw, struct scmi_xfer *xfer,
+                        struct scmi_chan_info *cinfo, bool async)
+{
+       struct scmi_xfer_raw_waiter *rw = NULL;
+
+       mutex_lock(&raw->free_mtx);
+       if (!list_empty(&raw->free_waiters)) {
+               rw = list_first_entry(&raw->free_waiters,
+                                     struct scmi_xfer_raw_waiter, node);
+               list_del_init(&rw->node);
+
+               if (async) {
+                       reinit_completion(&rw->async_response);
+                       xfer->async_done = &rw->async_response;
+               }
+
+               rw->cinfo = cinfo;
+               rw->xfer = xfer;
+       }
+       mutex_unlock(&raw->free_mtx);
+
+       return rw;
+}
+
+static void scmi_xfer_raw_waiter_put(struct scmi_raw_mode_info *raw,
+                                    struct scmi_xfer_raw_waiter *rw)
+{
+       if (rw->xfer) {
+               rw->xfer->async_done = NULL;
+               rw->xfer = NULL;
+       }
+
+       mutex_lock(&raw->free_mtx);
+       list_add_tail(&rw->node, &raw->free_waiters);
+       mutex_unlock(&raw->free_mtx);
+}
+
+static void scmi_xfer_raw_waiter_enqueue(struct scmi_raw_mode_info *raw,
+                                        struct scmi_xfer_raw_waiter *rw)
+{
+       /* A timestamp for the deferred worker to know how much this has aged */
+       rw->start_jiffies = jiffies;
+
+       trace_scmi_xfer_response_wait(rw->xfer->transfer_id, rw->xfer->hdr.id,
+                                     rw->xfer->hdr.protocol_id,
+                                     rw->xfer->hdr.seq,
+                                     raw->desc->max_rx_timeout_ms,
+                                     rw->xfer->hdr.poll_completion);
+
+       mutex_lock(&raw->active_mtx);
+       list_add_tail(&rw->node, &raw->active_waiters);
+       mutex_unlock(&raw->active_mtx);
+
+       /* kick waiter work */
+       queue_work(raw->wait_wq, &raw->waiters_work);
+}
+
+static struct scmi_xfer_raw_waiter *
+scmi_xfer_raw_waiter_dequeue(struct scmi_raw_mode_info *raw)
+{
+       struct scmi_xfer_raw_waiter *rw = NULL;
+
+       mutex_lock(&raw->active_mtx);
+       if (!list_empty(&raw->active_waiters)) {
+               rw = list_first_entry(&raw->active_waiters,
+                                     struct scmi_xfer_raw_waiter, node);
+               list_del_init(&rw->node);
+       }
+       mutex_unlock(&raw->active_mtx);
+
+       return rw;
+}
+
+/**
+ * scmi_xfer_raw_worker  - Work function to wait for Raw xfers completions
+ *
+ * @work: A reference to the work.
+ *
+ * In SCMI Raw mode, once a user-provided injected SCMI message is sent, we
+ * cannot wait to receive its response (if any) in the context of the injection
+ * routines so as not to leave the userspace write syscall, which delivered the
+ * SCMI message to send, pending till eventually a reply is received.
+ * Userspace should and will poll/wait instead on the read syscalls which will
+ * be in charge of reading a received reply (if any).
+ *
+ * Even though reply messages are collected and reported into the SCMI Raw layer
+ * on the RX path, nonetheless we have to properly wait for their completion as
+ * usual (and async_completion too if needed) in order to properly release the
+ * xfer structure at the end: to do this out of the context of the write/send
+ * these waiting jobs are delegated to this deferred worker.
+ *
+ * Any sent xfer, to be waited for, is timestamped and queued for later
+ * consumption by this worker: queue aging is accounted for while choosing a
+ * timeout for the completion, BUT we do not really care here if we end up
+ * accidentally waiting for a bit too long.
+ */
+static void scmi_xfer_raw_worker(struct work_struct *work)
+{
+       struct scmi_raw_mode_info *raw;
+       struct device *dev;
+       unsigned long max_tmo;
+
+       raw = container_of(work, struct scmi_raw_mode_info, waiters_work);
+       dev = raw->handle->dev;
+       max_tmo = msecs_to_jiffies(raw->desc->max_rx_timeout_ms);
+
+       do {
+               int ret = 0;
+               unsigned int timeout_ms;
+               unsigned long aging;
+               struct scmi_xfer *xfer;
+               struct scmi_xfer_raw_waiter *rw;
+               struct scmi_chan_info *cinfo;
+
+               rw = scmi_xfer_raw_waiter_dequeue(raw);
+               if (!rw)
+                       return;
+
+               cinfo = rw->cinfo;
+               xfer = rw->xfer;
+               /*
+                * Waiters are queued by wait-deadline at the end, so some of
+                * them could have been already expired when processed, BUT we
+                * have to check the completion status anyway just in case a
+                * virtually expired (aged) transaction was indeed completed
+                * fine and we'll have to wait for the asynchronous part (if
+                * any): for this reason a 1 ms timeout is used for already
+                * expired/aged xfers.
+                */
+               aging = jiffies - rw->start_jiffies;
+               timeout_ms = max_tmo > aging ?
+                       jiffies_to_msecs(max_tmo - aging) : 1;
+
+               ret = scmi_xfer_raw_wait_for_message_response(cinfo, xfer,
+                                                             timeout_ms);
+               if (!ret && xfer->hdr.status)
+                       ret = scmi_to_linux_errno(xfer->hdr.status);
+
+               if (raw->desc->ops->mark_txdone)
+                       raw->desc->ops->mark_txdone(rw->cinfo, ret, xfer);
+
+               trace_scmi_xfer_end(xfer->transfer_id, xfer->hdr.id,
+                                   xfer->hdr.protocol_id, xfer->hdr.seq, ret);
+
+               /* Wait also for an async delayed response if needed */
+               if (!ret && xfer->async_done) {
+                       unsigned long tmo = msecs_to_jiffies(SCMI_MAX_RESPONSE_TIMEOUT);
+
+                       if (!wait_for_completion_timeout(xfer->async_done, tmo))
+                               dev_err(dev,
+                                       "timed out in RAW delayed resp - HDR:%08X\n",
+                                       pack_scmi_header(&xfer->hdr));
+               }
+
+               /* Release waiter and xfer */
+               scmi_xfer_raw_put(raw->handle, xfer);
+               scmi_xfer_raw_waiter_put(raw, rw);
+       } while (1);
+}
+
+static void scmi_xfer_raw_reset(struct scmi_raw_mode_info *raw)
+{
+       int i;
+
+       dev_info(raw->handle->dev, "Resetting SCMI Raw stack.\n");
+
+       for (i = 0; i < SCMI_RAW_MAX_QUEUE; i++)
+               scmi_raw_buffer_queue_flush(raw->q[i]);
+}
+
+/**
+ * scmi_xfer_raw_get_init  - An helper to build a valid xfer from the provided
+ * bare SCMI message.
+ *
+ * @raw: A reference to the Raw instance.
+ * @buf: A buffer containing the whole SCMI message to send (including the
+ *      header) in little-endian binary formmat.
+ * @len: Length of the message in @buf.
+ * @p: A pointer to return the initialized Raw xfer.
+ *
+ * After an xfer is picked from the TX pool and filled in with the message
+ * content, the xfer is registered as pending with the core in the usual way
+ * using the original sequence number provided by the user with the message.
+ *
+ * Note that, in case the testing user application is NOT using distinct
+ * sequence-numbers between successive SCMI messages such registration could
+ * fail temporarily if the previous message, using the same sequence number,
+ * had still not released; in such a case we just wait and retry.
+ *
+ * Return: 0 on Success
+ */
+static int scmi_xfer_raw_get_init(struct scmi_raw_mode_info *raw, void *buf,
+                                 size_t len, struct scmi_xfer **p)
+{
+       u32 msg_hdr;
+       size_t tx_size;
+       struct scmi_xfer *xfer;
+       int ret, retry = SCMI_XFER_RAW_MAX_RETRIES;
+       struct device *dev = raw->handle->dev;
+
+       if (!buf || len < sizeof(u32))
+               return -EINVAL;
+
+       tx_size = len - sizeof(u32);
+       /* Ensure we have sane transfer sizes */
+       if (tx_size > raw->desc->max_msg_size)
+               return -ERANGE;
+
+       xfer = scmi_xfer_raw_get(raw->handle);
+       if (IS_ERR(xfer)) {
+               dev_warn(dev, "RAW - Cannot get a free RAW xfer !\n");
+               return PTR_ERR(xfer);
+       }
+
+       /* Build xfer from the provided SCMI bare LE message */
+       msg_hdr = le32_to_cpu(*((__le32 *)buf));
+       unpack_scmi_header(msg_hdr, &xfer->hdr);
+       xfer->hdr.seq = (u16)MSG_XTRACT_TOKEN(msg_hdr);
+       /* Polling not supported */
+       xfer->hdr.poll_completion = false;
+       xfer->hdr.status = SCMI_SUCCESS;
+       xfer->tx.len = tx_size;
+       xfer->rx.len = raw->desc->max_msg_size;
+       /* Clear the whole TX buffer */
+       memset(xfer->tx.buf, 0x00, raw->desc->max_msg_size);
+       if (xfer->tx.len)
+               memcpy(xfer->tx.buf, (u8 *)buf + sizeof(msg_hdr), xfer->tx.len);
+       *p = xfer;
+
+       /*
+        * In flight registration can temporarily fail in case of Raw messages
+        * if the user injects messages without using monotonically increasing
+        * sequence numbers since, in Raw mode, the xfer (and the token) is
+        * finally released later by a deferred worker. Just retry for a while.
+        */
+       do {
+               ret = scmi_xfer_raw_inflight_register(raw->handle, xfer);
+               if (ret) {
+                       dev_dbg(dev,
+                               "...retrying[%d] inflight registration\n",
+                               retry);
+                       msleep(raw->desc->max_rx_timeout_ms /
+                              SCMI_XFER_RAW_MAX_RETRIES);
+               }
+       } while (ret && --retry);
+
+       if (ret) {
+               dev_warn(dev,
+                        "RAW - Could NOT register xfer %d in-flight HDR:0x%08X\n",
+                        xfer->hdr.seq, msg_hdr);
+               scmi_xfer_raw_put(raw->handle, xfer);
+       }
+
+       return ret;
+}
+
+/**
+ * scmi_do_xfer_raw_start  - An helper to send a valid raw xfer
+ *
+ * @raw: A reference to the Raw instance.
+ * @xfer: The xfer to send
+ * @async: A flag stating if an asynchronous command is required.
+ *
+ * This function send a previously built raw xfer using an appropriate channel
+ * and queues the related waiting work.
+ *
+ * Note that we need to know explicitly if the required command is meant to be
+ * asynchronous in kind since we have to properly setup the waiter.
+ * (and deducing this from the payload is weak and do not scale given there is
+ *  NOT a common header-flag stating if the command is asynchronous or not)
+ *
+ * Return: 0 on Success
+ */
+static int scmi_do_xfer_raw_start(struct scmi_raw_mode_info *raw,
+                                 struct scmi_xfer *xfer, bool async)
+{
+       int ret;
+       struct scmi_chan_info *cinfo;
+       struct scmi_xfer_raw_waiter *rw;
+       struct device *dev = raw->handle->dev;
+
+       cinfo = scmi_xfer_raw_channel_get(raw->handle, xfer->hdr.protocol_id);
+       if (IS_ERR(cinfo))
+               return PTR_ERR(cinfo);
+
+       rw = scmi_xfer_raw_waiter_get(raw, xfer, cinfo, async);
+       if (!rw) {
+               dev_warn(dev, "RAW - Cannot get a free waiter !\n");
+               return -ENOMEM;
+       }
+
+       /* True ONLY if also supported by transport. */
+       if (is_polling_enabled(cinfo, raw->desc))
+               xfer->hdr.poll_completion = true;
+
+       reinit_completion(&xfer->done);
+       /* Make sure xfer state update is visible before sending */
+       smp_store_mb(xfer->state, SCMI_XFER_SENT_OK);
+
+       trace_scmi_xfer_begin(xfer->transfer_id, xfer->hdr.id,
+                             xfer->hdr.protocol_id, xfer->hdr.seq,
+                             xfer->hdr.poll_completion);
+
+       ret = raw->desc->ops->send_message(rw->cinfo, xfer);
+       if (ret) {
+               dev_err(dev, "Failed to send RAW message %d\n", ret);
+               scmi_xfer_raw_waiter_put(raw, rw);
+               return ret;
+       }
+
+       trace_scmi_msg_dump(raw->id, cinfo->id, xfer->hdr.protocol_id,
+                           xfer->hdr.id, "cmnd", xfer->hdr.seq,
+                           xfer->hdr.status,
+                           xfer->tx.buf, xfer->tx.len);
+
+       scmi_xfer_raw_waiter_enqueue(raw, rw);
+
+       return ret;
+}
+
+/**
+ * scmi_raw_message_send  - An helper to build and send an SCMI command using
+ * the provided SCMI bare message buffer
+ *
+ * @raw: A reference to the Raw instance.
+ * @buf: A buffer containing the whole SCMI message to send (including the
+ *      header) in little-endian binary format.
+ * @len: Length of the message in @buf.
+ * @async: A flag stating if an asynchronous command is required.
+ *
+ * Return: 0 on Success
+ */
+static int scmi_raw_message_send(struct scmi_raw_mode_info *raw,
+                                void *buf, size_t len, bool async)
+{
+       int ret;
+       struct scmi_xfer *xfer;
+
+       ret = scmi_xfer_raw_get_init(raw, buf, len, &xfer);
+       if (ret)
+               return ret;
+
+       ret = scmi_do_xfer_raw_start(raw, xfer, async);
+       if (ret)
+               scmi_xfer_raw_put(raw->handle, xfer);
+
+       return ret;
+}
+
+static struct scmi_raw_buffer *
+scmi_raw_message_dequeue(struct scmi_raw_queue *q, bool o_nonblock)
+{
+       unsigned long flags;
+       struct scmi_raw_buffer *rb;
+
+       spin_lock_irqsave(&q->msg_q_lock, flags);
+       while (list_empty(&q->msg_q)) {
+               spin_unlock_irqrestore(&q->msg_q_lock, flags);
+
+               if (o_nonblock)
+                       return ERR_PTR(-EAGAIN);
+
+               if (wait_event_interruptible(q->wq, !list_empty(&q->msg_q)))
+                       return ERR_PTR(-ERESTARTSYS);
+
+               spin_lock_irqsave(&q->msg_q_lock, flags);
+       }
+
+       rb = scmi_raw_buffer_dequeue_unlocked(q);
+
+       spin_unlock_irqrestore(&q->msg_q_lock, flags);
+
+       return rb;
+}
+
+/**
+ * scmi_raw_message_receive  - An helper to dequeue and report the next
+ * available enqueued raw message payload that has been collected.
+ *
+ * @raw: A reference to the Raw instance.
+ * @buf: A buffer to get hold of the whole SCMI message received and represented
+ *      in little-endian binary format.
+ * @len: Length of @buf.
+ * @size: The effective size of the message copied into @buf
+ * @idx: The index of the queue to pick the next queued message from.
+ * @o_nonblock: A flag to request a non-blocking message dequeue.
+ *
+ * Return: 0 on Success
+ */
+static int scmi_raw_message_receive(struct scmi_raw_mode_info *raw,
+                                   void *buf, size_t len, size_t *size,
+                                   unsigned int idx,
+                                   bool o_nonblock)
+{
+       int ret = 0;
+       struct scmi_raw_buffer *rb;
+       struct scmi_raw_queue *q = raw->q[idx];
+
+       rb = scmi_raw_message_dequeue(q, o_nonblock);
+       if (IS_ERR(rb)) {
+               dev_dbg(raw->handle->dev, "RAW - No message available!\n");
+               return PTR_ERR(rb);
+       }
+
+       if (rb->msg.len <= len) {
+               memcpy(buf, rb->msg.buf, rb->msg.len);
+               *size = rb->msg.len;
+       } else {
+               ret = -ENOSPC;
+       }
+
+       scmi_raw_buffer_put(q, rb);
+
+       return ret;
+}
+
+/* SCMI Raw debugfs helpers */
+
+static ssize_t scmi_dbg_raw_mode_common_read(struct file *filp,
+                                            char __user *buf,
+                                            size_t count, loff_t *ppos,
+                                            unsigned int idx)
+{
+       ssize_t cnt;
+       struct scmi_dbg_raw_data *rd = filp->private_data;
+
+       if (!rd->rx_size) {
+               int ret;
+
+               ret = scmi_raw_message_receive(rd->raw, rd->rx.buf, rd->rx.len,
+                                              &rd->rx_size, idx,
+                                              filp->f_flags & O_NONBLOCK);
+               if (ret) {
+                       rd->rx_size = 0;
+                       return ret;
+               }
+
+               /* Reset any previous filepos change, including writes */
+               *ppos = 0;
+       } else if (*ppos == rd->rx_size) {
+               /* Return EOF once all the message has been read-out */
+               rd->rx_size = 0;
+               return 0;
+       }
+
+       cnt = simple_read_from_buffer(buf, count, ppos,
+                                     rd->rx.buf, rd->rx_size);
+
+       return cnt;
+}
+
+static ssize_t scmi_dbg_raw_mode_common_write(struct file *filp,
+                                             const char __user *buf,
+                                             size_t count, loff_t *ppos,
+                                             bool async)
+{
+       int ret;
+       struct scmi_dbg_raw_data *rd = filp->private_data;
+
+       if (count > rd->tx.len - rd->tx_size)
+               return -ENOSPC;
+
+       /* On first write attempt @count carries the total full message size. */
+       if (!rd->tx_size)
+               rd->tx_req_size = count;
+
+       /*
+        * Gather a full message, possibly across multiple interrupted wrrtes,
+        * before sending it with a single RAW xfer.
+        */
+       if (rd->tx_size < rd->tx_req_size) {
+               size_t cnt;
+
+               cnt = simple_write_to_buffer(rd->tx.buf, rd->tx.len, ppos,
+                                            buf, count);
+               rd->tx_size += cnt;
+               if (cnt < count)
+                       return cnt;
+       }
+
+       ret = scmi_raw_message_send(rd->raw, rd->tx.buf, rd->tx_size, async);
+
+       /* Reset ppos for next message ... */
+       rd->tx_size = 0;
+       *ppos = 0;
+
+       return ret ?: count;
+}
+
+static __poll_t scmi_test_dbg_raw_common_poll(struct file *filp,
+                                             struct poll_table_struct *wait,
+                                             unsigned int idx)
+{
+       unsigned long flags;
+       struct scmi_dbg_raw_data *rd = filp->private_data;
+       struct scmi_raw_queue *q = rd->raw->q[idx];
+       __poll_t mask = 0;
+
+       poll_wait(filp, &q->wq, wait);
+
+       spin_lock_irqsave(&q->msg_q_lock, flags);
+       if (!list_empty(&q->msg_q))
+               mask = EPOLLIN | EPOLLRDNORM;
+       spin_unlock_irqrestore(&q->msg_q_lock, flags);
+
+       return mask;
+}
+
+static ssize_t scmi_dbg_raw_mode_message_read(struct file *filp,
+                                             char __user *buf,
+                                             size_t count, loff_t *ppos)
+{
+       return scmi_dbg_raw_mode_common_read(filp, buf, count, ppos,
+                                            SCMI_RAW_REPLY_QUEUE);
+}
+
+static ssize_t scmi_dbg_raw_mode_message_write(struct file *filp,
+                                              const char __user *buf,
+                                              size_t count, loff_t *ppos)
+{
+       return scmi_dbg_raw_mode_common_write(filp, buf, count, ppos, false);
+}
+
+static __poll_t scmi_dbg_raw_mode_message_poll(struct file *filp,
+                                              struct poll_table_struct *wait)
+{
+       return scmi_test_dbg_raw_common_poll(filp, wait, SCMI_RAW_REPLY_QUEUE);
+}
+
+static int scmi_dbg_raw_mode_open(struct inode *inode, struct file *filp)
+{
+       struct scmi_raw_mode_info *raw;
+       struct scmi_dbg_raw_data *rd;
+
+       if (!inode->i_private)
+               return -ENODEV;
+
+       raw = inode->i_private;
+       rd = kzalloc(sizeof(*rd), GFP_KERNEL);
+       if (!rd)
+               return -ENOMEM;
+
+       rd->rx.len = raw->desc->max_msg_size + sizeof(u32);
+       rd->rx.buf = kzalloc(rd->rx.len, GFP_KERNEL);
+       if (!rd->rx.buf) {
+               kfree(rd);
+               return -ENOMEM;
+       }
+
+       rd->tx.len = raw->desc->max_msg_size + sizeof(u32);
+       rd->tx.buf = kzalloc(rd->tx.len, GFP_KERNEL);
+       if (!rd->tx.buf) {
+               kfree(rd->rx.buf);
+               kfree(rd);
+               return -ENOMEM;
+       }
+
+       rd->raw = raw;
+       filp->private_data = rd;
+
+       return 0;
+}
+
+static int scmi_dbg_raw_mode_release(struct inode *inode, struct file *filp)
+{
+       struct scmi_dbg_raw_data *rd = filp->private_data;
+
+       kfree(rd->rx.buf);
+       kfree(rd->tx.buf);
+       kfree(rd);
+
+       return 0;
+}
+
+static ssize_t scmi_dbg_raw_mode_reset_write(struct file *filp,
+                                            const char __user *buf,
+                                            size_t count, loff_t *ppos)
+{
+       struct scmi_dbg_raw_data *rd = filp->private_data;
+
+       scmi_xfer_raw_reset(rd->raw);
+
+       return count;
+}
+
+static const struct file_operations scmi_dbg_raw_mode_reset_fops = {
+       .open = scmi_dbg_raw_mode_open,
+       .release = scmi_dbg_raw_mode_release,
+       .write = scmi_dbg_raw_mode_reset_write,
+       .owner = THIS_MODULE,
+};
+
+static const struct file_operations scmi_dbg_raw_mode_message_fops = {
+       .open = scmi_dbg_raw_mode_open,
+       .release = scmi_dbg_raw_mode_release,
+       .read = scmi_dbg_raw_mode_message_read,
+       .write = scmi_dbg_raw_mode_message_write,
+       .poll = scmi_dbg_raw_mode_message_poll,
+       .owner = THIS_MODULE,
+};
+
+static ssize_t scmi_dbg_raw_mode_message_async_write(struct file *filp,
+                                                    const char __user *buf,
+                                                    size_t count, loff_t *ppos)
+{
+       return scmi_dbg_raw_mode_common_write(filp, buf, count, ppos, true);
+}
+
+static const struct file_operations scmi_dbg_raw_mode_message_async_fops = {
+       .open = scmi_dbg_raw_mode_open,
+       .release = scmi_dbg_raw_mode_release,
+       .read = scmi_dbg_raw_mode_message_read,
+       .write = scmi_dbg_raw_mode_message_async_write,
+       .poll = scmi_dbg_raw_mode_message_poll,
+       .owner = THIS_MODULE,
+};
+
+static ssize_t scmi_test_dbg_raw_mode_notif_read(struct file *filp,
+                                                char __user *buf,
+                                                size_t count, loff_t *ppos)
+{
+       return scmi_dbg_raw_mode_common_read(filp, buf, count, ppos,
+                                            SCMI_RAW_NOTIF_QUEUE);
+}
+
+static __poll_t
+scmi_test_dbg_raw_mode_notif_poll(struct file *filp,
+                                 struct poll_table_struct *wait)
+{
+       return scmi_test_dbg_raw_common_poll(filp, wait, SCMI_RAW_NOTIF_QUEUE);
+}
+
+static const struct file_operations scmi_dbg_raw_mode_notification_fops = {
+       .open = scmi_dbg_raw_mode_open,
+       .release = scmi_dbg_raw_mode_release,
+       .read = scmi_test_dbg_raw_mode_notif_read,
+       .poll = scmi_test_dbg_raw_mode_notif_poll,
+       .owner = THIS_MODULE,
+};
+
+static ssize_t scmi_test_dbg_raw_mode_errors_read(struct file *filp,
+                                                 char __user *buf,
+                                                 size_t count, loff_t *ppos)
+{
+       return scmi_dbg_raw_mode_common_read(filp, buf, count, ppos,
+                                            SCMI_RAW_ERRS_QUEUE);
+}
+
+static __poll_t
+scmi_test_dbg_raw_mode_errors_poll(struct file *filp,
+                                  struct poll_table_struct *wait)
+{
+       return scmi_test_dbg_raw_common_poll(filp, wait, SCMI_RAW_ERRS_QUEUE);
+}
+
+static const struct file_operations scmi_dbg_raw_mode_errors_fops = {
+       .open = scmi_dbg_raw_mode_open,
+       .release = scmi_dbg_raw_mode_release,
+       .read = scmi_test_dbg_raw_mode_errors_read,
+       .poll = scmi_test_dbg_raw_mode_errors_poll,
+       .owner = THIS_MODULE,
+};
+
+static struct scmi_raw_queue *
+scmi_raw_queue_init(struct scmi_raw_mode_info *raw)
+{
+       int i;
+       struct scmi_raw_buffer *rb;
+       struct device *dev = raw->handle->dev;
+       struct scmi_raw_queue *q;
+
+       q = devm_kzalloc(dev, sizeof(*q), GFP_KERNEL);
+       if (!q)
+               return ERR_PTR(-ENOMEM);
+
+       rb = devm_kcalloc(dev, raw->tx_max_msg, sizeof(*rb), GFP_KERNEL);
+       if (!rb)
+               return ERR_PTR(-ENOMEM);
+
+       spin_lock_init(&q->free_bufs_lock);
+       INIT_LIST_HEAD(&q->free_bufs);
+       for (i = 0; i < raw->tx_max_msg; i++, rb++) {
+               rb->max_len = raw->desc->max_msg_size + sizeof(u32);
+               rb->msg.buf = devm_kzalloc(dev, rb->max_len, GFP_KERNEL);
+               if (!rb->msg.buf)
+                       return ERR_PTR(-ENOMEM);
+               scmi_raw_buffer_put(q, rb);
+       }
+
+       spin_lock_init(&q->msg_q_lock);
+       INIT_LIST_HEAD(&q->msg_q);
+       init_waitqueue_head(&q->wq);
+
+       return q;
+}
+
+static int scmi_xfer_raw_worker_init(struct scmi_raw_mode_info *raw)
+{
+       int i;
+       struct scmi_xfer_raw_waiter *rw;
+       struct device *dev = raw->handle->dev;
+
+       rw = devm_kcalloc(dev, raw->tx_max_msg, sizeof(*rw), GFP_KERNEL);
+       if (!rw)
+               return -ENOMEM;
+
+       raw->wait_wq = alloc_workqueue("scmi-raw-wait-wq-%d",
+                                      WQ_UNBOUND | WQ_FREEZABLE |
+                                      WQ_HIGHPRI, WQ_SYSFS, raw->id);
+       if (!raw->wait_wq)
+               return -ENOMEM;
+
+       mutex_init(&raw->free_mtx);
+       INIT_LIST_HEAD(&raw->free_waiters);
+       mutex_init(&raw->active_mtx);
+       INIT_LIST_HEAD(&raw->active_waiters);
+
+       for (i = 0; i < raw->tx_max_msg; i++, rw++) {
+               init_completion(&rw->async_response);
+               scmi_xfer_raw_waiter_put(raw, rw);
+       }
+       INIT_WORK(&raw->waiters_work, scmi_xfer_raw_worker);
+
+       return 0;
+}
+
+static int scmi_raw_mode_setup(struct scmi_raw_mode_info *raw)
+{
+       int ret, idx;
+       void *gid;
+       struct device *dev = raw->handle->dev;
+
+       gid = devres_open_group(dev, NULL, GFP_KERNEL);
+       if (!gid)
+               return -ENOMEM;
+
+       for (idx = 0; idx < SCMI_RAW_MAX_QUEUE; idx++) {
+               raw->q[idx] = scmi_raw_queue_init(raw);
+               if (IS_ERR(raw->q[idx])) {
+                       ret = PTR_ERR(raw->q[idx]);
+                       goto err;
+               }
+       }
+
+       ret = scmi_xfer_raw_worker_init(raw);
+       if (ret)
+               goto err;
+
+       devres_close_group(dev, gid);
+       raw->gid = gid;
+
+       return 0;
+
+err:
+       devres_release_group(dev, gid);
+       return ret;
+}
+
+/**
+ * scmi_raw_mode_init  - Function to initialize the SCMI Raw stack
+ *
+ * @handle: Pointer to SCMI entity handle
+ * @top_dentry: A reference to the top Raw debugfs dentry
+ * @instance_id: The ID of the underlying SCMI platform instance represented by
+ *              this Raw instance
+ * @desc: Reference to the transport operations
+ * @tx_max_msg: Max number of in-flight messages allowed by the transport
+ *
+ * This function prepare the SCMI Raw stack and creates the debugfs API.
+ *
+ * Return: An opaque handle to the Raw instance on Success, an ERR_PTR otherwise
+ */
+void *scmi_raw_mode_init(const struct scmi_handle *handle,
+                        struct dentry *top_dentry, int instance_id,
+                        const struct scmi_desc *desc, int tx_max_msg)
+{
+       int ret;
+       struct scmi_raw_mode_info *raw;
+       struct device *dev;
+
+       if (!handle || !desc)
+               return ERR_PTR(-EINVAL);
+
+       dev = handle->dev;
+       raw = devm_kzalloc(dev, sizeof(*raw), GFP_KERNEL);
+       if (!raw)
+               return ERR_PTR(-ENOMEM);
+
+       raw->handle = handle;
+       raw->desc = desc;
+       raw->tx_max_msg = tx_max_msg;
+       raw->id = instance_id;
+
+       ret = scmi_raw_mode_setup(raw);
+       if (ret) {
+               devm_kfree(dev, raw);
+               return ERR_PTR(ret);
+       }
+
+       raw->dentry = debugfs_create_dir("raw", top_dentry);
+
+       debugfs_create_file("reset", 0200, raw->dentry, raw,
+                           &scmi_dbg_raw_mode_reset_fops);
+
+       debugfs_create_file("message", 0600, raw->dentry, raw,
+                           &scmi_dbg_raw_mode_message_fops);
+
+       debugfs_create_file("message_async", 0600, raw->dentry, raw,
+                           &scmi_dbg_raw_mode_message_async_fops);
+
+       debugfs_create_file("notification", 0400, raw->dentry, raw,
+                           &scmi_dbg_raw_mode_notification_fops);
+
+       debugfs_create_file("errors", 0400, raw->dentry, raw,
+                           &scmi_dbg_raw_mode_errors_fops);
+
+       dev_info(dev, "SCMI RAW Mode initialized for instance %d\n", raw->id);
+
+       return raw;
+}
+
+/**
+ * scmi_raw_mode_cleanup  - Function to cleanup the SCMI Raw stack
+ *
+ * @r: An opaque handle to an initialized SCMI Raw instance
+ */
+void scmi_raw_mode_cleanup(void *r)
+{
+       struct scmi_raw_mode_info *raw = r;
+
+       if (!raw)
+               return;
+
+       debugfs_remove_recursive(raw->dentry);
+
+       cancel_work_sync(&raw->waiters_work);
+       destroy_workqueue(raw->wait_wq);
+}
+
+static int scmi_xfer_raw_collect(void *msg, size_t *msg_len,
+                                struct scmi_xfer *xfer)
+{
+       __le32 *m;
+       size_t msg_size;
+
+       if (!xfer || !msg || !msg_len)
+               return -EINVAL;
+
+       /* Account for hdr ...*/
+       msg_size = xfer->rx.len + sizeof(u32);
+       /* ... and status if needed */
+       if (xfer->hdr.type != MSG_TYPE_NOTIFICATION)
+               msg_size += sizeof(u32);
+
+       if (msg_size > *msg_len)
+               return -ENOSPC;
+
+       m = msg;
+       *m = cpu_to_le32(pack_scmi_header(&xfer->hdr));
+       if (xfer->hdr.type != MSG_TYPE_NOTIFICATION)
+               *++m = cpu_to_le32(xfer->hdr.status);
+
+       memcpy(++m, xfer->rx.buf, xfer->rx.len);
+
+       *msg_len = msg_size;
+
+       return 0;
+}
+
+/**
+ * scmi_raw_message_report  - Helper to report back valid reponses/notifications
+ * to raw message requests.
+ *
+ * @r: An opaque reference to the raw instance configuration
+ * @xfer: The xfer containing the message to be reported
+ * @idx: The index of the queue.
+ *
+ * If Raw mode is enabled, this is called from the SCMI core on the regular RX
+ * path to save and enqueue the response/notification payload carried by this
+ * xfer into a dedicated scmi_raw_buffer for later consumption by the user.
+ *
+ * This way the caller can free the related xfer immediately afterwards and the
+ * user can read back the raw message payload at its own pace (if ever) without
+ * holding an xfer for too long.
+ */
+void scmi_raw_message_report(void *r, struct scmi_xfer *xfer, unsigned int idx)
+{
+       int ret;
+       unsigned long flags;
+       struct scmi_raw_buffer *rb;
+       struct device *dev;
+       struct scmi_raw_queue *q;
+       struct scmi_raw_mode_info *raw = r;
+
+       if (!raw || (idx == SCMI_RAW_REPLY_QUEUE && !SCMI_XFER_IS_RAW(xfer)))
+               return;
+
+       dev = raw->handle->dev;
+       q = raw->q[idx];
+
+       /*
+        * Grab the msg_q_lock upfront to avoid a possible race between
+        * realizing the free list was empty and effectively picking the next
+        * buffer to use from the oldest one enqueued and still unread on this
+        * msg_q.
+        *
+        * Note that nowhere else these locks are taken together, so no risk of
+        * deadlocks du eto inversion.
+        */
+       spin_lock_irqsave(&q->msg_q_lock, flags);
+       rb = scmi_raw_buffer_get(q);
+       if (!rb) {
+               /*
+                * Immediate and delayed replies to previously injected Raw
+                * commands MUST be read back from userspace to free the buffers:
+                * if this is not happening something is seriously broken and
+                * must be fixed at the application level: complain loudly.
+                */
+               if (idx == SCMI_RAW_REPLY_QUEUE) {
+                       spin_unlock_irqrestore(&q->msg_q_lock, flags);
+                       dev_warn(dev,
+                                "RAW[%d] - Buffers exhausted. Dropping report.\n",
+                                idx);
+                       return;
+               }
+
+               /*
+                * Notifications and errors queues are instead handled in a
+                * circular manner: unread old buffers are just overwritten by
+                * newer ones.
+                *
+                * The main reason for this is that notifications originated
+                * by Raw requests cannot be distinguished from normal ones, so
+                * your Raw buffers queues risk to be flooded and depleted by
+                * notifications if you left it mistakenly enabled or when in
+                * coexistence mode.
+                */
+               rb = scmi_raw_buffer_dequeue_unlocked(q);
+               if (WARN_ON(!rb)) {
+                       spin_unlock_irqrestore(&q->msg_q_lock, flags);
+                       return;
+               }
+
+               /* Reset to full buffer length */
+               rb->msg.len = rb->max_len;
+
+               dev_warn_once(dev,
+                             "RAW[%d] - Buffers exhausted. Re-using oldest.\n",
+                             idx);
+       }
+       spin_unlock_irqrestore(&q->msg_q_lock, flags);
+
+       ret = scmi_xfer_raw_collect(rb->msg.buf, &rb->msg.len, xfer);
+       if (ret) {
+               dev_warn(dev, "RAW - Cannot collect xfer into buffer !\n");
+               scmi_raw_buffer_put(q, rb);
+               return;
+       }
+
+       scmi_raw_buffer_enqueue(q, rb);
+}
+
+static void scmi_xfer_raw_fill(struct scmi_raw_mode_info *raw,
+                              struct scmi_chan_info *cinfo,
+                              struct scmi_xfer *xfer, u32 msg_hdr)
+{
+       /* Unpack received HDR as it is */
+       unpack_scmi_header(msg_hdr, &xfer->hdr);
+       xfer->hdr.seq = MSG_XTRACT_TOKEN(msg_hdr);
+
+       memset(xfer->rx.buf, 0x00, xfer->rx.len);
+
+       raw->desc->ops->fetch_response(cinfo, xfer);
+}
+
+/**
+ * scmi_raw_error_report  - Helper to report back timed-out or generally
+ * unexpected replies.
+ *
+ * @r: An opaque reference to the raw instance configuration
+ * @cinfo: A reference to the channel to use to retrieve the broken xfer
+ * @msg_hdr: The SCMI message header of the message to fetch and report
+ * @priv: Any private data related to the xfer.
+ *
+ * If Raw mode is enabled, this is called from the SCMI core on the RX path in
+ * case of errors to save and enqueue the bad message payload carried by the
+ * message that has just been received.
+ *
+ * Note that we have to manually fetch any available payload into a temporary
+ * xfer to be able to save and enqueue the message, since the regular RX error
+ * path which had called this would have not fetched the message payload having
+ * classified it as an error.
+ */
+void scmi_raw_error_report(void *r, struct scmi_chan_info *cinfo,
+                          u32 msg_hdr, void *priv)
+{
+       struct scmi_xfer xfer;
+       struct scmi_raw_mode_info *raw = r;
+
+       if (!raw)
+               return;
+
+       xfer.rx.len = raw->desc->max_msg_size;
+       xfer.rx.buf = kzalloc(xfer.rx.len, GFP_ATOMIC);
+       if (!xfer.rx.buf) {
+               dev_info(raw->handle->dev,
+                        "Cannot report Raw error for HDR:0x%X - ENOMEM\n",
+                        msg_hdr);
+               return;
+       }
+
+       /* Any transport-provided priv must be passed back down to transport */
+       if (priv)
+               /* Ensure priv is visible */
+               smp_store_mb(xfer.priv, priv);
+
+       scmi_xfer_raw_fill(raw, cinfo, &xfer, msg_hdr);
+       scmi_raw_message_report(raw, &xfer, SCMI_RAW_ERRS_QUEUE);
+
+       kfree(xfer.rx.buf);
+}
diff --git a/drivers/firmware/arm_scmi/raw_mode.h b/drivers/firmware/arm_scmi/raw_mode.h
new file mode 100644 (file)
index 0000000..1e3d166
--- /dev/null
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * System Control and Management Interface (SCMI) Message Protocol
+ * Raw mode support header.
+ *
+ * Copyright (C) 2022 ARM Ltd.
+ */
+#ifndef _SCMI_RAW_MODE_H
+#define _SCMI_RAW_MODE_H
+
+#include "common.h"
+
+enum {
+       SCMI_RAW_REPLY_QUEUE,
+       SCMI_RAW_NOTIF_QUEUE,
+       SCMI_RAW_ERRS_QUEUE,
+       SCMI_RAW_MAX_QUEUE
+};
+
+void *scmi_raw_mode_init(const struct scmi_handle *handle,
+                        struct dentry *top_dentry, int instance_id,
+                        const struct scmi_desc *desc, int tx_max_msg);
+void scmi_raw_mode_cleanup(void *raw);
+
+void scmi_raw_message_report(void *raw, struct scmi_xfer *xfer,
+                            unsigned int idx);
+void scmi_raw_error_report(void *raw, struct scmi_chan_info *cinfo,
+                          u32 msg_hdr, void *priv);
+
+#endif /* _SCMI_RAW_MODE_H */