// SPDX-License-Identifier: MIT /* * Copyright © 2026 Intel Corporation */ #include #include #include #include #include #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; 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; } sc->phase_bit ^= 1; 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); sc->phase_bit = 0; 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 | (sc->phase_bit ? 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); sc->phase_bit = 0; 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_mailbox_init - Initialize System Controller mailbox interface * @sc: System controller structure * * Initialize system controller mailbox interface for communication. */ void xe_sysctrl_mailbox_init(struct xe_sysctrl *sc) { u32 ctrl_reg; ctrl_reg = xe_mmio_read32(sc->mmio, SYSCTRL_MB_CTRL); sc->phase_bit = (ctrl_reg & SYSCTRL_FRAME_PHASE) ? 1 : 0; } /** * 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; }