mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs.git
synced 2026-10-31 06:14:39 +01:00
The mailbox PHASE bit in SYSCTRL_MB_CTRL is toggled per-message and
was tracked in software as sc->phase_bit, reset to 0 on error paths.
If the cached value ever drifts from what the hardware last saw, all
following messages carry the wrong phase and get silently misread by
firmware.
Drop the cache and read PHASE directly from SYSCTRL_MB_CTRL before
each frame instead, removing xe_sysctrl_mailbox_init() and its call
sites along with it.
Fixes: 1f95f61818 ("drm/xe/xe_sysctrl: Add System Controller mailbox communication support")
Signed-off-by: Anoop Vijay <[email protected]>
Reviewed-by: Umesh Nerlige Ramappa <[email protected]>
Reviewed-by: Rodrigo Vivi <[email protected]>
Link: https://patch.msgid.link/[email protected]
Signed-off-by: Rodrigo Vivi <[email protected]>
(cherry picked from commit 04984fcdbf6876c940c01026a7404c1e9cc91ba7)
Signed-off-by: Rodrigo Vivi <[email protected]>
386 lines
10 KiB
C
386 lines
10 KiB
C
// SPDX-License-Identifier: MIT
|
|
/*
|
|
* Copyright © 2026 Intel Corporation
|
|
*/
|
|
|
|
#include <linux/bitfield.h>
|
|
#include <linux/cleanup.h>
|
|
#include <linux/minmax.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/string.h>
|
|
|
|
#include "regs/xe_sysctrl_regs.h"
|
|
#include "xe_device.h"
|
|
#include "xe_mmio.h"
|
|
#include "xe_pm.h"
|
|
#include "xe_printk.h"
|
|
#include "xe_sysctrl.h"
|
|
#include "xe_sysctrl_mailbox.h"
|
|
#include "xe_sysctrl_mailbox_types.h"
|
|
|
|
struct xe_sysctrl_mailbox_msg_hdr {
|
|
__le32 data;
|
|
} __packed;
|
|
|
|
#define XE_SYSCTRL_HDR_GROUP_ID(hdr) \
|
|
FIELD_GET(SYSCTRL_HDR_GROUP_ID_MASK, le32_to_cpu((hdr)->data))
|
|
|
|
#define XE_SYSCTRL_HDR_COMMAND(hdr) \
|
|
FIELD_GET(SYSCTRL_HDR_COMMAND_MASK, le32_to_cpu((hdr)->data))
|
|
|
|
#define XE_SYSCTRL_HDR_IS_RESPONSE(hdr) \
|
|
FIELD_GET(SYSCTRL_HDR_IS_RESPONSE, le32_to_cpu((hdr)->data))
|
|
|
|
#define XE_SYSCTRL_HDR_RESULT(hdr) \
|
|
FIELD_GET(SYSCTRL_HDR_RESULT_MASK, le32_to_cpu((hdr)->data))
|
|
|
|
static bool sysctrl_wait_bit_clear(struct xe_sysctrl *sc, u32 bit_mask,
|
|
unsigned int timeout_ms)
|
|
{
|
|
int ret;
|
|
|
|
ret = xe_mmio_wait32_not(sc->mmio, SYSCTRL_MB_CTRL, bit_mask, bit_mask,
|
|
timeout_ms * 1000, NULL, false);
|
|
|
|
return ret == 0;
|
|
}
|
|
|
|
static bool sysctrl_wait_bit_set(struct xe_sysctrl *sc, u32 bit_mask,
|
|
unsigned int timeout_ms)
|
|
{
|
|
int ret;
|
|
|
|
ret = xe_mmio_wait32(sc->mmio, SYSCTRL_MB_CTRL, bit_mask, bit_mask,
|
|
timeout_ms * 1000, NULL, false);
|
|
|
|
return ret == 0;
|
|
}
|
|
|
|
static int sysctrl_write_frame(struct xe_sysctrl *sc, const void *frame,
|
|
size_t len)
|
|
{
|
|
static const struct xe_reg regs[] = {
|
|
SYSCTRL_MB_DATA0, SYSCTRL_MB_DATA1, SYSCTRL_MB_DATA2, SYSCTRL_MB_DATA3
|
|
};
|
|
struct xe_device *xe = sc_to_xe(sc);
|
|
u32 val[XE_SYSCTRL_MB_FRAME_SIZE / sizeof(u32)] = {0};
|
|
u32 dw = DIV_ROUND_UP(len, sizeof(u32));
|
|
u32 i;
|
|
|
|
xe_assert(xe, len > 0 && len <= XE_SYSCTRL_MB_FRAME_SIZE);
|
|
|
|
memcpy(val, frame, len);
|
|
|
|
for (i = 0; i < dw; i++)
|
|
xe_mmio_write32(sc->mmio, regs[i], val[i]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sysctrl_read_frame(struct xe_sysctrl *sc, void *frame,
|
|
size_t len)
|
|
{
|
|
static const struct xe_reg regs[] = {
|
|
SYSCTRL_MB_DATA0, SYSCTRL_MB_DATA1, SYSCTRL_MB_DATA2, SYSCTRL_MB_DATA3
|
|
};
|
|
struct xe_device *xe = sc_to_xe(sc);
|
|
u32 val[XE_SYSCTRL_MB_FRAME_SIZE / sizeof(u32)] = {0};
|
|
u32 dw = DIV_ROUND_UP(len, sizeof(u32));
|
|
u32 i;
|
|
|
|
xe_assert(xe, len > 0 && len <= XE_SYSCTRL_MB_FRAME_SIZE);
|
|
|
|
for (i = 0; i < dw; i++)
|
|
val[i] = xe_mmio_read32(sc->mmio, regs[i]);
|
|
|
|
memcpy(frame, val, len);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void sysctrl_clear_response(struct xe_sysctrl *sc)
|
|
{
|
|
xe_mmio_rmw32(sc->mmio, SYSCTRL_MB_CTRL, SYSCTRL_MB_CTRL_RUN_BUSY_OUT, 0);
|
|
}
|
|
|
|
static int sysctrl_prepare_command(struct xe_device *xe,
|
|
u8 group_id, u8 command,
|
|
const void *data_in, size_t data_in_len,
|
|
u8 **mbox_cmd, size_t *cmd_size)
|
|
{
|
|
struct xe_sysctrl_mailbox_msg_hdr *hdr;
|
|
size_t size;
|
|
u8 *buffer;
|
|
|
|
xe_assert(xe, command <= SYSCTRL_HDR_COMMAND_MAX);
|
|
|
|
if (data_in_len > XE_SYSCTRL_MB_MAX_MESSAGE_SIZE - sizeof(*hdr)) {
|
|
xe_err(xe, "sysctrl: Input data too large: %zu bytes\n", data_in_len);
|
|
return -EINVAL;
|
|
}
|
|
|
|
size = sizeof(*hdr) + data_in_len;
|
|
|
|
buffer = kmalloc(size, GFP_KERNEL);
|
|
if (!buffer)
|
|
return -ENOMEM;
|
|
|
|
hdr = (struct xe_sysctrl_mailbox_msg_hdr *)buffer;
|
|
hdr->data = cpu_to_le32(FIELD_PREP(SYSCTRL_HDR_GROUP_ID_MASK, group_id) |
|
|
FIELD_PREP(SYSCTRL_HDR_COMMAND_MASK, command));
|
|
|
|
if (data_in && data_in_len)
|
|
memcpy(buffer + sizeof(*hdr), data_in, data_in_len);
|
|
|
|
*mbox_cmd = buffer;
|
|
*cmd_size = size;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sysctrl_send_frames(struct xe_sysctrl *sc,
|
|
const u8 *mbox_cmd,
|
|
size_t cmd_size, unsigned int timeout_ms)
|
|
{
|
|
struct xe_device *xe = sc_to_xe(sc);
|
|
u32 ctrl_reg, total_frames, frame;
|
|
size_t bytes_sent, frame_size;
|
|
bool phase;
|
|
|
|
total_frames = DIV_ROUND_UP(cmd_size, XE_SYSCTRL_MB_FRAME_SIZE);
|
|
|
|
if (!sysctrl_wait_bit_clear(sc, SYSCTRL_MB_CTRL_RUN_BUSY, timeout_ms)) {
|
|
xe_err(xe, "sysctrl: Mailbox busy\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
ctrl_reg = xe_mmio_read32(sc->mmio, SYSCTRL_MB_CTRL);
|
|
phase = !(ctrl_reg & SYSCTRL_FRAME_PHASE);
|
|
bytes_sent = 0;
|
|
|
|
for (frame = 0; frame < total_frames; frame++) {
|
|
frame_size = min_t(size_t, cmd_size - bytes_sent, XE_SYSCTRL_MB_FRAME_SIZE);
|
|
|
|
if (sysctrl_write_frame(sc, mbox_cmd + bytes_sent, frame_size)) {
|
|
xe_err(xe, "sysctrl: Failed to write frame %u\n", frame);
|
|
return -EIO;
|
|
}
|
|
|
|
ctrl_reg = SYSCTRL_MB_CTRL_RUN_BUSY |
|
|
REG_FIELD_PREP(SYSCTRL_FRAME_CURRENT_MASK, frame) |
|
|
REG_FIELD_PREP(SYSCTRL_FRAME_TOTAL_MASK, total_frames - 1) |
|
|
SYSCTRL_MB_CTRL_CMD |
|
|
(phase ? SYSCTRL_FRAME_PHASE : 0);
|
|
|
|
xe_mmio_write32(sc->mmio, SYSCTRL_MB_CTRL, ctrl_reg);
|
|
|
|
if (!sysctrl_wait_bit_clear(sc, SYSCTRL_MB_CTRL_RUN_BUSY, timeout_ms)) {
|
|
xe_err(xe, "sysctrl: Frame %u acknowledgment timeout\n", frame);
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
bytes_sent += frame_size;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sysctrl_process_frame(struct xe_sysctrl *sc, void *out,
|
|
size_t frame_size, unsigned int timeout_ms,
|
|
bool *done)
|
|
{
|
|
u32 curr_frame, total_frames, ctrl_reg;
|
|
struct xe_device *xe = sc_to_xe(sc);
|
|
int ret;
|
|
|
|
if (!sysctrl_wait_bit_set(sc, SYSCTRL_MB_CTRL_RUN_BUSY_OUT, timeout_ms)) {
|
|
xe_err(xe, "sysctrl: Response frame timeout\n");
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
ctrl_reg = xe_mmio_read32(sc->mmio, SYSCTRL_MB_CTRL);
|
|
total_frames = FIELD_GET(SYSCTRL_FRAME_TOTAL_MASK, ctrl_reg);
|
|
curr_frame = FIELD_GET(SYSCTRL_FRAME_CURRENT_MASK, ctrl_reg);
|
|
|
|
ret = sysctrl_read_frame(sc, out, frame_size);
|
|
if (ret)
|
|
return ret;
|
|
|
|
sysctrl_clear_response(sc);
|
|
|
|
if (curr_frame == total_frames)
|
|
*done = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sysctrl_receive_frames(struct xe_sysctrl *sc,
|
|
const struct xe_sysctrl_mailbox_msg_hdr *req,
|
|
void *data_out, size_t data_out_len,
|
|
size_t *rdata_len, unsigned int timeout_ms)
|
|
{
|
|
struct xe_sysctrl_mailbox_msg_hdr *hdr;
|
|
struct xe_device *xe = sc_to_xe(sc);
|
|
size_t remain = sizeof(*hdr) + data_out_len;
|
|
u8 *buffer __free(kfree) = kzalloc(remain, GFP_KERNEL);
|
|
size_t frame_size;
|
|
bool done = false;
|
|
int ret = 0;
|
|
u8 *out;
|
|
|
|
if (!buffer)
|
|
return -ENOMEM;
|
|
|
|
out = buffer;
|
|
while (!done && remain) {
|
|
frame_size = min_t(size_t, remain, XE_SYSCTRL_MB_FRAME_SIZE);
|
|
|
|
ret = sysctrl_process_frame(sc, out, frame_size, timeout_ms,
|
|
&done);
|
|
if (ret)
|
|
return ret;
|
|
|
|
remain -= frame_size;
|
|
out += frame_size;
|
|
}
|
|
|
|
hdr = (struct xe_sysctrl_mailbox_msg_hdr *)buffer;
|
|
|
|
if (!XE_SYSCTRL_HDR_IS_RESPONSE(hdr) ||
|
|
XE_SYSCTRL_HDR_GROUP_ID(hdr) != XE_SYSCTRL_HDR_GROUP_ID(req) ||
|
|
XE_SYSCTRL_HDR_COMMAND(hdr) != XE_SYSCTRL_HDR_COMMAND(req)) {
|
|
xe_err(xe, "sysctrl: Response header mismatch\n");
|
|
return -EPROTO;
|
|
}
|
|
|
|
if (XE_SYSCTRL_HDR_RESULT(hdr) != 0) {
|
|
xe_err(xe, "sysctrl: Firmware error: 0x%02lx\n",
|
|
XE_SYSCTRL_HDR_RESULT(hdr));
|
|
return -EIO;
|
|
}
|
|
|
|
memcpy(data_out, hdr + 1, data_out_len);
|
|
*rdata_len = out - buffer - sizeof(*hdr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sysctrl_send_command(struct xe_sysctrl *sc,
|
|
const u8 *mbox_cmd, size_t cmd_size,
|
|
void *data_out, size_t data_out_len,
|
|
size_t *rdata_len, unsigned int timeout_ms)
|
|
{
|
|
const struct xe_sysctrl_mailbox_msg_hdr *hdr;
|
|
size_t received;
|
|
int ret;
|
|
|
|
ret = sysctrl_send_frames(sc, mbox_cmd, cmd_size, timeout_ms);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (!data_out || !rdata_len)
|
|
return 0;
|
|
|
|
hdr = (const struct xe_sysctrl_mailbox_msg_hdr *)mbox_cmd;
|
|
|
|
ret = sysctrl_receive_frames(sc, hdr, data_out, data_out_len,
|
|
&received, timeout_ms);
|
|
if (ret)
|
|
return ret;
|
|
|
|
*rdata_len = received;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* xe_sysctrl_create_command() - Create system controller command
|
|
* @command: Sysctrl command structure
|
|
* @group_id: Command group ID
|
|
* @cmd_id: Command ID
|
|
* @request: Pointer to request buffer (can be NULL)
|
|
* @request_len: Size of request buffer
|
|
* @response: Pointer to response buffer
|
|
* @response_len: Size of response buffer
|
|
*
|
|
* Helper function to create sysctrl command to be sent via %xe_sysctrl_send_command()
|
|
*/
|
|
void xe_sysctrl_create_command(struct xe_sysctrl_mailbox_command *command, u8 group_id, u8 cmd_id,
|
|
void *request, size_t request_len, void *response,
|
|
size_t response_len)
|
|
{
|
|
struct xe_sysctrl_app_msg_hdr header = {0};
|
|
|
|
header.data = FIELD_PREP(APP_HDR_GROUP_ID_MASK, group_id) |
|
|
FIELD_PREP(APP_HDR_COMMAND_MASK, cmd_id);
|
|
|
|
command->header = header;
|
|
command->data_in = request;
|
|
command->data_in_len = request_len;
|
|
command->data_out = response;
|
|
command->data_out_len = response_len;
|
|
}
|
|
|
|
/**
|
|
* xe_sysctrl_send_command() - Send mailbox command to System Controller
|
|
* @sc: System Controller instance
|
|
* @cmd: Command descriptor containing request header and payload buffers
|
|
* @rdata_len: Pointer to store actual response data length
|
|
*
|
|
* Sends a mailbox command to System Controller firmware using
|
|
* System Controller mailbox and waits for a response.
|
|
*
|
|
* Request payload is provided via @cmd->data_in and @cmd->data_in_len.
|
|
* If a response is expected, @cmd->data_out must point to a buffer of
|
|
* size @cmd->data_out_len supplied by caller.
|
|
*
|
|
* On success, @rdata_len is updated with number of valid response bytes
|
|
* returned by firmware, bounded by @cmd->data_out_len.
|
|
*
|
|
* Return: 0 on success, or negative errno on failure.
|
|
*/
|
|
int xe_sysctrl_send_command(struct xe_sysctrl *sc,
|
|
struct xe_sysctrl_mailbox_command *cmd,
|
|
size_t *rdata_len)
|
|
{
|
|
struct xe_device *xe = sc_to_xe(sc);
|
|
u8 group_id, command_code;
|
|
u8 *mbox_cmd = NULL;
|
|
size_t cmd_size = 0;
|
|
int ret;
|
|
|
|
guard(xe_pm_runtime_noresume)(xe);
|
|
|
|
if (!xe->info.has_sysctrl)
|
|
return -ENODEV;
|
|
|
|
xe_assert(xe, cmd->data_in || cmd->data_out);
|
|
xe_assert(xe, !cmd->data_in || cmd->data_in_len);
|
|
xe_assert(xe, !cmd->data_out || cmd->data_out_len);
|
|
|
|
group_id = XE_SYSCTRL_APP_HDR_GROUP_ID(&cmd->header);
|
|
command_code = XE_SYSCTRL_APP_HDR_COMMAND(&cmd->header);
|
|
|
|
might_sleep();
|
|
|
|
ret = sysctrl_prepare_command(xe, group_id, command_code,
|
|
cmd->data_in, cmd->data_in_len,
|
|
&mbox_cmd, &cmd_size);
|
|
if (ret) {
|
|
xe_err(xe, "sysctrl: Failed to prepare command: %pe\n", ERR_PTR(ret));
|
|
return ret;
|
|
}
|
|
|
|
guard(mutex)(&sc->cmd_lock);
|
|
|
|
ret = sysctrl_send_command(sc, mbox_cmd, cmd_size,
|
|
cmd->data_out, cmd->data_out_len, rdata_len,
|
|
XE_SYSCTRL_MB_DEFAULT_TIMEOUT_MS);
|
|
if (ret)
|
|
xe_err(xe, "sysctrl: Mailbox command failed: %pe\n", ERR_PTR(ret));
|
|
|
|
kfree(mbox_cmd);
|
|
|
|
return ret;
|
|
}
|