mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-10-26 10:57:38 +08:00
The Arm MPAM-Fb specification [1] describes a protocol to access MSC registers through a firmware interface. This requires a shared memory region to hold the message, and a mailbox to trigger the access. For ACPI this is wrapped as a PCC channel, described using existing ACPI abstractions. Add code to parse those PCC table descriptions associated with an MSC, and store the parsed information in the MSC struct. There can be multiple PCC channels, and each channel can serve multiple MSCs, so we need to keep track of the channel usage, using a list and a refcount. This will be used by the MPAM-Fb access wrapper code. [1] https://developer.arm.com/documentation/den0144/latest Signed-off-by: Andre Przywara <andre.przywara@arm.com> Tested-by: Ritwick Sharma <ritwick.sharma@arm.com> # on Arm AGI CPU Reviewed-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com> Tested-by: Srivathsa L Rao <srivathsa.rao@oss.qualcomm.com> Reviewed-by: Srivathsa L Rao <srivathsa.rao@oss.qualcomm.com> Reviewed-by: Gavin Shan <gshan@redhat.com> Reviewed-by: Ben Horgan <ben.horgan@arm.com> Signed-off-by: James Morse <james.morse@arm.com>
259 lines
6.9 KiB
C
259 lines
6.9 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
// Copyright (C) 2024-2026 Arm Ltd.
|
|
|
|
#include <linux/arm_mpam.h>
|
|
#include <linux/cleanup.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/mailbox_client.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/types.h>
|
|
|
|
#include <acpi/pcc.h>
|
|
#include <asm/mpam.h>
|
|
|
|
#include "mpam_internal.h"
|
|
|
|
#define MPAM_MSC_TOKEN_MASK GENMASK_U32(27, 18)
|
|
#define MPAM_MSC_PROT_ID_MASK GENMASK_U32(17, 10)
|
|
|
|
#define MPAM_FB_PROTOCOL_ID 0x1a
|
|
|
|
#define MPAM_PROTOCOL_VERSION_CMD 0x0
|
|
#define MPAM_FB_VERSION_MAJOR_MASK GENMASK_U32(31, 16)
|
|
#define MPAM_FB_VERSION_MINOR_MASK GENMASK_U32(15, 0)
|
|
|
|
#define MPAM_MSC_READ_CMD 0x4
|
|
#define MPAM_MSC_WRITE_CMD 0x5
|
|
|
|
#define MPAM_FB_PROT_HEADER_LEN sizeof(u32)
|
|
/* The longest message is MPAM_MSC_WRITE, with 4 parameters. */
|
|
#define MPAM_FB_MAX_MSG_SIZE (4 * sizeof(u32))
|
|
|
|
#define MPAM_FB_SUCCESS 0
|
|
#define MPAM_FB_ERR_NOT_SUPPORTED -1
|
|
#define MPAM_FB_ERR_INVALID_PARAMETERS -2
|
|
#define MPAM_FB_ERR_DENIED -3
|
|
#define MPAM_FB_ERR_NOT_FOUND -4
|
|
#define MPAM_FB_ERR_OUT_OF_RANGE -5
|
|
#define MPAM_FB_ERR_BUSY -6
|
|
#define MPAM_FB_ERR_COMMS_ERROR -7
|
|
#define MPAM_FB_ERR_GENERIC_ERROR -8
|
|
#define MPAM_FB_ERR_HW_ERROR -9
|
|
#define MPAM_FB_ERR_PROTOCOL_ERROR -10
|
|
#define MPAM_FB_ERR_IN_USE -11
|
|
|
|
static atomic_t mpam_fb_token = ATOMIC_INIT(0);
|
|
|
|
static int mpam_fb_to_linux_errno(int mpam_fb_code)
|
|
{
|
|
switch (mpam_fb_code) {
|
|
case MPAM_FB_ERR_NOT_SUPPORTED:
|
|
return -EOPNOTSUPP;
|
|
case MPAM_FB_ERR_INVALID_PARAMETERS:
|
|
return -EINVAL;
|
|
case MPAM_FB_ERR_NOT_FOUND:
|
|
return -ENOENT;
|
|
case MPAM_FB_ERR_OUT_OF_RANGE:
|
|
return -ERANGE;
|
|
case MPAM_FB_ERR_BUSY:
|
|
return -EBUSY;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static void mpam_fb_build_version_message(unsigned int token,
|
|
void __iomem *msg_buf)
|
|
{
|
|
struct acpi_pcct_ext_pcc_shared_memory __iomem *pcc_shmem = msg_buf;
|
|
|
|
/* .signature is filled by the platform */
|
|
writel_relaxed(PCC_CMD_COMPLETION_NOTIFY, &pcc_shmem->flags);
|
|
writel_relaxed(MPAM_FB_PROT_HEADER_LEN, &pcc_shmem->length);
|
|
writel_relaxed(MPAM_PROTOCOL_VERSION_CMD |
|
|
FIELD_PREP(MPAM_MSC_TOKEN_MASK, token) |
|
|
FIELD_PREP(MPAM_MSC_PROT_ID_MASK, MPAM_FB_PROTOCOL_ID),
|
|
&pcc_shmem->command);
|
|
}
|
|
|
|
static void mpam_fb_build_read_message(int msc_fb_id, int reg,
|
|
unsigned int token,
|
|
void __iomem *msg_buf)
|
|
{
|
|
struct acpi_pcct_ext_pcc_shared_memory __iomem *pcc_shmem = msg_buf;
|
|
struct mpam_fb_read_payload {
|
|
u32 msc_fb_id;
|
|
u32 flags;
|
|
u32 reg_offset;
|
|
} __packed __iomem *payload = msg_buf + sizeof(*pcc_shmem);
|
|
int msg_size = MPAM_FB_PROT_HEADER_LEN + sizeof(*payload);
|
|
|
|
/* .signature is filled by the platform */
|
|
writel_relaxed(PCC_CMD_COMPLETION_NOTIFY, &pcc_shmem->flags);
|
|
writel_relaxed(msg_size, &pcc_shmem->length);
|
|
writel_relaxed(MPAM_MSC_READ_CMD |
|
|
FIELD_PREP(MPAM_MSC_TOKEN_MASK, token) |
|
|
FIELD_PREP(MPAM_MSC_PROT_ID_MASK, MPAM_FB_PROTOCOL_ID),
|
|
&pcc_shmem->command);
|
|
|
|
writel_relaxed(msc_fb_id, &payload->msc_fb_id);
|
|
writel_relaxed(0, &payload->flags);
|
|
writel_relaxed(reg, &payload->reg_offset);
|
|
}
|
|
|
|
static void mpam_fb_build_write_message(int msc_fb_id, int reg, u32 val,
|
|
unsigned int token,
|
|
void __iomem *msg_buf)
|
|
{
|
|
struct acpi_pcct_ext_pcc_shared_memory __iomem *pcc_shmem = msg_buf;
|
|
struct mpam_fb_write_payload {
|
|
u32 msc_fb_id;
|
|
u32 flags;
|
|
u32 reg_offset;
|
|
u32 value;
|
|
} __packed __iomem *payload = msg_buf + sizeof(*pcc_shmem);
|
|
int msg_size = MPAM_FB_PROT_HEADER_LEN + sizeof(*payload);
|
|
|
|
/* .signature is filled by the platform */
|
|
writel_relaxed(PCC_CMD_COMPLETION_NOTIFY, &pcc_shmem->flags);
|
|
writel_relaxed(msg_size, &pcc_shmem->length);
|
|
writel_relaxed(MPAM_MSC_WRITE_CMD |
|
|
FIELD_PREP(MPAM_MSC_TOKEN_MASK, token) |
|
|
FIELD_PREP(MPAM_MSC_PROT_ID_MASK, MPAM_FB_PROTOCOL_ID),
|
|
&pcc_shmem->command);
|
|
|
|
writel_relaxed(msc_fb_id, &payload->msc_fb_id);
|
|
writel_relaxed(0, &payload->flags);
|
|
writel_relaxed(reg, &payload->reg_offset);
|
|
writel_relaxed(val, &payload->value);
|
|
}
|
|
|
|
static int mpam_fb_send_request(struct mpam_msc *msc, u32 msc_fb_id,
|
|
u16 reg, u32 *result, int mpam_fb_command)
|
|
{
|
|
unsigned int token = atomic_inc_return(&mpam_fb_token);
|
|
struct acpi_pcct_ext_pcc_shared_memory __iomem *pcc_shmem;
|
|
struct mpam_pcc_chan *pcc_chan;
|
|
struct pcc_mbox_chan *chan;
|
|
void __iomem *payload_ofs;
|
|
int mpam_fb_err = 0;
|
|
u32 status;
|
|
int ret;
|
|
|
|
pcc_chan = msc->pcc_chan;
|
|
if (!pcc_chan)
|
|
return -ENODEV;
|
|
|
|
chan = pcc_chan->pcc_chan;
|
|
|
|
/* prune token to fit into the 10 bits inside the command register */
|
|
token = FIELD_GET(MPAM_MSC_TOKEN_MASK,
|
|
FIELD_PREP(MPAM_MSC_TOKEN_MASK, token));
|
|
|
|
mutex_lock(&pcc_chan->pcc_chan_lock);
|
|
|
|
switch (mpam_fb_command) {
|
|
case MPAM_PROTOCOL_VERSION_CMD:
|
|
mpam_fb_build_version_message(token, chan->shmem);
|
|
break;
|
|
case MPAM_MSC_READ_CMD:
|
|
mpam_fb_build_read_message(msc_fb_id, reg, token, chan->shmem);
|
|
break;
|
|
case MPAM_MSC_WRITE_CMD:
|
|
mpam_fb_build_write_message(msc_fb_id, reg, *result,
|
|
token, chan->shmem);
|
|
break;
|
|
default:
|
|
dev_err(&msc->pdev->dev, "unsupported MPAM-Fb command %d\n",
|
|
mpam_fb_command);
|
|
ret = -EINVAL;
|
|
goto out_err;
|
|
}
|
|
|
|
dma_wmb();
|
|
ret = mbox_send_message(chan->mchan, NULL);
|
|
if (ret < 0)
|
|
goto out_err;
|
|
|
|
pcc_shmem = chan->shmem;
|
|
payload_ofs = chan->shmem + sizeof(*pcc_shmem);
|
|
status = readl(&pcc_shmem->command);
|
|
if (FIELD_GET(MPAM_MSC_TOKEN_MASK, status) != token) {
|
|
ret = -ETIMEDOUT;
|
|
goto out_err;
|
|
}
|
|
|
|
mpam_fb_err = readl(payload_ofs + 0x0);
|
|
if (mpam_fb_err < 0) {
|
|
ret = mpam_fb_to_linux_errno(mpam_fb_err);
|
|
goto out_err;
|
|
}
|
|
|
|
if (mpam_fb_command != MPAM_MSC_WRITE_CMD)
|
|
*result = readl(payload_ofs + 0x4);
|
|
|
|
mutex_unlock(&pcc_chan->pcc_chan_lock);
|
|
|
|
return 0;
|
|
|
|
out_err:
|
|
mutex_unlock(&pcc_chan->pcc_chan_lock);
|
|
|
|
mpam_fb_disable_mpam(ret, mpam_fb_err);
|
|
|
|
/*
|
|
* TODO: Error codes are filtered in higher layers, return something
|
|
* that is known there for now, to actually report them.
|
|
*/
|
|
return -EIO;
|
|
}
|
|
|
|
int mpam_fb_send_read_request(struct mpam_msc *msc, u16 reg, u32 *result)
|
|
{
|
|
return mpam_fb_send_request(msc, msc->fb_id, reg, result,
|
|
MPAM_MSC_READ_CMD);
|
|
}
|
|
|
|
int mpam_fb_send_write_request(struct mpam_msc *msc, u16 reg, u32 value)
|
|
{
|
|
return mpam_fb_send_request(msc, msc->fb_id, reg, &value,
|
|
MPAM_MSC_WRITE_CMD);
|
|
}
|
|
|
|
/* We only support MPAM-Fb protocol version 1.x */
|
|
int mpam_fb_check_protocol_version(struct mpam_msc *msc)
|
|
{
|
|
u32 version;
|
|
int ret;
|
|
|
|
ret = mpam_fb_send_request(msc, 0, 0, &version,
|
|
MPAM_PROTOCOL_VERSION_CMD);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (FIELD_GET(MPAM_FB_VERSION_MAJOR_MASK, version) != 1) {
|
|
pr_err("Incompatible MPAM-Fb protocol version %d.%d\n",
|
|
FIELD_GET(MPAM_FB_VERSION_MAJOR_MASK, version),
|
|
FIELD_GET(MPAM_FB_VERSION_MINOR_MASK, version));
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int mpam_fb_check_shared_buffer_size(struct mpam_msc *msc)
|
|
{
|
|
int min_buffer_size = MPAM_FB_MAX_MSG_SIZE +
|
|
sizeof(struct acpi_pcct_ext_pcc_shared_memory);
|
|
|
|
if (msc->pcc_chan->pcc_chan->shmem_size < min_buffer_size) {
|
|
pr_err("MPAM-Fb PCC channel size too small.\n");
|
|
|
|
return -ENOMEM;
|
|
}
|
|
|
|
return 0;
|
|
}
|