10ef7c5ebSAndre Przywara // SPDX-License-Identifier: GPL-2.0
20ef7c5ebSAndre Przywara // Copyright (C) 2024-2026 Arm Ltd.
30ef7c5ebSAndre Przywara
40ef7c5ebSAndre Przywara #include <linux/arm_mpam.h>
50ef7c5ebSAndre Przywara #include <linux/cleanup.h>
60ef7c5ebSAndre Przywara #include <linux/errno.h>
70ef7c5ebSAndre Przywara #include <linux/mailbox_client.h>
80ef7c5ebSAndre Przywara #include <linux/mutex.h>
90ef7c5ebSAndre Przywara #include <linux/platform_device.h>
100ef7c5ebSAndre Przywara #include <linux/types.h>
110ef7c5ebSAndre Przywara
120ef7c5ebSAndre Przywara #include <acpi/pcc.h>
130ef7c5ebSAndre Przywara #include <asm/mpam.h>
140ef7c5ebSAndre Przywara
150ef7c5ebSAndre Przywara #include "mpam_internal.h"
160ef7c5ebSAndre Przywara
170ef7c5ebSAndre Przywara #define MPAM_MSC_TOKEN_MASK GENMASK_U32(27, 18)
180ef7c5ebSAndre Przywara #define MPAM_MSC_PROT_ID_MASK GENMASK_U32(17, 10)
190ef7c5ebSAndre Przywara
200ef7c5ebSAndre Przywara #define MPAM_FB_PROTOCOL_ID 0x1a
210ef7c5ebSAndre Przywara
220ef7c5ebSAndre Przywara #define MPAM_PROTOCOL_VERSION_CMD 0x0
23*cb351e3fSAndre Przywara #define MPAM_FB_VERSION_MAJOR_MASK GENMASK_U32(31, 16)
24*cb351e3fSAndre Przywara #define MPAM_FB_VERSION_MINOR_MASK GENMASK_U32(15, 0)
25*cb351e3fSAndre Przywara
260ef7c5ebSAndre Przywara #define MPAM_MSC_READ_CMD 0x4
270ef7c5ebSAndre Przywara #define MPAM_MSC_WRITE_CMD 0x5
280ef7c5ebSAndre Przywara
290ef7c5ebSAndre Przywara #define MPAM_FB_PROT_HEADER_LEN sizeof(u32)
30*cb351e3fSAndre Przywara /* The longest message is MPAM_MSC_WRITE, with 4 parameters. */
31*cb351e3fSAndre Przywara #define MPAM_FB_MAX_MSG_SIZE (4 * sizeof(u32))
320ef7c5ebSAndre Przywara
330ef7c5ebSAndre Przywara #define MPAM_FB_SUCCESS 0
340ef7c5ebSAndre Przywara #define MPAM_FB_ERR_NOT_SUPPORTED -1
350ef7c5ebSAndre Przywara #define MPAM_FB_ERR_INVALID_PARAMETERS -2
360ef7c5ebSAndre Przywara #define MPAM_FB_ERR_DENIED -3
370ef7c5ebSAndre Przywara #define MPAM_FB_ERR_NOT_FOUND -4
380ef7c5ebSAndre Przywara #define MPAM_FB_ERR_OUT_OF_RANGE -5
390ef7c5ebSAndre Przywara #define MPAM_FB_ERR_BUSY -6
400ef7c5ebSAndre Przywara #define MPAM_FB_ERR_COMMS_ERROR -7
410ef7c5ebSAndre Przywara #define MPAM_FB_ERR_GENERIC_ERROR -8
420ef7c5ebSAndre Przywara #define MPAM_FB_ERR_HW_ERROR -9
430ef7c5ebSAndre Przywara #define MPAM_FB_ERR_PROTOCOL_ERROR -10
440ef7c5ebSAndre Przywara #define MPAM_FB_ERR_IN_USE -11
450ef7c5ebSAndre Przywara
460ef7c5ebSAndre Przywara static atomic_t mpam_fb_token = ATOMIC_INIT(0);
470ef7c5ebSAndre Przywara
mpam_fb_to_linux_errno(int mpam_fb_code)480ef7c5ebSAndre Przywara static int mpam_fb_to_linux_errno(int mpam_fb_code)
490ef7c5ebSAndre Przywara {
500ef7c5ebSAndre Przywara switch (mpam_fb_code) {
510ef7c5ebSAndre Przywara case MPAM_FB_ERR_NOT_SUPPORTED:
520ef7c5ebSAndre Przywara return -EOPNOTSUPP;
530ef7c5ebSAndre Przywara case MPAM_FB_ERR_INVALID_PARAMETERS:
540ef7c5ebSAndre Przywara return -EINVAL;
550ef7c5ebSAndre Przywara case MPAM_FB_ERR_NOT_FOUND:
560ef7c5ebSAndre Przywara return -ENOENT;
570ef7c5ebSAndre Przywara case MPAM_FB_ERR_OUT_OF_RANGE:
580ef7c5ebSAndre Przywara return -ERANGE;
590ef7c5ebSAndre Przywara case MPAM_FB_ERR_BUSY:
600ef7c5ebSAndre Przywara return -EBUSY;
610ef7c5ebSAndre Przywara default:
620ef7c5ebSAndre Przywara return -EINVAL;
630ef7c5ebSAndre Przywara }
640ef7c5ebSAndre Przywara }
650ef7c5ebSAndre Przywara
mpam_fb_build_version_message(unsigned int token,void __iomem * msg_buf)660ef7c5ebSAndre Przywara static void mpam_fb_build_version_message(unsigned int token,
670ef7c5ebSAndre Przywara void __iomem *msg_buf)
680ef7c5ebSAndre Przywara {
690ef7c5ebSAndre Przywara struct acpi_pcct_ext_pcc_shared_memory __iomem *pcc_shmem = msg_buf;
700ef7c5ebSAndre Przywara
710ef7c5ebSAndre Przywara /* .signature is filled by the platform */
720ef7c5ebSAndre Przywara writel_relaxed(PCC_CMD_COMPLETION_NOTIFY, &pcc_shmem->flags);
730ef7c5ebSAndre Przywara writel_relaxed(MPAM_FB_PROT_HEADER_LEN, &pcc_shmem->length);
740ef7c5ebSAndre Przywara writel_relaxed(MPAM_PROTOCOL_VERSION_CMD |
750ef7c5ebSAndre Przywara FIELD_PREP(MPAM_MSC_TOKEN_MASK, token) |
760ef7c5ebSAndre Przywara FIELD_PREP(MPAM_MSC_PROT_ID_MASK, MPAM_FB_PROTOCOL_ID),
770ef7c5ebSAndre Przywara &pcc_shmem->command);
780ef7c5ebSAndre Przywara }
790ef7c5ebSAndre Przywara
mpam_fb_build_read_message(int msc_fb_id,int reg,unsigned int token,void __iomem * msg_buf)800ef7c5ebSAndre Przywara static void mpam_fb_build_read_message(int msc_fb_id, int reg,
810ef7c5ebSAndre Przywara unsigned int token,
820ef7c5ebSAndre Przywara void __iomem *msg_buf)
830ef7c5ebSAndre Przywara {
840ef7c5ebSAndre Przywara struct acpi_pcct_ext_pcc_shared_memory __iomem *pcc_shmem = msg_buf;
850ef7c5ebSAndre Przywara struct mpam_fb_read_payload {
860ef7c5ebSAndre Przywara u32 msc_fb_id;
870ef7c5ebSAndre Przywara u32 flags;
880ef7c5ebSAndre Przywara u32 reg_offset;
890ef7c5ebSAndre Przywara } __packed __iomem *payload = msg_buf + sizeof(*pcc_shmem);
900ef7c5ebSAndre Przywara int msg_size = MPAM_FB_PROT_HEADER_LEN + sizeof(*payload);
910ef7c5ebSAndre Przywara
920ef7c5ebSAndre Przywara /* .signature is filled by the platform */
930ef7c5ebSAndre Przywara writel_relaxed(PCC_CMD_COMPLETION_NOTIFY, &pcc_shmem->flags);
940ef7c5ebSAndre Przywara writel_relaxed(msg_size, &pcc_shmem->length);
950ef7c5ebSAndre Przywara writel_relaxed(MPAM_MSC_READ_CMD |
960ef7c5ebSAndre Przywara FIELD_PREP(MPAM_MSC_TOKEN_MASK, token) |
970ef7c5ebSAndre Przywara FIELD_PREP(MPAM_MSC_PROT_ID_MASK, MPAM_FB_PROTOCOL_ID),
980ef7c5ebSAndre Przywara &pcc_shmem->command);
990ef7c5ebSAndre Przywara
1000ef7c5ebSAndre Przywara writel_relaxed(msc_fb_id, &payload->msc_fb_id);
1010ef7c5ebSAndre Przywara writel_relaxed(0, &payload->flags);
1020ef7c5ebSAndre Przywara writel_relaxed(reg, &payload->reg_offset);
1030ef7c5ebSAndre Przywara }
1040ef7c5ebSAndre Przywara
mpam_fb_build_write_message(int msc_fb_id,int reg,u32 val,unsigned int token,void __iomem * msg_buf)1050ef7c5ebSAndre Przywara static void mpam_fb_build_write_message(int msc_fb_id, int reg, u32 val,
1060ef7c5ebSAndre Przywara unsigned int token,
1070ef7c5ebSAndre Przywara void __iomem *msg_buf)
1080ef7c5ebSAndre Przywara {
1090ef7c5ebSAndre Przywara struct acpi_pcct_ext_pcc_shared_memory __iomem *pcc_shmem = msg_buf;
1100ef7c5ebSAndre Przywara struct mpam_fb_write_payload {
1110ef7c5ebSAndre Przywara u32 msc_fb_id;
1120ef7c5ebSAndre Przywara u32 flags;
1130ef7c5ebSAndre Przywara u32 reg_offset;
1140ef7c5ebSAndre Przywara u32 value;
1150ef7c5ebSAndre Przywara } __packed __iomem *payload = msg_buf + sizeof(*pcc_shmem);
1160ef7c5ebSAndre Przywara int msg_size = MPAM_FB_PROT_HEADER_LEN + sizeof(*payload);
1170ef7c5ebSAndre Przywara
1180ef7c5ebSAndre Przywara /* .signature is filled by the platform */
1190ef7c5ebSAndre Przywara writel_relaxed(PCC_CMD_COMPLETION_NOTIFY, &pcc_shmem->flags);
1200ef7c5ebSAndre Przywara writel_relaxed(msg_size, &pcc_shmem->length);
1210ef7c5ebSAndre Przywara writel_relaxed(MPAM_MSC_WRITE_CMD |
1220ef7c5ebSAndre Przywara FIELD_PREP(MPAM_MSC_TOKEN_MASK, token) |
1230ef7c5ebSAndre Przywara FIELD_PREP(MPAM_MSC_PROT_ID_MASK, MPAM_FB_PROTOCOL_ID),
1240ef7c5ebSAndre Przywara &pcc_shmem->command);
1250ef7c5ebSAndre Przywara
1260ef7c5ebSAndre Przywara writel_relaxed(msc_fb_id, &payload->msc_fb_id);
1270ef7c5ebSAndre Przywara writel_relaxed(0, &payload->flags);
1280ef7c5ebSAndre Przywara writel_relaxed(reg, &payload->reg_offset);
1290ef7c5ebSAndre Przywara writel_relaxed(val, &payload->value);
1300ef7c5ebSAndre Przywara }
1310ef7c5ebSAndre Przywara
mpam_fb_send_request(struct mpam_msc * msc,u32 msc_fb_id,u16 reg,u32 * result,int mpam_fb_command)1320ef7c5ebSAndre Przywara static int mpam_fb_send_request(struct mpam_msc *msc, u32 msc_fb_id,
1330ef7c5ebSAndre Przywara u16 reg, u32 *result, int mpam_fb_command)
1340ef7c5ebSAndre Przywara {
1350ef7c5ebSAndre Przywara unsigned int token = atomic_inc_return(&mpam_fb_token);
1360ef7c5ebSAndre Przywara struct acpi_pcct_ext_pcc_shared_memory __iomem *pcc_shmem;
1370ef7c5ebSAndre Przywara struct mpam_pcc_chan *pcc_chan;
1380ef7c5ebSAndre Przywara struct pcc_mbox_chan *chan;
1390ef7c5ebSAndre Przywara void __iomem *payload_ofs;
1400ef7c5ebSAndre Przywara int mpam_fb_err = 0;
1410ef7c5ebSAndre Przywara u32 status;
1420ef7c5ebSAndre Przywara int ret;
1430ef7c5ebSAndre Przywara
1440ef7c5ebSAndre Przywara pcc_chan = msc->pcc_chan;
1450ef7c5ebSAndre Przywara if (!pcc_chan)
1460ef7c5ebSAndre Przywara return -ENODEV;
1470ef7c5ebSAndre Przywara
1480ef7c5ebSAndre Przywara chan = pcc_chan->pcc_chan;
1490ef7c5ebSAndre Przywara
1500ef7c5ebSAndre Przywara /* prune token to fit into the 10 bits inside the command register */
1510ef7c5ebSAndre Przywara token = FIELD_GET(MPAM_MSC_TOKEN_MASK,
1520ef7c5ebSAndre Przywara FIELD_PREP(MPAM_MSC_TOKEN_MASK, token));
1530ef7c5ebSAndre Przywara
1540ef7c5ebSAndre Przywara mutex_lock(&pcc_chan->pcc_chan_lock);
1550ef7c5ebSAndre Przywara
1560ef7c5ebSAndre Przywara switch (mpam_fb_command) {
1570ef7c5ebSAndre Przywara case MPAM_PROTOCOL_VERSION_CMD:
1580ef7c5ebSAndre Przywara mpam_fb_build_version_message(token, chan->shmem);
1590ef7c5ebSAndre Przywara break;
1600ef7c5ebSAndre Przywara case MPAM_MSC_READ_CMD:
1610ef7c5ebSAndre Przywara mpam_fb_build_read_message(msc_fb_id, reg, token, chan->shmem);
1620ef7c5ebSAndre Przywara break;
1630ef7c5ebSAndre Przywara case MPAM_MSC_WRITE_CMD:
1640ef7c5ebSAndre Przywara mpam_fb_build_write_message(msc_fb_id, reg, *result,
1650ef7c5ebSAndre Przywara token, chan->shmem);
1660ef7c5ebSAndre Przywara break;
1670ef7c5ebSAndre Przywara default:
1680ef7c5ebSAndre Przywara dev_err(&msc->pdev->dev, "unsupported MPAM-Fb command %d\n",
1690ef7c5ebSAndre Przywara mpam_fb_command);
1700ef7c5ebSAndre Przywara ret = -EINVAL;
1710ef7c5ebSAndre Przywara goto out_err;
1720ef7c5ebSAndre Przywara }
1730ef7c5ebSAndre Przywara
1740ef7c5ebSAndre Przywara dma_wmb();
1750ef7c5ebSAndre Przywara ret = mbox_send_message(chan->mchan, NULL);
1760ef7c5ebSAndre Przywara if (ret < 0)
1770ef7c5ebSAndre Przywara goto out_err;
1780ef7c5ebSAndre Przywara
1790ef7c5ebSAndre Przywara pcc_shmem = chan->shmem;
1800ef7c5ebSAndre Przywara payload_ofs = chan->shmem + sizeof(*pcc_shmem);
1810ef7c5ebSAndre Przywara status = readl(&pcc_shmem->command);
1820ef7c5ebSAndre Przywara if (FIELD_GET(MPAM_MSC_TOKEN_MASK, status) != token) {
1830ef7c5ebSAndre Przywara ret = -ETIMEDOUT;
1840ef7c5ebSAndre Przywara goto out_err;
1850ef7c5ebSAndre Przywara }
1860ef7c5ebSAndre Przywara
1870ef7c5ebSAndre Przywara mpam_fb_err = readl(payload_ofs + 0x0);
1880ef7c5ebSAndre Przywara if (mpam_fb_err < 0) {
1890ef7c5ebSAndre Przywara ret = mpam_fb_to_linux_errno(mpam_fb_err);
1900ef7c5ebSAndre Przywara goto out_err;
1910ef7c5ebSAndre Przywara }
1920ef7c5ebSAndre Przywara
1930ef7c5ebSAndre Przywara if (mpam_fb_command != MPAM_MSC_WRITE_CMD)
1940ef7c5ebSAndre Przywara *result = readl(payload_ofs + 0x4);
1950ef7c5ebSAndre Przywara
1960ef7c5ebSAndre Przywara mutex_unlock(&pcc_chan->pcc_chan_lock);
1970ef7c5ebSAndre Przywara
1980ef7c5ebSAndre Przywara return 0;
1990ef7c5ebSAndre Przywara
2000ef7c5ebSAndre Przywara out_err:
2010ef7c5ebSAndre Przywara mutex_unlock(&pcc_chan->pcc_chan_lock);
2020ef7c5ebSAndre Przywara
2030ef7c5ebSAndre Przywara mpam_fb_disable_mpam(ret, mpam_fb_err);
2040ef7c5ebSAndre Przywara
2050ef7c5ebSAndre Przywara /*
2060ef7c5ebSAndre Przywara * TODO: Error codes are filtered in higher layers, return something
2070ef7c5ebSAndre Przywara * that is known there for now, to actually report them.
2080ef7c5ebSAndre Przywara */
2090ef7c5ebSAndre Przywara return -EIO;
2100ef7c5ebSAndre Przywara }
2110ef7c5ebSAndre Przywara
mpam_fb_send_read_request(struct mpam_msc * msc,u16 reg,u32 * result)2120ef7c5ebSAndre Przywara int mpam_fb_send_read_request(struct mpam_msc *msc, u16 reg, u32 *result)
2130ef7c5ebSAndre Przywara {
2140ef7c5ebSAndre Przywara return mpam_fb_send_request(msc, msc->fb_id, reg, result,
2150ef7c5ebSAndre Przywara MPAM_MSC_READ_CMD);
2160ef7c5ebSAndre Przywara }
2170ef7c5ebSAndre Przywara
mpam_fb_send_write_request(struct mpam_msc * msc,u16 reg,u32 value)2180ef7c5ebSAndre Przywara int mpam_fb_send_write_request(struct mpam_msc *msc, u16 reg, u32 value)
2190ef7c5ebSAndre Przywara {
2200ef7c5ebSAndre Przywara return mpam_fb_send_request(msc, msc->fb_id, reg, &value,
2210ef7c5ebSAndre Przywara MPAM_MSC_WRITE_CMD);
2220ef7c5ebSAndre Przywara }
223*cb351e3fSAndre Przywara
224*cb351e3fSAndre Przywara /* We only support MPAM-Fb protocol version 1.x */
mpam_fb_check_protocol_version(struct mpam_msc * msc)225*cb351e3fSAndre Przywara int mpam_fb_check_protocol_version(struct mpam_msc *msc)
226*cb351e3fSAndre Przywara {
227*cb351e3fSAndre Przywara u32 version;
228*cb351e3fSAndre Przywara int ret;
229*cb351e3fSAndre Przywara
230*cb351e3fSAndre Przywara ret = mpam_fb_send_request(msc, 0, 0, &version,
231*cb351e3fSAndre Przywara MPAM_PROTOCOL_VERSION_CMD);
232*cb351e3fSAndre Przywara if (ret)
233*cb351e3fSAndre Przywara return ret;
234*cb351e3fSAndre Przywara
235*cb351e3fSAndre Przywara if (FIELD_GET(MPAM_FB_VERSION_MAJOR_MASK, version) != 1) {
236*cb351e3fSAndre Przywara pr_err("Incompatible MPAM-Fb protocol version %d.%d\n",
237*cb351e3fSAndre Przywara FIELD_GET(MPAM_FB_VERSION_MAJOR_MASK, version),
238*cb351e3fSAndre Przywara FIELD_GET(MPAM_FB_VERSION_MINOR_MASK, version));
239*cb351e3fSAndre Przywara
240*cb351e3fSAndre Przywara return -EINVAL;
241*cb351e3fSAndre Przywara }
242*cb351e3fSAndre Przywara
243*cb351e3fSAndre Przywara return 0;
244*cb351e3fSAndre Przywara }
245*cb351e3fSAndre Przywara
mpam_fb_check_shared_buffer_size(struct mpam_msc * msc)246*cb351e3fSAndre Przywara int mpam_fb_check_shared_buffer_size(struct mpam_msc *msc)
247*cb351e3fSAndre Przywara {
248*cb351e3fSAndre Przywara int min_buffer_size = MPAM_FB_MAX_MSG_SIZE +
249*cb351e3fSAndre Przywara sizeof(struct acpi_pcct_ext_pcc_shared_memory);
250*cb351e3fSAndre Przywara
251*cb351e3fSAndre Przywara if (msc->pcc_chan->pcc_chan->shmem_size < min_buffer_size) {
252*cb351e3fSAndre Przywara pr_err("MPAM-Fb PCC channel size too small.\n");
253*cb351e3fSAndre Przywara
254*cb351e3fSAndre Przywara return -ENOMEM;
255*cb351e3fSAndre Przywara }
256*cb351e3fSAndre Przywara
257*cb351e3fSAndre Przywara return 0;
258*cb351e3fSAndre Przywara }
259