mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-14 01:56:45 +07:00
9724722fde
Few commands provide the list of description partially and require
to be called consecutively until all the descriptors are fetched
completely. In such cases, we don't release the buffers and reuse
them for consecutive transmits.
However, currently we don't reset the Rx size which will be set as
per the response for the last transmit. This may result in incorrect
response size being interpretted as the firmware may repond with size
greater than the one set but we read only upto the size set by previous
response.
Let us reset the receive buffer size to max possible in such cases as
we don't know the exact size of the response.
Link: https://lore.kernel.org/r/20201012141746.32575-1-sudeep.holla@arm.com
Fixes: b6f20ff8bd
("firmware: arm_scmi: add common infrastructure and support for base protocol")
Reported-by: Etienne Carriere <etienne.carriere@linaro.org>
Signed-off-by: Sudeep Holla <sudeep.holla@arm.com>
368 lines
9.0 KiB
C
368 lines
9.0 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* System Control and Management Interface (SCMI) Base Protocol
|
|
*
|
|
* Copyright (C) 2018 ARM Ltd.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "SCMI Notifications BASE - " fmt
|
|
|
|
#include <linux/scmi_protocol.h>
|
|
|
|
#include "common.h"
|
|
#include "notify.h"
|
|
|
|
#define SCMI_BASE_NUM_SOURCES 1
|
|
#define SCMI_BASE_MAX_CMD_ERR_COUNT 1024
|
|
|
|
enum scmi_base_protocol_cmd {
|
|
BASE_DISCOVER_VENDOR = 0x3,
|
|
BASE_DISCOVER_SUB_VENDOR = 0x4,
|
|
BASE_DISCOVER_IMPLEMENT_VERSION = 0x5,
|
|
BASE_DISCOVER_LIST_PROTOCOLS = 0x6,
|
|
BASE_DISCOVER_AGENT = 0x7,
|
|
BASE_NOTIFY_ERRORS = 0x8,
|
|
BASE_SET_DEVICE_PERMISSIONS = 0x9,
|
|
BASE_SET_PROTOCOL_PERMISSIONS = 0xa,
|
|
BASE_RESET_AGENT_CONFIGURATION = 0xb,
|
|
};
|
|
|
|
struct scmi_msg_resp_base_attributes {
|
|
u8 num_protocols;
|
|
u8 num_agents;
|
|
__le16 reserved;
|
|
};
|
|
|
|
struct scmi_msg_base_error_notify {
|
|
__le32 event_control;
|
|
#define BASE_TP_NOTIFY_ALL BIT(0)
|
|
};
|
|
|
|
struct scmi_base_error_notify_payld {
|
|
__le32 agent_id;
|
|
__le32 error_status;
|
|
#define IS_FATAL_ERROR(x) ((x) & BIT(31))
|
|
#define ERROR_CMD_COUNT(x) FIELD_GET(GENMASK(9, 0), (x))
|
|
__le64 msg_reports[SCMI_BASE_MAX_CMD_ERR_COUNT];
|
|
};
|
|
|
|
/**
|
|
* scmi_base_attributes_get() - gets the implementation details
|
|
* that are associated with the base protocol.
|
|
*
|
|
* @handle: SCMI entity handle
|
|
*
|
|
* Return: 0 on success, else appropriate SCMI error.
|
|
*/
|
|
static int scmi_base_attributes_get(const struct scmi_handle *handle)
|
|
{
|
|
int ret;
|
|
struct scmi_xfer *t;
|
|
struct scmi_msg_resp_base_attributes *attr_info;
|
|
struct scmi_revision_info *rev = handle->version;
|
|
|
|
ret = scmi_xfer_get_init(handle, PROTOCOL_ATTRIBUTES,
|
|
SCMI_PROTOCOL_BASE, 0, sizeof(*attr_info), &t);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = scmi_do_xfer(handle, t);
|
|
if (!ret) {
|
|
attr_info = t->rx.buf;
|
|
rev->num_protocols = attr_info->num_protocols;
|
|
rev->num_agents = attr_info->num_agents;
|
|
}
|
|
|
|
scmi_xfer_put(handle, t);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* scmi_base_vendor_id_get() - gets vendor/subvendor identifier ASCII string.
|
|
*
|
|
* @handle: SCMI entity handle
|
|
* @sub_vendor: specify true if sub-vendor ID is needed
|
|
*
|
|
* Return: 0 on success, else appropriate SCMI error.
|
|
*/
|
|
static int
|
|
scmi_base_vendor_id_get(const struct scmi_handle *handle, bool sub_vendor)
|
|
{
|
|
u8 cmd;
|
|
int ret, size;
|
|
char *vendor_id;
|
|
struct scmi_xfer *t;
|
|
struct scmi_revision_info *rev = handle->version;
|
|
|
|
if (sub_vendor) {
|
|
cmd = BASE_DISCOVER_SUB_VENDOR;
|
|
vendor_id = rev->sub_vendor_id;
|
|
size = ARRAY_SIZE(rev->sub_vendor_id);
|
|
} else {
|
|
cmd = BASE_DISCOVER_VENDOR;
|
|
vendor_id = rev->vendor_id;
|
|
size = ARRAY_SIZE(rev->vendor_id);
|
|
}
|
|
|
|
ret = scmi_xfer_get_init(handle, cmd, SCMI_PROTOCOL_BASE, 0, size, &t);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = scmi_do_xfer(handle, t);
|
|
if (!ret)
|
|
memcpy(vendor_id, t->rx.buf, size);
|
|
|
|
scmi_xfer_put(handle, t);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* scmi_base_implementation_version_get() - gets a vendor-specific
|
|
* implementation 32-bit version. The format of the version number is
|
|
* vendor-specific
|
|
*
|
|
* @handle: SCMI entity handle
|
|
*
|
|
* Return: 0 on success, else appropriate SCMI error.
|
|
*/
|
|
static int
|
|
scmi_base_implementation_version_get(const struct scmi_handle *handle)
|
|
{
|
|
int ret;
|
|
__le32 *impl_ver;
|
|
struct scmi_xfer *t;
|
|
struct scmi_revision_info *rev = handle->version;
|
|
|
|
ret = scmi_xfer_get_init(handle, BASE_DISCOVER_IMPLEMENT_VERSION,
|
|
SCMI_PROTOCOL_BASE, 0, sizeof(*impl_ver), &t);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = scmi_do_xfer(handle, t);
|
|
if (!ret) {
|
|
impl_ver = t->rx.buf;
|
|
rev->impl_ver = le32_to_cpu(*impl_ver);
|
|
}
|
|
|
|
scmi_xfer_put(handle, t);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* scmi_base_implementation_list_get() - gets the list of protocols it is
|
|
* OSPM is allowed to access
|
|
*
|
|
* @handle: SCMI entity handle
|
|
* @protocols_imp: pointer to hold the list of protocol identifiers
|
|
*
|
|
* Return: 0 on success, else appropriate SCMI error.
|
|
*/
|
|
static int scmi_base_implementation_list_get(const struct scmi_handle *handle,
|
|
u8 *protocols_imp)
|
|
{
|
|
u8 *list;
|
|
int ret, loop;
|
|
struct scmi_xfer *t;
|
|
__le32 *num_skip, *num_ret;
|
|
u32 tot_num_ret = 0, loop_num_ret;
|
|
struct device *dev = handle->dev;
|
|
|
|
ret = scmi_xfer_get_init(handle, BASE_DISCOVER_LIST_PROTOCOLS,
|
|
SCMI_PROTOCOL_BASE, sizeof(*num_skip), 0, &t);
|
|
if (ret)
|
|
return ret;
|
|
|
|
num_skip = t->tx.buf;
|
|
num_ret = t->rx.buf;
|
|
list = t->rx.buf + sizeof(*num_ret);
|
|
|
|
do {
|
|
/* Set the number of protocols to be skipped/already read */
|
|
*num_skip = cpu_to_le32(tot_num_ret);
|
|
|
|
ret = scmi_do_xfer(handle, t);
|
|
if (ret)
|
|
break;
|
|
|
|
loop_num_ret = le32_to_cpu(*num_ret);
|
|
if (tot_num_ret + loop_num_ret > MAX_PROTOCOLS_IMP) {
|
|
dev_err(dev, "No. of Protocol > MAX_PROTOCOLS_IMP");
|
|
break;
|
|
}
|
|
|
|
for (loop = 0; loop < loop_num_ret; loop++)
|
|
protocols_imp[tot_num_ret + loop] = *(list + loop);
|
|
|
|
tot_num_ret += loop_num_ret;
|
|
|
|
scmi_reset_rx_to_maxsz(handle, t);
|
|
} while (loop_num_ret);
|
|
|
|
scmi_xfer_put(handle, t);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* scmi_base_discover_agent_get() - discover the name of an agent
|
|
*
|
|
* @handle: SCMI entity handle
|
|
* @id: Agent identifier
|
|
* @name: Agent identifier ASCII string
|
|
*
|
|
* An agent id of 0 is reserved to identify the platform itself.
|
|
* Generally operating system is represented as "OSPM"
|
|
*
|
|
* Return: 0 on success, else appropriate SCMI error.
|
|
*/
|
|
static int scmi_base_discover_agent_get(const struct scmi_handle *handle,
|
|
int id, char *name)
|
|
{
|
|
int ret;
|
|
struct scmi_xfer *t;
|
|
|
|
ret = scmi_xfer_get_init(handle, BASE_DISCOVER_AGENT,
|
|
SCMI_PROTOCOL_BASE, sizeof(__le32),
|
|
SCMI_MAX_STR_SIZE, &t);
|
|
if (ret)
|
|
return ret;
|
|
|
|
put_unaligned_le32(id, t->tx.buf);
|
|
|
|
ret = scmi_do_xfer(handle, t);
|
|
if (!ret)
|
|
strlcpy(name, t->rx.buf, SCMI_MAX_STR_SIZE);
|
|
|
|
scmi_xfer_put(handle, t);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int scmi_base_error_notify(const struct scmi_handle *handle, bool enable)
|
|
{
|
|
int ret;
|
|
u32 evt_cntl = enable ? BASE_TP_NOTIFY_ALL : 0;
|
|
struct scmi_xfer *t;
|
|
struct scmi_msg_base_error_notify *cfg;
|
|
|
|
ret = scmi_xfer_get_init(handle, BASE_NOTIFY_ERRORS,
|
|
SCMI_PROTOCOL_BASE, sizeof(*cfg), 0, &t);
|
|
if (ret)
|
|
return ret;
|
|
|
|
cfg = t->tx.buf;
|
|
cfg->event_control = cpu_to_le32(evt_cntl);
|
|
|
|
ret = scmi_do_xfer(handle, t);
|
|
|
|
scmi_xfer_put(handle, t);
|
|
return ret;
|
|
}
|
|
|
|
static int scmi_base_set_notify_enabled(const struct scmi_handle *handle,
|
|
u8 evt_id, u32 src_id, bool enable)
|
|
{
|
|
int ret;
|
|
|
|
ret = scmi_base_error_notify(handle, enable);
|
|
if (ret)
|
|
pr_debug("FAIL_ENABLED - evt[%X] ret:%d\n", evt_id, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void *scmi_base_fill_custom_report(const struct scmi_handle *handle,
|
|
u8 evt_id, ktime_t timestamp,
|
|
const void *payld, size_t payld_sz,
|
|
void *report, u32 *src_id)
|
|
{
|
|
int i;
|
|
const struct scmi_base_error_notify_payld *p = payld;
|
|
struct scmi_base_error_report *r = report;
|
|
|
|
/*
|
|
* BaseError notification payload is variable in size but
|
|
* up to a maximum length determined by the struct ponted by p.
|
|
* Instead payld_sz is the effective length of this notification
|
|
* payload so cannot be greater of the maximum allowed size as
|
|
* pointed by p.
|
|
*/
|
|
if (evt_id != SCMI_EVENT_BASE_ERROR_EVENT || sizeof(*p) < payld_sz)
|
|
return NULL;
|
|
|
|
r->timestamp = timestamp;
|
|
r->agent_id = le32_to_cpu(p->agent_id);
|
|
r->fatal = IS_FATAL_ERROR(le32_to_cpu(p->error_status));
|
|
r->cmd_count = ERROR_CMD_COUNT(le32_to_cpu(p->error_status));
|
|
for (i = 0; i < r->cmd_count; i++)
|
|
r->reports[i] = le64_to_cpu(p->msg_reports[i]);
|
|
*src_id = 0;
|
|
|
|
return r;
|
|
}
|
|
|
|
static const struct scmi_event base_events[] = {
|
|
{
|
|
.id = SCMI_EVENT_BASE_ERROR_EVENT,
|
|
.max_payld_sz = sizeof(struct scmi_base_error_notify_payld),
|
|
.max_report_sz = sizeof(struct scmi_base_error_report) +
|
|
SCMI_BASE_MAX_CMD_ERR_COUNT * sizeof(u64),
|
|
},
|
|
};
|
|
|
|
static const struct scmi_event_ops base_event_ops = {
|
|
.set_notify_enabled = scmi_base_set_notify_enabled,
|
|
.fill_custom_report = scmi_base_fill_custom_report,
|
|
};
|
|
|
|
int scmi_base_protocol_init(struct scmi_handle *h)
|
|
{
|
|
int id, ret;
|
|
u8 *prot_imp;
|
|
u32 version;
|
|
char name[SCMI_MAX_STR_SIZE];
|
|
const struct scmi_handle *handle = h;
|
|
struct device *dev = handle->dev;
|
|
struct scmi_revision_info *rev = handle->version;
|
|
|
|
ret = scmi_version_get(handle, SCMI_PROTOCOL_BASE, &version);
|
|
if (ret)
|
|
return ret;
|
|
|
|
prot_imp = devm_kcalloc(dev, MAX_PROTOCOLS_IMP, sizeof(u8), GFP_KERNEL);
|
|
if (!prot_imp)
|
|
return -ENOMEM;
|
|
|
|
rev->major_ver = PROTOCOL_REV_MAJOR(version),
|
|
rev->minor_ver = PROTOCOL_REV_MINOR(version);
|
|
|
|
scmi_base_attributes_get(handle);
|
|
scmi_base_vendor_id_get(handle, false);
|
|
scmi_base_vendor_id_get(handle, true);
|
|
scmi_base_implementation_version_get(handle);
|
|
scmi_base_implementation_list_get(handle, prot_imp);
|
|
scmi_setup_protocol_implemented(handle, prot_imp);
|
|
|
|
dev_info(dev, "SCMI Protocol v%d.%d '%s:%s' Firmware version 0x%x\n",
|
|
rev->major_ver, rev->minor_ver, rev->vendor_id,
|
|
rev->sub_vendor_id, rev->impl_ver);
|
|
dev_dbg(dev, "Found %d protocol(s) %d agent(s)\n", rev->num_protocols,
|
|
rev->num_agents);
|
|
|
|
scmi_register_protocol_events(handle, SCMI_PROTOCOL_BASE,
|
|
(4 * SCMI_PROTO_QUEUE_SZ),
|
|
&base_event_ops, base_events,
|
|
ARRAY_SIZE(base_events),
|
|
SCMI_BASE_NUM_SOURCES);
|
|
|
|
for (id = 0; id < rev->num_agents; id++) {
|
|
scmi_base_discover_agent_get(handle, id, name);
|
|
dev_dbg(dev, "Agent %d: %s\n", id, name);
|
|
}
|
|
|
|
return 0;
|
|
}
|