Commit 7860701d authored by Cristian Marussi's avatar Cristian Marussi Committed by Sudeep Holla

firmware: arm_scmi: Add per-channel raw injection support

On a system configured with multiple transport channels, expose a few
additional debugfs per-channel entries to allow a user to explicitly select
which transport channel to use for the SCMI message injection.
Signed-off-by: default avatarCristian Marussi <cristian.marussi@arm.com>
Tested-by: default avatarFlorian Fainelli <f.fainelli@gmail.com>
Tested-by: default avatarVincent Guittot <vincent.guittot@linaro.org>
Link: https://lore.kernel.org/r/20230118121426.492864-18-cristian.marussi@arm.comSigned-off-by: default avatarSudeep Holla <sudeep.holla@arm.com>
parent 9c54633e
......@@ -64,3 +64,46 @@ Description: SCMI Raw stack reset facility; writing a value to this entry
Can be used to reset and clean the SCMI Raw stack between to
different test-run.
Users: Debugging, any userspace test suite
What: /sys/kernel/debug/scmi/<n>/raw/channels/<m>/message
Date: March 2023
KernelVersion: 6.3
Contact: cristian.marussi@arm.com
Description: SCMI Raw synchronous message injection/snooping facility; write
a complete SCMI synchronous command message (header included)
in little-endian binary format to have it sent to the configured
backend SCMI server for instance <n> through the <m> transport
channel.
Any subsequently received response can be read from this same
entry if it arrived on channel <m> within the configured
timeout.
Each write to the entry causes one command request to be built
and sent while the replies are read back one message at time
(receiving an EOF at each message boundary).
Note that these per-channel entries rooted at <..>/channels
exist only if the transport is configured to have more than
one channel.
Users: Debugging, any userspace test suite
What: /sys/kernel/debug/scmi/<n>/raw/channels/<m>/message_async
Date: March 2023
KernelVersion: 6.3
Contact: cristian.marussi@arm.com
Description: SCMI Raw asynchronous message injection/snooping facility; write
a complete SCMI asynchronous command message (header included)
in little-endian binary format to have it sent to the configured
backend SCMI server for instance <n> through the <m> transport
channel.
Any subsequently received response can be read from this same
entry if it arrived on channel <m> within the configured
timeout.
Any additional delayed response received afterwards can be read
from this same entry too if it arrived within the configured
timeout.
Each write to the entry causes one command request to be built
and sent while the replies are read back one message at time
(receiving an EOF at each message boundary).
Note that these per-channel entries rooted at <..>/channels
exist only if the transport is configured to have more than
one channel.
Users: Debugging, any userspace test suite
......@@ -27,6 +27,8 @@
#include "protocols.h"
#include "notify.h"
#define SCMI_MAX_CHANNELS 256
#define SCMI_MAX_RESPONSE_TIMEOUT (2 * MSEC_PER_SEC)
enum scmi_error_codes {
......
......@@ -660,6 +660,7 @@ void scmi_xfer_raw_put(const struct scmi_handle *handle, struct scmi_xfer *xfer)
struct scmi_info *info = handle_to_scmi_info(handle);
xfer->flags &= ~SCMI_XFER_FLAG_IS_RAW;
xfer->flags &= ~SCMI_XFER_FLAG_CHAN_SET;
return __scmi_xfer_put(&info->tx_minfo, xfer);
}
......@@ -896,7 +897,8 @@ static void scmi_handle_notification(struct scmi_chan_info *cinfo,
if (IS_ENABLED(CONFIG_ARM_SCMI_RAW_MODE_SUPPORT)) {
xfer->hdr.seq = MSG_XTRACT_TOKEN(msg_hdr);
scmi_raw_message_report(info->raw, xfer, SCMI_RAW_NOTIF_QUEUE);
scmi_raw_message_report(info->raw, xfer, SCMI_RAW_NOTIF_QUEUE,
cinfo->id);
}
__scmi_xfer_put(minfo, xfer);
......@@ -955,7 +957,9 @@ static void scmi_handle_response(struct scmi_chan_info *cinfo,
* poll loop.
*/
if (!xfer->hdr.poll_completion)
scmi_raw_message_report(info->raw, xfer, SCMI_RAW_REPLY_QUEUE);
scmi_raw_message_report(info->raw, xfer,
SCMI_RAW_REPLY_QUEUE,
cinfo->id);
}
scmi_xfer_command_release(info, xfer);
......@@ -1078,7 +1082,8 @@ static int scmi_wait_for_reply(struct device *dev, const struct scmi_desc *desc,
handle_to_scmi_info(cinfo->handle);
scmi_raw_message_report(info->raw, xfer,
SCMI_RAW_REPLY_QUEUE);
SCMI_RAW_REPLY_QUEUE,
cinfo->id);
}
}
} else {
......@@ -2608,14 +2613,35 @@ static struct scmi_debug_info *scmi_debugfs_common_setup(struct scmi_info *info)
static int scmi_debugfs_raw_mode_setup(struct scmi_info *info)
{
int ret = 0;
int id, num_chans = 0, ret = 0;
struct scmi_chan_info *cinfo;
u8 channels[SCMI_MAX_CHANNELS] = {};
DECLARE_BITMAP(protos, SCMI_MAX_CHANNELS) = {};
if (!info->dbg)
return -EINVAL;
/* Enumerate all channels to collect their ids */
idr_for_each_entry(&info->tx_idr, cinfo, id) {
/*
* Cannot happen, but be defensive.
* Zero as num_chans is ok, warn and carry on.
*/
if (num_chans >= SCMI_MAX_CHANNELS || !cinfo) {
dev_warn(info->dev,
"SCMI RAW - Error enumerating channels\n");
break;
}
if (!test_bit(cinfo->id, protos)) {
channels[num_chans++] = cinfo->id;
set_bit(cinfo->id, protos);
}
}
info->raw = scmi_raw_mode_init(&info->handle, info->dbg->top_dentry,
info->id, info->desc,
info->tx_minfo.max_msg);
info->id, channels, num_chans,
info->desc, info->tx_minfo.max_msg);
if (IS_ERR(info->raw)) {
dev_err(info->dev, "Failed to initialize SCMI RAW Mode !\n");
ret = PTR_ERR(info->raw);
......
......@@ -138,6 +138,9 @@ struct scmi_xfer {
int state;
#define SCMI_XFER_FLAG_IS_RAW BIT(0)
#define SCMI_XFER_IS_RAW(x) ((x)->flags & SCMI_XFER_FLAG_IS_RAW)
#define SCMI_XFER_FLAG_CHAN_SET BIT(1)
#define SCMI_XFER_IS_CHAN_SET(x) \
((x)->flags & SCMI_XFER_FLAG_CHAN_SET)
int flags;
/* A lock to protect state and busy fields */
spinlock_t lock;
......
......@@ -37,10 +37,17 @@
* which in turn is rooted under the corresponding underlying SCMI instance.
*
* /sys/kernel/debug/scmi/
* |-- 0
* `-- 0
* |-- atomic_threshold_us
* |-- instance_name
* |-- raw
* | |-- channels
* | | |-- 0x10
* | | | |-- message
* | | | `-- message_async
* | | `-- 0x13
* | | |-- message
* | | `-- message_async
* | |-- errors
* | |-- message
* | |-- message_async
......@@ -65,6 +72,15 @@
* 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.
......@@ -95,7 +111,6 @@
#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>
......@@ -104,6 +119,7 @@
#include <linux/poll.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/xarray.h>
#include "common.h"
......@@ -140,6 +156,7 @@ struct scmi_raw_queue {
* @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
......@@ -159,6 +176,7 @@ struct scmi_raw_mode_info {
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;
......@@ -208,6 +226,8 @@ struct scmi_raw_buffer {
* 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.
......@@ -216,6 +236,7 @@ struct scmi_raw_buffer {
* @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;
......@@ -224,6 +245,16 @@ struct scmi_dbg_raw_data {
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;
......@@ -563,6 +594,8 @@ static int scmi_xfer_raw_get_init(struct scmi_raw_mode_info *raw, void *buf,
*
* @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
......@@ -576,14 +609,20 @@ static int scmi_xfer_raw_get_init(struct scmi_raw_mode_info *raw, void *buf,
* Return: 0 on Success
*/
static int scmi_do_xfer_raw_start(struct scmi_raw_mode_info *raw,
struct scmi_xfer *xfer, bool async)
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;
cinfo = scmi_xfer_raw_channel_get(raw->handle, xfer->hdr.protocol_id);
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);
......@@ -630,12 +669,13 @@ static int scmi_do_xfer_raw_start(struct scmi_raw_mode_info *raw,
* @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, bool async)
void *buf, size_t len, u8 chan_id, bool async)
{
int ret;
struct scmi_xfer *xfer;
......@@ -644,7 +684,7 @@ static int scmi_raw_message_send(struct scmi_raw_mode_info *raw,
if (ret)
return ret;
ret = scmi_do_xfer_raw_start(raw, xfer, async);
ret = scmi_do_xfer_raw_start(raw, xfer, chan_id, async);
if (ret)
scmi_xfer_raw_put(raw->handle, xfer);
......@@ -687,18 +727,23 @@ scmi_raw_message_dequeue(struct scmi_raw_queue *q, bool o_nonblock)
* @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 idx, unsigned int chan_id,
bool o_nonblock)
{
int ret = 0;
struct scmi_raw_buffer *rb;
struct scmi_raw_queue *q = raw->q[idx];
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)) {
......@@ -732,7 +777,7 @@ static ssize_t scmi_dbg_raw_mode_common_read(struct file *filp,
int ret;
ret = scmi_raw_message_receive(rd->raw, rd->rx.buf, rd->rx.len,
&rd->rx_size, idx,
&rd->rx_size, idx, rd->chan_id,
filp->f_flags & O_NONBLOCK);
if (ret) {
rd->rx_size = 0;
......@@ -782,7 +827,8 @@ static ssize_t scmi_dbg_raw_mode_common_write(struct file *filp,
return cnt;
}
ret = scmi_raw_message_send(rd->raw, rd->tx.buf, rd->tx_size, async);
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;
......@@ -797,9 +843,13 @@ static __poll_t scmi_test_dbg_raw_common_poll(struct file *filp,
{
unsigned long flags;
struct scmi_dbg_raw_data *rd = filp->private_data;
struct scmi_raw_queue *q = rd->raw->q[idx];
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);
......@@ -833,8 +883,10 @@ static __poll_t scmi_dbg_raw_mode_message_poll(struct file *filp,
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;
......@@ -859,6 +911,10 @@ static int scmi_dbg_raw_mode_open(struct inode *inode, struct file *filp)
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;
......@@ -1028,7 +1084,8 @@ static int scmi_xfer_raw_worker_init(struct scmi_raw_mode_info *raw)
return 0;
}
static int scmi_raw_mode_setup(struct scmi_raw_mode_info *raw)
static int scmi_raw_mode_setup(struct scmi_raw_mode_info *raw,
u8 *channels, int num_chans)
{
int ret, idx;
void *gid;
......@@ -1046,15 +1103,43 @@ static int scmi_raw_mode_setup(struct scmi_raw_mode_info *raw)
}
}
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;
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;
......@@ -1067,6 +1152,8 @@ static int scmi_raw_mode_setup(struct scmi_raw_mode_info *raw)
* @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
*
......@@ -1076,6 +1163,7 @@ static int scmi_raw_mode_setup(struct scmi_raw_mode_info *raw)
*/
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;
......@@ -1095,7 +1183,7 @@ void *scmi_raw_mode_init(const struct scmi_handle *handle,
raw->tx_max_msg = tx_max_msg;
raw->id = instance_id;
ret = scmi_raw_mode_setup(raw);
ret = scmi_raw_mode_setup(raw, channels, num_chans);
if (ret) {
devm_kfree(dev, raw);
return ERR_PTR(ret);
......@@ -1118,6 +1206,32 @@ void *scmi_raw_mode_init(const struct scmi_handle *handle,
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;
......@@ -1139,6 +1253,7 @@ void scmi_raw_mode_cleanup(void *r)
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,
......@@ -1178,6 +1293,7 @@ static int scmi_xfer_raw_collect(void *msg, size_t *msg_len,
* @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
......@@ -1187,7 +1303,8 @@ static int scmi_xfer_raw_collect(void *msg, size_t *msg_len,
* 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)
void scmi_raw_message_report(void *r, struct scmi_xfer *xfer,
unsigned int idx, unsigned int chan_id)
{
int ret;
unsigned long flags;
......@@ -1200,7 +1317,8 @@ void scmi_raw_message_report(void *r, struct scmi_xfer *xfer, unsigned int idx)
return;
dev = raw->handle->dev;
q = raw->q[idx];
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
......@@ -1319,7 +1437,7 @@ void scmi_raw_error_report(void *r, struct scmi_chan_info *cinfo,
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);
scmi_raw_message_report(raw, &xfer, SCMI_RAW_ERRS_QUEUE, 0);
kfree(xfer.rx.buf);
}
......@@ -19,11 +19,12 @@ enum {
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);
void scmi_raw_mode_cleanup(void *raw);
void scmi_raw_message_report(void *raw, struct scmi_xfer *xfer,
unsigned int idx);
unsigned int idx, unsigned int chan_id);
void scmi_raw_error_report(void *raw, struct scmi_chan_info *cinfo,
u32 msg_hdr, void *priv);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment