2023-08-30 17:31:07 +02:00
|
|
|
// 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, unless CONFIG_ARM_SCMI_RAW_MODE_SUPPORT_COEX is enabled: in this
|
|
|
|
* latter case the regular SCMI stack drivers will be loaded as usual and it is
|
|
|
|
* up to the user of this interface to take care of manually inhibiting the
|
|
|
|
* regular SCMI drivers in order to avoid interferences during the test runs.
|
|
|
|
*
|
|
|
|
* 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
|
|
|
|
* | |-- channels
|
|
|
|
* | | |-- 0x10
|
|
|
|
* | | | |-- message
|
|
|
|
* | | | `-- message_async
|
|
|
|
* | | `-- 0x13
|
|
|
|
* | | |-- message
|
|
|
|
* | | `-- message_async
|
|
|
|
* | |-- 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.
|
|
|
|
*
|
|
|
|
* with the per-channel entries rooted at /channels being present only on a
|
|
|
|
* system where multiple transport channels have been configured.
|
|
|
|
*
|
|
|
|
* Such per-channel entries can be used to explicitly choose a specific channel
|
|
|
|
* for SCMI bare message injection, in contrast with the general entries above
|
|
|
|
* where, instead, the selection of the proper channel to use is automatically
|
|
|
|
* performed based the protocol embedded in the injected message and on how the
|
|
|
|
* transport is configured on the system.
|
|
|
|
*
|
|
|
|
* 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/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 <linux/xarray.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
|
|
|
|
* @chans_q: An XArray mapping optional additional per-channel queues
|
|
|
|
* @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 xarray chans_q;
|
|
|
|
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
|
|
|
|
*
|
|
|
|
* @chan_id: The preferred channel to use: if zero the channel is automatically
|
|
|
|
* selected based on protocol.
|
|
|
|
* @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 {
|
|
|
|
u8 chan_id;
|
|
|
|
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_queue *
|
|
|
|
scmi_raw_queue_select(struct scmi_raw_mode_info *raw, unsigned int idx,
|
|
|
|
unsigned int chan_id)
|
|
|
|
{
|
|
|
|
if (!chan_id)
|
|
|
|
return raw->q[idx];
|
|
|
|
|
|
|
|
return xa_load(&raw->chans_q, chan_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
* @chan_id: The channel ID to use, if zero the channels is automatically
|
|
|
|
* selected based on the protocol used.
|
|
|
|
* @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, u8 chan_id,
|
|
|
|
bool async)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
struct scmi_chan_info *cinfo;
|
|
|
|
struct scmi_xfer_raw_waiter *rw;
|
|
|
|
struct device *dev = raw->handle->dev;
|
|
|
|
|
|
|
|
if (!chan_id)
|
|
|
|
chan_id = xfer->hdr.protocol_id;
|
|
|
|
else
|
|
|
|
xfer->flags |= SCMI_XFER_FLAG_CHAN_SET;
|
|
|
|
|
|
|
|
cinfo = scmi_xfer_raw_channel_get(raw->handle, chan_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.
|
|
|
|
* @chan_id: The channel ID to use.
|
|
|
|
* @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, u8 chan_id, 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, chan_id, 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.
|
|
|
|
* @chan_id: The channel ID to use.
|
|
|
|
* @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, unsigned int chan_id,
|
|
|
|
bool o_nonblock)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
struct scmi_raw_buffer *rb;
|
|
|
|
struct scmi_raw_queue *q;
|
|
|
|
|
|
|
|
q = scmi_raw_queue_select(raw, idx, chan_id);
|
|
|
|
if (!q)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
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, rd->chan_id,
|
|
|
|
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) {
|
2023-10-24 12:59:35 +02:00
|
|
|
ssize_t cnt;
|
2023-08-30 17:31:07 +02:00
|
|
|
|
|
|
|
cnt = simple_write_to_buffer(rd->tx.buf, rd->tx.len, ppos,
|
|
|
|
buf, count);
|
2023-10-24 12:59:35 +02:00
|
|
|
if (cnt < 0)
|
|
|
|
return cnt;
|
|
|
|
|
2023-08-30 17:31:07 +02:00
|
|
|
rd->tx_size += cnt;
|
|
|
|
if (cnt < count)
|
|
|
|
return cnt;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = scmi_raw_message_send(rd->raw, rd->tx.buf, rd->tx_size,
|
|
|
|
rd->chan_id, 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;
|
|
|
|
__poll_t mask = 0;
|
|
|
|
|
|
|
|
q = scmi_raw_queue_select(rd->raw, idx, rd->chan_id);
|
|
|
|
if (!q)
|
|
|
|
return mask;
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
u8 id;
|
|
|
|
struct scmi_raw_mode_info *raw;
|
|
|
|
struct scmi_dbg_raw_data *rd;
|
|
|
|
const char *id_str = filp->f_path.dentry->d_parent->d_name.name;
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Grab channel ID from debugfs entry naming if any */
|
|
|
|
if (!kstrtou8(id_str, 16, &id))
|
|
|
|
rd->chan_id = id;
|
|
|
|
|
|
|
|
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, 0, 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,
|
|
|
|
u8 *channels, int num_chans)
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
xa_init(&raw->chans_q);
|
|
|
|
if (num_chans > 1) {
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < num_chans; i++) {
|
|
|
|
void *xret;
|
|
|
|
struct scmi_raw_queue *q;
|
|
|
|
|
|
|
|
q = scmi_raw_queue_init(raw);
|
|
|
|
if (IS_ERR(q)) {
|
|
|
|
ret = PTR_ERR(q);
|
|
|
|
goto err_xa;
|
|
|
|
}
|
|
|
|
|
|
|
|
xret = xa_store(&raw->chans_q, channels[i], q,
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (xa_err(xret)) {
|
|
|
|
dev_err(dev,
|
|
|
|
"Fail to allocate Raw queue 0x%02X\n",
|
|
|
|
channels[i]);
|
|
|
|
ret = xa_err(xret);
|
|
|
|
goto err_xa;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = scmi_xfer_raw_worker_init(raw);
|
|
|
|
if (ret)
|
|
|
|
goto err_xa;
|
|
|
|
|
|
|
|
devres_close_group(dev, gid);
|
|
|
|
raw->gid = gid;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err_xa:
|
|
|
|
xa_destroy(&raw->chans_q);
|
|
|
|
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
|
|
|
|
* @channels: The list of the existing channels
|
|
|
|
* @num_chans: The number of entries in @channels
|
|
|
|
* @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,
|
|
|
|
u8 *channels, int num_chans,
|
|
|
|
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, channels, num_chans);
|
|
|
|
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);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Expose per-channel entries if multiple channels available.
|
|
|
|
* Just ignore errors while setting up these interfaces since we
|
|
|
|
* have anyway already a working core Raw support.
|
|
|
|
*/
|
|
|
|
if (num_chans > 1) {
|
|
|
|
int i;
|
|
|
|
struct dentry *top_chans;
|
|
|
|
|
|
|
|
top_chans = debugfs_create_dir("channels", raw->dentry);
|
|
|
|
|
|
|
|
for (i = 0; i < num_chans; i++) {
|
|
|
|
char cdir[8];
|
|
|
|
struct dentry *chd;
|
|
|
|
|
|
|
|
snprintf(cdir, 8, "0x%02X", channels[i]);
|
|
|
|
chd = debugfs_create_dir(cdir, top_chans);
|
|
|
|
|
|
|
|
debugfs_create_file("message", 0600, chd, raw,
|
|
|
|
&scmi_dbg_raw_mode_message_fops);
|
|
|
|
|
|
|
|
debugfs_create_file("message_async", 0600, chd, raw,
|
|
|
|
&scmi_dbg_raw_mode_message_async_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);
|
|
|
|
xa_destroy(&raw->chans_q);
|
|
|
|
}
|
|
|
|
|
|
|
|
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.
|
|
|
|
* @chan_id: The channel ID to use.
|
|
|
|
*
|
|
|
|
* 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, unsigned int chan_id)
|
|
|
|
{
|
|
|
|
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 = scmi_raw_queue_select(raw, idx,
|
|
|
|
SCMI_XFER_IS_CHAN_SET(xfer) ? chan_id : 0);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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, 0);
|
|
|
|
|
|
|
|
kfree(xfer.rx.buf);
|
|
|
|
}
|