| // 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; | |
| } |