linux-stable/drivers/firmware/arm_scmi/system.c
Cristian Marussi b5efc28a75 firmware: arm_scmi: Add protocol versioning checks
Platform and agent supported protocols versions do not necessarily match.

When talking to an older SCMI platform, supporting only older protocol
versions, the kernel SCMI agent will downgrade the version of the used
protocol to match the platform and avoid compatibility issues.

In the case where the kernel/OSPM agent happens to communicate with a
newer platform which can support newer protocol versions unknown to
the agent, and potentially backward incompatible, the agent currently
carries on, silently, in a best-effort approach.

Note that the SCMI specification doesn't provide means to explicitly
detect the protocol versions used by the agents, neither it is required
to support multiple, older, protocol versions.

Add an explicit protocol version check to let the agent detect when this
version mismatch happens and warn the user about this condition.

Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
Link: https://lore.kernel.org/r/20231201135858.2367651-1-cristian.marussi@arm.com
Signed-off-by: Sudeep Holla <sudeep.holla@arm.com>
2023-12-01 16:46:10 +00:00

162 lines
4 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* System Control and Management Interface (SCMI) System Power Protocol
*
* Copyright (C) 2020-2022 ARM Ltd.
*/
#define pr_fmt(fmt) "SCMI Notifications SYSTEM - " fmt
#include <linux/module.h>
#include <linux/scmi_protocol.h>
#include "protocols.h"
#include "notify.h"
/* Updated only after ALL the mandatory features for that version are merged */
#define SCMI_PROTOCOL_SUPPORTED_VERSION 0x20000
#define SCMI_SYSTEM_NUM_SOURCES 1
enum scmi_system_protocol_cmd {
SYSTEM_POWER_STATE_NOTIFY = 0x5,
};
struct scmi_system_power_state_notify {
__le32 notify_enable;
};
struct scmi_system_power_state_notifier_payld {
__le32 agent_id;
__le32 flags;
__le32 system_state;
__le32 timeout;
};
struct scmi_system_info {
u32 version;
bool graceful_timeout_supported;
};
static int scmi_system_request_notify(const struct scmi_protocol_handle *ph,
bool enable)
{
int ret;
struct scmi_xfer *t;
struct scmi_system_power_state_notify *notify;
ret = ph->xops->xfer_get_init(ph, SYSTEM_POWER_STATE_NOTIFY,
sizeof(*notify), 0, &t);
if (ret)
return ret;
notify = t->tx.buf;
notify->notify_enable = enable ? cpu_to_le32(BIT(0)) : 0;
ret = ph->xops->do_xfer(ph, t);
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_system_set_notify_enabled(const struct scmi_protocol_handle *ph,
u8 evt_id, u32 src_id, bool enable)
{
int ret;
ret = scmi_system_request_notify(ph, enable);
if (ret)
pr_debug("FAIL_ENABLE - evt[%X] - ret:%d\n", evt_id, ret);
return ret;
}
static void *
scmi_system_fill_custom_report(const struct scmi_protocol_handle *ph,
u8 evt_id, ktime_t timestamp,
const void *payld, size_t payld_sz,
void *report, u32 *src_id)
{
size_t expected_sz;
const struct scmi_system_power_state_notifier_payld *p = payld;
struct scmi_system_power_state_notifier_report *r = report;
struct scmi_system_info *pinfo = ph->get_priv(ph);
expected_sz = pinfo->graceful_timeout_supported ?
sizeof(*p) : sizeof(*p) - sizeof(__le32);
if (evt_id != SCMI_EVENT_SYSTEM_POWER_STATE_NOTIFIER ||
payld_sz != expected_sz)
return NULL;
r->timestamp = timestamp;
r->agent_id = le32_to_cpu(p->agent_id);
r->flags = le32_to_cpu(p->flags);
r->system_state = le32_to_cpu(p->system_state);
if (pinfo->graceful_timeout_supported &&
r->system_state == SCMI_SYSTEM_SHUTDOWN &&
SCMI_SYSPOWER_IS_REQUEST_GRACEFUL(r->flags))
r->timeout = le32_to_cpu(p->timeout);
else
r->timeout = 0x00;
*src_id = 0;
return r;
}
static const struct scmi_event system_events[] = {
{
.id = SCMI_EVENT_SYSTEM_POWER_STATE_NOTIFIER,
.max_payld_sz =
sizeof(struct scmi_system_power_state_notifier_payld),
.max_report_sz =
sizeof(struct scmi_system_power_state_notifier_report),
},
};
static const struct scmi_event_ops system_event_ops = {
.set_notify_enabled = scmi_system_set_notify_enabled,
.fill_custom_report = scmi_system_fill_custom_report,
};
static const struct scmi_protocol_events system_protocol_events = {
.queue_sz = SCMI_PROTO_QUEUE_SZ,
.ops = &system_event_ops,
.evts = system_events,
.num_events = ARRAY_SIZE(system_events),
.num_sources = SCMI_SYSTEM_NUM_SOURCES,
};
static int scmi_system_protocol_init(const struct scmi_protocol_handle *ph)
{
int ret;
u32 version;
struct scmi_system_info *pinfo;
ret = ph->xops->version_get(ph, &version);
if (ret)
return ret;
dev_dbg(ph->dev, "System Power Version %d.%d\n",
PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version));
pinfo = devm_kzalloc(ph->dev, sizeof(*pinfo), GFP_KERNEL);
if (!pinfo)
return -ENOMEM;
pinfo->version = version;
if (PROTOCOL_REV_MAJOR(pinfo->version) >= 0x2)
pinfo->graceful_timeout_supported = true;
return ph->set_priv(ph, pinfo, version);
}
static const struct scmi_protocol scmi_system = {
.id = SCMI_PROTOCOL_SYSTEM,
.owner = THIS_MODULE,
.instance_init = &scmi_system_protocol_init,
.ops = NULL,
.events = &system_protocol_events,
.supported_version = SCMI_PROTOCOL_SUPPORTED_VERSION,
};
DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(system, scmi_system)