| // SPDX-License-Identifier: MIT | |
| /* | |
| * Copyright © 2026 Intel Corporation | |
| */ | |
| #include <linux/device.h> | |
| #include <linux/mutex.h> | |
| #include <drm/drm_managed.h> | |
| #include "regs/xe_irq_regs.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_soc_remapper.h" | |
| #include "xe_sysctrl.h" | |
| #include "xe_sysctrl_mailbox.h" | |
| #include "xe_sysctrl_mailbox_types.h" | |
| #include "xe_sysctrl_types.h" | |
| /** | |
| * DOC: System Controller (sysctrl) | |
| * | |
| * System Controller (sysctrl) is a firmware-managed entity on Intel dGPUs | |
| * responsible for selected low-level platform management functions. | |
| * Communication between driver and System Controller is performed | |
| * via a mailbox interface, enabling command and response exchange. | |
| * | |
| * This module provides initialization and support code for interacting | |
| * with System Controller through the mailbox interface. | |
| */ | |
| /* Application status flags reported in xe_sysctrl_app_status_resp.flags */ | |
| #define XE_SYSCTRL_APP_RESP_VALID BIT(0) | |
| #define XE_SYSCTRL_APP_RESP_BOOTED BIT(1) | |
| #define XE_SYSCTRL_APP_RESP_INITIALIZED BIT(2) | |
| /* | |
| * Known System Controller application identifiers, keyed by firmware | |
| * application ID. | |
| */ | |
| enum xe_sysctrl_app_id { | |
| XE_SYSCTRL_APP_OCODE = 0x0C, | |
| XE_SYSCTRL_APP_DIAG = 0x0D, | |
| }; | |
| static void sysctrl_fini(void *arg) | |
| { | |
| struct xe_device *xe = arg; | |
| struct xe_sysctrl *sc = &xe->sc; | |
| disable_work_sync(&sc->work); | |
| xe->soc_remapper.set_sysctrl_region(xe, 0); | |
| } | |
| static void xe_sysctrl_work(struct work_struct *work) | |
| { | |
| struct xe_sysctrl *sc = container_of(work, struct xe_sysctrl, work); | |
| struct xe_device *xe = sc_to_xe(sc); | |
| guard(xe_pm_runtime)(xe); | |
| xe_sysctrl_event(sc); | |
| } | |
| /** | |
| * xe_sysctrl_init() - Initialize System Controller subsystem | |
| * @xe: xe device instance | |
| * | |
| * Entry point for System Controller initialization, called from xe_device_probe. | |
| * This function checks platform support and initializes the system controller. | |
| * | |
| * Return: 0 on success, error code on failure | |
| */ | |
| int xe_sysctrl_init(struct xe_device *xe) | |
| { | |
| struct xe_tile *tile = xe_device_get_root_tile(xe); | |
| struct xe_sysctrl *sc = &xe->sc; | |
| int ret; | |
| if (!xe->info.has_soc_remapper_sysctrl) | |
| return 0; | |
| if (!xe->info.has_sysctrl) | |
| return 0; | |
| sc->mmio = devm_kzalloc(xe->drm.dev, sizeof(*sc->mmio), GFP_KERNEL); | |
| if (!sc->mmio) | |
| return -ENOMEM; | |
| xe_mmio_init(sc->mmio, tile, tile->mmio.regs, tile->mmio.regs_size); | |
| sc->mmio->adj_offset = SYSCTRL_BASE; | |
| sc->mmio->adj_limit = U32_MAX; | |
| ret = devm_mutex_init(xe->drm.dev, &sc->cmd_lock); | |
| if (ret) | |
| return ret; | |
| ret = devm_mutex_init(xe->drm.dev, &sc->event_lock); | |
| if (ret) | |
| return ret; | |
| xe->soc_remapper.set_sysctrl_region(xe, SYSCTRL_MAILBOX_INDEX); | |
| INIT_WORK(&sc->work, xe_sysctrl_work); | |
| return devm_add_action_or_reset(xe->drm.dev, sysctrl_fini, xe); | |
| } | |
| /** | |
| * xe_sysctrl_irq_handler() - Handler for System Controller interrupts | |
| * @xe: xe device instance | |
| * @master_ctl: interrupt register | |
| * | |
| * Handle interrupts generated by System Controller. | |
| */ | |
| void xe_sysctrl_irq_handler(struct xe_device *xe, u32 master_ctl) | |
| { | |
| struct xe_sysctrl *sc = &xe->sc; | |
| if (!xe->info.has_sysctrl || !sc->work.func) | |
| return; | |
| if (master_ctl & SYSCTRL_IRQ) | |
| schedule_work(&sc->work); | |
| } | |
| /** | |
| * xe_sysctrl_pm_resume() - System Controller resume handler | |
| * @xe: xe device instance | |
| * | |
| * Invoked during system resume (S3/S4 to S0) and runtime resume from D3cold. | |
| * Restores SoC remapper configuration. | |
| */ | |
| void xe_sysctrl_pm_resume(struct xe_device *xe) | |
| { | |
| if (!xe->info.has_soc_remapper_sysctrl) | |
| return; | |
| if (!xe->info.has_sysctrl) | |
| return; | |
| xe->soc_remapper.set_sysctrl_region(xe, SYSCTRL_MAILBOX_INDEX); | |
| } | |
| static enum xe_sysctrl_fw_status | |
| xe_sysctrl_check_app_status(struct xe_device *xe, enum xe_sysctrl_app_id app_id) | |
| { | |
| struct xe_sysctrl_app_status_req req = {}; | |
| struct xe_sysctrl_app_status_resp resp = {}; | |
| struct xe_sysctrl_mailbox_command cmd = {}; | |
| size_t out_len = 0; | |
| u32 flags; | |
| int ret; | |
| req.app_id = (u8)app_id; | |
| xe_sysctrl_create_command(&cmd, XE_SYSCTRL_GROUP_CORE, XE_SYSCTRL_CMD_GET_APP_STATUS_BY_ID, | |
| &req, sizeof(req), &resp, sizeof(resp)); | |
| ret = xe_sysctrl_send_command(&xe->sc, &cmd, &out_len); | |
| if (ret) | |
| return XE_SYSCTRL_FIRMWARE_COMM_FAILURE; | |
| if (out_len != sizeof(resp)) { | |
| xe_err(xe, "sysctrl: unexpected get app status response length %zu (expected %zu)\n", | |
| out_len, sizeof(resp)); | |
| return XE_SYSCTRL_FIRMWARE_COMM_FAILURE; | |
| } | |
| flags = resp.flags; | |
| if (!(flags & XE_SYSCTRL_APP_RESP_VALID)) | |
| return XE_SYSCTRL_FIRMWARE_APP_INVALID; | |
| if (!(flags & XE_SYSCTRL_APP_RESP_BOOTED)) | |
| return XE_SYSCTRL_FIRMWARE_APP_NOT_LOADED; | |
| if (!(flags & XE_SYSCTRL_APP_RESP_INITIALIZED)) | |
| return XE_SYSCTRL_FIRMWARE_APP_BOOTED; | |
| return XE_SYSCTRL_FIRMWARE_APP_INITIALIZED; | |
| } | |
| /** | |
| * xe_sysctrl_is_oobmsm_fw_ready() - Check if oCode firmware is fully initialized | |
| * @xe: xe device instance | |
| * | |
| * Returns true if oCode firmware has reached the initialized state, indicating | |
| * it is ready to handle requests. | |
| * | |
| * Callers must only invoke this on platforms where System Controller is | |
| * present (xe->info.has_sysctrl). | |
| * | |
| * Return: true if oCode firmware is initialized, false otherwise | |
| */ | |
| bool xe_sysctrl_is_oobmsm_fw_ready(struct xe_device *xe) | |
| { | |
| return xe_sysctrl_check_app_status(xe, XE_SYSCTRL_APP_OCODE) == | |
| XE_SYSCTRL_FIRMWARE_APP_INITIALIZED; | |
| } | |
| /** | |
| * xe_sysctrl_is_diag_fw_ready() - Check if diag firmware is fully initialized | |
| * @xe: xe device instance | |
| * | |
| * Returns true if diag firmware has reached the initialized state, indicating | |
| * it is ready to handle requests. | |
| * | |
| * Callers must only invoke this on platforms where System Controller is | |
| * present (xe->info.has_sysctrl). | |
| * | |
| * Return: true if diag firmware is initialized, false otherwise | |
| */ | |
| bool xe_sysctrl_is_diag_fw_ready(struct xe_device *xe) | |
| { | |
| return xe_sysctrl_check_app_status(xe, XE_SYSCTRL_APP_DIAG) == | |
| XE_SYSCTRL_FIRMWARE_APP_INITIALIZED; | |
| } |