Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,217 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2014-2021 Intel Corporation
*/
#ifndef _ABI_GUC_ACTIONS_ABI_H
#define _ABI_GUC_ACTIONS_ABI_H
/**
* DOC: HOST2GUC_SELF_CFG
*
* This message is used by Host KMD to setup of the `GuC Self Config KLVs`_.
*
* This message must be sent as `MMIO HXG Message`_.
*
* +---+-------+--------------------------------------------------------------+
* | | Bits | Description |
* +===+=======+==============================================================+
* | 0 | 31 | ORIGIN = GUC_HXG_ORIGIN_HOST_ |
* | +-------+--------------------------------------------------------------+
* | | 30:28 | TYPE = GUC_HXG_TYPE_REQUEST_ |
* | +-------+--------------------------------------------------------------+
* | | 27:16 | DATA0 = MBZ |
* | +-------+--------------------------------------------------------------+
* | | 15:0 | ACTION = _`GUC_ACTION_HOST2GUC_SELF_CFG` = 0x0508 |
* +---+-------+--------------------------------------------------------------+
* | 1 | 31:16 | **KLV_KEY** - KLV key, see `GuC Self Config KLVs`_ |
* | +-------+--------------------------------------------------------------+
* | | 15:0 | **KLV_LEN** - KLV length |
* | | | |
* | | | - 32 bit KLV = 1 |
* | | | - 64 bit KLV = 2 |
* +---+-------+--------------------------------------------------------------+
* | 2 | 31:0 | **VALUE32** - Bits 31-0 of the KLV value |
* +---+-------+--------------------------------------------------------------+
* | 3 | 31:0 | **VALUE64** - Bits 63-32 of the KLV value (**KLV_LEN** = 2) |
* +---+-------+--------------------------------------------------------------+
*
* +---+-------+--------------------------------------------------------------+
* | | Bits | Description |
* +===+=======+==============================================================+
* | 0 | 31 | ORIGIN = GUC_HXG_ORIGIN_GUC_ |
* | +-------+--------------------------------------------------------------+
* | | 30:28 | TYPE = GUC_HXG_TYPE_RESPONSE_SUCCESS_ |
* | +-------+--------------------------------------------------------------+
* | | 27:0 | DATA0 = **NUM** - 1 if KLV was parsed, 0 if not recognized |
* +---+-------+--------------------------------------------------------------+
*/
#define GUC_ACTION_HOST2GUC_SELF_CFG 0x0508
#define HOST2GUC_SELF_CFG_REQUEST_MSG_LEN (GUC_HXG_REQUEST_MSG_MIN_LEN + 3u)
#define HOST2GUC_SELF_CFG_REQUEST_MSG_0_MBZ GUC_HXG_REQUEST_MSG_0_DATA0
#define HOST2GUC_SELF_CFG_REQUEST_MSG_1_KLV_KEY (0xffffU << 16)
#define HOST2GUC_SELF_CFG_REQUEST_MSG_1_KLV_LEN (0xffff << 0)
#define HOST2GUC_SELF_CFG_REQUEST_MSG_2_VALUE32 GUC_HXG_REQUEST_MSG_n_DATAn
#define HOST2GUC_SELF_CFG_REQUEST_MSG_3_VALUE64 GUC_HXG_REQUEST_MSG_n_DATAn
#define HOST2GUC_SELF_CFG_RESPONSE_MSG_LEN GUC_HXG_RESPONSE_MSG_MIN_LEN
#define HOST2GUC_SELF_CFG_RESPONSE_MSG_0_NUM GUC_HXG_RESPONSE_MSG_0_DATA0
/**
* DOC: HOST2GUC_CONTROL_CTB
*
* This H2G action allows Vf Host to enable or disable H2G and G2H `CT Buffer`_.
*
* This message must be sent as `MMIO HXG Message`_.
*
* +---+-------+--------------------------------------------------------------+
* | | Bits | Description |
* +===+=======+==============================================================+
* | 0 | 31 | ORIGIN = GUC_HXG_ORIGIN_HOST_ |
* | +-------+--------------------------------------------------------------+
* | | 30:28 | TYPE = GUC_HXG_TYPE_REQUEST_ |
* | +-------+--------------------------------------------------------------+
* | | 27:16 | DATA0 = MBZ |
* | +-------+--------------------------------------------------------------+
* | | 15:0 | ACTION = _`GUC_ACTION_HOST2GUC_CONTROL_CTB` = 0x4509 |
* +---+-------+--------------------------------------------------------------+
* | 1 | 31:0 | **CONTROL** - control `CTB based communication`_ |
* | | | |
* | | | - _`GUC_CTB_CONTROL_DISABLE` = 0 |
* | | | - _`GUC_CTB_CONTROL_ENABLE` = 1 |
* +---+-------+--------------------------------------------------------------+
*
* +---+-------+--------------------------------------------------------------+
* | | Bits | Description |
* +===+=======+==============================================================+
* | 0 | 31 | ORIGIN = GUC_HXG_ORIGIN_GUC_ |
* | +-------+--------------------------------------------------------------+
* | | 30:28 | TYPE = GUC_HXG_TYPE_RESPONSE_SUCCESS_ |
* | +-------+--------------------------------------------------------------+
* | | 27:0 | DATA0 = MBZ |
* +---+-------+--------------------------------------------------------------+
*/
#define GUC_ACTION_HOST2GUC_CONTROL_CTB 0x4509
#define HOST2GUC_CONTROL_CTB_REQUEST_MSG_LEN (GUC_HXG_REQUEST_MSG_MIN_LEN + 1u)
#define HOST2GUC_CONTROL_CTB_REQUEST_MSG_0_MBZ GUC_HXG_REQUEST_MSG_0_DATA0
#define HOST2GUC_CONTROL_CTB_REQUEST_MSG_1_CONTROL GUC_HXG_REQUEST_MSG_n_DATAn
#define GUC_CTB_CONTROL_DISABLE 0u
#define GUC_CTB_CONTROL_ENABLE 1u
#define HOST2GUC_CONTROL_CTB_RESPONSE_MSG_LEN GUC_HXG_RESPONSE_MSG_MIN_LEN
#define HOST2GUC_CONTROL_CTB_RESPONSE_MSG_0_MBZ GUC_HXG_RESPONSE_MSG_0_DATA0
/* legacy definitions */
enum intel_guc_action {
INTEL_GUC_ACTION_DEFAULT = 0x0,
INTEL_GUC_ACTION_REQUEST_PREEMPTION = 0x2,
INTEL_GUC_ACTION_REQUEST_ENGINE_RESET = 0x3,
INTEL_GUC_ACTION_ALLOCATE_DOORBELL = 0x10,
INTEL_GUC_ACTION_DEALLOCATE_DOORBELL = 0x20,
INTEL_GUC_ACTION_LOG_BUFFER_FILE_FLUSH_COMPLETE = 0x30,
INTEL_GUC_ACTION_UK_LOG_ENABLE_LOGGING = 0x40,
INTEL_GUC_ACTION_FORCE_LOG_BUFFER_FLUSH = 0x302,
INTEL_GUC_ACTION_ENTER_S_STATE = 0x501,
INTEL_GUC_ACTION_EXIT_S_STATE = 0x502,
INTEL_GUC_ACTION_GLOBAL_SCHED_POLICY_CHANGE = 0x506,
INTEL_GUC_ACTION_UPDATE_SCHEDULING_POLICIES_KLV = 0x509,
INTEL_GUC_ACTION_SCHED_CONTEXT = 0x1000,
INTEL_GUC_ACTION_SCHED_CONTEXT_MODE_SET = 0x1001,
INTEL_GUC_ACTION_SCHED_CONTEXT_MODE_DONE = 0x1002,
INTEL_GUC_ACTION_SCHED_ENGINE_MODE_SET = 0x1003,
INTEL_GUC_ACTION_SCHED_ENGINE_MODE_DONE = 0x1004,
INTEL_GUC_ACTION_V69_SET_CONTEXT_PRIORITY = 0x1005,
INTEL_GUC_ACTION_V69_SET_CONTEXT_EXECUTION_QUANTUM = 0x1006,
INTEL_GUC_ACTION_V69_SET_CONTEXT_PREEMPTION_TIMEOUT = 0x1007,
INTEL_GUC_ACTION_CONTEXT_RESET_NOTIFICATION = 0x1008,
INTEL_GUC_ACTION_ENGINE_FAILURE_NOTIFICATION = 0x1009,
INTEL_GUC_ACTION_HOST2GUC_UPDATE_CONTEXT_POLICIES = 0x100B,
INTEL_GUC_ACTION_SETUP_PC_GUCRC = 0x3004,
INTEL_GUC_ACTION_AUTHENTICATE_HUC = 0x4000,
INTEL_GUC_ACTION_GET_HWCONFIG = 0x4100,
INTEL_GUC_ACTION_REGISTER_CONTEXT = 0x4502,
INTEL_GUC_ACTION_DEREGISTER_CONTEXT = 0x4503,
INTEL_GUC_ACTION_DEREGISTER_CONTEXT_DONE = 0x4600,
INTEL_GUC_ACTION_REGISTER_CONTEXT_MULTI_LRC = 0x4601,
INTEL_GUC_ACTION_CLIENT_SOFT_RESET = 0x5507,
INTEL_GUC_ACTION_SET_ENG_UTIL_BUFF = 0x550A,
INTEL_GUC_ACTION_TLB_INVALIDATION = 0x7000,
INTEL_GUC_ACTION_TLB_INVALIDATION_DONE = 0x7001,
INTEL_GUC_ACTION_STATE_CAPTURE_NOTIFICATION = 0x8002,
INTEL_GUC_ACTION_NOTIFY_FLUSH_LOG_BUFFER_TO_FILE = 0x8003,
INTEL_GUC_ACTION_NOTIFY_CRASH_DUMP_POSTED = 0x8004,
INTEL_GUC_ACTION_NOTIFY_EXCEPTION = 0x8005,
INTEL_GUC_ACTION_LIMIT
};
enum intel_guc_rc_options {
INTEL_GUCRC_HOST_CONTROL,
INTEL_GUCRC_FIRMWARE_CONTROL,
};
enum intel_guc_preempt_options {
INTEL_GUC_PREEMPT_OPTION_DROP_WORK_Q = 0x4,
INTEL_GUC_PREEMPT_OPTION_DROP_SUBMIT_Q = 0x8,
};
enum intel_guc_report_status {
INTEL_GUC_REPORT_STATUS_UNKNOWN = 0x0,
INTEL_GUC_REPORT_STATUS_ACKED = 0x1,
INTEL_GUC_REPORT_STATUS_ERROR = 0x2,
INTEL_GUC_REPORT_STATUS_COMPLETE = 0x4,
};
enum intel_guc_sleep_state_status {
INTEL_GUC_SLEEP_STATE_SUCCESS = 0x1,
INTEL_GUC_SLEEP_STATE_PREEMPT_TO_IDLE_FAILED = 0x2,
INTEL_GUC_SLEEP_STATE_ENGINE_RESET_FAILED = 0x3
#define INTEL_GUC_SLEEP_STATE_INVALID_MASK 0x80000000
};
#define GUC_LOG_CONTROL_LOGGING_ENABLED (1 << 0)
#define GUC_LOG_CONTROL_VERBOSITY_SHIFT 4
#define GUC_LOG_CONTROL_VERBOSITY_MASK (0xF << GUC_LOG_CONTROL_VERBOSITY_SHIFT)
#define GUC_LOG_CONTROL_DEFAULT_LOGGING (1 << 8)
enum intel_guc_state_capture_event_status {
INTEL_GUC_STATE_CAPTURE_EVENT_STATUS_SUCCESS = 0x0,
INTEL_GUC_STATE_CAPTURE_EVENT_STATUS_NOSPACE = 0x1,
};
#define INTEL_GUC_STATE_CAPTURE_EVENT_STATUS_MASK 0x000000FF
#define INTEL_GUC_TLB_INVAL_TYPE_MASK REG_GENMASK(7, 0)
#define INTEL_GUC_TLB_INVAL_MODE_MASK REG_GENMASK(11, 8)
#define INTEL_GUC_TLB_INVAL_FLUSH_CACHE REG_BIT(31)
enum intel_guc_tlb_invalidation_type {
INTEL_GUC_TLB_INVAL_ENGINES = 0x0,
INTEL_GUC_TLB_INVAL_GUC = 0x3,
};
/*
* 0: Heavy mode of Invalidation:
* The pipeline of the engine(s) for which the invalidation is targeted to is
* blocked, and all the in-flight transactions are guaranteed to be Globally
* Observed before completing the TLB invalidation
* 1: Lite mode of Invalidation:
* TLBs of the targeted engine(s) are immediately invalidated.
* In-flight transactions are NOT guaranteed to be Globally Observed before
* completing TLB invalidation.
* Light Invalidation Mode is to be used only when
* it can be guaranteed (by SW) that the address translations remain invariant
* for the in-flight transactions across the TLB invalidation. In other words,
* this mode can be used when the TLB invalidation is intended to clear out the
* stale cached translations that are no longer in use. Light Invalidation Mode
* is much faster than the Heavy Invalidation Mode, as it does not wait for the
* in-flight transactions to be GOd.
*/
enum intel_guc_tlb_inval_mode {
INTEL_GUC_TLB_INVAL_MODE_HEAVY = 0x0,
INTEL_GUC_TLB_INVAL_MODE_LITE = 0x1,
};
#endif /* _ABI_GUC_ACTIONS_ABI_H */
@@ -0,0 +1,275 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2021 Intel Corporation
*/
#ifndef _GUC_ACTIONS_SLPC_ABI_H_
#define _GUC_ACTIONS_SLPC_ABI_H_
#include <linux/types.h>
/**
* DOC: SLPC SHARED DATA STRUCTURE
*
* +----+------+--------------------------------------------------------------+
* | CL | Bytes| Description |
* +====+======+==============================================================+
* | 1 | 0-3 | SHARED DATA SIZE |
* | +------+--------------------------------------------------------------+
* | | 4-7 | GLOBAL STATE |
* | +------+--------------------------------------------------------------+
* | | 8-11 | DISPLAY DATA ADDRESS |
* | +------+--------------------------------------------------------------+
* | | 12:63| PADDING |
* +----+------+--------------------------------------------------------------+
* | | 0:63 | PADDING(PLATFORM INFO) |
* +----+------+--------------------------------------------------------------+
* | 3 | 0-3 | TASK STATE DATA |
* + +------+--------------------------------------------------------------+
* | | 4:63 | PADDING |
* +----+------+--------------------------------------------------------------+
* |4-21|0:1087| OVERRIDE PARAMS AND BIT FIELDS |
* +----+------+--------------------------------------------------------------+
* | | | PADDING + EXTRA RESERVED PAGE |
* +----+------+--------------------------------------------------------------+
*/
/*
* SLPC exposes certain parameters for global configuration by the host.
* These are referred to as override parameters, because in most cases
* the host will not need to modify the default values used by SLPC.
* SLPC remembers the default values which allows the host to easily restore
* them by simply unsetting the override. The host can set or unset override
* parameters during SLPC (re-)initialization using the SLPC Reset event.
* The host can also set or unset override parameters on the fly using the
* Parameter Set and Parameter Unset events
*/
#define SLPC_MAX_OVERRIDE_PARAMETERS 256
#define SLPC_OVERRIDE_BITFIELD_SIZE \
(SLPC_MAX_OVERRIDE_PARAMETERS / 32)
#define SLPC_PAGE_SIZE_BYTES 4096
#define SLPC_CACHELINE_SIZE_BYTES 64
#define SLPC_SHARED_DATA_SIZE_BYTE_HEADER SLPC_CACHELINE_SIZE_BYTES
#define SLPC_SHARED_DATA_SIZE_BYTE_PLATFORM_INFO SLPC_CACHELINE_SIZE_BYTES
#define SLPC_SHARED_DATA_SIZE_BYTE_TASK_STATE SLPC_CACHELINE_SIZE_BYTES
#define SLPC_SHARED_DATA_MODE_DEFN_TABLE_SIZE SLPC_PAGE_SIZE_BYTES
#define SLPC_SHARED_DATA_SIZE_BYTE_MAX (2 * SLPC_PAGE_SIZE_BYTES)
/*
* Cacheline size aligned (Total size needed for
* SLPM_KMD_MAX_OVERRIDE_PARAMETERS=256 is 1088 bytes)
*/
#define SLPC_OVERRIDE_PARAMS_TOTAL_BYTES (((((SLPC_MAX_OVERRIDE_PARAMETERS * 4) \
+ ((SLPC_MAX_OVERRIDE_PARAMETERS / 32) * 4)) \
+ (SLPC_CACHELINE_SIZE_BYTES - 1)) / SLPC_CACHELINE_SIZE_BYTES) * \
SLPC_CACHELINE_SIZE_BYTES)
#define SLPC_SHARED_DATA_SIZE_BYTE_OTHER (SLPC_SHARED_DATA_SIZE_BYTE_MAX - \
(SLPC_SHARED_DATA_SIZE_BYTE_HEADER \
+ SLPC_SHARED_DATA_SIZE_BYTE_PLATFORM_INFO \
+ SLPC_SHARED_DATA_SIZE_BYTE_TASK_STATE \
+ SLPC_OVERRIDE_PARAMS_TOTAL_BYTES \
+ SLPC_SHARED_DATA_MODE_DEFN_TABLE_SIZE))
enum slpc_task_enable {
SLPC_PARAM_TASK_DEFAULT = 0,
SLPC_PARAM_TASK_ENABLED,
SLPC_PARAM_TASK_DISABLED,
SLPC_PARAM_TASK_UNKNOWN
};
enum slpc_global_state {
SLPC_GLOBAL_STATE_NOT_RUNNING = 0,
SLPC_GLOBAL_STATE_INITIALIZING = 1,
SLPC_GLOBAL_STATE_RESETTING = 2,
SLPC_GLOBAL_STATE_RUNNING = 3,
SLPC_GLOBAL_STATE_SHUTTING_DOWN = 4,
SLPC_GLOBAL_STATE_ERROR = 5
};
enum slpc_param_id {
SLPC_PARAM_TASK_ENABLE_GTPERF = 0,
SLPC_PARAM_TASK_DISABLE_GTPERF = 1,
SLPC_PARAM_TASK_ENABLE_BALANCER = 2,
SLPC_PARAM_TASK_DISABLE_BALANCER = 3,
SLPC_PARAM_TASK_ENABLE_DCC = 4,
SLPC_PARAM_TASK_DISABLE_DCC = 5,
SLPC_PARAM_GLOBAL_MIN_GT_UNSLICE_FREQ_MHZ = 6,
SLPC_PARAM_GLOBAL_MAX_GT_UNSLICE_FREQ_MHZ = 7,
SLPC_PARAM_GLOBAL_MIN_GT_SLICE_FREQ_MHZ = 8,
SLPC_PARAM_GLOBAL_MAX_GT_SLICE_FREQ_MHZ = 9,
SLPC_PARAM_GTPERF_THRESHOLD_MAX_FPS = 10,
SLPC_PARAM_GLOBAL_DISABLE_GT_FREQ_MANAGEMENT = 11,
SLPC_PARAM_GTPERF_ENABLE_FRAMERATE_STALLING = 12,
SLPC_PARAM_GLOBAL_DISABLE_RC6_MODE_CHANGE = 13,
SLPC_PARAM_GLOBAL_OC_UNSLICE_FREQ_MHZ = 14,
SLPC_PARAM_GLOBAL_OC_SLICE_FREQ_MHZ = 15,
SLPC_PARAM_GLOBAL_ENABLE_IA_GT_BALANCING = 16,
SLPC_PARAM_GLOBAL_ENABLE_ADAPTIVE_BURST_TURBO = 17,
SLPC_PARAM_GLOBAL_ENABLE_EVAL_MODE = 18,
SLPC_PARAM_GLOBAL_ENABLE_BALANCER_IN_NON_GAMING_MODE = 19,
SLPC_PARAM_GLOBAL_RT_MODE_TURBO_FREQ_DELTA_MHZ = 20,
SLPC_PARAM_PWRGATE_RC_MODE = 21,
SLPC_PARAM_EDR_MODE_COMPUTE_TIMEOUT_MS = 22,
SLPC_PARAM_EDR_QOS_FREQ_MHZ = 23,
SLPC_PARAM_MEDIA_FF_RATIO_MODE = 24,
SLPC_PARAM_ENABLE_IA_FREQ_LIMITING = 25,
SLPC_PARAM_STRATEGIES = 26,
SLPC_PARAM_POWER_PROFILE = 27,
SLPC_PARAM_IGNORE_EFFICIENT_FREQUENCY = 28,
SLPC_MAX_PARAM = 32,
};
enum slpc_media_ratio_mode {
SLPC_MEDIA_RATIO_MODE_DYNAMIC_CONTROL = 0,
SLPC_MEDIA_RATIO_MODE_FIXED_ONE_TO_ONE = 1,
SLPC_MEDIA_RATIO_MODE_FIXED_ONE_TO_TWO = 2,
};
enum slpc_gucrc_mode {
SLPC_GUCRC_MODE_HW = 0,
SLPC_GUCRC_MODE_GUCRC_NO_RC6 = 1,
SLPC_GUCRC_MODE_GUCRC_STATIC_TIMEOUT = 2,
SLPC_GUCRC_MODE_GUCRC_DYNAMIC_HYSTERESIS = 3,
SLPC_GUCRC_MODE_MAX,
};
enum slpc_event_id {
SLPC_EVENT_RESET = 0,
SLPC_EVENT_SHUTDOWN = 1,
SLPC_EVENT_PLATFORM_INFO_CHANGE = 2,
SLPC_EVENT_DISPLAY_MODE_CHANGE = 3,
SLPC_EVENT_FLIP_COMPLETE = 4,
SLPC_EVENT_QUERY_TASK_STATE = 5,
SLPC_EVENT_PARAMETER_SET = 6,
SLPC_EVENT_PARAMETER_UNSET = 7,
};
struct slpc_task_state_data {
union {
u32 task_status_padding;
struct {
u32 status;
#define SLPC_GTPERF_TASK_ENABLED REG_BIT(0)
#define SLPC_DCC_TASK_ENABLED REG_BIT(11)
#define SLPC_IN_DCC REG_BIT(12)
#define SLPC_BALANCER_ENABLED REG_BIT(15)
#define SLPC_IBC_TASK_ENABLED REG_BIT(16)
#define SLPC_BALANCER_IA_LMT_ENABLED REG_BIT(17)
#define SLPC_BALANCER_IA_LMT_ACTIVE REG_BIT(18)
};
};
union {
u32 freq_padding;
struct {
#define SLPC_MAX_UNSLICE_FREQ_MASK REG_GENMASK(7, 0)
#define SLPC_MIN_UNSLICE_FREQ_MASK REG_GENMASK(15, 8)
#define SLPC_MAX_SLICE_FREQ_MASK REG_GENMASK(23, 16)
#define SLPC_MIN_SLICE_FREQ_MASK REG_GENMASK(31, 24)
u32 freq;
};
};
} __packed;
struct slpc_shared_data_header {
/* Total size in bytes of this shared buffer. */
u32 size;
u32 global_state;
u32 display_data_addr;
} __packed;
struct slpc_override_params {
u32 bits[SLPC_OVERRIDE_BITFIELD_SIZE];
u32 values[SLPC_MAX_OVERRIDE_PARAMETERS];
} __packed;
struct slpc_shared_data {
struct slpc_shared_data_header header;
u8 shared_data_header_pad[SLPC_SHARED_DATA_SIZE_BYTE_HEADER -
sizeof(struct slpc_shared_data_header)];
u8 platform_info_pad[SLPC_SHARED_DATA_SIZE_BYTE_PLATFORM_INFO];
struct slpc_task_state_data task_state_data;
u8 task_state_data_pad[SLPC_SHARED_DATA_SIZE_BYTE_TASK_STATE -
sizeof(struct slpc_task_state_data)];
struct slpc_override_params override_params;
u8 override_params_pad[SLPC_OVERRIDE_PARAMS_TOTAL_BYTES -
sizeof(struct slpc_override_params)];
u8 shared_data_pad[SLPC_SHARED_DATA_SIZE_BYTE_OTHER];
/* PAGE 2 (4096 bytes), mode based parameter will be removed soon */
u8 reserved_mode_definition[4096];
} __packed;
struct slpc_context_frequency_request {
u32 frequency_request:16;
u32 reserved:12;
u32 is_compute:1;
u32 ignore_busyness:1;
u32 is_minimum:1;
u32 is_predefined:1;
} __packed;
#define SLPC_CTX_FREQ_REQ_IS_COMPUTE REG_BIT(28)
struct slpc_optimized_strategies {
u32 compute:1;
u32 async_flip:1;
u32 media:1;
u32 vsync_flip:1;
u32 reserved:28;
} __packed;
#define SLPC_OPTIMIZED_STRATEGY_COMPUTE REG_BIT(0)
enum slpc_power_profiles {
SLPC_POWER_PROFILES_BASE = 0x0,
SLPC_POWER_PROFILES_POWER_SAVING = 0x1
};
/**
* DOC: SLPC H2G MESSAGE FORMAT
*
* +---+-------+--------------------------------------------------------------+
* | | Bits | Description |
* +===+=======+==============================================================+
* | 0 | 31 | ORIGIN = GUC_HXG_ORIGIN_HOST_ |
* | +-------+--------------------------------------------------------------+
* | | 30:28 | TYPE = GUC_HXG_TYPE_REQUEST_ |
* | +-------+--------------------------------------------------------------+
* | | 27:16 | DATA0 = MBZ |
* | +-------+--------------------------------------------------------------+
* | | 15:0 | ACTION = _`GUC_ACTION_HOST2GUC_PC_SLPM_REQUEST` = 0x3003 |
* +---+-------+--------------------------------------------------------------+
* | 1 | 31:8 | **EVENT_ID** |
* + +-------+--------------------------------------------------------------+
* | | 7:0 | **EVENT_ARGC** - number of data arguments |
* +---+-------+--------------------------------------------------------------+
* | 2 | 31:0 | **EVENT_DATA1** |
* +---+-------+--------------------------------------------------------------+
* |...| 31:0 | ... |
* +---+-------+--------------------------------------------------------------+
* |2+n| 31:0 | **EVENT_DATAn** |
* +---+-------+--------------------------------------------------------------+
*/
#define GUC_ACTION_HOST2GUC_PC_SLPC_REQUEST 0x3003
#define HOST2GUC_PC_SLPC_REQUEST_MSG_MIN_LEN \
(GUC_HXG_REQUEST_MSG_MIN_LEN + 1u)
#define HOST2GUC_PC_SLPC_EVENT_MAX_INPUT_ARGS 9
#define HOST2GUC_PC_SLPC_REQUEST_MSG_MAX_LEN \
(HOST2GUC_PC_SLPC_REQUEST_REQUEST_MSG_MIN_LEN + \
HOST2GUC_PC_SLPC_EVENT_MAX_INPUT_ARGS)
#define HOST2GUC_PC_SLPC_REQUEST_MSG_0_MBZ GUC_HXG_REQUEST_MSG_0_DATA0
#define HOST2GUC_PC_SLPC_REQUEST_MSG_1_EVENT_ID (0xff << 8)
#define HOST2GUC_PC_SLPC_REQUEST_MSG_1_EVENT_ARGC (0xff << 0)
#define HOST2GUC_PC_SLPC_REQUEST_MSG_N_EVENT_DATA_N GUC_HXG_REQUEST_MSG_n_DATAn
#endif
@@ -0,0 +1,170 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2014-2021 Intel Corporation
*/
#ifndef _ABI_GUC_COMMUNICATION_CTB_ABI_H
#define _ABI_GUC_COMMUNICATION_CTB_ABI_H
#include <linux/types.h>
#include <linux/build_bug.h>
#include "guc_messages_abi.h"
/**
* DOC: CT Buffer
*
* Circular buffer used to send `CTB Message`_
*/
/**
* DOC: CTB Descriptor
*
* +---+-------+--------------------------------------------------------------+
* | | Bits | Description |
* +===+=======+==============================================================+
* | 0 | 31:0 | **HEAD** - offset (in dwords) to the last dword that was |
* | | | read from the `CT Buffer`_. |
* | | | It can only be updated by the receiver. |
* +---+-------+--------------------------------------------------------------+
* | 1 | 31:0 | **TAIL** - offset (in dwords) to the last dword that was |
* | | | written to the `CT Buffer`_. |
* | | | It can only be updated by the sender. |
* +---+-------+--------------------------------------------------------------+
* | 2 | 31:0 | **STATUS** - status of the CTB |
* | | | |
* | | | - _`GUC_CTB_STATUS_NO_ERROR` = 0 (normal operation) |
* | | | - _`GUC_CTB_STATUS_OVERFLOW` = 1 (head/tail too large) |
* | | | - _`GUC_CTB_STATUS_UNDERFLOW` = 2 (truncated message) |
* | | | - _`GUC_CTB_STATUS_MISMATCH` = 4 (head/tail modified) |
* | | | - _`GUC_CTB_STATUS_UNUSED` = 8 (CTB is not in use) |
* +---+-------+--------------------------------------------------------------+
* |...| | RESERVED = MBZ |
* +---+-------+--------------------------------------------------------------+
* | 15| 31:0 | RESERVED = MBZ |
* +---+-------+--------------------------------------------------------------+
*/
struct guc_ct_buffer_desc {
u32 head;
u32 tail;
u32 status;
#define GUC_CTB_STATUS_NO_ERROR 0
#define GUC_CTB_STATUS_OVERFLOW BIT(0)
#define GUC_CTB_STATUS_UNDERFLOW BIT(1)
#define GUC_CTB_STATUS_MISMATCH BIT(2)
#define GUC_CTB_STATUS_UNUSED BIT(3)
u32 reserved[13];
} __packed;
static_assert(sizeof(struct guc_ct_buffer_desc) == 64);
/**
* DOC: CTB Message
*
* +---+-------+--------------------------------------------------------------+
* | | Bits | Description |
* +===+=======+==============================================================+
* | 0 | 31:16 | **FENCE** - message identifier |
* | +-------+--------------------------------------------------------------+
* | | 15:12 | **FORMAT** - format of the CTB message |
* | | | - _`GUC_CTB_FORMAT_HXG` = 0 - see `CTB HXG Message`_ |
* | +-------+--------------------------------------------------------------+
* | | 11:8 | **RESERVED** |
* | +-------+--------------------------------------------------------------+
* | | 7:0 | **NUM_DWORDS** - length of the CTB message (w/o header) |
* +---+-------+--------------------------------------------------------------+
* | 1 | 31:0 | optional (depends on FORMAT) |
* +---+-------+ |
* |...| | |
* +---+-------+ |
* | n | 31:0 | |
* +---+-------+--------------------------------------------------------------+
*/
#define GUC_CTB_HDR_LEN 1u
#define GUC_CTB_MSG_MIN_LEN GUC_CTB_HDR_LEN
#define GUC_CTB_MSG_MAX_LEN 256u
#define GUC_CTB_MSG_0_FENCE (0xffffU << 16)
#define GUC_CTB_MSG_0_FORMAT (0xf << 12)
#define GUC_CTB_FORMAT_HXG 0u
#define GUC_CTB_MSG_0_RESERVED (0xf << 8)
#define GUC_CTB_MSG_0_NUM_DWORDS (0xff << 0)
/**
* DOC: CTB HXG Message
*
* +---+-------+--------------------------------------------------------------+
* | | Bits | Description |
* +===+=======+==============================================================+
* | 0 | 31:16 | FENCE |
* | +-------+--------------------------------------------------------------+
* | | 15:12 | FORMAT = GUC_CTB_FORMAT_HXG_ |
* | +-------+--------------------------------------------------------------+
* | | 11:8 | RESERVED = MBZ |
* | +-------+--------------------------------------------------------------+
* | | 7:0 | NUM_DWORDS = length (in dwords) of the embedded HXG message |
* +---+-------+--------------------------------------------------------------+
* | 1 | 31:0 | |
* +---+-------+ |
* |...| | [Embedded `HXG Message`_] |
* +---+-------+ |
* | n | 31:0 | |
* +---+-------+--------------------------------------------------------------+
*/
#define GUC_CTB_HXG_MSG_MIN_LEN (GUC_CTB_MSG_MIN_LEN + GUC_HXG_MSG_MIN_LEN)
#define GUC_CTB_HXG_MSG_MAX_LEN GUC_CTB_MSG_MAX_LEN
/**
* DOC: CTB based communication
*
* The CTB (command transport buffer) communication between Host and GuC
* is based on u32 data stream written to the shared buffer. One buffer can
* be used to transmit data only in one direction (one-directional channel).
*
* Current status of the each buffer is stored in the buffer descriptor.
* Buffer descriptor holds tail and head fields that represents active data
* stream. The tail field is updated by the data producer (sender), and head
* field is updated by the data consumer (receiver)::
*
* +------------+
* | DESCRIPTOR | +=================+============+========+
* +============+ | | MESSAGE(s) | |
* | address |--------->+=================+============+========+
* +------------+
* | head | ^-----head--------^
* +------------+
* | tail | ^---------tail-----------------^
* +------------+
* | size | ^---------------size--------------------^
* +------------+
*
* Each message in data stream starts with the single u32 treated as a header,
* followed by optional set of u32 data that makes message specific payload::
*
* +------------+---------+---------+---------+
* | MESSAGE |
* +------------+---------+---------+---------+
* | msg[0] | [1] | ... | [n-1] |
* +------------+---------+---------+---------+
* | MESSAGE | MESSAGE PAYLOAD |
* + HEADER +---------+---------+---------+
* | | 0 | ... | n |
* +======+=====+=========+=========+=========+
* | 31:16| code| | | |
* +------+-----+ | | |
* | 15:5|flags| | | |
* +------+-----+ | | |
* | 4:0| len| | | |
* +------+-----+---------+---------+---------+
*
* ^-------------len-------------^
*
* The message header consists of:
*
* - **len**, indicates length of the message payload (in u32)
* - **code**, indicates message code
* - **flags**, holds various bits to control message handling
*/
#endif /* _ABI_GUC_COMMUNICATION_CTB_ABI_H */
@@ -0,0 +1,49 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2014-2021 Intel Corporation
*/
#ifndef _ABI_GUC_COMMUNICATION_MMIO_ABI_H
#define _ABI_GUC_COMMUNICATION_MMIO_ABI_H
/**
* DOC: GuC MMIO based communication
*
* The MMIO based communication between Host and GuC relies on special
* hardware registers which format could be defined by the software
* (so called scratch registers).
*
* Each MMIO based message, both Host to GuC (H2G) and GuC to Host (G2H)
* messages, which maximum length depends on number of available scratch
* registers, is directly written into those scratch registers.
*
* For Gen9+, there are 16 software scratch registers 0xC180-0xC1B8,
* but no H2G command takes more than 4 parameters and the GuC firmware
* itself uses an 4-element array to store the H2G message.
*
* For Gen11+, there are additional 4 registers 0x190240-0x19024C, which
* are, regardless on lower count, preferred over legacy ones.
*
* The MMIO based communication is mainly used during driver initialization
* phase to setup the `CTB based communication`_ that will be used afterwards.
*/
#define GUC_MAX_MMIO_MSG_LEN 4
/**
* DOC: MMIO HXG Message
*
* Format of the MMIO messages follows definitions of `HXG Message`_.
*
* +---+-------+--------------------------------------------------------------+
* | | Bits | Description |
* +===+=======+==============================================================+
* | 0 | 31:0 | |
* +---+-------+ |
* |...| | [Embedded `HXG Message`_] |
* +---+-------+ |
* | n | 31:0 | |
* +---+-------+--------------------------------------------------------------+
*/
#endif /* _ABI_GUC_COMMUNICATION_MMIO_ABI_H */
@@ -0,0 +1,60 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2014-2021 Intel Corporation
*/
#ifndef _ABI_GUC_ERRORS_ABI_H
#define _ABI_GUC_ERRORS_ABI_H
enum intel_guc_response_status {
INTEL_GUC_RESPONSE_STATUS_SUCCESS = 0x0,
INTEL_GUC_RESPONSE_NOT_SUPPORTED = 0x20,
INTEL_GUC_RESPONSE_NO_ATTRIBUTE_TABLE = 0x201,
INTEL_GUC_RESPONSE_NO_DECRYPTION_KEY = 0x202,
INTEL_GUC_RESPONSE_DECRYPTION_FAILED = 0x204,
INTEL_GUC_RESPONSE_STATUS_GENERIC_FAIL = 0xF000,
};
enum intel_guc_load_status {
INTEL_GUC_LOAD_STATUS_DEFAULT = 0x00,
INTEL_GUC_LOAD_STATUS_START = 0x01,
INTEL_GUC_LOAD_STATUS_ERROR_DEVID_BUILD_MISMATCH = 0x02,
INTEL_GUC_LOAD_STATUS_GUC_PREPROD_BUILD_MISMATCH = 0x03,
INTEL_GUC_LOAD_STATUS_ERROR_DEVID_INVALID_GUCTYPE = 0x04,
INTEL_GUC_LOAD_STATUS_HWCONFIG_START = 0x05,
INTEL_GUC_LOAD_STATUS_HWCONFIG_DONE = 0x06,
INTEL_GUC_LOAD_STATUS_HWCONFIG_ERROR = 0x07,
INTEL_GUC_LOAD_STATUS_GDT_DONE = 0x10,
INTEL_GUC_LOAD_STATUS_IDT_DONE = 0x20,
INTEL_GUC_LOAD_STATUS_LAPIC_DONE = 0x30,
INTEL_GUC_LOAD_STATUS_GUCINT_DONE = 0x40,
INTEL_GUC_LOAD_STATUS_DPC_READY = 0x50,
INTEL_GUC_LOAD_STATUS_DPC_ERROR = 0x60,
INTEL_GUC_LOAD_STATUS_EXCEPTION = 0x70,
INTEL_GUC_LOAD_STATUS_INIT_DATA_INVALID = 0x71,
INTEL_GUC_LOAD_STATUS_PXP_TEARDOWN_CTRL_ENABLED = 0x72,
INTEL_GUC_LOAD_STATUS_INVALID_INIT_DATA_RANGE_START,
INTEL_GUC_LOAD_STATUS_MPU_DATA_INVALID = 0x73,
INTEL_GUC_LOAD_STATUS_INIT_MMIO_SAVE_RESTORE_INVALID = 0x74,
INTEL_GUC_LOAD_STATUS_KLV_WORKAROUND_INIT_ERROR = 0x75,
INTEL_GUC_LOAD_STATUS_INVALID_INIT_DATA_RANGE_END,
INTEL_GUC_LOAD_STATUS_READY = 0xF0,
};
enum intel_bootrom_load_status {
INTEL_BOOTROM_STATUS_NO_KEY_FOUND = 0x13,
INTEL_BOOTROM_STATUS_AES_PROD_KEY_FOUND = 0x1A,
INTEL_BOOTROM_STATUS_PROD_KEY_CHECK_FAILURE = 0x2B,
INTEL_BOOTROM_STATUS_RSA_FAILED = 0x50,
INTEL_BOOTROM_STATUS_PAVPC_FAILED = 0x73,
INTEL_BOOTROM_STATUS_WOPCM_FAILED = 0x74,
INTEL_BOOTROM_STATUS_LOADLOC_FAILED = 0x75,
INTEL_BOOTROM_STATUS_JUMP_PASSED = 0x76,
INTEL_BOOTROM_STATUS_JUMP_FAILED = 0x77,
INTEL_BOOTROM_STATUS_RC6CTXCONFIG_FAILED = 0x79,
INTEL_BOOTROM_STATUS_MPUMAP_INCORRECT = 0x7A,
INTEL_BOOTROM_STATUS_EXCEPTION = 0x7E,
};
#endif /* _ABI_GUC_ERRORS_ABI_H */
@@ -0,0 +1,113 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2021 Intel Corporation
*/
#ifndef _ABI_GUC_KLVS_ABI_H
#define _ABI_GUC_KLVS_ABI_H
#include <linux/types.h>
/**
* DOC: GuC KLV
*
* +---+-------+--------------------------------------------------------------+
* | | Bits | Description |
* +===+=======+==============================================================+
* | 0 | 31:16 | **KEY** - KLV key identifier |
* | | | - `GuC Self Config KLVs`_ |
* | | | |
* | +-------+--------------------------------------------------------------+
* | | 15:0 | **LEN** - length of VALUE (in 32bit dwords) |
* +---+-------+--------------------------------------------------------------+
* | 1 | 31:0 | **VALUE** - actual value of the KLV (format depends on KEY) |
* +---+-------+ |
* |...| | |
* +---+-------+ |
* | n | 31:0 | |
* +---+-------+--------------------------------------------------------------+
*/
#define GUC_KLV_LEN_MIN 1u
#define GUC_KLV_0_KEY (0xffffu << 16)
#define GUC_KLV_0_LEN (0xffffu << 0)
#define GUC_KLV_n_VALUE (0xffffffffu << 0)
/**
* DOC: GuC Self Config KLVs
*
* `GuC KLV`_ keys available for use with HOST2GUC_SELF_CFG_.
*
* _`GUC_KLV_SELF_CFG_H2G_CTB_ADDR` : 0x0902
* Refers to 64 bit Global Gfx address of H2G `CT Buffer`_.
* Should be above WOPCM address but below APIC base address for native mode.
*
* _`GUC_KLV_SELF_CFG_H2G_CTB_DESCRIPTOR_ADDR` : 0x0903
* Refers to 64 bit Global Gfx address of H2G `CTB Descriptor`_.
* Should be above WOPCM address but below APIC base address for native mode.
*
* _`GUC_KLV_SELF_CFG_H2G_CTB_SIZE` : 0x0904
* Refers to size of H2G `CT Buffer`_ in bytes.
* Should be a multiple of 4K.
*
* _`GUC_KLV_SELF_CFG_G2H_CTB_ADDR` : 0x0905
* Refers to 64 bit Global Gfx address of G2H `CT Buffer`_.
* Should be above WOPCM address but below APIC base address for native mode.
*
* _`GUC_KLV_SELF_CFG_G2H_CTB_DESCRIPTOR_ADDR` : 0x0906
* Refers to 64 bit Global Gfx address of G2H `CTB Descriptor`_.
* Should be above WOPCM address but below APIC base address for native mode.
*
* _`GUC_KLV_SELF_CFG_G2H_CTB_SIZE` : 0x0907
* Refers to size of G2H `CT Buffer`_ in bytes.
* Should be a multiple of 4K.
*/
#define GUC_KLV_SELF_CFG_H2G_CTB_ADDR_KEY 0x0902
#define GUC_KLV_SELF_CFG_H2G_CTB_ADDR_LEN 2u
#define GUC_KLV_SELF_CFG_H2G_CTB_DESCRIPTOR_ADDR_KEY 0x0903
#define GUC_KLV_SELF_CFG_H2G_CTB_DESCRIPTOR_ADDR_LEN 2u
#define GUC_KLV_SELF_CFG_H2G_CTB_SIZE_KEY 0x0904
#define GUC_KLV_SELF_CFG_H2G_CTB_SIZE_LEN 1u
#define GUC_KLV_SELF_CFG_G2H_CTB_ADDR_KEY 0x0905
#define GUC_KLV_SELF_CFG_G2H_CTB_ADDR_LEN 2u
#define GUC_KLV_SELF_CFG_G2H_CTB_DESCRIPTOR_ADDR_KEY 0x0906
#define GUC_KLV_SELF_CFG_G2H_CTB_DESCRIPTOR_ADDR_LEN 2u
#define GUC_KLV_SELF_CFG_G2H_CTB_SIZE_KEY 0x0907
#define GUC_KLV_SELF_CFG_G2H_CTB_SIZE_LEN 1u
/*
* Global scheduling policy update keys.
*/
enum {
GUC_SCHEDULING_POLICIES_KLV_ID_RENDER_COMPUTE_YIELD = 0x1001,
};
/*
* Per context scheduling policy update keys.
*/
enum {
GUC_CONTEXT_POLICIES_KLV_ID_EXECUTION_QUANTUM = 0x2001,
GUC_CONTEXT_POLICIES_KLV_ID_PREEMPTION_TIMEOUT = 0x2002,
GUC_CONTEXT_POLICIES_KLV_ID_SCHEDULING_PRIORITY = 0x2003,
GUC_CONTEXT_POLICIES_KLV_ID_PREEMPT_TO_IDLE_ON_QUANTUM_EXPIRY = 0x2004,
GUC_CONTEXT_POLICIES_KLV_ID_SLPM_GT_FREQUENCY = 0x2005,
GUC_CONTEXT_POLICIES_KLV_NUM_IDS = 5,
};
/*
* Workaround keys:
*/
enum {
GUC_WORKAROUND_KLV_SERIALIZED_RA_MODE = 0x9001,
GUC_WORKAROUND_KLV_BLOCK_INTERRUPTS_WHEN_MGSR_BLOCKED = 0x9002,
GUC_WORKAROUND_KLV_AVOID_GFX_CLEAR_WHILE_ACTIVE = 0x9006,
};
#endif /* _ABI_GUC_KLVS_ABI_H */
@@ -0,0 +1,264 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2014-2021 Intel Corporation
*/
#ifndef _ABI_GUC_MESSAGES_ABI_H
#define _ABI_GUC_MESSAGES_ABI_H
/**
* DOC: HXG Message
*
* All messages exchanged with GuC are defined using 32 bit dwords.
* First dword is treated as a message header. Remaining dwords are optional.
*
* +---+-------+--------------------------------------------------------------+
* | | Bits | Description |
* +===+=======+==============================================================+
* | | | |
* | 0 | 31 | **ORIGIN** - originator of the message |
* | | | - _`GUC_HXG_ORIGIN_HOST` = 0 |
* | | | - _`GUC_HXG_ORIGIN_GUC` = 1 |
* | | | |
* | +-------+--------------------------------------------------------------+
* | | 30:28 | **TYPE** - message type |
* | | | - _`GUC_HXG_TYPE_REQUEST` = 0 |
* | | | - _`GUC_HXG_TYPE_EVENT` = 1 |
* | | | - _`GUC_HXG_TYPE_FAST_REQUEST` = 2 |
* | | | - _`GUC_HXG_TYPE_NO_RESPONSE_BUSY` = 3 |
* | | | - _`GUC_HXG_TYPE_NO_RESPONSE_RETRY` = 5 |
* | | | - _`GUC_HXG_TYPE_RESPONSE_FAILURE` = 6 |
* | | | - _`GUC_HXG_TYPE_RESPONSE_SUCCESS` = 7 |
* | +-------+--------------------------------------------------------------+
* | | 27:0 | **AUX** - auxiliary data (depends on TYPE) |
* +---+-------+--------------------------------------------------------------+
* | 1 | 31:0 | |
* +---+-------+ |
* |...| | **PAYLOAD** - optional payload (depends on TYPE) |
* +---+-------+ |
* | n | 31:0 | |
* +---+-------+--------------------------------------------------------------+
*/
#define GUC_HXG_MSG_MIN_LEN 1u
#define GUC_HXG_MSG_0_ORIGIN (0x1U << 31)
#define GUC_HXG_ORIGIN_HOST 0u
#define GUC_HXG_ORIGIN_GUC 1u
#define GUC_HXG_MSG_0_TYPE (0x7 << 28)
#define GUC_HXG_TYPE_REQUEST 0u
#define GUC_HXG_TYPE_EVENT 1u
#define GUC_HXG_TYPE_FAST_REQUEST 2u
#define GUC_HXG_TYPE_NO_RESPONSE_BUSY 3u
#define GUC_HXG_TYPE_NO_RESPONSE_RETRY 5u
#define GUC_HXG_TYPE_RESPONSE_FAILURE 6u
#define GUC_HXG_TYPE_RESPONSE_SUCCESS 7u
#define GUC_HXG_MSG_0_AUX (0xfffffff << 0)
#define GUC_HXG_MSG_n_PAYLOAD (0xffffffff << 0)
/**
* DOC: HXG Request
*
* The `HXG Request`_ message should be used to initiate synchronous activity
* for which confirmation or return data is expected.
*
* The recipient of this message shall use `HXG Response`_, `HXG Failure`_
* or `HXG Retry`_ message as a definite reply, and may use `HXG Busy`_
* message as a intermediate reply.
*
* Format of @DATA0 and all @DATAn fields depends on the @ACTION code.
*
* +---+-------+--------------------------------------------------------------+
* | | Bits | Description |
* +===+=======+==============================================================+
* | 0 | 31 | ORIGIN |
* | +-------+--------------------------------------------------------------+
* | | 30:28 | TYPE = GUC_HXG_TYPE_REQUEST_ |
* | +-------+--------------------------------------------------------------+
* | | 27:16 | **DATA0** - request data (depends on ACTION) |
* | +-------+--------------------------------------------------------------+
* | | 15:0 | **ACTION** - requested action code |
* +---+-------+--------------------------------------------------------------+
* | 1 | 31:0 | |
* +---+-------+ |
* |...| | **DATAn** - optional data (depends on ACTION) |
* +---+-------+ |
* | n | 31:0 | |
* +---+-------+--------------------------------------------------------------+
*/
#define GUC_HXG_REQUEST_MSG_MIN_LEN GUC_HXG_MSG_MIN_LEN
#define GUC_HXG_REQUEST_MSG_0_DATA0 (0xfff << 16)
#define GUC_HXG_REQUEST_MSG_0_ACTION (0xffff << 0)
#define GUC_HXG_REQUEST_MSG_n_DATAn GUC_HXG_MSG_n_PAYLOAD
/**
* DOC: HXG Fast Request
*
* The `HXG Request`_ message should be used to initiate asynchronous activity
* for which confirmation or return data is not expected.
*
* If confirmation is required then `HXG Request`_ shall be used instead.
*
* The recipient of this message may only use `HXG Failure`_ message if it was
* unable to accept this request (like invalid data).
*
* Format of `HXG Fast Request`_ message is same as `HXG Request`_ except @TYPE.
*
* +---+-------+--------------------------------------------------------------+
* | | Bits | Description |
* +===+=======+==============================================================+
* | 0 | 31 | ORIGIN - see `HXG Message`_ |
* | +-------+--------------------------------------------------------------+
* | | 30:28 | TYPE = `GUC_HXG_TYPE_FAST_REQUEST`_ |
* | +-------+--------------------------------------------------------------+
* | | 27:16 | DATA0 - see `HXG Request`_ |
* | +-------+--------------------------------------------------------------+
* | | 15:0 | ACTION - see `HXG Request`_ |
* +---+-------+--------------------------------------------------------------+
* |...| | DATAn - see `HXG Request`_ |
* +---+-------+--------------------------------------------------------------+
*/
/**
* DOC: HXG Event
*
* The `HXG Event`_ message should be used to initiate asynchronous activity
* that does not involves immediate confirmation nor data.
*
* Format of @DATA0 and all @DATAn fields depends on the @ACTION code.
*
* +---+-------+--------------------------------------------------------------+
* | | Bits | Description |
* +===+=======+==============================================================+
* | 0 | 31 | ORIGIN |
* | +-------+--------------------------------------------------------------+
* | | 30:28 | TYPE = GUC_HXG_TYPE_EVENT_ |
* | +-------+--------------------------------------------------------------+
* | | 27:16 | **DATA0** - event data (depends on ACTION) |
* | +-------+--------------------------------------------------------------+
* | | 15:0 | **ACTION** - event action code |
* +---+-------+--------------------------------------------------------------+
* | 1 | 31:0 | |
* +---+-------+ |
* |...| | **DATAn** - optional event data (depends on ACTION) |
* +---+-------+ |
* | n | 31:0 | |
* +---+-------+--------------------------------------------------------------+
*/
#define GUC_HXG_EVENT_MSG_MIN_LEN GUC_HXG_MSG_MIN_LEN
#define GUC_HXG_EVENT_MSG_0_DATA0 (0xfff << 16)
#define GUC_HXG_EVENT_MSG_0_ACTION (0xffff << 0)
#define GUC_HXG_EVENT_MSG_n_DATAn GUC_HXG_MSG_n_PAYLOAD
/**
* DOC: HXG Busy
*
* The `HXG Busy`_ message may be used to acknowledge reception of the `HXG Request`_
* message if the recipient expects that it processing will be longer than default
* timeout.
*
* The @COUNTER field may be used as a progress indicator.
*
* +---+-------+--------------------------------------------------------------+
* | | Bits | Description |
* +===+=======+==============================================================+
* | 0 | 31 | ORIGIN |
* | +-------+--------------------------------------------------------------+
* | | 30:28 | TYPE = GUC_HXG_TYPE_NO_RESPONSE_BUSY_ |
* | +-------+--------------------------------------------------------------+
* | | 27:0 | **COUNTER** - progress indicator |
* +---+-------+--------------------------------------------------------------+
*/
#define GUC_HXG_BUSY_MSG_LEN GUC_HXG_MSG_MIN_LEN
#define GUC_HXG_BUSY_MSG_0_COUNTER GUC_HXG_MSG_0_AUX
/**
* DOC: HXG Retry
*
* The `HXG Retry`_ message should be used by recipient to indicate that the
* `HXG Request`_ message was dropped and it should be resent again.
*
* The @REASON field may be used to provide additional information.
*
* +---+-------+--------------------------------------------------------------+
* | | Bits | Description |
* +===+=======+==============================================================+
* | 0 | 31 | ORIGIN |
* | +-------+--------------------------------------------------------------+
* | | 30:28 | TYPE = GUC_HXG_TYPE_NO_RESPONSE_RETRY_ |
* | +-------+--------------------------------------------------------------+
* | | 27:0 | **REASON** - reason for retry |
* | | | - _`GUC_HXG_RETRY_REASON_UNSPECIFIED` = 0 |
* +---+-------+--------------------------------------------------------------+
*/
#define GUC_HXG_RETRY_MSG_LEN GUC_HXG_MSG_MIN_LEN
#define GUC_HXG_RETRY_MSG_0_REASON GUC_HXG_MSG_0_AUX
#define GUC_HXG_RETRY_REASON_UNSPECIFIED 0u
/**
* DOC: HXG Failure
*
* The `HXG Failure`_ message shall be used as a reply to the `HXG Request`_
* message that could not be processed due to an error.
*
* +---+-------+--------------------------------------------------------------+
* | | Bits | Description |
* +===+=======+==============================================================+
* | 0 | 31 | ORIGIN |
* | +-------+--------------------------------------------------------------+
* | | 30:28 | TYPE = GUC_HXG_TYPE_RESPONSE_FAILURE_ |
* | +-------+--------------------------------------------------------------+
* | | 27:16 | **HINT** - additional error hint |
* | +-------+--------------------------------------------------------------+
* | | 15:0 | **ERROR** - error/result code |
* +---+-------+--------------------------------------------------------------+
*/
#define GUC_HXG_FAILURE_MSG_LEN GUC_HXG_MSG_MIN_LEN
#define GUC_HXG_FAILURE_MSG_0_HINT (0xfff << 16)
#define GUC_HXG_FAILURE_MSG_0_ERROR (0xffff << 0)
/**
* DOC: HXG Response
*
* The `HXG Response`_ message shall be used as a reply to the `HXG Request`_
* message that was successfully processed without an error.
*
* +---+-------+--------------------------------------------------------------+
* | | Bits | Description |
* +===+=======+==============================================================+
* | 0 | 31 | ORIGIN |
* | +-------+--------------------------------------------------------------+
* | | 30:28 | TYPE = GUC_HXG_TYPE_RESPONSE_SUCCESS_ |
* | +-------+--------------------------------------------------------------+
* | | 27:0 | **DATA0** - data (depends on ACTION from `HXG Request`_) |
* +---+-------+--------------------------------------------------------------+
* | 1 | 31:0 | |
* +---+-------+ |
* |...| | **DATAn** - data (depends on ACTION from `HXG Request`_) |
* +---+-------+ |
* | n | 31:0 | |
* +---+-------+--------------------------------------------------------------+
*/
#define GUC_HXG_RESPONSE_MSG_MIN_LEN GUC_HXG_MSG_MIN_LEN
#define GUC_HXG_RESPONSE_MSG_0_DATA0 GUC_HXG_MSG_0_AUX
#define GUC_HXG_RESPONSE_MSG_n_DATAn GUC_HXG_MSG_n_PAYLOAD
/* deprecated */
#define INTEL_GUC_MSG_TYPE_SHIFT 28
#define INTEL_GUC_MSG_TYPE_MASK (0xF << INTEL_GUC_MSG_TYPE_SHIFT)
#define INTEL_GUC_MSG_DATA_SHIFT 16
#define INTEL_GUC_MSG_DATA_MASK (0xFFF << INTEL_GUC_MSG_DATA_SHIFT)
#define INTEL_GUC_MSG_CODE_SHIFT 0
#define INTEL_GUC_MSG_CODE_MASK (0xFFFF << INTEL_GUC_MSG_CODE_SHIFT)
enum intel_guc_msg_type {
INTEL_GUC_MSG_TYPE_REQUEST = 0x0,
INTEL_GUC_MSG_TYPE_RESPONSE = 0xF,
};
#endif /* _ABI_GUC_MESSAGES_ABI_H */
@@ -0,0 +1,226 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2021-2022 Intel Corporation
*/
#ifndef _INTEL_GUC_CAPTURE_FWIF_H
#define _INTEL_GUC_CAPTURE_FWIF_H
#include <linux/types.h>
#include "intel_guc_fwif.h"
struct intel_guc;
struct file;
/*
* struct __guc_capture_bufstate
*
* Book-keeping structure used to track read and write pointers
* as we extract error capture data from the GuC-log-buffer's
* error-capture region as a stream of dwords.
*/
struct __guc_capture_bufstate {
u32 size;
void *data;
u32 rd;
u32 wr;
};
/*
* struct __guc_capture_parsed_output - extracted error capture node
*
* A single unit of extracted error-capture output data grouped together
* at an engine-instance level. We keep these nodes in a linked list.
* See cachelist and outlist below.
*/
struct __guc_capture_parsed_output {
/*
* A single set of 3 capture lists: a global-list
* an engine-class-list and an engine-instance list.
* outlist in __guc_capture_parsed_output will keep
* a linked list of these nodes that will eventually
* be detached from outlist and attached into to
* i915_gpu_codedump in response to a context reset
*/
struct list_head link;
bool is_partial;
u32 eng_class;
u32 eng_inst;
u32 guc_id;
u32 lrca;
struct gcap_reg_list_info {
u32 vfid;
u32 num_regs;
struct guc_mmio_reg *regs;
} reginfo[GUC_CAPTURE_LIST_TYPE_MAX];
#define GCAP_PARSED_REGLIST_INDEX_GLOBAL BIT(GUC_CAPTURE_LIST_TYPE_GLOBAL)
#define GCAP_PARSED_REGLIST_INDEX_ENGCLASS BIT(GUC_CAPTURE_LIST_TYPE_ENGINE_CLASS)
#define GCAP_PARSED_REGLIST_INDEX_ENGINST BIT(GUC_CAPTURE_LIST_TYPE_ENGINE_INSTANCE)
};
/*
* struct guc_debug_capture_list_header / struct guc_debug_capture_list
*
* As part of ADS registration, these header structures (followed by
* an array of 'struct guc_mmio_reg' entries) are used to register with
* GuC microkernel the list of registers we want it to dump out prior
* to a engine reset.
*/
struct guc_debug_capture_list_header {
u32 info;
#define GUC_CAPTURELISTHDR_NUMDESCR GENMASK(15, 0)
} __packed;
struct guc_debug_capture_list {
struct guc_debug_capture_list_header header;
struct guc_mmio_reg regs[];
} __packed;
/*
* struct __guc_mmio_reg_descr / struct __guc_mmio_reg_descr_group
*
* intel_guc_capture module uses these structures to maintain static
* tables (per unique platform) that consists of lists of registers
* (offsets, names, flags,...) that are used at the ADS registration
* time as well as during runtime processing and reporting of error-
* capture states generated by GuC just prior to engine reset events.
*/
struct __guc_mmio_reg_descr {
i915_reg_t reg;
u32 flags;
u32 mask;
const char *regname;
};
struct __guc_mmio_reg_descr_group {
const struct __guc_mmio_reg_descr *list;
u32 num_regs;
u32 owner; /* see enum guc_capture_owner */
u32 type; /* see enum guc_capture_type */
u32 engine; /* as per MAX_ENGINE_CLASS */
struct __guc_mmio_reg_descr *extlist; /* only used for steered registers */
};
/*
* struct guc_state_capture_header_t / struct guc_state_capture_t /
* guc_state_capture_group_header_t / guc_state_capture_group_t
*
* Prior to resetting engines that have hung or faulted, GuC microkernel
* reports the engine error-state (register values that was read) by
* logging them into the shared GuC log buffer using these hierarchy
* of structures.
*/
struct guc_state_capture_header_t {
u32 owner;
#define CAP_HDR_CAPTURE_VFID GENMASK(7, 0)
u32 info;
#define CAP_HDR_CAPTURE_TYPE GENMASK(3, 0) /* see enum guc_capture_type */
#define CAP_HDR_ENGINE_CLASS GENMASK(7, 4) /* see GUC_MAX_ENGINE_CLASSES */
#define CAP_HDR_ENGINE_INSTANCE GENMASK(11, 8)
u32 lrca; /* if type-instance, LRCA (address) that hung, else set to ~0 */
u32 guc_id; /* if type-instance, context index of hung context, else set to ~0 */
u32 num_mmios;
#define CAP_HDR_NUM_MMIOS GENMASK(9, 0)
} __packed;
struct guc_state_capture_t {
struct guc_state_capture_header_t header;
struct guc_mmio_reg mmio_entries[];
} __packed;
enum guc_capture_group_types {
GUC_STATE_CAPTURE_GROUP_TYPE_FULL,
GUC_STATE_CAPTURE_GROUP_TYPE_PARTIAL,
GUC_STATE_CAPTURE_GROUP_TYPE_MAX,
};
struct guc_state_capture_group_header_t {
u32 owner;
#define CAP_GRP_HDR_CAPTURE_VFID GENMASK(7, 0)
u32 info;
#define CAP_GRP_HDR_NUM_CAPTURES GENMASK(7, 0)
#define CAP_GRP_HDR_CAPTURE_TYPE GENMASK(15, 8) /* guc_capture_group_types */
} __packed;
/* this is the top level structure where an error-capture dump starts */
struct guc_state_capture_group_t {
struct guc_state_capture_group_header_t grp_header;
struct guc_state_capture_t capture_entries[];
} __packed;
/*
* struct __guc_capture_ads_cache
*
* A structure to cache register lists that were populated and registered
* with GuC at startup during ADS registration. This allows much quicker
* GuC resets without re-parsing all the tables for the given gt.
*/
struct __guc_capture_ads_cache {
bool is_valid;
void *ptr;
size_t size;
int status;
};
/**
* struct intel_guc_state_capture
*
* Internal context of the intel_guc_capture module.
*/
struct intel_guc_state_capture {
/**
* @reglists: static table of register lists used for error-capture state.
*/
const struct __guc_mmio_reg_descr_group *reglists;
/**
* @extlists: allocated table of steered register lists used for error-capture state.
*
* NOTE: steered registers have multiple instances depending on the HW configuration
* (slices or dual-sub-slices) and thus depends on HW fuses discovered at startup
*/
struct __guc_mmio_reg_descr_group *extlists;
/**
* @ads_cache: cached register lists that is ADS format ready
*/
struct __guc_capture_ads_cache ads_cache[GUC_CAPTURE_LIST_INDEX_MAX]
[GUC_CAPTURE_LIST_TYPE_MAX]
[GUC_MAX_ENGINE_CLASSES];
/**
* @ads_null_cache: ADS null cache.
*/
void *ads_null_cache;
/**
* @cachelist: Pool of pre-allocated nodes for error capture output
*
* We need this pool of pre-allocated nodes because we cannot
* dynamically allocate new nodes when receiving the G2H notification
* because the event handlers for all G2H event-processing is called
* by the ct processing worker queue and when that queue is being
* processed, there is no absolute guarantee that we are not in the
* midst of a GT reset operation (which doesn't allow allocations).
*/
struct list_head cachelist;
#define PREALLOC_NODES_MAX_COUNT (3 * GUC_MAX_ENGINE_CLASSES * GUC_MAX_INSTANCES_PER_CLASS)
#define PREALLOC_NODES_DEFAULT_NUMREGS 64
/**
* @max_mmio_per_node: Max MMIO per node.
*/
int max_mmio_per_node;
/**
* @outlist: Pool of pre-allocated nodes for error capture output
*
* A linked list of parsed GuC error-capture output data before
* reporting with formatting via i915_gpu_coredump. Each node in this linked list shall
* contain a single engine-capture including global, engine-class and
* engine-instance register dumps as per guc_capture_parsed_output_node
*/
struct list_head outlist;
};
#endif /* _INTEL_GUC_CAPTURE_FWIF_H */
@@ -0,0 +1,135 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2023 Intel Corporation
*/
#ifndef _INTEL_GSC_BINARY_HEADERS_H_
#define _INTEL_GSC_BINARY_HEADERS_H_
#include <linux/types.h>
struct intel_gsc_version {
u16 major;
u16 minor;
u16 hotfix;
u16 build;
} __packed;
struct intel_gsc_partition {
u32 offset;
u32 size;
} __packed;
struct intel_gsc_layout_pointers {
u8 rom_bypass_vector[16];
/* size of pointers layout not including ROM bypass vector */
u16 size;
/*
* bit0: Backup copy of layout pointers exist
* bits1-15: reserved
*/
u8 flags;
u8 reserved;
u32 crc32;
struct intel_gsc_partition datap;
struct intel_gsc_partition boot1;
struct intel_gsc_partition boot2;
struct intel_gsc_partition boot3;
struct intel_gsc_partition boot4;
struct intel_gsc_partition boot5;
struct intel_gsc_partition temp_pages;
} __packed;
/* Boot partition structures */
struct intel_gsc_bpdt_header {
u32 signature;
#define INTEL_GSC_BPDT_HEADER_SIGNATURE 0x000055AA
u16 descriptor_count; /* num of entries after the header */
u8 version;
u8 configuration;
u32 crc32;
u32 build_version;
struct intel_gsc_version tool_version;
} __packed;
struct intel_gsc_bpdt_entry {
/*
* Bits 0-15: BPDT entry type
* Bits 16-17: reserved
* Bit 18: code sub-partition
* Bits 19-31: reserved
*/
u32 type;
#define INTEL_GSC_BPDT_ENTRY_TYPE_MASK GENMASK(15, 0)
#define INTEL_GSC_BPDT_ENTRY_TYPE_GSC_RBE 0x1
u32 sub_partition_offset; /* from the base of the BPDT header */
u32 sub_partition_size;
} __packed;
/* Code partition directory (CPD) structures */
struct intel_gsc_cpd_header_v2 {
u32 header_marker;
#define INTEL_GSC_CPD_HEADER_MARKER 0x44504324
u32 num_of_entries;
u8 header_version;
u8 entry_version;
u8 header_length; /* in bytes */
u8 flags;
u32 partition_name;
u32 crc32;
} __packed;
struct intel_gsc_cpd_entry {
u8 name[12];
/*
* Bits 0-24: offset from the beginning of the code partition
* Bit 25: huffman compressed
* Bits 26-31: reserved
*/
u32 offset;
#define INTEL_GSC_CPD_ENTRY_OFFSET_MASK GENMASK(24, 0)
#define INTEL_GSC_CPD_ENTRY_HUFFMAN_COMP BIT(25)
/*
* Module/Item length, in bytes. For Huffman-compressed modules, this
* refers to the uncompressed size. For software-compressed modules,
* this refers to the compressed size.
*/
u32 length;
u8 reserved[4];
} __packed;
struct intel_gsc_manifest_header {
u32 header_type; /* 0x4 for manifest type */
u32 header_length; /* in dwords */
u32 header_version;
u32 flags;
u32 vendor;
u32 date;
u32 size; /* In dwords, size of entire manifest (header + extensions) */
u32 header_id;
u32 internal_data;
struct intel_gsc_version fw_version;
u32 security_version;
struct intel_gsc_version meu_kit_version;
u32 meu_manifest_version;
u8 general_data[4];
u8 reserved3[56];
u32 modulus_size; /* in dwords */
u32 exponent_size; /* in dwords */
} __packed;
#endif
@@ -0,0 +1,524 @@
// SPDX-License-Identifier: MIT
/*
* Copyright © 2022 Intel Corporation
*/
#include "gem/i915_gem_lmem.h"
#include "gt/intel_engine_pm.h"
#include "gt/intel_gpu_commands.h"
#include "gt/intel_gt.h"
#include "gt/intel_gt_print.h"
#include "gt/intel_ring.h"
#include "intel_gsc_binary_headers.h"
#include "intel_gsc_fw.h"
#include "intel_gsc_uc_heci_cmd_submit.h"
#include "i915_reg.h"
static bool gsc_is_in_reset(struct intel_uncore *uncore)
{
u32 fw_status = intel_uncore_read(uncore, HECI_FWSTS(MTL_GSC_HECI1_BASE, 1));
return REG_FIELD_GET(HECI1_FWSTS1_CURRENT_STATE, fw_status) ==
HECI1_FWSTS1_CURRENT_STATE_RESET;
}
static u32 gsc_uc_get_fw_status(struct intel_uncore *uncore, bool needs_wakeref)
{
intel_wakeref_t wakeref;
u32 fw_status = 0;
if (needs_wakeref)
wakeref = intel_runtime_pm_get(uncore->rpm);
fw_status = intel_uncore_read(uncore, HECI_FWSTS(MTL_GSC_HECI1_BASE, 1));
if (needs_wakeref)
intel_runtime_pm_put(uncore->rpm, wakeref);
return fw_status;
}
bool intel_gsc_uc_fw_proxy_init_done(struct intel_gsc_uc *gsc, bool needs_wakeref)
{
return REG_FIELD_GET(HECI1_FWSTS1_CURRENT_STATE,
gsc_uc_get_fw_status(gsc_uc_to_gt(gsc)->uncore,
needs_wakeref)) ==
HECI1_FWSTS1_PROXY_STATE_NORMAL;
}
int intel_gsc_uc_fw_proxy_get_status(struct intel_gsc_uc *gsc)
{
if (!(IS_ENABLED(CONFIG_INTEL_MEI_GSC_PROXY)))
return -ENODEV;
if (!intel_uc_fw_is_loadable(&gsc->fw))
return -ENODEV;
if (__intel_uc_fw_status(&gsc->fw) == INTEL_UC_FIRMWARE_LOAD_FAIL)
return -ENOLINK;
if (!intel_gsc_uc_fw_proxy_init_done(gsc, true))
return -EAGAIN;
return 0;
}
bool intel_gsc_uc_fw_init_done(struct intel_gsc_uc *gsc)
{
return gsc_uc_get_fw_status(gsc_uc_to_gt(gsc)->uncore, false) &
HECI1_FWSTS1_INIT_COMPLETE;
}
static inline u32 cpd_entry_offset(const struct intel_gsc_cpd_entry *entry)
{
return entry->offset & INTEL_GSC_CPD_ENTRY_OFFSET_MASK;
}
int intel_gsc_fw_get_binary_info(struct intel_uc_fw *gsc_fw, const void *data, size_t size)
{
struct intel_gsc_uc *gsc = container_of(gsc_fw, struct intel_gsc_uc, fw);
struct intel_gt *gt = gsc_uc_to_gt(gsc);
const struct intel_gsc_layout_pointers *layout = data;
const struct intel_gsc_bpdt_header *bpdt_header = NULL;
const struct intel_gsc_bpdt_entry *bpdt_entry = NULL;
const struct intel_gsc_cpd_header_v2 *cpd_header = NULL;
const struct intel_gsc_cpd_entry *cpd_entry = NULL;
const struct intel_gsc_manifest_header *manifest;
struct intel_uc_fw_ver min_ver = { 0 };
size_t min_size = sizeof(*layout);
int i;
if (size < min_size) {
gt_err(gt, "GSC FW too small! %zu < %zu\n", size, min_size);
return -ENODATA;
}
/*
* The GSC binary starts with the pointer layout, which contains the
* locations of the various partitions of the binary. The one we're
* interested in to get the version is the boot1 partition, where we can
* find a BPDT header followed by entries, one of which points to the
* RBE sub-section of the partition. From here, we can parse the CPD
* header and the following entries to find the manifest location
* (entry identified by the "RBEP.man" name), from which we can finally
* extract the version.
*
* --------------------------------------------------
* [ intel_gsc_layout_pointers ]
* [ ... ]
* [ boot1.offset >---------------------------]------o
* [ ... ] |
* -------------------------------------------------- |
* |
* -------------------------------------------------- |
* [ intel_gsc_bpdt_header ]<-----o
* --------------------------------------------------
* [ intel_gsc_bpdt_entry[] ]
* [ entry1 ]
* [ ... ]
* [ entryX ]
* [ type == GSC_RBE ]
* [ offset >-----------------------------]------o
* [ ... ] |
* -------------------------------------------------- |
* |
* -------------------------------------------------- |
* [ intel_gsc_cpd_header_v2 ]<-----o
* --------------------------------------------------
* [ intel_gsc_cpd_entry[] ]
* [ entry1 ]
* [ ... ]
* [ entryX ]
* [ "RBEP.man" ]
* [ ... ]
* [ offset >----------------------------]------o
* [ ... ] |
* -------------------------------------------------- |
* |
* -------------------------------------------------- |
* [ intel_gsc_manifest_header ]<-----o
* [ ... ]
* [ intel_gsc_version fw_version ]
* [ ... ]
* --------------------------------------------------
*/
min_size = layout->boot1.offset + layout->boot1.size;
if (size < min_size) {
gt_err(gt, "GSC FW too small for boot section! %zu < %zu\n",
size, min_size);
return -ENODATA;
}
min_size = sizeof(*bpdt_header);
if (layout->boot1.size < min_size) {
gt_err(gt, "GSC FW boot section too small for BPDT header: %u < %zu\n",
layout->boot1.size, min_size);
return -ENODATA;
}
bpdt_header = data + layout->boot1.offset;
if (bpdt_header->signature != INTEL_GSC_BPDT_HEADER_SIGNATURE) {
gt_err(gt, "invalid signature for BPDT header: 0x%08x!\n",
bpdt_header->signature);
return -EINVAL;
}
min_size += sizeof(*bpdt_entry) * bpdt_header->descriptor_count;
if (layout->boot1.size < min_size) {
gt_err(gt, "GSC FW boot section too small for BPDT entries: %u < %zu\n",
layout->boot1.size, min_size);
return -ENODATA;
}
bpdt_entry = (void *)bpdt_header + sizeof(*bpdt_header);
for (i = 0; i < bpdt_header->descriptor_count; i++, bpdt_entry++) {
if ((bpdt_entry->type & INTEL_GSC_BPDT_ENTRY_TYPE_MASK) !=
INTEL_GSC_BPDT_ENTRY_TYPE_GSC_RBE)
continue;
cpd_header = (void *)bpdt_header + bpdt_entry->sub_partition_offset;
min_size = bpdt_entry->sub_partition_offset + sizeof(*cpd_header);
break;
}
if (!cpd_header) {
gt_err(gt, "couldn't find CPD header in GSC binary!\n");
return -ENODATA;
}
if (layout->boot1.size < min_size) {
gt_err(gt, "GSC FW boot section too small for CPD header: %u < %zu\n",
layout->boot1.size, min_size);
return -ENODATA;
}
if (cpd_header->header_marker != INTEL_GSC_CPD_HEADER_MARKER) {
gt_err(gt, "invalid marker for CPD header in GSC bin: 0x%08x!\n",
cpd_header->header_marker);
return -EINVAL;
}
min_size += sizeof(*cpd_entry) * cpd_header->num_of_entries;
if (layout->boot1.size < min_size) {
gt_err(gt, "GSC FW boot section too small for CPD entries: %u < %zu\n",
layout->boot1.size, min_size);
return -ENODATA;
}
cpd_entry = (void *)cpd_header + cpd_header->header_length;
for (i = 0; i < cpd_header->num_of_entries; i++, cpd_entry++) {
if (strcmp(cpd_entry->name, "RBEP.man") == 0) {
manifest = (void *)cpd_header + cpd_entry_offset(cpd_entry);
intel_uc_fw_version_from_gsc_manifest(&gsc->release,
manifest);
gsc->security_version = manifest->security_version;
break;
}
}
/*
* ARL SKUs require newer firmwares, but the blob is actually common
* across all MTL and ARL SKUs, so we need to do an explicit version check
* here rather than using a separate table entry. If a too old version
* is found, then just don't use GSC rather than aborting the driver load.
* Note that the major number in the GSC FW version is used to indicate
* the platform, so we expect it to always be 102 for MTL/ARL binaries.
*/
if (IS_ARROWLAKE_S(gt->i915))
min_ver = (struct intel_uc_fw_ver){ 102, 0, 10, 1878 };
else if (IS_ARROWLAKE_H(gt->i915) || IS_ARROWLAKE_U(gt->i915))
min_ver = (struct intel_uc_fw_ver){ 102, 1, 15, 1926 };
if (IS_METEORLAKE(gt->i915) && gsc->release.major != 102) {
gt_info(gt, "Invalid GSC firmware for MTL/ARL, got %d.%d.%d.%d but need 102.x.x.x",
gsc->release.major, gsc->release.minor,
gsc->release.patch, gsc->release.build);
return -EINVAL;
}
if (min_ver.major) {
bool too_old = false;
if (gsc->release.minor < min_ver.minor) {
too_old = true;
} else if (gsc->release.minor == min_ver.minor) {
if (gsc->release.patch < min_ver.patch) {
too_old = true;
} else if (gsc->release.patch == min_ver.patch) {
if (gsc->release.build < min_ver.build)
too_old = true;
}
}
if (too_old) {
gt_info(gt, "GSC firmware too old for ARL, got %d.%d.%d.%d but need at least %d.%d.%d.%d",
gsc->release.major, gsc->release.minor,
gsc->release.patch, gsc->release.build,
min_ver.major, min_ver.minor,
min_ver.patch, min_ver.build);
return -EINVAL;
}
}
return 0;
}
static int emit_gsc_fw_load(struct i915_request *rq, struct intel_gsc_uc *gsc)
{
u32 offset = i915_ggtt_offset(gsc->local);
u32 *cs;
cs = intel_ring_begin(rq, 4);
if (IS_ERR(cs))
return PTR_ERR(cs);
*cs++ = GSC_FW_LOAD;
*cs++ = lower_32_bits(offset);
*cs++ = upper_32_bits(offset);
*cs++ = (gsc->local->size / SZ_4K) | HECI1_FW_LIMIT_VALID;
intel_ring_advance(rq, cs);
return 0;
}
static int gsc_fw_load(struct intel_gsc_uc *gsc)
{
struct intel_context *ce = gsc->ce;
struct i915_request *rq;
int err;
if (!ce)
return -ENODEV;
rq = i915_request_create(ce);
if (IS_ERR(rq))
return PTR_ERR(rq);
if (ce->engine->emit_init_breadcrumb) {
err = ce->engine->emit_init_breadcrumb(rq);
if (err)
goto out_rq;
}
err = emit_gsc_fw_load(rq, gsc);
if (err)
goto out_rq;
err = ce->engine->emit_flush(rq, 0);
out_rq:
i915_request_get(rq);
if (unlikely(err))
i915_request_set_error_once(rq, err);
i915_request_add(rq);
if (!err && i915_request_wait(rq, 0, msecs_to_jiffies(500)) < 0)
err = -ETIME;
i915_request_put(rq);
if (err)
gt_err(gsc_uc_to_gt(gsc), "Request submission for GSC load failed %pe\n",
ERR_PTR(err));
return err;
}
static int gsc_fw_load_prepare(struct intel_gsc_uc *gsc)
{
struct intel_gt *gt = gsc_uc_to_gt(gsc);
void *src;
if (!gsc->local)
return -ENODEV;
if (gsc->local->size < gsc->fw.size)
return -ENOSPC;
src = i915_gem_object_pin_map_unlocked(gsc->fw.obj,
intel_gt_coherent_map_type(gt, gsc->fw.obj, true));
if (IS_ERR(src))
return PTR_ERR(src);
memcpy_toio(gsc->local_vaddr, src, gsc->fw.size);
memset_io(gsc->local_vaddr + gsc->fw.size, 0, gsc->local->size - gsc->fw.size);
intel_guc_write_barrier(gt_to_guc(gt));
i915_gem_object_unpin_map(gsc->fw.obj);
return 0;
}
static int gsc_fw_wait(struct intel_gt *gt)
{
return intel_wait_for_register(gt->uncore,
HECI_FWSTS(MTL_GSC_HECI1_BASE, 1),
HECI1_FWSTS1_INIT_COMPLETE,
HECI1_FWSTS1_INIT_COMPLETE,
500);
}
struct intel_gsc_mkhi_header {
u8 group_id;
#define MKHI_GROUP_ID_GFX_SRV 0x30
u8 command;
#define MKHI_GFX_SRV_GET_HOST_COMPATIBILITY_VERSION (0x42)
u8 reserved;
u8 result;
} __packed;
struct mtl_gsc_ver_msg_in {
struct intel_gsc_mtl_header header;
struct intel_gsc_mkhi_header mkhi;
} __packed;
struct mtl_gsc_ver_msg_out {
struct intel_gsc_mtl_header header;
struct intel_gsc_mkhi_header mkhi;
u16 proj_major;
u16 compat_major;
u16 compat_minor;
u16 reserved[5];
} __packed;
#define GSC_VER_PKT_SZ SZ_4K
static int gsc_fw_query_compatibility_version(struct intel_gsc_uc *gsc)
{
struct intel_gt *gt = gsc_uc_to_gt(gsc);
struct mtl_gsc_ver_msg_in *msg_in;
struct mtl_gsc_ver_msg_out *msg_out;
struct i915_vma *vma;
u64 offset;
void *vaddr;
int err;
err = intel_guc_allocate_and_map_vma(gt_to_guc(gt), GSC_VER_PKT_SZ * 2,
&vma, &vaddr);
if (err) {
gt_err(gt, "failed to allocate vma for GSC version query\n");
return err;
}
offset = i915_ggtt_offset(vma);
msg_in = vaddr;
msg_out = vaddr + GSC_VER_PKT_SZ;
intel_gsc_uc_heci_cmd_emit_mtl_header(&msg_in->header,
HECI_MEADDRESS_MKHI,
sizeof(*msg_in), 0);
msg_in->mkhi.group_id = MKHI_GROUP_ID_GFX_SRV;
msg_in->mkhi.command = MKHI_GFX_SRV_GET_HOST_COMPATIBILITY_VERSION;
err = intel_gsc_uc_heci_cmd_submit_packet(&gt->uc.gsc,
offset,
sizeof(*msg_in),
offset + GSC_VER_PKT_SZ,
GSC_VER_PKT_SZ);
if (err) {
gt_err(gt,
"failed to submit GSC request for compatibility version: %d\n",
err);
goto out_vma;
}
if (msg_out->header.message_size != sizeof(*msg_out)) {
gt_err(gt, "invalid GSC reply length %u [expected %zu], s=0x%x, f=0x%x, r=0x%x\n",
msg_out->header.message_size, sizeof(*msg_out),
msg_out->header.status, msg_out->header.flags, msg_out->mkhi.result);
err = -EPROTO;
goto out_vma;
}
gsc->fw.file_selected.ver.major = msg_out->compat_major;
gsc->fw.file_selected.ver.minor = msg_out->compat_minor;
out_vma:
i915_vma_unpin_and_release(&vma, I915_VMA_RELEASE_MAP);
return err;
}
int intel_gsc_uc_fw_upload(struct intel_gsc_uc *gsc)
{
struct intel_gt *gt = gsc_uc_to_gt(gsc);
struct intel_uc_fw *gsc_fw = &gsc->fw;
int err;
/* check current fw status */
if (intel_gsc_uc_fw_init_done(gsc)) {
if (GEM_WARN_ON(!intel_uc_fw_is_loaded(gsc_fw)))
intel_uc_fw_change_status(gsc_fw, INTEL_UC_FIRMWARE_TRANSFERRED);
return -EEXIST;
}
if (!intel_uc_fw_is_loadable(gsc_fw))
return -ENOEXEC;
/* FW blob is ok, so clean the status */
intel_uc_fw_sanitize(&gsc->fw);
if (!gsc_is_in_reset(gt->uncore))
return -EIO;
err = gsc_fw_load_prepare(gsc);
if (err)
goto fail;
/*
* GSC is only killed by an FLR, so we need to trigger one on unload to
* make sure we stop it. This is because we assign a chunk of memory to
* the GSC as part of the FW load , so we need to make sure it stops
* using it when we release it to the system on driver unload. Note that
* this is not a problem of the unload per-se, because the GSC will not
* touch that memory unless there are requests for it coming from the
* driver; therefore, no accesses will happen while i915 is not loaded,
* but if we re-load the driver then the GSC might wake up and try to
* access that old memory location again.
* Given that an FLR is a very disruptive action (see the FLR function
* for details), we want to do it as the last action before releasing
* the access to the MMIO bar, which means we need to do it as part of
* the primary uncore cleanup.
* An alternative approach to the FLR would be to use a memory location
* that survives driver unload, like e.g. stolen memory, and keep the
* GSC loaded across reloads. However, this requires us to make sure we
* preserve that memory location on unload and then determine and
* reserve its offset on each subsequent load, which is not trivial, so
* it is easier to just kill everything and start fresh.
*/
intel_uncore_set_flr_on_fini(&gt->i915->uncore);
err = gsc_fw_load(gsc);
if (err)
goto fail;
err = gsc_fw_wait(gt);
if (err)
goto fail;
err = gsc_fw_query_compatibility_version(gsc);
if (err)
goto fail;
/* we only support compatibility version 1.0 at the moment */
err = intel_uc_check_file_version(gsc_fw, NULL);
if (err)
goto fail;
/* FW is not fully operational until we enable SW proxy */
intel_uc_fw_change_status(gsc_fw, INTEL_UC_FIRMWARE_TRANSFERRED);
gt_info(gt, "Loaded GSC firmware %s (cv%u.%u, r%u.%u.%u.%u, svn %u)\n",
gsc_fw->file_selected.path,
gsc_fw->file_selected.ver.major, gsc_fw->file_selected.ver.minor,
gsc->release.major, gsc->release.minor,
gsc->release.patch, gsc->release.build,
gsc->security_version);
return 0;
fail:
return intel_uc_fw_mark_load_failed(gsc_fw, err);
}
@@ -0,0 +1,21 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2022 Intel Corporation
*/
#ifndef _INTEL_GSC_FW_H_
#define _INTEL_GSC_FW_H_
#include <linux/types.h>
struct intel_gsc_uc;
struct intel_uc_fw;
struct intel_uncore;
int intel_gsc_fw_get_binary_info(struct intel_uc_fw *gsc_fw, const void *data, size_t size);
int intel_gsc_uc_fw_upload(struct intel_gsc_uc *gsc);
bool intel_gsc_uc_fw_init_done(struct intel_gsc_uc *gsc);
bool intel_gsc_uc_fw_proxy_init_done(struct intel_gsc_uc *gsc, bool needs_wakeref);
int intel_gsc_uc_fw_proxy_get_status(struct intel_gsc_uc *gsc);
#endif
@@ -0,0 +1,429 @@
// SPDX-License-Identifier: MIT
/*
* Copyright © 2023 Intel Corporation
*/
#include <linux/component.h>
#include <drm/intel/i915_component.h>
#include <drm/intel/i915_gsc_proxy_mei_interface.h>
#include "gt/intel_gt.h"
#include "gt/intel_gt_print.h"
#include "i915_drv.h"
#include "i915_reg.h"
#include "i915_wait_util.h"
#include "intel_gsc_proxy.h"
#include "intel_gsc_uc.h"
#include "intel_gsc_uc_heci_cmd_submit.h"
/*
* GSC proxy:
* The GSC uC needs to communicate with the CSME to perform certain operations.
* Since the GSC can't perform this communication directly on platforms where it
* is integrated in GT, i915 needs to transfer the messages from GSC to CSME
* and back. i915 must manually start the proxy flow after the GSC is loaded to
* signal to GSC that we're ready to handle its messages and allow it to query
* its init data from CSME; GSC will then trigger an HECI2 interrupt if it needs
* to send messages to CSME again.
* The proxy flow is as follow:
* 1 - i915 submits a request to GSC asking for the message to CSME
* 2 - GSC replies with the proxy header + payload for CSME
* 3 - i915 sends the reply from GSC as-is to CSME via the mei proxy component
* 4 - CSME replies with the proxy header + payload for GSC
* 5 - i915 submits a request to GSC with the reply from CSME
* 6 - GSC replies either with a new header + payload (same as step 2, so we
* restart from there) or with an end message.
*/
/*
* The component should load quite quickly in most cases, but it could take
* a bit. Using a very big timeout just to cover the worst case scenario
*/
#define GSC_PROXY_INIT_TIMEOUT_MS 20000
/* the protocol supports up to 32K in each direction */
#define GSC_PROXY_BUFFER_SIZE SZ_32K
#define GSC_PROXY_CHANNEL_SIZE (GSC_PROXY_BUFFER_SIZE * 2)
#define GSC_PROXY_MAX_MSG_SIZE (GSC_PROXY_BUFFER_SIZE - sizeof(struct intel_gsc_mtl_header))
/* FW-defined proxy header */
struct intel_gsc_proxy_header {
/*
* hdr:
* Bits 0-7: type of the proxy message (see enum intel_gsc_proxy_type)
* Bits 8-15: rsvd
* Bits 16-31: length in bytes of the payload following the proxy header
*/
u32 hdr;
#define GSC_PROXY_TYPE GENMASK(7, 0)
#define GSC_PROXY_PAYLOAD_LENGTH GENMASK(31, 16)
u32 source; /* Source of the Proxy message */
u32 destination; /* Destination of the Proxy message */
#define GSC_PROXY_ADDRESSING_KMD 0x10000
#define GSC_PROXY_ADDRESSING_GSC 0x20000
#define GSC_PROXY_ADDRESSING_CSME 0x30000
u32 status; /* Command status */
} __packed;
/* FW-defined proxy types */
enum intel_gsc_proxy_type {
GSC_PROXY_MSG_TYPE_PROXY_INVALID = 0,
GSC_PROXY_MSG_TYPE_PROXY_QUERY = 1,
GSC_PROXY_MSG_TYPE_PROXY_PAYLOAD = 2,
GSC_PROXY_MSG_TYPE_PROXY_END = 3,
GSC_PROXY_MSG_TYPE_PROXY_NOTIFICATION = 4,
};
struct gsc_proxy_msg {
struct intel_gsc_mtl_header header;
struct intel_gsc_proxy_header proxy_header;
} __packed;
static int proxy_send_to_csme(struct intel_gsc_uc *gsc)
{
struct intel_gt *gt = gsc_uc_to_gt(gsc);
struct i915_gsc_proxy_component *comp = gsc->proxy.component;
struct intel_gsc_mtl_header *hdr;
void *in = gsc->proxy.to_csme;
void *out = gsc->proxy.to_gsc;
u32 in_size;
int ret;
/* CSME msg only includes the proxy */
hdr = in;
in += sizeof(struct intel_gsc_mtl_header);
out += sizeof(struct intel_gsc_mtl_header);
in_size = hdr->message_size - sizeof(struct intel_gsc_mtl_header);
/* the message must contain at least the proxy header */
if (in_size < sizeof(struct intel_gsc_proxy_header) ||
in_size > GSC_PROXY_MAX_MSG_SIZE) {
gt_err(gt, "Invalid CSME message size: %u\n", in_size);
return -EINVAL;
}
ret = comp->ops->send(comp->mei_dev, in, in_size);
if (ret < 0) {
gt_err(gt, "Failed to send CSME message\n");
return ret;
}
ret = comp->ops->recv(comp->mei_dev, out, GSC_PROXY_MAX_MSG_SIZE);
if (ret < 0) {
gt_err(gt, "Failed to receive CSME message\n");
return ret;
}
return ret;
}
static int proxy_send_to_gsc(struct intel_gsc_uc *gsc)
{
struct intel_gt *gt = gsc_uc_to_gt(gsc);
u32 *marker = gsc->proxy.to_csme; /* first dw of the reply header */
u64 addr_in = i915_ggtt_offset(gsc->proxy.vma);
u64 addr_out = addr_in + GSC_PROXY_BUFFER_SIZE;
u32 size = ((struct gsc_proxy_msg *)gsc->proxy.to_gsc)->header.message_size;
int err;
/* the message must contain at least the gsc and proxy headers */
if (size < sizeof(struct gsc_proxy_msg) || size > GSC_PROXY_BUFFER_SIZE) {
gt_err(gt, "Invalid GSC proxy message size: %u\n", size);
return -EINVAL;
}
/* clear the message marker */
*marker = 0;
/* make sure the marker write is flushed */
wmb();
/* send the request */
err = intel_gsc_uc_heci_cmd_submit_packet(gsc, addr_in, size,
addr_out, GSC_PROXY_BUFFER_SIZE);
if (!err) {
/* wait for the reply to show up */
err = wait_for(*marker != 0, 300);
if (err)
gt_err(gt, "Failed to get a proxy reply from gsc\n");
}
return err;
}
static int validate_proxy_header(struct intel_gsc_proxy_header *header,
u32 source, u32 dest)
{
u32 type = FIELD_GET(GSC_PROXY_TYPE, header->hdr);
u32 length = FIELD_GET(GSC_PROXY_PAYLOAD_LENGTH, header->hdr);
int ret = 0;
if (header->destination != dest || header->source != source) {
ret = -ENOEXEC;
goto fail;
}
switch (type) {
case GSC_PROXY_MSG_TYPE_PROXY_PAYLOAD:
if (length > 0)
break;
fallthrough;
case GSC_PROXY_MSG_TYPE_PROXY_INVALID:
ret = -EIO;
goto fail;
default:
break;
}
fail:
return ret;
}
static int proxy_query(struct intel_gsc_uc *gsc)
{
struct intel_gt *gt = gsc_uc_to_gt(gsc);
struct gsc_proxy_msg *to_gsc = gsc->proxy.to_gsc;
struct gsc_proxy_msg *to_csme = gsc->proxy.to_csme;
int ret;
intel_gsc_uc_heci_cmd_emit_mtl_header(&to_gsc->header,
HECI_MEADDRESS_PROXY,
sizeof(struct gsc_proxy_msg),
0);
to_gsc->proxy_header.hdr =
FIELD_PREP(GSC_PROXY_TYPE, GSC_PROXY_MSG_TYPE_PROXY_QUERY) |
FIELD_PREP(GSC_PROXY_PAYLOAD_LENGTH, 0);
to_gsc->proxy_header.source = GSC_PROXY_ADDRESSING_KMD;
to_gsc->proxy_header.destination = GSC_PROXY_ADDRESSING_GSC;
to_gsc->proxy_header.status = 0;
while (1) {
/* clear the GSC response header space */
memset(gsc->proxy.to_csme, 0, sizeof(struct gsc_proxy_msg));
/* send proxy message to GSC */
ret = proxy_send_to_gsc(gsc);
if (ret) {
gt_err(gt, "failed to send proxy message to GSC! %d\n", ret);
goto proxy_error;
}
/* stop if this was the last message */
if (FIELD_GET(GSC_PROXY_TYPE, to_csme->proxy_header.hdr) ==
GSC_PROXY_MSG_TYPE_PROXY_END)
break;
/* make sure the GSC-to-CSME proxy header is sane */
ret = validate_proxy_header(&to_csme->proxy_header,
GSC_PROXY_ADDRESSING_GSC,
GSC_PROXY_ADDRESSING_CSME);
if (ret) {
gt_err(gt, "invalid GSC to CSME proxy header! %d\n", ret);
goto proxy_error;
}
/* send the GSC message to the CSME */
ret = proxy_send_to_csme(gsc);
if (ret < 0) {
gt_err(gt, "failed to send proxy message to CSME! %d\n", ret);
goto proxy_error;
}
/* update the GSC message size with the returned value from CSME */
to_gsc->header.message_size = ret + sizeof(struct intel_gsc_mtl_header);
/* make sure the CSME-to-GSC proxy header is sane */
ret = validate_proxy_header(&to_gsc->proxy_header,
GSC_PROXY_ADDRESSING_CSME,
GSC_PROXY_ADDRESSING_GSC);
if (ret) {
gt_err(gt, "invalid CSME to GSC proxy header! %d\n", ret);
goto proxy_error;
}
}
proxy_error:
return ret < 0 ? ret : 0;
}
int intel_gsc_proxy_request_handler(struct intel_gsc_uc *gsc)
{
struct intel_gt *gt = gsc_uc_to_gt(gsc);
int err;
if (!gsc->proxy.component_added)
return -ENODEV;
assert_rpm_wakelock_held(gt->uncore->rpm);
/* when GSC is loaded, we can queue this before the component is bound */
err = wait_for(gsc->proxy.component, GSC_PROXY_INIT_TIMEOUT_MS);
if (err) {
gt_err(gt, "GSC proxy component didn't bind within the expected timeout\n");
return -EIO;
}
mutex_lock(&gsc->proxy.mutex);
if (!gsc->proxy.component) {
gt_err(gt, "GSC proxy worker called without the component being bound!\n");
err = -EIO;
} else {
/*
* write the status bit to clear it and allow new proxy
* interrupts to be generated while we handle the current
* request, but be sure not to write the reset bit
*/
intel_uncore_rmw(gt->uncore, HECI_H_CSR(MTL_GSC_HECI2_BASE),
HECI_H_CSR_RST, HECI_H_CSR_IS);
err = proxy_query(gsc);
}
mutex_unlock(&gsc->proxy.mutex);
return err;
}
void intel_gsc_proxy_irq_handler(struct intel_gsc_uc *gsc, u32 iir)
{
struct intel_gt *gt = gsc_uc_to_gt(gsc);
if (unlikely(!iir))
return;
lockdep_assert_held(gt->irq_lock);
if (!gsc->proxy.component) {
gt_err(gt, "GSC proxy irq received without the component being bound!\n");
return;
}
gsc->gsc_work_actions |= GSC_ACTION_SW_PROXY;
queue_work(gsc->wq, &gsc->work);
}
static int i915_gsc_proxy_component_bind(struct device *i915_kdev,
struct device *mei_kdev, void *data)
{
struct drm_i915_private *i915 = kdev_to_i915(i915_kdev);
struct intel_gt *gt = i915->media_gt;
struct intel_gsc_uc *gsc = &gt->uc.gsc;
intel_wakeref_t wakeref;
/* enable HECI2 IRQs */
with_intel_runtime_pm(&i915->runtime_pm, wakeref)
intel_uncore_rmw(gt->uncore, HECI_H_CSR(MTL_GSC_HECI2_BASE),
HECI_H_CSR_RST, HECI_H_CSR_IE);
mutex_lock(&gsc->proxy.mutex);
gsc->proxy.component = data;
gsc->proxy.component->mei_dev = mei_kdev;
mutex_unlock(&gsc->proxy.mutex);
gt_dbg(gt, "GSC proxy mei component bound\n");
return 0;
}
static void i915_gsc_proxy_component_unbind(struct device *i915_kdev,
struct device *mei_kdev, void *data)
{
struct drm_i915_private *i915 = kdev_to_i915(i915_kdev);
struct intel_gt *gt = i915->media_gt;
struct intel_gsc_uc *gsc = &gt->uc.gsc;
intel_wakeref_t wakeref;
mutex_lock(&gsc->proxy.mutex);
gsc->proxy.component = NULL;
mutex_unlock(&gsc->proxy.mutex);
/* disable HECI2 IRQs */
with_intel_runtime_pm(&i915->runtime_pm, wakeref)
intel_uncore_rmw(gt->uncore, HECI_H_CSR(MTL_GSC_HECI2_BASE),
HECI_H_CSR_IE | HECI_H_CSR_RST, 0);
gt_dbg(gt, "GSC proxy mei component unbound\n");
}
static const struct component_ops i915_gsc_proxy_component_ops = {
.bind = i915_gsc_proxy_component_bind,
.unbind = i915_gsc_proxy_component_unbind,
};
static int proxy_channel_alloc(struct intel_gsc_uc *gsc)
{
struct intel_gt *gt = gsc_uc_to_gt(gsc);
struct i915_vma *vma;
void *vaddr;
int err;
err = intel_guc_allocate_and_map_vma(gt_to_guc(gt),
GSC_PROXY_CHANNEL_SIZE,
&vma, &vaddr);
if (err)
return err;
gsc->proxy.vma = vma;
gsc->proxy.to_gsc = vaddr;
gsc->proxy.to_csme = vaddr + GSC_PROXY_BUFFER_SIZE;
return 0;
}
static void proxy_channel_free(struct intel_gsc_uc *gsc)
{
if (!gsc->proxy.vma)
return;
gsc->proxy.to_gsc = NULL;
gsc->proxy.to_csme = NULL;
i915_vma_unpin_and_release(&gsc->proxy.vma, I915_VMA_RELEASE_MAP);
}
void intel_gsc_proxy_fini(struct intel_gsc_uc *gsc)
{
struct intel_gt *gt = gsc_uc_to_gt(gsc);
struct drm_i915_private *i915 = gt->i915;
if (fetch_and_zero(&gsc->proxy.component_added))
component_del(i915->drm.dev, &i915_gsc_proxy_component_ops);
proxy_channel_free(gsc);
}
int intel_gsc_proxy_init(struct intel_gsc_uc *gsc)
{
int err;
struct intel_gt *gt = gsc_uc_to_gt(gsc);
struct drm_i915_private *i915 = gt->i915;
mutex_init(&gsc->proxy.mutex);
if (!IS_ENABLED(CONFIG_INTEL_MEI_GSC_PROXY)) {
gt_info(gt, "can't init GSC proxy due to missing mei component\n");
return -ENODEV;
}
err = proxy_channel_alloc(gsc);
if (err)
return err;
err = component_add_typed(i915->drm.dev, &i915_gsc_proxy_component_ops,
I915_COMPONENT_GSC_PROXY);
if (err < 0) {
gt_err(gt, "Failed to add GSC_PROXY component (%d)\n", err);
goto out_free;
}
gsc->proxy.component_added = true;
return 0;
out_free:
proxy_channel_free(gsc);
return err;
}
@@ -0,0 +1,18 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2023 Intel Corporation
*/
#ifndef _INTEL_GSC_PROXY_H_
#define _INTEL_GSC_PROXY_H_
#include <linux/types.h>
struct intel_gsc_uc;
int intel_gsc_proxy_init(struct intel_gsc_uc *gsc);
void intel_gsc_proxy_fini(struct intel_gsc_uc *gsc);
int intel_gsc_proxy_request_handler(struct intel_gsc_uc *gsc);
void intel_gsc_proxy_irq_handler(struct intel_gsc_uc *gsc, u32 iir);
#endif
@@ -0,0 +1,358 @@
// SPDX-License-Identifier: MIT
/*
* Copyright © 2022 Intel Corporation
*/
#include <linux/types.h>
#include "gt/intel_gt.h"
#include "gt/intel_gt_print.h"
#include "intel_gsc_fw.h"
#include "intel_gsc_proxy.h"
#include "intel_gsc_uc.h"
#include "i915_drv.h"
#include "i915_reg.h"
static void gsc_work(struct work_struct *work)
{
struct intel_gsc_uc *gsc = container_of(work, typeof(*gsc), work);
struct intel_gt *gt = gsc_uc_to_gt(gsc);
intel_wakeref_t wakeref;
u32 actions;
int ret;
wakeref = intel_runtime_pm_get(gt->uncore->rpm);
spin_lock_irq(gt->irq_lock);
actions = gsc->gsc_work_actions;
gsc->gsc_work_actions = 0;
spin_unlock_irq(gt->irq_lock);
if (actions & GSC_ACTION_FW_LOAD) {
ret = intel_gsc_uc_fw_upload(gsc);
if (!ret)
/* setup proxy on a new load */
actions |= GSC_ACTION_SW_PROXY;
else if (ret != -EEXIST)
goto out_put;
/*
* The HuC auth can be done both before or after the proxy init;
* if done after, a proxy request will be issued and must be
* serviced before the authentication can complete.
* Since this worker also handles proxy requests, we can't
* perform an action that requires the proxy from within it and
* then stall waiting for it, because we'd be blocking the
* service path. Therefore, it is easier for us to load HuC
* first and do proxy later. The GSC will ack the HuC auth and
* then send the HuC proxy request as part of the proxy init
* flow.
* Note that we can only do the GSC auth if the GuC auth was
* successful.
*/
if (intel_uc_uses_huc(&gt->uc) &&
intel_huc_is_authenticated(&gt->uc.huc, INTEL_HUC_AUTH_BY_GUC))
intel_huc_auth(&gt->uc.huc, INTEL_HUC_AUTH_BY_GSC);
}
if (actions & GSC_ACTION_SW_PROXY) {
if (!intel_gsc_uc_fw_init_done(gsc)) {
gt_err(gt, "Proxy request received with GSC not loaded!\n");
goto out_put;
}
ret = intel_gsc_proxy_request_handler(gsc);
if (ret) {
if (actions & GSC_ACTION_FW_LOAD) {
/*
* A proxy failure right after firmware load means the proxy-init
* step has failed so mark GSC as not usable after this
*/
gt_err(gt, "GSC proxy handler failed to init\n");
intel_uc_fw_change_status(&gsc->fw, INTEL_UC_FIRMWARE_LOAD_FAIL);
}
goto out_put;
}
/* mark the GSC FW init as done the first time we run this */
if (actions & GSC_ACTION_FW_LOAD) {
/*
* If there is a proxy establishment error, the GSC might still
* complete the request handling cleanly, so we need to check the
* status register to check if the proxy init was actually successful
*/
if (intel_gsc_uc_fw_proxy_init_done(gsc, false)) {
gt_dbg(gt, "GSC Proxy initialized\n");
intel_uc_fw_change_status(&gsc->fw, INTEL_UC_FIRMWARE_RUNNING);
} else {
gt_err(gt, "GSC status reports proxy init not complete\n");
intel_uc_fw_change_status(&gsc->fw, INTEL_UC_FIRMWARE_LOAD_FAIL);
}
}
}
out_put:
intel_runtime_pm_put(gt->uncore->rpm, wakeref);
}
static bool gsc_engine_supported(struct intel_gt *gt)
{
intel_engine_mask_t mask;
/*
* We reach here from i915_driver_early_probe for the primary GT before
* its engine mask is set, so we use the device info engine mask for it.
* For other GTs we expect the GT-specific mask to be set before we
* call this function.
*/
GEM_BUG_ON(!gt_is_root(gt) && !gt->info.engine_mask);
if (gt_is_root(gt))
mask = INTEL_INFO(gt->i915)->platform_engine_mask;
else
mask = gt->info.engine_mask;
return __HAS_ENGINE(mask, GSC0);
}
void intel_gsc_uc_init_early(struct intel_gsc_uc *gsc)
{
struct intel_gt *gt = gsc_uc_to_gt(gsc);
/*
* GSC FW needs to be copied to a dedicated memory allocations for
* loading (see gsc->local), so we don't need to GGTT map the FW image
* itself into GGTT.
*/
intel_uc_fw_init_early(&gsc->fw, INTEL_UC_FW_TYPE_GSC, false);
INIT_WORK(&gsc->work, gsc_work);
/* we can arrive here from i915_driver_early_probe for primary
* GT with it being not fully setup hence check device info's
* engine mask
*/
if (!gsc_engine_supported(gt)) {
intel_uc_fw_change_status(&gsc->fw, INTEL_UC_FIRMWARE_NOT_SUPPORTED);
return;
}
gsc->wq = alloc_ordered_workqueue("i915_gsc", 0);
if (!gsc->wq) {
gt_err(gt, "failed to allocate WQ for GSC, disabling FW\n");
intel_uc_fw_change_status(&gsc->fw, INTEL_UC_FIRMWARE_NOT_SUPPORTED);
}
}
static int gsc_allocate_and_map_vma(struct intel_gsc_uc *gsc, u32 size)
{
struct intel_gt *gt = gsc_uc_to_gt(gsc);
struct drm_i915_gem_object *obj;
struct i915_vma *vma;
void __iomem *vaddr;
int ret = 0;
/*
* The GSC FW doesn't immediately suspend after becoming idle, so there
* is a chance that it could still be awake after we successfully
* return from the pci suspend function, even if there are no pending
* operations.
* The FW might therefore try to access memory for its suspend operation
* after the kernel has completed the HW suspend flow; this can cause
* issues if the FW is mapped in normal RAM memory, as some of the
* involved HW units might've already lost power.
* The driver must therefore avoid this situation and the recommended
* way to do so is to use stolen memory for the GSC memory allocation,
* because stolen memory takes a different path in HW and it is
* guaranteed to always work as long as the GPU itself is awake (which
* it must be if the GSC is awake).
*/
obj = i915_gem_object_create_stolen(gt->i915, size);
if (IS_ERR(obj))
return PTR_ERR(obj);
vma = i915_gem_object_ggtt_pin(obj, NULL, 0, 0, 0);
if (IS_ERR(vma)) {
ret = PTR_ERR(vma);
goto err;
}
vaddr = i915_vma_pin_iomap(vma);
i915_vma_unpin(vma);
if (IS_ERR(vaddr)) {
ret = PTR_ERR(vaddr);
goto err;
}
i915_vma_make_unshrinkable(vma);
gsc->local = vma;
gsc->local_vaddr = vaddr;
return 0;
err:
i915_gem_object_put(obj);
return ret;
}
static void gsc_unmap_and_free_vma(struct intel_gsc_uc *gsc)
{
struct i915_vma *vma = fetch_and_zero(&gsc->local);
if (!vma)
return;
gsc->local_vaddr = NULL;
i915_vma_unpin_iomap(vma);
i915_gem_object_put(vma->obj);
}
int intel_gsc_uc_init(struct intel_gsc_uc *gsc)
{
static struct lock_class_key gsc_lock;
struct intel_gt *gt = gsc_uc_to_gt(gsc);
struct intel_engine_cs *engine = gt->engine[GSC0];
struct intel_context *ce;
int err;
err = intel_uc_fw_init(&gsc->fw);
if (err)
goto out;
err = gsc_allocate_and_map_vma(gsc, SZ_4M);
if (err)
goto out_fw;
ce = intel_engine_create_pinned_context(engine, engine->gt->vm, SZ_4K,
I915_GEM_HWS_GSC_ADDR,
&gsc_lock, "gsc_context");
if (IS_ERR(ce)) {
gt_err(gt, "failed to create GSC CS ctx for FW communication\n");
err = PTR_ERR(ce);
goto out_vma;
}
gsc->ce = ce;
/* if we fail to init proxy we still want to load GSC for PM */
intel_gsc_proxy_init(gsc);
intel_uc_fw_change_status(&gsc->fw, INTEL_UC_FIRMWARE_LOADABLE);
return 0;
out_vma:
gsc_unmap_and_free_vma(gsc);
out_fw:
intel_uc_fw_fini(&gsc->fw);
out:
gt_probe_error(gt, "GSC init failed %pe\n", ERR_PTR(err));
return err;
}
void intel_gsc_uc_fini(struct intel_gsc_uc *gsc)
{
if (!intel_uc_fw_is_loadable(&gsc->fw))
return;
flush_work(&gsc->work);
if (gsc->wq) {
destroy_workqueue(gsc->wq);
gsc->wq = NULL;
}
intel_gsc_proxy_fini(gsc);
if (gsc->ce)
intel_engine_destroy_pinned_context(fetch_and_zero(&gsc->ce));
gsc_unmap_and_free_vma(gsc);
intel_uc_fw_fini(&gsc->fw);
}
void intel_gsc_uc_flush_work(struct intel_gsc_uc *gsc)
{
if (!intel_uc_fw_is_loadable(&gsc->fw))
return;
flush_work(&gsc->work);
}
void intel_gsc_uc_resume(struct intel_gsc_uc *gsc)
{
if (!intel_uc_fw_is_loadable(&gsc->fw))
return;
/*
* we only want to start the GSC worker from here in the actual resume
* flow and not during driver load. This is because GSC load is slow and
* therefore we want to make sure that the default state init completes
* first to not slow down the init thread. A separate call to
* intel_gsc_uc_load_start will ensure that the GSC is loaded during
* driver load.
*/
if (!gsc_uc_to_gt(gsc)->engine[GSC0]->default_state)
return;
intel_gsc_uc_load_start(gsc);
}
void intel_gsc_uc_load_start(struct intel_gsc_uc *gsc)
{
struct intel_gt *gt = gsc_uc_to_gt(gsc);
if (!intel_uc_fw_is_loadable(&gsc->fw) || intel_uc_fw_is_in_error(&gsc->fw))
return;
if (intel_gsc_uc_fw_init_done(gsc))
return;
spin_lock_irq(gt->irq_lock);
gsc->gsc_work_actions |= GSC_ACTION_FW_LOAD;
spin_unlock_irq(gt->irq_lock);
queue_work(gsc->wq, &gsc->work);
}
void intel_gsc_uc_load_status(struct intel_gsc_uc *gsc, struct drm_printer *p)
{
struct intel_gt *gt = gsc_uc_to_gt(gsc);
struct intel_uncore *uncore = gt->uncore;
intel_wakeref_t wakeref;
if (!intel_gsc_uc_is_supported(gsc)) {
drm_printf(p, "GSC not supported\n");
return;
}
if (!intel_gsc_uc_is_wanted(gsc)) {
drm_printf(p, "GSC disabled\n");
return;
}
drm_printf(p, "GSC firmware: %s\n", gsc->fw.file_selected.path);
if (gsc->fw.file_selected.path != gsc->fw.file_wanted.path)
drm_printf(p, "GSC firmware wanted: %s\n", gsc->fw.file_wanted.path);
drm_printf(p, "\tstatus: %s\n", intel_uc_fw_status_repr(gsc->fw.status));
drm_printf(p, "Release: %u.%u.%u.%u\n",
gsc->release.major, gsc->release.minor,
gsc->release.patch, gsc->release.build);
drm_printf(p, "Compatibility Version: %u.%u [min expected %u.%u]\n",
gsc->fw.file_selected.ver.major, gsc->fw.file_selected.ver.minor,
gsc->fw.file_wanted.ver.major, gsc->fw.file_wanted.ver.minor);
drm_printf(p, "SVN: %u\n", gsc->security_version);
with_intel_runtime_pm(uncore->rpm, wakeref) {
u32 i;
for (i = 1; i <= 6; i++) {
u32 status = intel_uncore_read(uncore,
HECI_FWSTS(MTL_GSC_HECI1_BASE, i));
drm_printf(p, "HECI1 FWSTST%u = 0x%08x\n", i, status);
}
}
}
@@ -0,0 +1,85 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2022 Intel Corporation
*/
#ifndef _INTEL_GSC_UC_H_
#define _INTEL_GSC_UC_H_
#include "intel_uc_fw.h"
struct drm_printer;
struct i915_vma;
struct intel_context;
struct i915_gsc_proxy_component;
struct intel_gsc_uc {
/* Generic uC firmware management */
struct intel_uc_fw fw;
/* GSC-specific additions */
/*
* The GSC has 3 version numbers:
* - Release version (incremented with each build)
* - Security version (incremented on security fix)
* - Compatibility version (incremented on interface change)
*
* The one we care about to use the binary is the last one, so that's
* the one we save inside the intel_uc_fw structure. The other two
* versions are only used for debug/info purposes, so we save them here.
*
* Note that the release and security versions are available in the
* binary header, while the compatibility version must be queried after
* loading the binary.
*/
struct intel_uc_fw_ver release;
u32 security_version;
struct i915_vma *local; /* private memory for GSC usage */
void __iomem *local_vaddr; /* pointer to access the private memory */
struct intel_context *ce; /* for submission to GSC FW via GSC engine */
/* for delayed load and proxy handling */
struct workqueue_struct *wq;
struct work_struct work;
u32 gsc_work_actions; /* protected by gt->irq_lock */
#define GSC_ACTION_FW_LOAD BIT(0)
#define GSC_ACTION_SW_PROXY BIT(1)
struct {
struct i915_gsc_proxy_component *component;
bool component_added;
struct i915_vma *vma;
void *to_gsc;
void *to_csme;
struct mutex mutex; /* protects the tee channel binding */
} proxy;
};
void intel_gsc_uc_init_early(struct intel_gsc_uc *gsc);
int intel_gsc_uc_init(struct intel_gsc_uc *gsc);
void intel_gsc_uc_fini(struct intel_gsc_uc *gsc);
void intel_gsc_uc_suspend(struct intel_gsc_uc *gsc);
void intel_gsc_uc_resume(struct intel_gsc_uc *gsc);
void intel_gsc_uc_flush_work(struct intel_gsc_uc *gsc);
void intel_gsc_uc_load_start(struct intel_gsc_uc *gsc);
void intel_gsc_uc_load_status(struct intel_gsc_uc *gsc, struct drm_printer *p);
static inline bool intel_gsc_uc_is_supported(struct intel_gsc_uc *gsc)
{
return intel_uc_fw_is_supported(&gsc->fw);
}
static inline bool intel_gsc_uc_is_wanted(struct intel_gsc_uc *gsc)
{
return intel_uc_fw_is_enabled(&gsc->fw);
}
static inline bool intel_gsc_uc_is_used(struct intel_gsc_uc *gsc)
{
GEM_BUG_ON(__intel_uc_fw_status(&gsc->fw) == INTEL_UC_FIRMWARE_SELECTED);
return intel_uc_fw_is_available(&gsc->fw);
}
#endif
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: MIT
/*
* Copyright © 2023 Intel Corporation
*/
#include <drm/drm_print.h>
#include "gt/intel_gt.h"
#include "gt/intel_gt_debugfs.h"
#include "gt/intel_gt_print.h"
#include "intel_gsc_uc.h"
#include "intel_gsc_uc_debugfs.h"
#include "i915_drv.h"
static int gsc_info_show(struct seq_file *m, void *data)
{
struct drm_printer p = drm_seq_file_printer(m);
struct intel_gsc_uc *gsc = m->private;
if (!intel_gsc_uc_is_supported(gsc))
return -ENODEV;
intel_gsc_uc_load_status(gsc, &p);
return 0;
}
DEFINE_INTEL_GT_DEBUGFS_ATTRIBUTE(gsc_info);
void intel_gsc_uc_debugfs_register(struct intel_gsc_uc *gsc_uc, struct dentry *root)
{
static const struct intel_gt_debugfs_file files[] = {
{ .name = "gsc_info", .fops = &gsc_info_fops },
};
if (!intel_gsc_uc_is_supported(gsc_uc))
return;
intel_gt_debugfs_register_files(root, files, ARRAY_SIZE(files), gsc_uc);
}
@@ -0,0 +1,14 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2023 Intel Corporation
*/
#ifndef DEBUGFS_GSC_UC_H
#define DEBUGFS_GSC_UC_H
struct intel_gsc_uc;
struct dentry;
void intel_gsc_uc_debugfs_register(struct intel_gsc_uc *gsc, struct dentry *root);
#endif /* DEBUGFS_GSC_UC_H */
@@ -0,0 +1,231 @@
// SPDX-License-Identifier: MIT
/*
* Copyright © 2023 Intel Corporation
*/
#include <drm/drm_print.h>
#include "gt/intel_context.h"
#include "gt/intel_engine_pm.h"
#include "gt/intel_gpu_commands.h"
#include "gt/intel_gt.h"
#include "gt/intel_ring.h"
#include "i915_wait_util.h"
#include "intel_gsc_uc_heci_cmd_submit.h"
struct gsc_heci_pkt {
u64 addr_in;
u32 size_in;
u64 addr_out;
u32 size_out;
};
static int emit_gsc_heci_pkt(struct i915_request *rq, struct gsc_heci_pkt *pkt)
{
u32 *cs;
cs = intel_ring_begin(rq, 8);
if (IS_ERR(cs))
return PTR_ERR(cs);
*cs++ = GSC_HECI_CMD_PKT;
*cs++ = lower_32_bits(pkt->addr_in);
*cs++ = upper_32_bits(pkt->addr_in);
*cs++ = pkt->size_in;
*cs++ = lower_32_bits(pkt->addr_out);
*cs++ = upper_32_bits(pkt->addr_out);
*cs++ = pkt->size_out;
*cs++ = 0;
intel_ring_advance(rq, cs);
return 0;
}
int intel_gsc_uc_heci_cmd_submit_packet(struct intel_gsc_uc *gsc, u64 addr_in,
u32 size_in, u64 addr_out,
u32 size_out)
{
struct intel_context *ce = gsc->ce;
struct i915_request *rq;
struct gsc_heci_pkt pkt = {
.addr_in = addr_in,
.size_in = size_in,
.addr_out = addr_out,
.size_out = size_out
};
int err;
if (!ce)
return -ENODEV;
rq = i915_request_create(ce);
if (IS_ERR(rq))
return PTR_ERR(rq);
if (ce->engine->emit_init_breadcrumb) {
err = ce->engine->emit_init_breadcrumb(rq);
if (err)
goto out_rq;
}
err = emit_gsc_heci_pkt(rq, &pkt);
if (err)
goto out_rq;
err = ce->engine->emit_flush(rq, 0);
out_rq:
i915_request_get(rq);
if (unlikely(err))
i915_request_set_error_once(rq, err);
i915_request_add(rq);
if (!err) {
/*
* Start timeout for i915_request_wait only after considering one possible
* pending GSC-HECI submission cycle on the other (non-privileged) path.
*/
if (wait_for(i915_request_started(rq), GSC_HECI_REPLY_LATENCY_MS))
drm_dbg(&gsc_uc_to_gt(gsc)->i915->drm,
"Delay in gsc-heci-priv submission to gsccs-hw");
if (i915_request_wait(rq, 0, msecs_to_jiffies(GSC_HECI_REPLY_LATENCY_MS)) < 0)
err = -ETIME;
}
i915_request_put(rq);
if (err)
drm_err(&gsc_uc_to_gt(gsc)->i915->drm,
"Request submission for GSC heci cmd failed (%d)\n",
err);
return err;
}
void intel_gsc_uc_heci_cmd_emit_mtl_header(struct intel_gsc_mtl_header *header,
u8 heci_client_id, u32 message_size,
u64 host_session_id)
{
host_session_id &= ~HOST_SESSION_MASK;
if (host_session_id && heci_client_id == HECI_MEADDRESS_PXP)
host_session_id |= HOST_SESSION_PXP_SINGLE;
header->validity_marker = GSC_HECI_VALIDITY_MARKER;
header->heci_client_id = heci_client_id;
header->host_session_handle = host_session_id;
header->header_version = MTL_GSC_HEADER_VERSION;
header->message_size = message_size;
}
static void
emit_gsc_heci_pkt_nonpriv(u32 *cmd, struct intel_gsc_heci_non_priv_pkt *pkt)
{
*cmd++ = GSC_HECI_CMD_PKT;
*cmd++ = lower_32_bits(pkt->addr_in);
*cmd++ = upper_32_bits(pkt->addr_in);
*cmd++ = pkt->size_in;
*cmd++ = lower_32_bits(pkt->addr_out);
*cmd++ = upper_32_bits(pkt->addr_out);
*cmd++ = pkt->size_out;
*cmd++ = 0;
*cmd++ = MI_BATCH_BUFFER_END;
}
int
intel_gsc_uc_heci_cmd_submit_nonpriv(struct intel_gsc_uc *gsc,
struct intel_context *ce,
struct intel_gsc_heci_non_priv_pkt *pkt,
u32 *cmd, int timeout_ms)
{
struct intel_engine_cs *engine;
struct i915_gem_ww_ctx ww;
struct i915_request *rq;
int err, trials = 0;
i915_gem_ww_ctx_init(&ww, false);
retry:
err = i915_gem_object_lock(pkt->bb_vma->obj, &ww);
if (err)
goto out_ww;
err = i915_gem_object_lock(pkt->heci_pkt_vma->obj, &ww);
if (err)
goto out_ww;
err = intel_context_pin_ww(ce, &ww);
if (err)
goto out_ww;
rq = i915_request_create(ce);
if (IS_ERR(rq)) {
err = PTR_ERR(rq);
goto out_unpin_ce;
}
emit_gsc_heci_pkt_nonpriv(cmd, pkt);
err = i915_vma_move_to_active(pkt->bb_vma, rq, 0);
if (err)
goto out_rq;
err = i915_vma_move_to_active(pkt->heci_pkt_vma, rq, EXEC_OBJECT_WRITE);
if (err)
goto out_rq;
engine = rq->context->engine;
if (engine->emit_init_breadcrumb) {
err = engine->emit_init_breadcrumb(rq);
if (err)
goto out_rq;
}
err = engine->emit_bb_start(rq, i915_vma_offset(pkt->bb_vma), PAGE_SIZE, 0);
if (err)
goto out_rq;
err = ce->engine->emit_flush(rq, 0);
if (err)
drm_err(&gsc_uc_to_gt(gsc)->i915->drm,
"Failed emit-flush for gsc-heci-non-priv-pkterr=%d\n", err);
out_rq:
i915_request_get(rq);
if (unlikely(err))
i915_request_set_error_once(rq, err);
i915_request_add(rq);
if (!err) {
/*
* Start timeout for i915_request_wait only after considering one possible
* pending GSC-HECI submission cycle on the other (privileged) path.
*/
if (wait_for(i915_request_started(rq), GSC_HECI_REPLY_LATENCY_MS))
drm_dbg(&gsc_uc_to_gt(gsc)->i915->drm,
"Delay in gsc-heci-non-priv submission to gsccs-hw");
if (i915_request_wait(rq, I915_WAIT_INTERRUPTIBLE,
msecs_to_jiffies(timeout_ms)) < 0)
err = -ETIME;
}
i915_request_put(rq);
out_unpin_ce:
intel_context_unpin(ce);
out_ww:
if (err == -EDEADLK) {
err = i915_gem_ww_ctx_backoff(&ww);
if (!err) {
if (++trials < 10)
goto retry;
else
err = -EAGAIN;
}
}
i915_gem_ww_ctx_fini(&ww);
return err;
}
@@ -0,0 +1,93 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2023 Intel Corporation
*/
#ifndef _INTEL_GSC_UC_HECI_CMD_SUBMIT_H_
#define _INTEL_GSC_UC_HECI_CMD_SUBMIT_H_
#include <linux/types.h>
struct i915_vma;
struct intel_context;
struct intel_gsc_uc;
#define GSC_HECI_REPLY_LATENCY_MS 500
/*
* Max FW response time is 500ms, but this should be counted from the time the
* command has hit the GSC-CS hardware, not the preceding handoff to GuC CTB.
*/
struct intel_gsc_mtl_header {
u32 validity_marker;
#define GSC_HECI_VALIDITY_MARKER 0xA578875A
u8 heci_client_id;
#define HECI_MEADDRESS_MKHI 7
#define HECI_MEADDRESS_PROXY 10
#define HECI_MEADDRESS_PXP 17
#define HECI_MEADDRESS_HDCP 18
u8 reserved1;
u16 header_version;
#define MTL_GSC_HEADER_VERSION 1
/*
* FW allows host to decide host_session handle
* as it sees fit.
* For intertracebility reserving select bits(60-63)
* to differentiate caller-target subsystem
* 0000 - HDCP
* 0001 - PXP Single Session
*/
u64 host_session_handle;
#define HOST_SESSION_MASK REG_GENMASK64(63, 60)
#define HOST_SESSION_PXP_SINGLE BIT_ULL(60)
u64 gsc_message_handle;
u32 message_size; /* lower 20 bits only, upper 12 are reserved */
/*
* Flags mask:
* Bit 0: Pending
* Bit 1: Session Cleanup;
* Bits 2-15: Flags
* Bits 16-31: Extension Size
* According to internal spec flags are either input or output
* we distinguish the flags using OUTFLAG or INFLAG
*/
u32 flags;
#define GSC_OUTFLAG_MSG_PENDING BIT(0)
#define GSC_INFLAG_MSG_CLEANUP BIT(1)
u32 status;
} __packed;
int intel_gsc_uc_heci_cmd_submit_packet(struct intel_gsc_uc *gsc,
u64 addr_in, u32 size_in,
u64 addr_out, u32 size_out);
void intel_gsc_uc_heci_cmd_emit_mtl_header(struct intel_gsc_mtl_header *header,
u8 heci_client_id, u32 message_size,
u64 host_session_id);
struct intel_gsc_heci_non_priv_pkt {
u64 addr_in;
u32 size_in;
u64 addr_out;
u32 size_out;
struct i915_vma *heci_pkt_vma;
struct i915_vma *bb_vma;
};
void
intel_gsc_uc_heci_cmd_emit_mtl_header(struct intel_gsc_mtl_header *header,
u8 heci_client_id, u32 msg_size,
u64 host_session_id);
int
intel_gsc_uc_heci_cmd_submit_nonpriv(struct intel_gsc_uc *gsc,
struct intel_context *ce,
struct intel_gsc_heci_non_priv_pkt *pkt,
u32 *cs, int timeout_ms);
#endif
@@ -0,0 +1,978 @@
// SPDX-License-Identifier: MIT
/*
* Copyright © 2014-2019 Intel Corporation
*/
#include "gem/i915_gem_lmem.h"
#include "gt/intel_gt.h"
#include "gt/intel_gt_irq.h"
#include "gt/intel_gt_pm_irq.h"
#include "gt/intel_gt_regs.h"
#include "i915_drv.h"
#include "i915_irq.h"
#include "i915_reg.h"
#include "i915_wait_util.h"
#include "intel_guc.h"
#include "intel_guc_ads.h"
#include "intel_guc_capture.h"
#include "intel_guc_print.h"
#include "intel_guc_slpc.h"
#include "intel_guc_submission.h"
/**
* DOC: GuC
*
* The GuC is a microcontroller inside the GT HW, introduced in gen9. The GuC is
* designed to offload some of the functionality usually performed by the host
* driver; currently the main operations it can take care of are:
*
* - Authentication of the HuC, which is required to fully enable HuC usage.
* - Low latency graphics context scheduling (a.k.a. GuC submission).
* - GT Power management.
*
* The enable_guc module parameter can be used to select which of those
* operations to enable within GuC. Note that not all the operations are
* supported on all gen9+ platforms.
*
* Enabling the GuC is not mandatory and therefore the firmware is only loaded
* if at least one of the operations is selected. However, not loading the GuC
* might result in the loss of some features that do require the GuC (currently
* just the HuC, but more are expected to land in the future).
*/
void intel_guc_notify(struct intel_guc *guc)
{
struct intel_gt *gt = guc_to_gt(guc);
/*
* On Gen11+, the value written to the register is passes as a payload
* to the FW. However, the FW currently treats all values the same way
* (H2G interrupt), so we can just write the value that the HW expects
* on older gens.
*/
intel_uncore_write(gt->uncore, guc->notify_reg, GUC_SEND_TRIGGER);
}
static inline i915_reg_t guc_send_reg(struct intel_guc *guc, u32 i)
{
GEM_BUG_ON(!guc->send_regs.base);
GEM_BUG_ON(!guc->send_regs.count);
GEM_BUG_ON(i >= guc->send_regs.count);
return _MMIO(guc->send_regs.base + 4 * i);
}
void intel_guc_init_send_regs(struct intel_guc *guc)
{
struct intel_gt *gt = guc_to_gt(guc);
enum forcewake_domains fw_domains = 0;
unsigned int i;
GEM_BUG_ON(!guc->send_regs.base);
GEM_BUG_ON(!guc->send_regs.count);
for (i = 0; i < guc->send_regs.count; i++) {
fw_domains |= intel_uncore_forcewake_for_reg(gt->uncore,
guc_send_reg(guc, i),
FW_REG_READ | FW_REG_WRITE);
}
guc->send_regs.fw_domains = fw_domains;
}
static void gen9_reset_guc_interrupts(struct intel_guc *guc)
{
struct intel_gt *gt = guc_to_gt(guc);
assert_rpm_wakelock_held(&gt->i915->runtime_pm);
spin_lock_irq(gt->irq_lock);
gen6_gt_pm_reset_iir(gt, gt->pm_guc_events);
spin_unlock_irq(gt->irq_lock);
}
static void gen9_enable_guc_interrupts(struct intel_guc *guc)
{
struct intel_gt *gt = guc_to_gt(guc);
assert_rpm_wakelock_held(&gt->i915->runtime_pm);
spin_lock_irq(gt->irq_lock);
guc_WARN_ON_ONCE(guc, intel_uncore_read(gt->uncore, GEN8_GT_IIR(2)) &
gt->pm_guc_events);
gen6_gt_pm_enable_irq(gt, gt->pm_guc_events);
spin_unlock_irq(gt->irq_lock);
guc->interrupts.enabled = true;
}
static void gen9_disable_guc_interrupts(struct intel_guc *guc)
{
struct intel_gt *gt = guc_to_gt(guc);
assert_rpm_wakelock_held(&gt->i915->runtime_pm);
guc->interrupts.enabled = false;
spin_lock_irq(gt->irq_lock);
gen6_gt_pm_disable_irq(gt, gt->pm_guc_events);
spin_unlock_irq(gt->irq_lock);
intel_synchronize_irq(gt->i915);
gen9_reset_guc_interrupts(guc);
}
static bool __gen11_reset_guc_interrupts(struct intel_gt *gt)
{
u32 irq = gt->type == GT_MEDIA ? MTL_MGUC : GEN11_GUC;
lockdep_assert_held(gt->irq_lock);
return gen11_gt_reset_one_iir(gt, 0, irq);
}
static void gen11_reset_guc_interrupts(struct intel_guc *guc)
{
struct intel_gt *gt = guc_to_gt(guc);
spin_lock_irq(gt->irq_lock);
__gen11_reset_guc_interrupts(gt);
spin_unlock_irq(gt->irq_lock);
}
static void gen11_enable_guc_interrupts(struct intel_guc *guc)
{
struct intel_gt *gt = guc_to_gt(guc);
spin_lock_irq(gt->irq_lock);
__gen11_reset_guc_interrupts(gt);
spin_unlock_irq(gt->irq_lock);
guc->interrupts.enabled = true;
}
static void gen11_disable_guc_interrupts(struct intel_guc *guc)
{
struct intel_gt *gt = guc_to_gt(guc);
guc->interrupts.enabled = false;
intel_synchronize_irq(gt->i915);
gen11_reset_guc_interrupts(guc);
}
static void guc_dead_worker_func(struct work_struct *w)
{
struct intel_guc *guc = container_of(w, struct intel_guc, dead_guc_worker);
struct intel_gt *gt = guc_to_gt(guc);
unsigned long last = guc->last_dead_guc_jiffies;
unsigned long delta = jiffies_to_msecs(jiffies - last);
if (delta < 500) {
intel_gt_set_wedged(gt);
} else {
intel_gt_handle_error(gt, ALL_ENGINES, I915_ERROR_CAPTURE, "dead GuC");
guc->last_dead_guc_jiffies = jiffies;
}
}
void intel_guc_init_early(struct intel_guc *guc)
{
struct intel_gt *gt = guc_to_gt(guc);
struct drm_i915_private *i915 = gt->i915;
intel_uc_fw_init_early(&guc->fw, INTEL_UC_FW_TYPE_GUC, true);
intel_guc_ct_init_early(&guc->ct);
intel_guc_log_init_early(&guc->log);
intel_guc_submission_init_early(guc);
intel_guc_slpc_init_early(&guc->slpc);
intel_guc_rc_init_early(guc);
INIT_WORK(&guc->dead_guc_worker, guc_dead_worker_func);
mutex_init(&guc->send_mutex);
spin_lock_init(&guc->irq_lock);
if (GRAPHICS_VER(i915) >= 11) {
guc->interrupts.reset = gen11_reset_guc_interrupts;
guc->interrupts.enable = gen11_enable_guc_interrupts;
guc->interrupts.disable = gen11_disable_guc_interrupts;
if (gt->type == GT_MEDIA) {
guc->notify_reg = MEDIA_GUC_HOST_INTERRUPT;
guc->send_regs.base = i915_mmio_reg_offset(MEDIA_SOFT_SCRATCH(0));
} else {
guc->notify_reg = GEN11_GUC_HOST_INTERRUPT;
guc->send_regs.base = i915_mmio_reg_offset(GEN11_SOFT_SCRATCH(0));
}
guc->send_regs.count = GEN11_SOFT_SCRATCH_COUNT;
} else {
guc->notify_reg = GUC_SEND_INTERRUPT;
guc->interrupts.reset = gen9_reset_guc_interrupts;
guc->interrupts.enable = gen9_enable_guc_interrupts;
guc->interrupts.disable = gen9_disable_guc_interrupts;
guc->send_regs.base = i915_mmio_reg_offset(SOFT_SCRATCH(0));
guc->send_regs.count = GUC_MAX_MMIO_MSG_LEN;
BUILD_BUG_ON(GUC_MAX_MMIO_MSG_LEN > SOFT_SCRATCH_COUNT);
}
intel_guc_enable_msg(guc, INTEL_GUC_RECV_MSG_EXCEPTION |
INTEL_GUC_RECV_MSG_CRASH_DUMP_POSTED);
}
void intel_guc_init_late(struct intel_guc *guc)
{
intel_guc_ads_init_late(guc);
}
static u32 guc_ctl_debug_flags(struct intel_guc *guc)
{
u32 level = intel_guc_log_get_level(&guc->log);
u32 flags = 0;
if (!GUC_LOG_LEVEL_IS_VERBOSE(level))
flags |= GUC_LOG_DISABLED;
else
flags |= GUC_LOG_LEVEL_TO_VERBOSITY(level) <<
GUC_LOG_VERBOSITY_SHIFT;
return flags;
}
static u32 guc_ctl_feature_flags(struct intel_guc *guc)
{
struct intel_gt *gt = guc_to_gt(guc);
u32 flags = 0;
/*
* Enable PXP GuC autoteardown flow.
* NB: MTL does things differently.
*/
if (HAS_PXP(gt->i915) && !IS_METEORLAKE(gt->i915))
flags |= GUC_CTL_ENABLE_GUC_PXP_CTL;
if (!intel_guc_submission_is_used(guc))
flags |= GUC_CTL_DISABLE_SCHEDULER;
if (intel_guc_slpc_is_used(guc))
flags |= GUC_CTL_ENABLE_SLPC;
return flags;
}
static u32 guc_ctl_log_params_flags(struct intel_guc *guc)
{
struct intel_guc_log *log = &guc->log;
u32 offset, flags;
GEM_BUG_ON(!log->sizes_initialised);
offset = intel_guc_ggtt_offset(guc, log->vma) >> PAGE_SHIFT;
flags = GUC_LOG_VALID |
GUC_LOG_NOTIFY_ON_HALF_FULL |
log->sizes[GUC_LOG_SECTIONS_DEBUG].flag |
log->sizes[GUC_LOG_SECTIONS_CAPTURE].flag |
(log->sizes[GUC_LOG_SECTIONS_CRASH].count << GUC_LOG_CRASH_SHIFT) |
(log->sizes[GUC_LOG_SECTIONS_DEBUG].count << GUC_LOG_DEBUG_SHIFT) |
(log->sizes[GUC_LOG_SECTIONS_CAPTURE].count << GUC_LOG_CAPTURE_SHIFT) |
(offset << GUC_LOG_BUF_ADDR_SHIFT);
return flags;
}
static u32 guc_ctl_ads_flags(struct intel_guc *guc)
{
u32 ads = intel_guc_ggtt_offset(guc, guc->ads_vma) >> PAGE_SHIFT;
u32 flags = ads << GUC_ADS_ADDR_SHIFT;
return flags;
}
static u32 guc_ctl_wa_flags(struct intel_guc *guc)
{
struct intel_gt *gt = guc_to_gt(guc);
u32 flags = 0;
/* Wa_22012773006:gen11,gen12 < XeHP */
if (GRAPHICS_VER(gt->i915) >= 11 &&
GRAPHICS_VER_FULL(gt->i915) < IP_VER(12, 55))
flags |= GUC_WA_POLLCS;
/* Wa_14014475959 */
if (IS_GFX_GT_IP_STEP(gt, IP_VER(12, 70), STEP_A0, STEP_B0) ||
IS_DG2(gt->i915))
flags |= GUC_WA_HOLD_CCS_SWITCHOUT;
/* Wa_16019325821 */
/* Wa_14019159160 */
if (IS_GFX_GT_IP_RANGE(gt, IP_VER(12, 70), IP_VER(12, 74)))
flags |= GUC_WA_RCS_CCS_SWITCHOUT;
/*
* Wa_14012197797
* Wa_22011391025
*
* The same WA bit is used for both and 22011391025 is applicable to
* all DG2.
*
* Platforms post DG2 prevent this issue in hardware by stalling
* submissions. With this flag GuC will schedule as to avoid such
* stalls.
*/
if (IS_DG2(gt->i915) ||
(CCS_MASK(gt) && GRAPHICS_VER_FULL(gt->i915) >= IP_VER(12, 70)))
flags |= GUC_WA_DUAL_QUEUE;
/* Wa_22011802037: graphics version 11/12 */
if (intel_engine_reset_needs_wa_22011802037(gt))
flags |= GUC_WA_PRE_PARSER;
/*
* Wa_22012727170
* Wa_22012727685
*/
if (IS_DG2_G11(gt->i915))
flags |= GUC_WA_CONTEXT_ISOLATION;
/*
* Wa_14018913170: Applicable to all platforms supported by i915 so
* don't bother testing for all X/Y/Z platforms explicitly.
*/
if (GUC_FIRMWARE_VER(guc) >= MAKE_GUC_VER(70, 7, 0))
flags |= GUC_WA_ENABLE_TSC_CHECK_ON_RC6;
return flags;
}
static u32 guc_ctl_devid(struct intel_guc *guc)
{
struct drm_i915_private *i915 = guc_to_i915(guc);
return (INTEL_DEVID(i915) << 16) | INTEL_REVID(i915);
}
/*
* Initialise the GuC parameter block before starting the firmware
* transfer. These parameters are read by the firmware on startup
* and cannot be changed thereafter.
*/
static void guc_init_params(struct intel_guc *guc)
{
u32 *params = guc->params;
int i;
BUILD_BUG_ON(sizeof(guc->params) != GUC_CTL_MAX_DWORDS * sizeof(u32));
params[GUC_CTL_LOG_PARAMS] = guc_ctl_log_params_flags(guc);
params[GUC_CTL_FEATURE] = guc_ctl_feature_flags(guc);
params[GUC_CTL_DEBUG] = guc_ctl_debug_flags(guc);
params[GUC_CTL_ADS] = guc_ctl_ads_flags(guc);
params[GUC_CTL_WA] = guc_ctl_wa_flags(guc);
params[GUC_CTL_DEVID] = guc_ctl_devid(guc);
for (i = 0; i < GUC_CTL_MAX_DWORDS; i++)
guc_dbg(guc, "param[%2d] = %#x\n", i, params[i]);
}
/*
* Initialise the GuC parameter block before starting the firmware
* transfer. These parameters are read by the firmware on startup
* and cannot be changed thereafter.
*/
void intel_guc_write_params(struct intel_guc *guc)
{
struct intel_uncore *uncore = guc_to_gt(guc)->uncore;
int i;
/*
* All SOFT_SCRATCH registers are in FORCEWAKE_GT domain and
* they are power context saved so it's ok to release forcewake
* when we are done here and take it again at xfer time.
*/
intel_uncore_forcewake_get(uncore, FORCEWAKE_GT);
intel_uncore_write(uncore, SOFT_SCRATCH(0), 0);
for (i = 0; i < GUC_CTL_MAX_DWORDS; i++)
intel_uncore_write(uncore, SOFT_SCRATCH(1 + i), guc->params[i]);
intel_uncore_forcewake_put(uncore, FORCEWAKE_GT);
}
void intel_guc_dump_time_info(struct intel_guc *guc, struct drm_printer *p)
{
struct intel_gt *gt = guc_to_gt(guc);
intel_wakeref_t wakeref;
u32 stamp = 0;
u64 ktime;
with_intel_runtime_pm(&gt->i915->runtime_pm, wakeref)
stamp = intel_uncore_read(gt->uncore, GUCPMTIMESTAMP);
ktime = ktime_get_boottime_ns();
drm_printf(p, "Kernel timestamp: 0x%08llX [%llu]\n", ktime, ktime);
drm_printf(p, "GuC timestamp: 0x%08X [%u]\n", stamp, stamp);
drm_printf(p, "CS timestamp frequency: %u Hz, %u ns\n",
gt->clock_frequency, gt->clock_period_ns);
}
int intel_guc_init(struct intel_guc *guc)
{
int ret;
ret = intel_uc_fw_init(&guc->fw);
if (ret)
goto out;
ret = intel_guc_log_create(&guc->log);
if (ret)
goto err_fw;
ret = intel_guc_capture_init(guc);
if (ret)
goto err_log;
ret = intel_guc_ads_create(guc);
if (ret)
goto err_capture;
GEM_BUG_ON(!guc->ads_vma);
ret = intel_guc_ct_init(&guc->ct);
if (ret)
goto err_ads;
if (intel_guc_submission_is_used(guc)) {
/*
* This is stuff we need to have available at fw load time
* if we are planning to enable submission later
*/
ret = intel_guc_submission_init(guc);
if (ret)
goto err_ct;
}
if (intel_guc_slpc_is_used(guc)) {
ret = intel_guc_slpc_init(&guc->slpc);
if (ret)
goto err_submission;
}
/* now that everything is perma-pinned, initialize the parameters */
guc_init_params(guc);
intel_uc_fw_change_status(&guc->fw, INTEL_UC_FIRMWARE_LOADABLE);
return 0;
err_submission:
intel_guc_submission_fini(guc);
err_ct:
intel_guc_ct_fini(&guc->ct);
err_ads:
intel_guc_ads_destroy(guc);
err_capture:
intel_guc_capture_destroy(guc);
err_log:
intel_guc_log_destroy(&guc->log);
err_fw:
intel_uc_fw_fini(&guc->fw);
out:
intel_uc_fw_change_status(&guc->fw, INTEL_UC_FIRMWARE_INIT_FAIL);
guc_probe_error(guc, "failed with %pe\n", ERR_PTR(ret));
return ret;
}
void intel_guc_fini(struct intel_guc *guc)
{
if (!intel_uc_fw_is_loadable(&guc->fw))
return;
flush_work(&guc->dead_guc_worker);
if (intel_guc_slpc_is_used(guc))
intel_guc_slpc_fini(&guc->slpc);
if (intel_guc_submission_is_used(guc))
intel_guc_submission_fini(guc);
intel_guc_ct_fini(&guc->ct);
intel_guc_ads_destroy(guc);
intel_guc_capture_destroy(guc);
intel_guc_log_destroy(&guc->log);
intel_uc_fw_fini(&guc->fw);
}
/*
* This function implements the MMIO based host to GuC interface.
*/
int intel_guc_send_mmio(struct intel_guc *guc, const u32 *request, u32 len,
u32 *response_buf, u32 response_buf_size)
{
struct intel_uncore *uncore = guc_to_gt(guc)->uncore;
u32 header;
int i;
int ret;
GEM_BUG_ON(!len);
GEM_BUG_ON(len > guc->send_regs.count);
GEM_BUG_ON(FIELD_GET(GUC_HXG_MSG_0_ORIGIN, request[0]) != GUC_HXG_ORIGIN_HOST);
GEM_BUG_ON(FIELD_GET(GUC_HXG_MSG_0_TYPE, request[0]) != GUC_HXG_TYPE_REQUEST);
mutex_lock(&guc->send_mutex);
intel_uncore_forcewake_get(uncore, guc->send_regs.fw_domains);
retry:
for (i = 0; i < len; i++)
intel_uncore_write(uncore, guc_send_reg(guc, i), request[i]);
intel_uncore_posting_read(uncore, guc_send_reg(guc, i - 1));
intel_guc_notify(guc);
/*
* No GuC command should ever take longer than 10ms.
* Fast commands should still complete in 10us.
*/
ret = __intel_wait_for_register_fw(uncore,
guc_send_reg(guc, 0),
GUC_HXG_MSG_0_ORIGIN,
FIELD_PREP(GUC_HXG_MSG_0_ORIGIN,
GUC_HXG_ORIGIN_GUC),
10, 10, &header);
if (unlikely(ret)) {
timeout:
guc_err(guc, "mmio request %#x: no reply %x\n",
request[0], header);
goto out;
}
if (FIELD_GET(GUC_HXG_MSG_0_TYPE, header) == GUC_HXG_TYPE_NO_RESPONSE_BUSY) {
#define done ({ header = intel_uncore_read(uncore, guc_send_reg(guc, 0)); \
FIELD_GET(GUC_HXG_MSG_0_ORIGIN, header) != GUC_HXG_ORIGIN_GUC || \
FIELD_GET(GUC_HXG_MSG_0_TYPE, header) != GUC_HXG_TYPE_NO_RESPONSE_BUSY; })
ret = wait_for(done, 1000);
if (unlikely(ret))
goto timeout;
if (unlikely(FIELD_GET(GUC_HXG_MSG_0_ORIGIN, header) !=
GUC_HXG_ORIGIN_GUC))
goto proto;
#undef done
}
if (FIELD_GET(GUC_HXG_MSG_0_TYPE, header) == GUC_HXG_TYPE_NO_RESPONSE_RETRY) {
u32 reason = FIELD_GET(GUC_HXG_RETRY_MSG_0_REASON, header);
guc_dbg(guc, "mmio request %#x: retrying, reason %u\n",
request[0], reason);
goto retry;
}
if (FIELD_GET(GUC_HXG_MSG_0_TYPE, header) == GUC_HXG_TYPE_RESPONSE_FAILURE) {
u32 hint = FIELD_GET(GUC_HXG_FAILURE_MSG_0_HINT, header);
u32 error = FIELD_GET(GUC_HXG_FAILURE_MSG_0_ERROR, header);
guc_err(guc, "mmio request %#x: failure %x/%u\n",
request[0], error, hint);
ret = -ENXIO;
goto out;
}
if (FIELD_GET(GUC_HXG_MSG_0_TYPE, header) != GUC_HXG_TYPE_RESPONSE_SUCCESS) {
proto:
guc_err(guc, "mmio request %#x: unexpected reply %#x\n",
request[0], header);
ret = -EPROTO;
goto out;
}
if (response_buf) {
int count = min(response_buf_size, guc->send_regs.count);
GEM_BUG_ON(!count);
response_buf[0] = header;
for (i = 1; i < count; i++)
response_buf[i] = intel_uncore_read(uncore,
guc_send_reg(guc, i));
/* Use number of copied dwords as our return value */
ret = count;
} else {
/* Use data from the GuC response as our return value */
ret = FIELD_GET(GUC_HXG_RESPONSE_MSG_0_DATA0, header);
}
out:
intel_uncore_forcewake_put(uncore, guc->send_regs.fw_domains);
mutex_unlock(&guc->send_mutex);
return ret;
}
int intel_guc_crash_process_msg(struct intel_guc *guc, u32 action)
{
if (action == INTEL_GUC_ACTION_NOTIFY_CRASH_DUMP_POSTED)
guc_err(guc, "Crash dump notification\n");
else if (action == INTEL_GUC_ACTION_NOTIFY_EXCEPTION)
guc_err(guc, "Exception notification\n");
else
guc_err(guc, "Unknown crash notification: 0x%04X\n", action);
queue_work(system_dfl_wq, &guc->dead_guc_worker);
return 0;
}
int intel_guc_to_host_process_recv_msg(struct intel_guc *guc,
const u32 *payload, u32 len)
{
u32 msg;
if (unlikely(!len))
return -EPROTO;
/* Make sure to handle only enabled messages */
msg = payload[0] & guc->msg_enabled_mask;
if (msg & INTEL_GUC_RECV_MSG_CRASH_DUMP_POSTED)
guc_err(guc, "Received early crash dump notification!\n");
if (msg & INTEL_GUC_RECV_MSG_EXCEPTION)
guc_err(guc, "Received early exception notification!\n");
if (msg & (INTEL_GUC_RECV_MSG_CRASH_DUMP_POSTED | INTEL_GUC_RECV_MSG_EXCEPTION))
queue_work(system_dfl_wq, &guc->dead_guc_worker);
return 0;
}
/**
* intel_guc_auth_huc() - Send action to GuC to authenticate HuC ucode
* @guc: intel_guc structure
* @rsa_offset: rsa offset w.r.t ggtt base of huc vma
*
* Triggers a HuC firmware authentication request to the GuC via intel_guc_send
* INTEL_GUC_ACTION_AUTHENTICATE_HUC interface. This function is invoked by
* intel_huc_auth().
*
* Return: non-zero code on error
*/
int intel_guc_auth_huc(struct intel_guc *guc, u32 rsa_offset)
{
u32 action[] = {
INTEL_GUC_ACTION_AUTHENTICATE_HUC,
rsa_offset
};
return intel_guc_send(guc, action, ARRAY_SIZE(action));
}
/**
* intel_guc_suspend() - notify GuC entering suspend state
* @guc: the guc
*/
int intel_guc_suspend(struct intel_guc *guc)
{
int ret;
u32 action[] = {
INTEL_GUC_ACTION_CLIENT_SOFT_RESET,
};
if (!intel_guc_is_ready(guc))
return 0;
if (intel_guc_submission_is_used(guc)) {
flush_work(&guc->dead_guc_worker);
/*
* This H2G MMIO command tears down the GuC in two steps. First it will
* generate a G2H CTB for every active context indicating a reset. In
* practice the i915 shouldn't ever get a G2H as suspend should only be
* called when the GPU is idle. Next, it tears down the CTBs and this
* H2G MMIO command completes.
*
* Don't abort on a failure code from the GuC. Keep going and do the
* clean up in sanitize() and re-initialisation on resume and hopefully
* the error here won't be problematic.
*/
ret = intel_guc_send_mmio(guc, action, ARRAY_SIZE(action), NULL, 0);
if (ret)
guc_err(guc, "suspend: RESET_CLIENT action failed with %pe\n",
ERR_PTR(ret));
}
/* Signal that the GuC isn't running. */
intel_guc_sanitize(guc);
return 0;
}
/**
* intel_guc_resume() - notify GuC resuming from suspend state
* @guc: the guc
*/
int intel_guc_resume(struct intel_guc *guc)
{
/*
* NB: This function can still be called even if GuC submission is
* disabled, e.g. if GuC is enabled for HuC authentication only. Thus,
* if any code is later added here, it must be support doing nothing
* if submission is disabled (as per intel_guc_suspend).
*/
return 0;
}
/**
* DOC: GuC Memory Management
*
* GuC can't allocate any memory for its own usage, so all the allocations must
* be handled by the host driver. GuC accesses the memory via the GGTT, with the
* exception of the top and bottom parts of the 4GB address space, which are
* instead re-mapped by the GuC HW to memory location of the FW itself (WOPCM)
* or other parts of the HW. The driver must take care not to place objects that
* the GuC is going to access in these reserved ranges. The layout of the GuC
* address space is shown below:
*
* ::
*
* +===========> +====================+ <== FFFF_FFFF
* ^ | Reserved |
* | +====================+ <== GUC_GGTT_TOP
* | | |
* | | DRAM |
* GuC | |
* Address +===> +====================+ <== GuC ggtt_pin_bias
* Space ^ | |
* | | | |
* | GuC | GuC |
* | WOPCM | WOPCM |
* | Size | |
* | | | |
* v v | |
* +=======+===> +====================+ <== 0000_0000
*
* The lower part of GuC Address Space [0, ggtt_pin_bias) is mapped to GuC WOPCM
* while upper part of GuC Address Space [ggtt_pin_bias, GUC_GGTT_TOP) is mapped
* to DRAM. The value of the GuC ggtt_pin_bias is the GuC WOPCM size.
*/
/**
* intel_guc_allocate_vma() - Allocate a GGTT VMA for GuC usage
* @guc: the guc
* @size: size of area to allocate (both virtual space and memory)
*
* This is a wrapper to create an object for use with the GuC. In order to
* use it inside the GuC, an object needs to be pinned lifetime, so we allocate
* both some backing storage and a range inside the Global GTT. We must pin
* it in the GGTT somewhere other than than [0, GUC ggtt_pin_bias) because that
* range is reserved inside GuC.
*
* Return: A i915_vma if successful, otherwise an ERR_PTR.
*/
struct i915_vma *intel_guc_allocate_vma(struct intel_guc *guc, u32 size)
{
struct intel_gt *gt = guc_to_gt(guc);
struct drm_i915_gem_object *obj;
struct i915_vma *vma;
u64 flags;
int ret;
if (HAS_LMEM(gt->i915))
obj = i915_gem_object_create_lmem(gt->i915, size,
I915_BO_ALLOC_CPU_CLEAR |
I915_BO_ALLOC_CONTIGUOUS |
I915_BO_ALLOC_PM_EARLY);
else
obj = i915_gem_object_create_shmem(gt->i915, size);
if (IS_ERR(obj))
return ERR_CAST(obj);
/*
* Wa_22016122933: For Media version 13.0, all Media GT shared
* memory needs to be mapped as WC on CPU side and UC (PAT
* index 2) on GPU side.
*/
if (intel_gt_needs_wa_22016122933(gt))
i915_gem_object_set_cache_coherency(obj, I915_CACHE_NONE);
vma = i915_vma_instance(obj, &gt->ggtt->vm, NULL);
if (IS_ERR(vma))
goto err;
flags = PIN_OFFSET_BIAS | i915_ggtt_pin_bias(vma);
ret = i915_ggtt_pin(vma, NULL, 0, flags);
if (ret) {
vma = ERR_PTR(ret);
goto err;
}
return i915_vma_make_unshrinkable(vma);
err:
i915_gem_object_put(obj);
return vma;
}
/**
* intel_guc_allocate_and_map_vma() - Allocate and map VMA for GuC usage
* @guc: the guc
* @size: size of area to allocate (both virtual space and memory)
* @out_vma: return variable for the allocated vma pointer
* @out_vaddr: return variable for the obj mapping
*
* This wrapper calls intel_guc_allocate_vma() and then maps the allocated
* object with I915_MAP_WB.
*
* Return: 0 if successful, a negative errno code otherwise.
*/
int intel_guc_allocate_and_map_vma(struct intel_guc *guc, u32 size,
struct i915_vma **out_vma, void **out_vaddr)
{
struct i915_vma *vma;
void *vaddr;
vma = intel_guc_allocate_vma(guc, size);
if (IS_ERR(vma))
return PTR_ERR(vma);
vaddr = i915_gem_object_pin_map_unlocked(vma->obj,
intel_gt_coherent_map_type(guc_to_gt(guc),
vma->obj, true));
if (IS_ERR(vaddr)) {
i915_vma_unpin_and_release(&vma, 0);
return PTR_ERR(vaddr);
}
*out_vma = vma;
*out_vaddr = vaddr;
return 0;
}
static int __guc_action_self_cfg(struct intel_guc *guc, u16 key, u16 len, u64 value)
{
u32 request[HOST2GUC_SELF_CFG_REQUEST_MSG_LEN] = {
FIELD_PREP(GUC_HXG_MSG_0_ORIGIN, GUC_HXG_ORIGIN_HOST) |
FIELD_PREP(GUC_HXG_MSG_0_TYPE, GUC_HXG_TYPE_REQUEST) |
FIELD_PREP(GUC_HXG_REQUEST_MSG_0_ACTION, GUC_ACTION_HOST2GUC_SELF_CFG),
FIELD_PREP(HOST2GUC_SELF_CFG_REQUEST_MSG_1_KLV_KEY, key) |
FIELD_PREP(HOST2GUC_SELF_CFG_REQUEST_MSG_1_KLV_LEN, len),
FIELD_PREP(HOST2GUC_SELF_CFG_REQUEST_MSG_2_VALUE32, lower_32_bits(value)),
FIELD_PREP(HOST2GUC_SELF_CFG_REQUEST_MSG_3_VALUE64, upper_32_bits(value)),
};
int ret;
GEM_BUG_ON(len > 2);
GEM_BUG_ON(len == 1 && upper_32_bits(value));
/* Self config must go over MMIO */
ret = intel_guc_send_mmio(guc, request, ARRAY_SIZE(request), NULL, 0);
if (unlikely(ret < 0))
return ret;
if (unlikely(ret > 1))
return -EPROTO;
if (unlikely(!ret))
return -ENOKEY;
return 0;
}
static int __guc_self_cfg(struct intel_guc *guc, u16 key, u16 len, u64 value)
{
int err = __guc_action_self_cfg(guc, key, len, value);
if (unlikely(err))
guc_probe_error(guc, "Unsuccessful self-config (%pe) key %#hx value %#llx\n",
ERR_PTR(err), key, value);
return err;
}
int intel_guc_self_cfg32(struct intel_guc *guc, u16 key, u32 value)
{
return __guc_self_cfg(guc, key, 1, value);
}
int intel_guc_self_cfg64(struct intel_guc *guc, u16 key, u64 value)
{
return __guc_self_cfg(guc, key, 2, value);
}
/**
* intel_guc_load_status - dump information about GuC load status
* @guc: the GuC
* @p: the &drm_printer
*
* Pretty printer for GuC load status.
*/
void intel_guc_load_status(struct intel_guc *guc, struct drm_printer *p)
{
struct intel_gt *gt = guc_to_gt(guc);
struct intel_uncore *uncore = gt->uncore;
intel_wakeref_t wakeref;
if (!intel_guc_is_supported(guc)) {
drm_printf(p, "GuC not supported\n");
return;
}
if (!intel_guc_is_wanted(guc)) {
drm_printf(p, "GuC disabled\n");
return;
}
intel_uc_fw_dump(&guc->fw, p);
with_intel_runtime_pm(uncore->rpm, wakeref) {
u32 status = intel_uncore_read(uncore, GUC_STATUS);
u32 i;
drm_printf(p, "GuC status 0x%08x:\n", status);
drm_printf(p, "\tBootrom status = 0x%x\n",
(status & GS_BOOTROM_MASK) >> GS_BOOTROM_SHIFT);
drm_printf(p, "\tuKernel status = 0x%x\n",
(status & GS_UKERNEL_MASK) >> GS_UKERNEL_SHIFT);
drm_printf(p, "\tMIA Core status = 0x%x\n",
(status & GS_MIA_MASK) >> GS_MIA_SHIFT);
drm_puts(p, "Scratch registers:\n");
for (i = 0; i < 16; i++) {
drm_printf(p, "\t%2d: \t0x%x\n",
i, intel_uncore_read(uncore, SOFT_SCRATCH(i)));
}
}
}
void intel_guc_write_barrier(struct intel_guc *guc)
{
struct intel_gt *gt = guc_to_gt(guc);
if (i915_gem_object_is_lmem(guc->ct.vma->obj)) {
/*
* Ensure intel_uncore_write_fw can be used rather than
* intel_uncore_write.
*/
GEM_BUG_ON(guc->send_regs.fw_domains);
/*
* This register is used by the i915 and GuC for MMIO based
* communication. Once we are in this code CTBs are the only
* method the i915 uses to communicate with the GuC so it is
* safe to write to this register (a value of 0 is NOP for MMIO
* communication). If we ever start mixing CTBs and MMIOs a new
* register will have to be chosen. This function is also used
* to enforce ordering of a work queue item write and an update
* to the process descriptor. When a work queue is being used,
* CTBs are also the only mechanism of communication.
*/
intel_uncore_write_fw(gt->uncore, GEN11_SOFT_SCRATCH(0), 0);
} else {
/* wmb() sufficient for a barrier if in smem */
wmb();
}
}
@@ -0,0 +1,554 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2014-2019 Intel Corporation
*/
#ifndef _INTEL_GUC_H_
#define _INTEL_GUC_H_
#include <linux/delay.h>
#include <linux/iosys-map.h>
#include <linux/xarray.h>
#include "intel_guc_ct.h"
#include "intel_guc_fw.h"
#include "intel_guc_fwif.h"
#include "intel_guc_log.h"
#include "intel_guc_reg.h"
#include "intel_guc_slpc_types.h"
#include "intel_uc_fw.h"
#include "intel_uncore.h"
#include "i915_utils.h"
#include "i915_vma.h"
struct __guc_ads_blob;
struct intel_guc_state_capture;
/**
* struct intel_guc - Top level structure of GuC.
*
* It handles firmware loading and manages client pool. intel_guc owns an
* i915_sched_engine for submission.
*/
struct intel_guc {
/** @fw: the GuC firmware */
struct intel_uc_fw fw;
/** @log: sub-structure containing GuC log related data and objects */
struct intel_guc_log log;
/** @ct: the command transport communication channel */
struct intel_guc_ct ct;
/** @slpc: sub-structure containing SLPC related data and objects */
struct intel_guc_slpc slpc;
/** @capture: the error-state-capture module's data and objects */
struct intel_guc_state_capture *capture;
/** @dbgfs_node: debugfs node */
struct dentry *dbgfs_node;
/** @sched_engine: Global engine used to submit requests to GuC */
struct i915_sched_engine *sched_engine;
/**
* @stalled_request: if GuC can't process a request for any reason, we
* save it until GuC restarts processing. No other request can be
* submitted until the stalled request is processed.
*/
struct i915_request *stalled_request;
/**
* @submission_stall_reason: reason why submission is stalled
*/
enum {
STALL_NONE,
STALL_REGISTER_CONTEXT,
STALL_MOVE_LRC_TAIL,
STALL_ADD_REQUEST,
} submission_stall_reason;
/* intel_guc_recv interrupt related state */
/** @irq_lock: protects GuC irq state */
spinlock_t irq_lock;
/**
* @msg_enabled_mask: mask of events that are processed when receiving
* an INTEL_GUC_ACTION_DEFAULT G2H message.
*/
unsigned int msg_enabled_mask;
/**
* @outstanding_submission_g2h: number of outstanding GuC to Host
* responses related to GuC submission, used to determine if the GT is
* idle
*/
atomic_t outstanding_submission_g2h;
/** @tlb_lookup: xarray to store all pending TLB invalidation requests */
struct xarray tlb_lookup;
/**
* @serial_slot: id to the initial waiter created in tlb_lookup,
* which is used only when failed to allocate new waiter.
*/
u32 serial_slot;
/** @next_seqno: the next id (sequence number) to allocate. */
u32 next_seqno;
/** @interrupts: pointers to GuC interrupt-managing functions. */
struct {
bool enabled;
void (*reset)(struct intel_guc *guc);
void (*enable)(struct intel_guc *guc);
void (*disable)(struct intel_guc *guc);
} interrupts;
/**
* @submission_state: sub-structure for submission state protected by
* single lock
*/
struct {
/**
* @submission_state.lock: protects everything in
* submission_state, ce->guc_id.id, and ce->guc_id.ref
* when transitioning in and out of zero
*/
spinlock_t lock;
/**
* @submission_state.guc_ids: used to allocate new
* guc_ids, single-lrc
*/
struct ida guc_ids;
/**
* @submission_state.num_guc_ids: Number of guc_ids, selftest
* feature to be able to reduce this number while testing.
*/
int num_guc_ids;
/**
* @submission_state.guc_ids_bitmap: used to allocate
* new guc_ids, multi-lrc
*/
unsigned long *guc_ids_bitmap;
/**
* @submission_state.guc_id_list: list of intel_context
* with valid guc_ids but no refs
*/
struct list_head guc_id_list;
/**
* @submission_state.guc_ids_in_use: Number single-lrc
* guc_ids in use
*/
unsigned int guc_ids_in_use;
/**
* @submission_state.destroyed_contexts: list of contexts
* waiting to be destroyed (deregistered with the GuC)
*/
struct list_head destroyed_contexts;
/**
* @submission_state.destroyed_worker: worker to deregister
* contexts, need as we need to take a GT PM reference and
* can't from destroy function as it might be in an atomic
* context (no sleeping)
*/
struct work_struct destroyed_worker;
/**
* @submission_state.reset_fail_worker: worker to trigger
* a GT reset after an engine reset fails
*/
struct work_struct reset_fail_worker;
/**
* @submission_state.reset_fail_mask: mask of engines that
* failed to reset
*/
intel_engine_mask_t reset_fail_mask;
/**
* @submission_state.sched_disable_delay_ms: schedule
* disable delay, in ms, for contexts
*/
unsigned int sched_disable_delay_ms;
/**
* @submission_state.sched_disable_gucid_threshold:
* threshold of min remaining available guc_ids before
* we start bypassing the schedule disable delay
*/
unsigned int sched_disable_gucid_threshold;
} submission_state;
/**
* @submission_supported: tracks whether we support GuC submission on
* the current platform
*/
bool submission_supported;
/** @submission_selected: tracks whether the user enabled GuC submission */
bool submission_selected;
/** @submission_initialized: tracks whether GuC submission has been initialised */
bool submission_initialized;
/** @submission_version: Submission API version of the currently loaded firmware */
struct intel_uc_fw_ver submission_version;
/**
* @rc_supported: tracks whether we support GuC rc on the current platform
*/
bool rc_supported;
/** @rc_selected: tracks whether the user enabled GuC rc */
bool rc_selected;
/** @ads_vma: object allocated to hold the GuC ADS */
struct i915_vma *ads_vma;
/** @ads_map: contents of the GuC ADS */
struct iosys_map ads_map;
/** @ads_regset_size: size of the save/restore regsets in the ADS */
u32 ads_regset_size;
/**
* @ads_regset_count: number of save/restore registers in the ADS for
* each engine
*/
u32 ads_regset_count[I915_NUM_ENGINES];
/** @ads_regset: save/restore regsets in the ADS */
struct guc_mmio_reg *ads_regset;
/** @ads_golden_ctxt_size: size of the golden contexts in the ADS */
u32 ads_golden_ctxt_size;
/** @ads_waklv_size: size of workaround KLVs */
u32 ads_waklv_size;
/** @ads_capture_size: size of register lists in the ADS used for error capture */
u32 ads_capture_size;
/** @lrc_desc_pool_v69: object allocated to hold the GuC LRC descriptor pool */
struct i915_vma *lrc_desc_pool_v69;
/** @lrc_desc_pool_vaddr_v69: contents of the GuC LRC descriptor pool */
void *lrc_desc_pool_vaddr_v69;
/**
* @context_lookup: used to resolve intel_context from guc_id, if a
* context is present in this structure it is registered with the GuC
*/
struct xarray context_lookup;
/** @params: Control params for fw initialization */
u32 params[GUC_CTL_MAX_DWORDS];
/** @send_regs: GuC's FW specific registers used for sending MMIO H2G */
struct {
u32 base;
unsigned int count;
enum forcewake_domains fw_domains;
} send_regs;
/** @notify_reg: register used to send interrupts to the GuC FW */
i915_reg_t notify_reg;
/**
* @mmio_msg: notification bitmask that the GuC writes in one of its
* registers when the CT channel is disabled, to be processed when the
* channel is back up.
*/
u32 mmio_msg;
/** @send_mutex: used to serialize the intel_guc_send actions */
struct mutex send_mutex;
/**
* @timestamp: GT timestamp object that stores a copy of the timestamp
* and adjusts it for overflow using a worker.
*/
struct {
/**
* @timestamp.lock: Lock protecting the below fields and
* the engine stats.
*/
spinlock_t lock;
/**
* @timestamp.gt_stamp: 64-bit extended value of the GT
* timestamp.
*/
u64 gt_stamp;
/**
* @timestamp.ping_delay: Period for polling the GT
* timestamp for overflow.
*/
unsigned long ping_delay;
/**
* @timestamp.work: Periodic work to adjust GT timestamp,
* engine and context usage for overflows.
*/
struct delayed_work work;
/**
* @timestamp.shift: Right shift value for the gpm timestamp
*/
u32 shift;
/**
* @timestamp.last_stat_jiffies: jiffies at last actual
* stats collection time. We use this timestamp to ensure
* we don't oversample the stats because runtime power
* management events can trigger stats collection at much
* higher rates than required.
*/
unsigned long last_stat_jiffies;
} timestamp;
/**
* @dead_guc_worker: Asynchronous worker thread for forcing a GuC reset.
* Specifically used when the G2H handler wants to issue a reset. Resets
* require flushing the G2H queue. So, the G2H processing itself must not
* trigger a reset directly. Instead, go via this worker.
*/
struct work_struct dead_guc_worker;
/**
* @last_dead_guc_jiffies: timestamp of previous 'dead guc' occurrence
* used to prevent a fundamentally broken system from continuously
* reloading the GuC.
*/
unsigned long last_dead_guc_jiffies;
#ifdef CONFIG_DRM_I915_SELFTEST
/**
* @number_guc_id_stolen: The number of guc_ids that have been stolen
*/
int number_guc_id_stolen;
/**
* @fast_response_selftest: Backdoor to CT handler for fast response selftest
*/
u32 fast_response_selftest;
#endif
};
struct intel_guc_tlb_wait {
struct wait_queue_head wq;
bool busy;
};
/*
* GuC version number components are only 8-bit, so converting to a 32bit 8.8.8
* integer works.
*/
#define MAKE_GUC_VER(maj, min, pat) (((maj) << 16) | ((min) << 8) | (pat))
#define MAKE_GUC_VER_STRUCT(ver) MAKE_GUC_VER((ver).major, (ver).minor, (ver).patch)
#define GUC_SUBMIT_VER(guc) MAKE_GUC_VER_STRUCT((guc)->submission_version)
#define GUC_FIRMWARE_VER(guc) MAKE_GUC_VER_STRUCT((guc)->fw.file_selected.ver)
static inline struct intel_guc *log_to_guc(struct intel_guc_log *log)
{
return container_of(log, struct intel_guc, log);
}
static
inline int intel_guc_send(struct intel_guc *guc, const u32 *action, u32 len)
{
return intel_guc_ct_send(&guc->ct, action, len, NULL, 0, 0);
}
static
inline int intel_guc_send_nb(struct intel_guc *guc, const u32 *action, u32 len,
u32 g2h_len_dw)
{
return intel_guc_ct_send(&guc->ct, action, len, NULL, 0,
MAKE_SEND_FLAGS(g2h_len_dw));
}
static inline int
intel_guc_send_and_receive(struct intel_guc *guc, const u32 *action, u32 len,
u32 *response_buf, u32 response_buf_size)
{
return intel_guc_ct_send(&guc->ct, action, len,
response_buf, response_buf_size, 0);
}
static inline int intel_guc_send_busy_loop(struct intel_guc *guc,
const u32 *action,
u32 len,
u32 g2h_len_dw,
bool loop)
{
int err;
unsigned int sleep_period_ms = 1;
bool not_atomic = !in_atomic() && !irqs_disabled();
/*
* FIXME: Have caller pass in if we are in an atomic context to avoid
* using in_atomic(). It is likely safe here as we check for irqs
* disabled which basically all the spin locks in the i915 do but
* regardless this should be cleaned up.
*/
/* No sleeping with spin locks, just busy loop */
might_sleep_if(loop && not_atomic);
retry:
err = intel_guc_send_nb(guc, action, len, g2h_len_dw);
if (unlikely(err == -EBUSY && loop)) {
if (likely(not_atomic)) {
if (msleep_interruptible(sleep_period_ms))
return -EINTR;
sleep_period_ms = sleep_period_ms << 1;
} else {
cpu_relax();
}
goto retry;
}
return err;
}
/* Only call this from the interrupt handler code */
static inline void intel_guc_to_host_event_handler(struct intel_guc *guc)
{
if (guc->interrupts.enabled)
intel_guc_ct_event_handler(&guc->ct);
}
/* GuC addresses above GUC_GGTT_TOP also don't map through the GTT */
#define GUC_GGTT_TOP 0xFEE00000
/**
* intel_guc_ggtt_offset() - Get and validate the GGTT offset of @vma
* @guc: intel_guc structure.
* @vma: i915 graphics virtual memory area.
*
* GuC does not allow any gfx GGTT address that falls into range
* [0, ggtt.pin_bias), which is reserved for Boot ROM, SRAM and WOPCM.
* Currently, in order to exclude [0, ggtt.pin_bias) address space from
* GGTT, all gfx objects used by GuC are allocated with intel_guc_allocate_vma()
* and pinned with PIN_OFFSET_BIAS along with the value of ggtt.pin_bias.
*
* Return: GGTT offset of the @vma.
*/
static inline u32 intel_guc_ggtt_offset(struct intel_guc *guc,
struct i915_vma *vma)
{
u32 offset = i915_ggtt_offset(vma);
GEM_BUG_ON(offset < i915_ggtt_pin_bias(vma));
GEM_BUG_ON(range_overflows_t(u64, offset, vma->size, GUC_GGTT_TOP));
return offset;
}
void intel_guc_init_early(struct intel_guc *guc);
void intel_guc_init_late(struct intel_guc *guc);
void intel_guc_init_send_regs(struct intel_guc *guc);
void intel_guc_write_params(struct intel_guc *guc);
int intel_guc_init(struct intel_guc *guc);
void intel_guc_fini(struct intel_guc *guc);
void intel_guc_notify(struct intel_guc *guc);
int intel_guc_send_mmio(struct intel_guc *guc, const u32 *action, u32 len,
u32 *response_buf, u32 response_buf_size);
int intel_guc_to_host_process_recv_msg(struct intel_guc *guc,
const u32 *payload, u32 len);
int intel_guc_auth_huc(struct intel_guc *guc, u32 rsa_offset);
int intel_guc_suspend(struct intel_guc *guc);
int intel_guc_resume(struct intel_guc *guc);
struct i915_vma *intel_guc_allocate_vma(struct intel_guc *guc, u32 size);
int intel_guc_allocate_and_map_vma(struct intel_guc *guc, u32 size,
struct i915_vma **out_vma, void **out_vaddr);
int intel_guc_self_cfg32(struct intel_guc *guc, u16 key, u32 value);
int intel_guc_self_cfg64(struct intel_guc *guc, u16 key, u64 value);
static inline bool intel_guc_is_supported(struct intel_guc *guc)
{
return intel_uc_fw_is_supported(&guc->fw);
}
static inline bool intel_guc_is_wanted(struct intel_guc *guc)
{
return intel_uc_fw_is_enabled(&guc->fw);
}
static inline bool intel_guc_is_used(struct intel_guc *guc)
{
GEM_BUG_ON(__intel_uc_fw_status(&guc->fw) == INTEL_UC_FIRMWARE_SELECTED);
return intel_uc_fw_is_available(&guc->fw);
}
static inline bool intel_guc_is_fw_running(struct intel_guc *guc)
{
return intel_uc_fw_is_running(&guc->fw);
}
static inline bool intel_guc_is_ready(struct intel_guc *guc)
{
return intel_guc_is_fw_running(guc) && intel_guc_ct_enabled(&guc->ct);
}
static inline void intel_guc_reset_interrupts(struct intel_guc *guc)
{
guc->interrupts.reset(guc);
}
static inline void intel_guc_enable_interrupts(struct intel_guc *guc)
{
guc->interrupts.enable(guc);
}
static inline void intel_guc_disable_interrupts(struct intel_guc *guc)
{
guc->interrupts.disable(guc);
}
static inline int intel_guc_sanitize(struct intel_guc *guc)
{
intel_uc_fw_sanitize(&guc->fw);
intel_guc_disable_interrupts(guc);
intel_guc_ct_sanitize(&guc->ct);
guc->mmio_msg = 0;
return 0;
}
static inline void intel_guc_enable_msg(struct intel_guc *guc, u32 mask)
{
spin_lock_irq(&guc->irq_lock);
guc->msg_enabled_mask |= mask;
spin_unlock_irq(&guc->irq_lock);
}
static inline void intel_guc_disable_msg(struct intel_guc *guc, u32 mask)
{
spin_lock_irq(&guc->irq_lock);
guc->msg_enabled_mask &= ~mask;
spin_unlock_irq(&guc->irq_lock);
}
int intel_guc_wait_for_idle(struct intel_guc *guc, long timeout);
int intel_guc_deregister_done_process_msg(struct intel_guc *guc,
const u32 *msg, u32 len);
int intel_guc_sched_done_process_msg(struct intel_guc *guc,
const u32 *msg, u32 len);
int intel_guc_context_reset_process_msg(struct intel_guc *guc,
const u32 *msg, u32 len);
int intel_guc_engine_failure_process_msg(struct intel_guc *guc,
const u32 *msg, u32 len);
int intel_guc_error_capture_process_msg(struct intel_guc *guc,
const u32 *msg, u32 len);
int intel_guc_crash_process_msg(struct intel_guc *guc, u32 action);
struct intel_engine_cs *
intel_guc_lookup_engine(struct intel_guc *guc, u8 guc_class, u8 instance);
void intel_guc_find_hung_context(struct intel_engine_cs *engine);
int intel_guc_global_policies_update(struct intel_guc *guc);
void intel_guc_context_ban(struct intel_context *ce, struct i915_request *rq);
void intel_guc_submission_reset_prepare(struct intel_guc *guc);
void intel_guc_submission_reset(struct intel_guc *guc, intel_engine_mask_t stalled);
void intel_guc_submission_reset_finish(struct intel_guc *guc);
void intel_guc_submission_cancel_requests(struct intel_guc *guc);
void intel_guc_load_status(struct intel_guc *guc, struct drm_printer *p);
void intel_guc_write_barrier(struct intel_guc *guc);
void intel_guc_dump_time_info(struct intel_guc *guc, struct drm_printer *p);
int intel_guc_sched_disable_gucid_threshold_max(struct intel_guc *guc);
bool intel_guc_tlb_invalidation_is_available(struct intel_guc *guc);
int intel_guc_invalidate_tlb_engines(struct intel_guc *guc);
int intel_guc_invalidate_tlb_guc(struct intel_guc *guc);
int intel_guc_tlb_invalidation_done(struct intel_guc *guc,
const u32 *payload, u32 len);
void wake_up_all_tlb_invalidate(struct intel_guc *guc);
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,25 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2014-2019 Intel Corporation
*/
#ifndef _INTEL_GUC_ADS_H_
#define _INTEL_GUC_ADS_H_
#include <linux/types.h>
#include <linux/iosys-map.h>
struct intel_guc;
struct drm_printer;
struct intel_engine_cs;
int intel_guc_ads_create(struct intel_guc *guc);
void intel_guc_ads_destroy(struct intel_guc *guc);
void intel_guc_ads_init_late(struct intel_guc *guc);
void intel_guc_ads_reset(struct intel_guc *guc);
void intel_guc_ads_print_policy_info(struct intel_guc *guc,
struct drm_printer *p);
struct iosys_map intel_guc_engine_usage_record_map(struct intel_engine_cs *engine);
u32 intel_guc_engine_usage_offset(struct intel_guc *guc);
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,35 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2021-2021 Intel Corporation
*/
#ifndef _INTEL_GUC_CAPTURE_H
#define _INTEL_GUC_CAPTURE_H
#include <linux/types.h>
struct drm_i915_error_state_buf;
struct guc_gt_system_info;
struct intel_engine_coredump;
struct intel_engine_cs;
struct intel_context;
struct intel_gt;
struct intel_guc;
void intel_guc_capture_free_node(struct intel_engine_coredump *ee);
int intel_guc_capture_print_engine_node(struct drm_i915_error_state_buf *m,
const struct intel_engine_coredump *ee);
void intel_guc_capture_get_matching_node(struct intel_gt *gt, struct intel_engine_coredump *ee,
struct intel_context *ce);
bool intel_guc_capture_is_matching_engine(struct intel_gt *gt, struct intel_context *ce,
struct intel_engine_cs *engine);
void intel_guc_capture_process(struct intel_guc *guc);
int intel_guc_capture_getlist(struct intel_guc *guc, u32 owner, u32 type, u32 classid,
void **outptr);
int intel_guc_capture_getlistsize(struct intel_guc *guc, u32 owner, u32 type, u32 classid,
size_t *size);
int intel_guc_capture_getnullheader(struct intel_guc *guc, void **outptr, size_t *size);
void intel_guc_capture_destroy(struct intel_guc *guc);
int intel_guc_capture_init(struct intel_guc *guc);
#endif /* _INTEL_GUC_CAPTURE_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,139 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2016-2019 Intel Corporation
*/
#ifndef _INTEL_GUC_CT_H_
#define _INTEL_GUC_CT_H_
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/stackdepot.h>
#include <linux/workqueue.h>
#include <linux/ktime.h>
#include <linux/wait.h>
#include "intel_guc_fwif.h"
struct i915_vma;
struct intel_guc;
struct drm_printer;
/**
* DOC: Command Transport (CT).
*
* Buffer based command transport is a replacement for MMIO based mechanism.
* It can be used to perform both host-2-guc and guc-to-host communication.
*/
/** Represents single command transport buffer.
*
* A single command transport buffer consists of two parts, the header
* record (command transport buffer descriptor) and the actual buffer which
* holds the commands.
*
* @lock: protects access to the commands buffer and buffer descriptor
* @desc: pointer to the buffer descriptor
* @cmds: pointer to the commands buffer
* @size: size of the commands buffer in dwords
* @resv_space: reserved space in buffer in dwords
* @head: local shadow copy of head in dwords
* @tail: local shadow copy of tail in dwords
* @space: local shadow copy of space in dwords
* @broken: flag to indicate if descriptor data is broken
*/
struct intel_guc_ct_buffer {
spinlock_t lock;
struct guc_ct_buffer_desc *desc;
u32 *cmds;
u32 size;
u32 resv_space;
u32 tail;
u32 head;
atomic_t space;
bool broken;
};
/** Top-level structure for Command Transport related data
*
* Includes a pair of CT buffers for bi-directional communication and tracking
* for the H2G and G2H requests sent and received through the buffers.
*/
struct intel_guc_ct {
struct i915_vma *vma;
bool enabled;
/* buffers for sending and receiving commands */
struct {
struct intel_guc_ct_buffer send;
struct intel_guc_ct_buffer recv;
} ctbs;
struct tasklet_struct receive_tasklet;
/** @wq: wait queue for g2h chanenl */
wait_queue_head_t wq;
struct {
u16 last_fence; /* last fence used to send request */
spinlock_t lock; /* protects pending requests list */
struct list_head pending; /* requests waiting for response */
struct list_head incoming; /* incoming requests */
struct work_struct worker; /* handler for incoming requests */
#if IS_ENABLED(CONFIG_DRM_I915_DEBUG_GEM)
struct {
u16 fence;
u16 action;
#if IS_ENABLED(CONFIG_DRM_I915_DEBUG_GUC)
depot_stack_handle_t stack;
#endif
} lost_and_found[SZ_16];
#endif
} requests;
/** @stall_time: time of first time a CTB submission is stalled */
ktime_t stall_time;
#if IS_ENABLED(CONFIG_DRM_I915_DEBUG)
int dead_ct_reason;
bool dead_ct_reported;
struct work_struct dead_ct_worker;
#endif
};
long intel_guc_ct_max_queue_time_jiffies(void);
void intel_guc_ct_init_early(struct intel_guc_ct *ct);
int intel_guc_ct_init(struct intel_guc_ct *ct);
void intel_guc_ct_fini(struct intel_guc_ct *ct);
int intel_guc_ct_enable(struct intel_guc_ct *ct);
void intel_guc_ct_disable(struct intel_guc_ct *ct);
static inline void intel_guc_ct_sanitize(struct intel_guc_ct *ct)
{
ct->enabled = false;
}
static inline bool intel_guc_ct_enabled(struct intel_guc_ct *ct)
{
return ct->enabled;
}
#define INTEL_GUC_CT_SEND_NB BIT(31)
#define INTEL_GUC_CT_SEND_G2H_DW_SHIFT 0
#define INTEL_GUC_CT_SEND_G2H_DW_MASK (0xff << INTEL_GUC_CT_SEND_G2H_DW_SHIFT)
#define MAKE_SEND_FLAGS(len) ({ \
typeof(len) len_ = (len); \
GEM_BUG_ON(!FIELD_FIT(INTEL_GUC_CT_SEND_G2H_DW_MASK, len_)); \
(FIELD_PREP(INTEL_GUC_CT_SEND_G2H_DW_MASK, len_) | INTEL_GUC_CT_SEND_NB); \
})
int intel_guc_ct_send(struct intel_guc_ct *ct, const u32 *action, u32 len,
u32 *response_buf, u32 response_buf_size, u32 flags);
void intel_guc_ct_event_handler(struct intel_guc_ct *ct);
void intel_guc_ct_print_info(struct intel_guc_ct *ct, struct drm_printer *p);
#endif /* _INTEL_GUC_CT_H_ */
@@ -0,0 +1,149 @@
// SPDX-License-Identifier: MIT
/*
* Copyright © 2020 Intel Corporation
*/
#include <drm/drm_print.h>
#include "gt/intel_gt_debugfs.h"
#include "gt/uc/intel_guc_ads.h"
#include "gt/uc/intel_guc_ct.h"
#include "gt/uc/intel_guc_slpc.h"
#include "gt/uc/intel_guc_submission.h"
#include "intel_guc.h"
#include "intel_guc_debugfs.h"
#include "intel_guc_log_debugfs.h"
static int guc_info_show(struct seq_file *m, void *data)
{
struct intel_guc *guc = m->private;
struct drm_printer p = drm_seq_file_printer(m);
if (!intel_guc_is_supported(guc))
return -ENODEV;
intel_guc_load_status(guc, &p);
drm_puts(&p, "\n");
intel_guc_log_info(&guc->log, &p);
if (!intel_guc_submission_is_used(guc))
return 0;
intel_guc_ct_print_info(&guc->ct, &p);
intel_guc_submission_print_info(guc, &p);
intel_guc_ads_print_policy_info(guc, &p);
return 0;
}
DEFINE_INTEL_GT_DEBUGFS_ATTRIBUTE(guc_info);
static int guc_registered_contexts_show(struct seq_file *m, void *data)
{
struct intel_guc *guc = m->private;
struct drm_printer p = drm_seq_file_printer(m);
if (!intel_guc_submission_is_used(guc))
return -ENODEV;
intel_guc_submission_print_context_info(guc, &p);
return 0;
}
DEFINE_INTEL_GT_DEBUGFS_ATTRIBUTE(guc_registered_contexts);
static int guc_slpc_info_show(struct seq_file *m, void *unused)
{
struct intel_guc *guc = m->private;
struct intel_guc_slpc *slpc = &guc->slpc;
struct drm_printer p = drm_seq_file_printer(m);
if (!intel_guc_slpc_is_used(guc))
return -ENODEV;
return intel_guc_slpc_print_info(slpc, &p);
}
DEFINE_INTEL_GT_DEBUGFS_ATTRIBUTE(guc_slpc_info);
static bool intel_eval_slpc_support(void *data)
{
struct intel_guc *guc = (struct intel_guc *)data;
return intel_guc_slpc_is_used(guc);
}
static int guc_sched_disable_delay_ms_get(void *data, u64 *val)
{
struct intel_guc *guc = data;
if (!intel_guc_submission_is_used(guc))
return -ENODEV;
*val = (u64)guc->submission_state.sched_disable_delay_ms;
return 0;
}
static int guc_sched_disable_delay_ms_set(void *data, u64 val)
{
struct intel_guc *guc = data;
if (!intel_guc_submission_is_used(guc))
return -ENODEV;
/* clamp to a practical limit, 1 minute is reasonable for a longest delay */
guc->submission_state.sched_disable_delay_ms = min_t(u64, val, 60000);
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(guc_sched_disable_delay_ms_fops,
guc_sched_disable_delay_ms_get,
guc_sched_disable_delay_ms_set, "%lld\n");
static int guc_sched_disable_gucid_threshold_get(void *data, u64 *val)
{
struct intel_guc *guc = data;
if (!intel_guc_submission_is_used(guc))
return -ENODEV;
*val = guc->submission_state.sched_disable_gucid_threshold;
return 0;
}
static int guc_sched_disable_gucid_threshold_set(void *data, u64 val)
{
struct intel_guc *guc = data;
if (!intel_guc_submission_is_used(guc))
return -ENODEV;
if (val > intel_guc_sched_disable_gucid_threshold_max(guc))
guc->submission_state.sched_disable_gucid_threshold =
intel_guc_sched_disable_gucid_threshold_max(guc);
else
guc->submission_state.sched_disable_gucid_threshold = val;
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(guc_sched_disable_gucid_threshold_fops,
guc_sched_disable_gucid_threshold_get,
guc_sched_disable_gucid_threshold_set, "%lld\n");
void intel_guc_debugfs_register(struct intel_guc *guc, struct dentry *root)
{
static const struct intel_gt_debugfs_file files[] = {
{ .name = "guc_info", .fops = &guc_info_fops },
{ .name = "guc_registered_contexts", .fops = &guc_registered_contexts_fops },
{ .name = "guc_slpc_info", .fops = &guc_slpc_info_fops,
.eval = intel_eval_slpc_support },
{ .name = "guc_sched_disable_delay_ms", .fops = &guc_sched_disable_delay_ms_fops },
{ .name = "guc_sched_disable_gucid_threshold",
.fops = &guc_sched_disable_gucid_threshold_fops },
};
if (!intel_guc_is_supported(guc))
return;
intel_gt_debugfs_register_files(root, files, ARRAY_SIZE(files), guc);
intel_guc_log_debugfs_register(&guc->log, root);
}
@@ -0,0 +1,14 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2020 Intel Corporation
*/
#ifndef DEBUGFS_GUC_H
#define DEBUGFS_GUC_H
struct intel_guc;
struct dentry;
void intel_guc_debugfs_register(struct intel_guc *guc, struct dentry *root);
#endif /* DEBUGFS_GUC_H */
@@ -0,0 +1,335 @@
// SPDX-License-Identifier: MIT
/*
* Copyright © 2014-2019 Intel Corporation
*
* Authors:
* Vinit Azad <vinit.azad@intel.com>
* Ben Widawsky <ben@bwidawsk.net>
* Dave Gordon <david.s.gordon@intel.com>
* Alex Dai <yu.dai@intel.com>
*/
#include "gt/intel_gt.h"
#include "gt/intel_gt_mcr.h"
#include "gt/intel_gt_regs.h"
#include "gt/intel_rps.h"
#include "i915_drv.h"
#include "i915_wait_util.h"
#include "intel_guc_fw.h"
#include "intel_guc_print.h"
static void guc_prepare_xfer(struct intel_gt *gt)
{
struct intel_uncore *uncore = gt->uncore;
u32 shim_flags = GUC_ENABLE_READ_CACHE_LOGIC |
GUC_ENABLE_READ_CACHE_FOR_SRAM_DATA |
GUC_ENABLE_READ_CACHE_FOR_WOPCM_DATA |
GUC_ENABLE_MIA_CLOCK_GATING;
if (GRAPHICS_VER_FULL(uncore->i915) < IP_VER(12, 55))
shim_flags |= GUC_DISABLE_SRAM_INIT_TO_ZEROES |
GUC_ENABLE_MIA_CACHING;
/* Must program this register before loading the ucode with DMA */
intel_uncore_write(uncore, GUC_SHIM_CONTROL, shim_flags);
if (IS_GEN9_LP(uncore->i915))
intel_uncore_write(uncore, GEN9LP_GT_PM_CONFIG, GT_DOORBELL_ENABLE);
else
intel_uncore_write(uncore, GEN9_GT_PM_CONFIG, GT_DOORBELL_ENABLE);
if (GRAPHICS_VER(uncore->i915) == 9) {
/* DOP Clock Gating Enable for GuC clocks */
intel_uncore_rmw(uncore, GEN7_MISCCPCTL, 0,
GEN8_DOP_CLOCK_GATE_GUC_ENABLE);
/* allows for 5us (in 10ns units) before GT can go to RC6 */
intel_uncore_write(uncore, GUC_ARAT_C6DIS, 0x1FF);
}
/*
* Starting from IP 12.50 we need to enable the mirroring of GuC
* internal state to debug registers. This is always enabled on previous
* IPs.
*/
if (GRAPHICS_VER_FULL(uncore->i915) >= IP_VER(12, 50))
intel_uncore_rmw(uncore, GUC_SHIM_CONTROL2, 0, GUC_ENABLE_DEBUG_REG);
}
static int guc_xfer_rsa_mmio(struct intel_uc_fw *guc_fw,
struct intel_uncore *uncore)
{
u32 rsa[UOS_RSA_SCRATCH_COUNT];
size_t copied;
int i;
copied = intel_uc_fw_copy_rsa(guc_fw, rsa, sizeof(rsa));
if (copied < sizeof(rsa))
return -ENOMEM;
for (i = 0; i < UOS_RSA_SCRATCH_COUNT; i++)
intel_uncore_write(uncore, UOS_RSA_SCRATCH(i), rsa[i]);
return 0;
}
static int guc_xfer_rsa_vma(struct intel_uc_fw *guc_fw,
struct intel_uncore *uncore)
{
struct intel_guc *guc = container_of(guc_fw, struct intel_guc, fw);
intel_uncore_write(uncore, UOS_RSA_SCRATCH(0),
intel_guc_ggtt_offset(guc, guc_fw->rsa_data));
return 0;
}
/* Copy RSA signature from the fw image to HW for verification */
static int guc_xfer_rsa(struct intel_uc_fw *guc_fw,
struct intel_uncore *uncore)
{
if (guc_fw->rsa_data)
return guc_xfer_rsa_vma(guc_fw, uncore);
else
return guc_xfer_rsa_mmio(guc_fw, uncore);
}
/*
* Read the GuC status register (GUC_STATUS) and store it in the
* specified location; then return a boolean indicating whether
* the value matches either completion or a known failure code.
*
* This is used for polling the GuC status in a wait_for()
* loop below.
*/
static inline bool guc_load_done(struct intel_uncore *uncore, u32 *status, bool *success)
{
u32 val = intel_uncore_read(uncore, GUC_STATUS);
u32 uk_val = REG_FIELD_GET(GS_UKERNEL_MASK, val);
u32 br_val = REG_FIELD_GET(GS_BOOTROM_MASK, val);
*status = val;
switch (uk_val) {
case INTEL_GUC_LOAD_STATUS_READY:
*success = true;
return true;
case INTEL_GUC_LOAD_STATUS_ERROR_DEVID_BUILD_MISMATCH:
case INTEL_GUC_LOAD_STATUS_GUC_PREPROD_BUILD_MISMATCH:
case INTEL_GUC_LOAD_STATUS_ERROR_DEVID_INVALID_GUCTYPE:
case INTEL_GUC_LOAD_STATUS_HWCONFIG_ERROR:
case INTEL_GUC_LOAD_STATUS_DPC_ERROR:
case INTEL_GUC_LOAD_STATUS_EXCEPTION:
case INTEL_GUC_LOAD_STATUS_INIT_DATA_INVALID:
case INTEL_GUC_LOAD_STATUS_MPU_DATA_INVALID:
case INTEL_GUC_LOAD_STATUS_INIT_MMIO_SAVE_RESTORE_INVALID:
case INTEL_GUC_LOAD_STATUS_KLV_WORKAROUND_INIT_ERROR:
*success = false;
return true;
}
switch (br_val) {
case INTEL_BOOTROM_STATUS_NO_KEY_FOUND:
case INTEL_BOOTROM_STATUS_RSA_FAILED:
case INTEL_BOOTROM_STATUS_PAVPC_FAILED:
case INTEL_BOOTROM_STATUS_WOPCM_FAILED:
case INTEL_BOOTROM_STATUS_LOADLOC_FAILED:
case INTEL_BOOTROM_STATUS_JUMP_FAILED:
case INTEL_BOOTROM_STATUS_RC6CTXCONFIG_FAILED:
case INTEL_BOOTROM_STATUS_MPUMAP_INCORRECT:
case INTEL_BOOTROM_STATUS_EXCEPTION:
case INTEL_BOOTROM_STATUS_PROD_KEY_CHECK_FAILURE:
*success = false;
return true;
}
return false;
}
/*
* Use a longer timeout for debug builds so that problems can be detected
* and analysed. But a shorter timeout for releases so that user's don't
* wait forever to find out there is a problem. Note that the only reason
* an end user should hit the timeout is in case of extreme thermal throttling.
* And a system that is that hot during boot is probably dead anyway!
*/
#if IS_ENABLED(CONFIG_DRM_I915_DEBUG_GEM)
#define GUC_LOAD_RETRY_LIMIT 20
#else
#define GUC_LOAD_RETRY_LIMIT 3
#endif
static int guc_wait_ucode(struct intel_guc *guc)
{
struct intel_gt *gt = guc_to_gt(guc);
struct intel_uncore *uncore = gt->uncore;
ktime_t before, after, delta;
bool success;
u32 status;
int ret, count;
u64 delta_ms;
u32 before_freq;
/*
* Wait for the GuC to start up.
*
* Measurements indicate this should take no more than 20ms
* (assuming the GT clock is at maximum frequency). So, a
* timeout here indicates that the GuC has failed and is unusable.
* (Higher levels of the driver may decide to reset the GuC and
* attempt the ucode load again if this happens.)
*
* FIXME: There is a known (but exceedingly unlikely) race condition
* where the asynchronous frequency management code could reduce
* the GT clock while a GuC reload is in progress (during a full
* GT reset). A fix is in progress but there are complex locking
* issues to be resolved. In the meantime bump the timeout to
* 200ms. Even at slowest clock, this should be sufficient. And
* in the working case, a larger timeout makes no difference.
*
* IFWI updates have also been seen to cause sporadic failures due to
* the requested frequency not being granted and thus the firmware
* load is attempted at minimum frequency. That can lead to load times
* in the seconds range. However, there is a limit on how long an
* individual wait_for() can wait. So wrap it in a loop.
*/
before_freq = intel_rps_read_actual_frequency(&gt->rps);
before = ktime_get();
for (count = 0; count < GUC_LOAD_RETRY_LIMIT; count++) {
ret = wait_for(guc_load_done(uncore, &status, &success), 1000);
if (!ret || !success)
break;
guc_dbg(guc, "load still in progress, count = %d, freq = %dMHz, status = 0x%08X [0x%02X/%02X]\n",
count, intel_rps_read_actual_frequency(&gt->rps), status,
REG_FIELD_GET(GS_BOOTROM_MASK, status),
REG_FIELD_GET(GS_UKERNEL_MASK, status));
}
after = ktime_get();
delta = ktime_sub(after, before);
delta_ms = ktime_to_ms(delta);
if (ret || !success) {
u32 ukernel = REG_FIELD_GET(GS_UKERNEL_MASK, status);
u32 bootrom = REG_FIELD_GET(GS_BOOTROM_MASK, status);
guc_info(guc, "load failed: status = 0x%08X, time = %lldms, freq = %dMHz, ret = %d\n",
status, delta_ms, intel_rps_read_actual_frequency(&gt->rps), ret);
guc_info(guc, "load failed: status: Reset = %d, BootROM = 0x%02X, UKernel = 0x%02X, MIA = 0x%02X, Auth = 0x%02X\n",
REG_FIELD_GET(GS_MIA_IN_RESET, status),
bootrom, ukernel,
REG_FIELD_GET(GS_MIA_MASK, status),
REG_FIELD_GET(GS_AUTH_STATUS_MASK, status));
switch (bootrom) {
case INTEL_BOOTROM_STATUS_NO_KEY_FOUND:
guc_info(guc, "invalid key requested, header = 0x%08X\n",
intel_uncore_read(uncore, GUC_HEADER_INFO));
ret = -ENOEXEC;
break;
case INTEL_BOOTROM_STATUS_RSA_FAILED:
guc_info(guc, "firmware signature verification failed\n");
ret = -ENOEXEC;
break;
case INTEL_BOOTROM_STATUS_PROD_KEY_CHECK_FAILURE:
guc_info(guc, "firmware production part check failure\n");
ret = -ENOEXEC;
break;
}
switch (ukernel) {
case INTEL_GUC_LOAD_STATUS_EXCEPTION:
guc_info(guc, "firmware exception. EIP: %#x\n",
intel_uncore_read(uncore, SOFT_SCRATCH(13)));
ret = -ENXIO;
break;
case INTEL_GUC_LOAD_STATUS_INIT_MMIO_SAVE_RESTORE_INVALID:
guc_info(guc, "illegal register in save/restore workaround list\n");
ret = -EPERM;
break;
case INTEL_GUC_LOAD_STATUS_KLV_WORKAROUND_INIT_ERROR:
guc_info(guc, "invalid w/a KLV entry\n");
ret = -EINVAL;
break;
case INTEL_GUC_LOAD_STATUS_HWCONFIG_START:
guc_info(guc, "still extracting hwconfig table.\n");
ret = -ETIMEDOUT;
break;
}
/* Uncommon/unexpected error, see earlier status code print for details */
if (ret == 0)
ret = -ENXIO;
} else if (delta_ms > 200) {
guc_warn(guc, "excessive init time: %lldms! [status = 0x%08X, count = %d, ret = %d]\n",
delta_ms, status, count, ret);
guc_warn(guc, "excessive init time: [freq = %dMHz -> %dMHz vs %dMHz, perf_limit_reasons = 0x%08X]\n",
before_freq, intel_rps_read_actual_frequency(&gt->rps),
intel_rps_get_requested_frequency(&gt->rps),
intel_uncore_read(uncore, intel_gt_perf_limit_reasons_reg(gt)));
} else {
guc_dbg(guc, "init took %lldms, freq = %dMHz -> %dMHz vs %dMHz, status = 0x%08X, count = %d, ret = %d\n",
delta_ms, before_freq, intel_rps_read_actual_frequency(&gt->rps),
intel_rps_get_requested_frequency(&gt->rps), status, count, ret);
}
return ret;
}
/**
* intel_guc_fw_upload() - load GuC uCode to device
* @guc: intel_guc structure
*
* Called from intel_uc_init_hw() during driver load, resume from sleep and
* after a GPU reset.
*
* The firmware image should have already been fetched into memory, so only
* check that fetch succeeded, and then transfer the image to the h/w.
*
* Return: non-zero code on error
*/
int intel_guc_fw_upload(struct intel_guc *guc)
{
struct intel_gt *gt = guc_to_gt(guc);
struct intel_uncore *uncore = gt->uncore;
int ret;
guc_prepare_xfer(gt);
/*
* Note that GuC needs the CSS header plus uKernel code to be copied
* by the DMA engine in one operation, whereas the RSA signature is
* loaded separately, either by copying it to the UOS_RSA_SCRATCH
* register (if key size <= 256) or through a ggtt-pinned vma (if key
* size > 256). The RSA size and therefore the way we provide it to the
* HW is fixed for each platform and hard-coded in the bootrom.
*/
ret = guc_xfer_rsa(&guc->fw, uncore);
if (ret)
goto out;
/*
* Current uCode expects the code to be loaded at 8k; locations below
* this are used for the stack.
*/
ret = intel_uc_fw_upload(&guc->fw, 0x2000, UOS_MOVE);
if (ret)
goto out;
ret = guc_wait_ucode(guc);
if (ret)
goto out;
intel_uc_fw_change_status(&guc->fw, INTEL_UC_FIRMWARE_RUNNING);
return 0;
out:
intel_uc_fw_change_status(&guc->fw, INTEL_UC_FIRMWARE_LOAD_FAIL);
return ret;
}
@@ -0,0 +1,13 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2017-2019 Intel Corporation
*/
#ifndef _INTEL_GUC_FW_H_
#define _INTEL_GUC_FW_H_
struct intel_guc;
int intel_guc_fw_upload(struct intel_guc *guc);
#endif
@@ -0,0 +1,508 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2014-2019 Intel Corporation
*/
#ifndef _INTEL_GUC_FWIF_H
#define _INTEL_GUC_FWIF_H
#include <linux/bits.h>
#include <linux/compiler.h>
#include <linux/types.h>
#include "gt/intel_engine_types.h"
#include "abi/guc_actions_abi.h"
#include "abi/guc_actions_slpc_abi.h"
#include "abi/guc_errors_abi.h"
#include "abi/guc_communication_mmio_abi.h"
#include "abi/guc_communication_ctb_abi.h"
#include "abi/guc_klvs_abi.h"
#include "abi/guc_messages_abi.h"
/* Payload length only i.e. don't include G2H header length */
#define G2H_LEN_DW_SCHED_CONTEXT_MODE_SET 2
#define G2H_LEN_DW_DEREGISTER_CONTEXT 1
#define G2H_LEN_DW_INVALIDATE_TLB 1
#define GUC_CONTEXT_DISABLE 0
#define GUC_CONTEXT_ENABLE 1
#define GUC_CLIENT_PRIORITY_KMD_HIGH 0
#define GUC_CLIENT_PRIORITY_HIGH 1
#define GUC_CLIENT_PRIORITY_KMD_NORMAL 2
#define GUC_CLIENT_PRIORITY_NORMAL 3
#define GUC_CLIENT_PRIORITY_NUM 4
#define GUC_MAX_CONTEXT_ID 65535
#define GUC_INVALID_CONTEXT_ID GUC_MAX_CONTEXT_ID
#define GUC_RENDER_CLASS 0
#define GUC_VIDEO_CLASS 1
#define GUC_VIDEOENHANCE_CLASS 2
#define GUC_BLITTER_CLASS 3
#define GUC_COMPUTE_CLASS 4
#define GUC_GSC_OTHER_CLASS 5
#define GUC_LAST_ENGINE_CLASS GUC_GSC_OTHER_CLASS
#define GUC_MAX_ENGINE_CLASSES 16
#define GUC_MAX_INSTANCES_PER_CLASS 32
#define GUC_DOORBELL_INVALID 256
/*
* Work queue item header definitions
*
* Work queue is circular buffer used to submit complex (multi-lrc) submissions
* to the GuC. A work queue item is an entry in the circular buffer.
*/
#define WQ_STATUS_ACTIVE 1
#define WQ_STATUS_SUSPENDED 2
#define WQ_STATUS_CMD_ERROR 3
#define WQ_STATUS_ENGINE_ID_NOT_USED 4
#define WQ_STATUS_SUSPENDED_FROM_RESET 5
#define WQ_TYPE_BATCH_BUF 0x1
#define WQ_TYPE_PSEUDO 0x2
#define WQ_TYPE_INORDER 0x3
#define WQ_TYPE_NOOP 0x4
#define WQ_TYPE_MULTI_LRC 0x5
#define WQ_TYPE_MASK GENMASK(7, 0)
#define WQ_LEN_MASK GENMASK(26, 16)
#define WQ_GUC_ID_MASK GENMASK(15, 0)
#define WQ_RING_TAIL_MASK GENMASK(28, 18)
#define GUC_STAGE_DESC_ATTR_ACTIVE BIT(0)
#define GUC_STAGE_DESC_ATTR_PENDING_DB BIT(1)
#define GUC_STAGE_DESC_ATTR_KERNEL BIT(2)
#define GUC_STAGE_DESC_ATTR_PREEMPT BIT(3)
#define GUC_STAGE_DESC_ATTR_RESET BIT(4)
#define GUC_STAGE_DESC_ATTR_WQLOCKED BIT(5)
#define GUC_STAGE_DESC_ATTR_PCH BIT(6)
#define GUC_STAGE_DESC_ATTR_TERMINATED BIT(7)
#define GUC_CTL_LOG_PARAMS 0
#define GUC_LOG_VALID BIT(0)
#define GUC_LOG_NOTIFY_ON_HALF_FULL BIT(1)
#define GUC_LOG_CAPTURE_ALLOC_UNITS BIT(2)
#define GUC_LOG_LOG_ALLOC_UNITS BIT(3)
#define GUC_LOG_CRASH_SHIFT 4
#define GUC_LOG_CRASH_MASK (0x3 << GUC_LOG_CRASH_SHIFT)
#define GUC_LOG_DEBUG_SHIFT 6
#define GUC_LOG_DEBUG_MASK (0xF << GUC_LOG_DEBUG_SHIFT)
#define GUC_LOG_CAPTURE_SHIFT 10
#define GUC_LOG_CAPTURE_MASK (0x3 << GUC_LOG_CAPTURE_SHIFT)
#define GUC_LOG_BUF_ADDR_SHIFT 12
#define GUC_CTL_WA 1
#define GUC_WA_GAM_CREDITS BIT(10)
#define GUC_WA_DUAL_QUEUE BIT(11)
#define GUC_WA_RCS_RESET_BEFORE_RC6 BIT(13)
#define GUC_WA_PRE_PARSER BIT(14)
#define GUC_WA_CONTEXT_ISOLATION BIT(15)
#define GUC_WA_RCS_CCS_SWITCHOUT BIT(16)
#define GUC_WA_HOLD_CCS_SWITCHOUT BIT(17)
#define GUC_WA_POLLCS BIT(18)
#define GUC_WA_RCS_REGS_IN_CCS_REGS_LIST BIT(21)
#define GUC_WA_ENABLE_TSC_CHECK_ON_RC6 BIT(22)
#define GUC_CTL_FEATURE 2
#define GUC_CTL_ENABLE_GUC_PXP_CTL BIT(1)
#define GUC_CTL_ENABLE_SLPC BIT(2)
#define GUC_CTL_DISABLE_SCHEDULER BIT(14)
#define GUC_CTL_DEBUG 3
#define GUC_LOG_VERBOSITY_SHIFT 0
#define GUC_LOG_VERBOSITY_LOW (0 << GUC_LOG_VERBOSITY_SHIFT)
#define GUC_LOG_VERBOSITY_MED (1 << GUC_LOG_VERBOSITY_SHIFT)
#define GUC_LOG_VERBOSITY_HIGH (2 << GUC_LOG_VERBOSITY_SHIFT)
#define GUC_LOG_VERBOSITY_ULTRA (3 << GUC_LOG_VERBOSITY_SHIFT)
/* Verbosity range-check limits, without the shift */
#define GUC_LOG_VERBOSITY_MIN 0
#define GUC_LOG_VERBOSITY_MAX 3
#define GUC_LOG_VERBOSITY_MASK 0x0000000f
#define GUC_LOG_DESTINATION_MASK (3 << 4)
#define GUC_LOG_DISABLED (1 << 6)
#define GUC_PROFILE_ENABLED (1 << 7)
#define GUC_CTL_ADS 4
#define GUC_ADS_ADDR_SHIFT 1
#define GUC_ADS_ADDR_MASK (0xFFFFF << GUC_ADS_ADDR_SHIFT)
#define GUC_CTL_DEVID 5
#define GUC_CTL_MAX_DWORDS (SOFT_SCRATCH_COUNT - 2) /* [1..14] */
/* Generic GT SysInfo data types */
#define GUC_GENERIC_GT_SYSINFO_SLICE_ENABLED 0
#define GUC_GENERIC_GT_SYSINFO_VDBOX_SFC_SUPPORT_MASK 1
#define GUC_GENERIC_GT_SYSINFO_DOORBELL_COUNT_PER_SQIDI 2
#define GUC_GENERIC_GT_SYSINFO_MAX 16
/*
* The class goes in bits [0..2] of the GuC ID, the instance in bits [3..6].
* Bit 7 can be used for operations that apply to all engine classes&instances.
*/
#define GUC_ENGINE_CLASS_SHIFT 0
#define GUC_ENGINE_CLASS_MASK (0x7 << GUC_ENGINE_CLASS_SHIFT)
#define GUC_ENGINE_INSTANCE_SHIFT 3
#define GUC_ENGINE_INSTANCE_MASK (0xf << GUC_ENGINE_INSTANCE_SHIFT)
#define GUC_ENGINE_ALL_INSTANCES BIT(7)
#define MAKE_GUC_ID(class, instance) \
(((class) << GUC_ENGINE_CLASS_SHIFT) | \
((instance) << GUC_ENGINE_INSTANCE_SHIFT))
#define GUC_ID_TO_ENGINE_CLASS(guc_id) \
(((guc_id) & GUC_ENGINE_CLASS_MASK) >> GUC_ENGINE_CLASS_SHIFT)
#define GUC_ID_TO_ENGINE_INSTANCE(guc_id) \
(((guc_id) & GUC_ENGINE_INSTANCE_MASK) >> GUC_ENGINE_INSTANCE_SHIFT)
#define SLPC_EVENT(id, c) (\
FIELD_PREP(HOST2GUC_PC_SLPC_REQUEST_MSG_1_EVENT_ID, id) | \
FIELD_PREP(HOST2GUC_PC_SLPC_REQUEST_MSG_1_EVENT_ARGC, c) \
)
/* the GuC arrays don't include OTHER_CLASS */
static u8 engine_class_guc_class_map[] = {
[RENDER_CLASS] = GUC_RENDER_CLASS,
[COPY_ENGINE_CLASS] = GUC_BLITTER_CLASS,
[VIDEO_DECODE_CLASS] = GUC_VIDEO_CLASS,
[VIDEO_ENHANCEMENT_CLASS] = GUC_VIDEOENHANCE_CLASS,
[OTHER_CLASS] = GUC_GSC_OTHER_CLASS,
[COMPUTE_CLASS] = GUC_COMPUTE_CLASS,
};
static u8 guc_class_engine_class_map[] = {
[GUC_RENDER_CLASS] = RENDER_CLASS,
[GUC_BLITTER_CLASS] = COPY_ENGINE_CLASS,
[GUC_VIDEO_CLASS] = VIDEO_DECODE_CLASS,
[GUC_VIDEOENHANCE_CLASS] = VIDEO_ENHANCEMENT_CLASS,
[GUC_COMPUTE_CLASS] = COMPUTE_CLASS,
[GUC_GSC_OTHER_CLASS] = OTHER_CLASS,
};
static inline u8 engine_class_to_guc_class(u8 class)
{
BUILD_BUG_ON(ARRAY_SIZE(engine_class_guc_class_map) != MAX_ENGINE_CLASS + 1);
GEM_BUG_ON(class > MAX_ENGINE_CLASS);
return engine_class_guc_class_map[class];
}
static inline u8 guc_class_to_engine_class(u8 guc_class)
{
BUILD_BUG_ON(ARRAY_SIZE(guc_class_engine_class_map) != GUC_LAST_ENGINE_CLASS + 1);
GEM_BUG_ON(guc_class > GUC_LAST_ENGINE_CLASS);
return guc_class_engine_class_map[guc_class];
}
/* Work item for submitting workloads into work queue of GuC. */
struct guc_wq_item {
u32 header;
u32 context_desc;
u32 submit_element_info;
u32 fence_id;
} __packed;
struct guc_process_desc_v69 {
u32 stage_id;
u64 db_base_addr;
u32 head;
u32 tail;
u32 error_offset;
u64 wq_base_addr;
u32 wq_size_bytes;
u32 wq_status;
u32 engine_presence;
u32 priority;
u32 reserved[36];
} __packed;
struct guc_sched_wq_desc {
u32 head;
u32 tail;
u32 error_offset;
u32 wq_status;
u32 reserved[28];
} __packed;
/* Helper for context registration H2G */
struct guc_ctxt_registration_info {
u32 flags;
u32 context_idx;
u32 engine_class;
u32 engine_submit_mask;
u32 wq_desc_lo;
u32 wq_desc_hi;
u32 wq_base_lo;
u32 wq_base_hi;
u32 wq_size;
u32 hwlrca_lo;
u32 hwlrca_hi;
};
#define CONTEXT_REGISTRATION_FLAG_KMD BIT(0)
/* Preempt to idle on quantum expiry */
#define CONTEXT_POLICY_FLAG_PREEMPT_TO_IDLE_V69 BIT(0)
/*
* GuC Context registration descriptor.
* FIXME: This is only required to exist during context registration.
* The current 1:1 between guc_lrc_desc and LRCs for the lifetime of the LRC
* is not required.
*/
struct guc_lrc_desc_v69 {
u32 hw_context_desc;
u32 slpm_perf_mode_hint; /* SPLC v1 only */
u32 slpm_freq_hint;
u32 engine_submit_mask; /* In logical space */
u8 engine_class;
u8 reserved0[3];
u32 priority;
u32 process_desc;
u32 wq_addr;
u32 wq_size;
u32 context_flags; /* CONTEXT_REGISTRATION_* */
/* Time for one workload to execute. (in micro seconds) */
u32 execution_quantum;
/* Time to wait for a preemption request to complete before issuing a
* reset. (in micro seconds).
*/
u32 preemption_timeout;
u32 policy_flags; /* CONTEXT_POLICY_* */
u32 reserved1[19];
} __packed;
/* 32-bit KLV structure as used by policy updates and others */
struct guc_klv_generic_dw_t {
u32 kl;
u32 value;
} __packed;
/* Format of the UPDATE_CONTEXT_POLICIES H2G data packet */
struct guc_update_context_policy_header {
u32 action;
u32 ctx_id;
} __packed;
struct guc_update_context_policy {
struct guc_update_context_policy_header header;
struct guc_klv_generic_dw_t klv[GUC_CONTEXT_POLICIES_KLV_NUM_IDS];
} __packed;
/* Format of the UPDATE_SCHEDULING_POLICIES H2G data packet */
struct guc_update_scheduling_policy_header {
u32 action;
} __packed;
/*
* Can't dynamically allocate memory for the scheduling policy KLV because
* it will be sent from within the reset path. Need a fixed size lump on
* the stack instead :(.
*
* Currently, there is only one KLV defined, which has 1 word of KL + 2 words of V.
*/
#define MAX_SCHEDULING_POLICY_SIZE 3
struct guc_update_scheduling_policy {
struct guc_update_scheduling_policy_header header;
u32 data[MAX_SCHEDULING_POLICY_SIZE];
} __packed;
#define GUC_POWER_UNSPECIFIED 0
#define GUC_POWER_D0 1
#define GUC_POWER_D1 2
#define GUC_POWER_D2 3
#define GUC_POWER_D3 4
/* Scheduling policy settings */
#define GLOBAL_SCHEDULE_POLICY_RC_YIELD_DURATION 100 /* in ms */
#define GLOBAL_SCHEDULE_POLICY_RC_YIELD_RATIO 50 /* in percent */
#define GLOBAL_POLICY_MAX_NUM_WI 15
/* Don't reset an engine upon preemption failure */
#define GLOBAL_POLICY_DISABLE_ENGINE_RESET BIT(0)
#define GLOBAL_POLICY_DEFAULT_DPC_PROMOTE_TIME_US 500000
/*
* GuC converts the timeout to clock ticks internally. Different platforms have
* different GuC clocks. Thus, the maximum value before overflow is platform
* dependent. Current worst case scenario is about 110s. So, the spec says to
* limit to 100s to be safe.
*/
#define GUC_POLICY_MAX_EXEC_QUANTUM_US (100 * 1000 * 1000UL)
#define GUC_POLICY_MAX_PREEMPT_TIMEOUT_US (100 * 1000 * 1000UL)
static inline u32 guc_policy_max_exec_quantum_ms(void)
{
BUILD_BUG_ON(GUC_POLICY_MAX_EXEC_QUANTUM_US >= UINT_MAX);
return GUC_POLICY_MAX_EXEC_QUANTUM_US / 1000;
}
static inline u32 guc_policy_max_preempt_timeout_ms(void)
{
BUILD_BUG_ON(GUC_POLICY_MAX_PREEMPT_TIMEOUT_US >= UINT_MAX);
return GUC_POLICY_MAX_PREEMPT_TIMEOUT_US / 1000;
}
struct guc_policies {
u32 submission_queue_depth[GUC_MAX_ENGINE_CLASSES];
/* In micro seconds. How much time to allow before DPC processing is
* called back via interrupt (to prevent DPC queue drain starving).
* Typically 1000s of micro seconds (example only, not granularity). */
u32 dpc_promote_time;
/* Must be set to take these new values. */
u32 is_valid;
/* Max number of WIs to process per call. A large value may keep CS
* idle. */
u32 max_num_work_items;
u32 global_flags;
u32 reserved[4];
} __packed;
/* GuC MMIO reg state struct */
struct guc_mmio_reg {
u32 offset;
u32 value;
u32 flags;
#define GUC_REGSET_MASKED BIT(0)
#define GUC_REGSET_NEEDS_STEERING BIT(1)
#define GUC_REGSET_MASKED_WITH_VALUE BIT(2)
#define GUC_REGSET_RESTORE_ONLY BIT(3)
#define GUC_REGSET_STEERING_GROUP GENMASK(15, 12)
#define GUC_REGSET_STEERING_INSTANCE GENMASK(23, 20)
u32 mask;
} __packed;
/* GuC register sets */
struct guc_mmio_reg_set {
u32 address;
u16 count;
u16 reserved;
} __packed;
/* HW info */
struct guc_gt_system_info {
u8 mapping_table[GUC_MAX_ENGINE_CLASSES][GUC_MAX_INSTANCES_PER_CLASS];
u32 engine_enabled_masks[GUC_MAX_ENGINE_CLASSES];
u32 generic_gt_sysinfo[GUC_GENERIC_GT_SYSINFO_MAX];
} __packed;
enum {
GUC_CAPTURE_LIST_INDEX_PF = 0,
GUC_CAPTURE_LIST_INDEX_VF = 1,
GUC_CAPTURE_LIST_INDEX_MAX = 2,
};
/*Register-types of GuC capture register lists */
enum guc_capture_type {
GUC_CAPTURE_LIST_TYPE_GLOBAL = 0,
GUC_CAPTURE_LIST_TYPE_ENGINE_CLASS,
GUC_CAPTURE_LIST_TYPE_ENGINE_INSTANCE,
GUC_CAPTURE_LIST_TYPE_MAX,
};
/* Class indices for capture_class and capture_instance arrays */
enum {
GUC_CAPTURE_LIST_CLASS_RENDER_COMPUTE = 0,
GUC_CAPTURE_LIST_CLASS_VIDEO = 1,
GUC_CAPTURE_LIST_CLASS_VIDEOENHANCE = 2,
GUC_CAPTURE_LIST_CLASS_BLITTER = 3,
GUC_CAPTURE_LIST_CLASS_GSC_OTHER = 4,
};
/* GuC Additional Data Struct */
struct guc_ads {
struct guc_mmio_reg_set reg_state_list[GUC_MAX_ENGINE_CLASSES][GUC_MAX_INSTANCES_PER_CLASS];
u32 reserved0;
u32 scheduler_policies;
u32 gt_system_info;
u32 reserved1;
u32 control_data;
u32 golden_context_lrca[GUC_MAX_ENGINE_CLASSES];
u32 eng_state_size[GUC_MAX_ENGINE_CLASSES];
u32 private_data;
u32 reserved2;
u32 capture_instance[GUC_CAPTURE_LIST_INDEX_MAX][GUC_MAX_ENGINE_CLASSES];
u32 capture_class[GUC_CAPTURE_LIST_INDEX_MAX][GUC_MAX_ENGINE_CLASSES];
u32 capture_global[GUC_CAPTURE_LIST_INDEX_MAX];
u32 wa_klv_addr_lo;
u32 wa_klv_addr_hi;
u32 wa_klv_size;
u32 reserved[11];
} __packed;
/* Engine usage stats */
struct guc_engine_usage_record {
u32 current_context_index;
u32 last_switch_in_stamp;
u32 reserved0;
u32 total_runtime;
u32 reserved1[4];
} __packed;
struct guc_engine_usage {
struct guc_engine_usage_record engines[GUC_MAX_ENGINE_CLASSES][GUC_MAX_INSTANCES_PER_CLASS];
} __packed;
/* GuC logging structures */
enum guc_log_buffer_type {
GUC_DEBUG_LOG_BUFFER,
GUC_CRASH_DUMP_LOG_BUFFER,
GUC_CAPTURE_LOG_BUFFER,
GUC_MAX_LOG_BUFFER
};
/*
* struct guc_log_buffer_state - GuC log buffer state
*
* Below state structure is used for coordination of retrieval of GuC firmware
* logs. Separate state is maintained for each log buffer type.
* read_ptr points to the location where i915 read last in log buffer and
* is read only for GuC firmware. write_ptr is incremented by GuC with number
* of bytes written for each log entry and is read only for i915.
* When any type of log buffer becomes half full, GuC sends a flush interrupt.
* GuC firmware expects that while it is writing to 2nd half of the buffer,
* first half would get consumed by Host and then get a flush completed
* acknowledgment from Host, so that it does not end up doing any overwrite
* causing loss of logs. So when buffer gets half filled & i915 has requested
* for interrupt, GuC will set flush_to_file field, set the sampled_write_ptr
* to the value of write_ptr and raise the interrupt.
* On receiving the interrupt i915 should read the buffer, clear flush_to_file
* field and also update read_ptr with the value of sample_write_ptr, before
* sending an acknowledgment to GuC. marker & version fields are for internal
* usage of GuC and opaque to i915. buffer_full_cnt field is incremented every
* time GuC detects the log buffer overflow.
*/
struct guc_log_buffer_state {
u32 marker[2];
u32 read_ptr;
u32 write_ptr;
u32 size;
u32 sampled_write_ptr;
u32 wrap_offset;
union {
struct {
u32 flush_to_file:1;
u32 buffer_full_cnt:4;
u32 reserved:27;
};
u32 flags;
};
u32 version;
} __packed;
/* This action will be programmed in C1BC - SOFT_SCRATCH_15_REG */
enum intel_guc_recv_message {
INTEL_GUC_RECV_MSG_CRASH_DUMP_POSTED = BIT(1),
INTEL_GUC_RECV_MSG_EXCEPTION = BIT(30),
};
#endif
@@ -0,0 +1,167 @@
// SPDX-License-Identifier: MIT
/*
* Copyright © 2022 Intel Corporation
*/
#include "gt/intel_gt.h"
#include "gt/intel_hwconfig.h"
#include "i915_drv.h"
#include "i915_memcpy.h"
#include "intel_guc_print.h"
/*
* GuC has a blob containing hardware configuration information (HWConfig).
* This is formatted as a simple and flexible KLV (Key/Length/Value) table.
*
* For example, a minimal version could be:
* enum device_attr {
* ATTR_SOME_VALUE = 0,
* ATTR_SOME_MASK = 1,
* };
*
* static const u32 hwconfig[] = {
* ATTR_SOME_VALUE,
* 1, // Value Length in DWords
* 8, // Value
*
* ATTR_SOME_MASK,
* 3,
* 0x00FFFFFFFF, 0xFFFFFFFF, 0xFF000000,
* };
*
* The attribute ids are defined in a hardware spec.
*/
static int __guc_action_get_hwconfig(struct intel_guc *guc,
u32 ggtt_offset, u32 ggtt_size)
{
u32 action[] = {
INTEL_GUC_ACTION_GET_HWCONFIG,
lower_32_bits(ggtt_offset),
upper_32_bits(ggtt_offset),
ggtt_size,
};
int ret;
guc_dbg(guc, "Querying HW config table: size = %d, offset = 0x%08X\n",
ggtt_size, ggtt_offset);
ret = intel_guc_send_mmio(guc, action, ARRAY_SIZE(action), NULL, 0);
if (ret == -ENXIO)
return -ENOENT;
return ret;
}
static int guc_hwconfig_discover_size(struct intel_guc *guc, struct intel_hwconfig *hwconfig)
{
int ret;
/*
* Sending a query with zero offset and size will return the
* size of the blob.
*/
ret = __guc_action_get_hwconfig(guc, 0, 0);
if (ret < 0)
return ret;
if (ret == 0)
return -EINVAL;
hwconfig->size = ret;
return 0;
}
static int guc_hwconfig_fill_buffer(struct intel_guc *guc, struct intel_hwconfig *hwconfig)
{
struct i915_vma *vma;
u32 ggtt_offset;
void *vaddr;
int ret;
GEM_BUG_ON(!hwconfig->size);
ret = intel_guc_allocate_and_map_vma(guc, hwconfig->size, &vma, &vaddr);
if (ret)
return ret;
ggtt_offset = intel_guc_ggtt_offset(guc, vma);
ret = __guc_action_get_hwconfig(guc, ggtt_offset, hwconfig->size);
if (ret >= 0)
memcpy(hwconfig->ptr, vaddr, hwconfig->size);
i915_vma_unpin_and_release(&vma, I915_VMA_RELEASE_MAP);
return ret;
}
static bool has_table(struct drm_i915_private *i915)
{
if (IS_ALDERLAKE_P(i915) && !IS_ALDERLAKE_P_N(i915))
return true;
if (GRAPHICS_VER_FULL(i915) >= IP_VER(12, 55))
return true;
return false;
}
/*
* intel_guc_hwconfig_init - Initialize the HWConfig
*
* Retrieve the HWConfig table from the GuC and save it locally.
* It can then be queried on demand by other users later on.
*/
static int guc_hwconfig_init(struct intel_gt *gt)
{
struct intel_hwconfig *hwconfig = &gt->info.hwconfig;
struct intel_guc *guc = gt_to_guc(gt);
int ret;
if (!has_table(gt->i915))
return 0;
ret = guc_hwconfig_discover_size(guc, hwconfig);
if (ret)
return ret;
hwconfig->ptr = kmalloc(hwconfig->size, GFP_KERNEL);
if (!hwconfig->ptr) {
hwconfig->size = 0;
return -ENOMEM;
}
ret = guc_hwconfig_fill_buffer(guc, hwconfig);
if (ret < 0) {
intel_gt_fini_hwconfig(gt);
return ret;
}
return 0;
}
/*
* intel_gt_init_hwconfig - Initialize the HWConfig if available
*
* Retrieve the HWConfig table if available on the current platform.
*/
int intel_gt_init_hwconfig(struct intel_gt *gt)
{
if (!intel_uc_uses_guc(&gt->uc))
return 0;
return guc_hwconfig_init(gt);
}
/*
* intel_gt_fini_hwconfig - Finalize the HWConfig
*
* Free up the memory allocation holding the table.
*/
void intel_gt_fini_hwconfig(struct intel_gt *gt)
{
struct intel_hwconfig *hwconfig = &gt->info.hwconfig;
kfree(hwconfig->ptr);
hwconfig->size = 0;
hwconfig->ptr = NULL;
}
@@ -0,0 +1,937 @@
// SPDX-License-Identifier: MIT
/*
* Copyright © 2014-2019 Intel Corporation
*/
#include <linux/debugfs.h>
#include <linux/string_helpers.h>
#include <drm/drm_managed.h>
#include "gt/intel_gt.h"
#include "i915_drv.h"
#include "i915_irq.h"
#include "i915_memcpy.h"
#include "intel_guc_capture.h"
#include "intel_guc_log.h"
#include "intel_guc_print.h"
#if IS_ENABLED(CONFIG_DRM_I915_DEBUG_GUC)
#define GUC_LOG_DEFAULT_CRASH_BUFFER_SIZE SZ_2M
#define GUC_LOG_DEFAULT_DEBUG_BUFFER_SIZE SZ_16M
#define GUC_LOG_DEFAULT_CAPTURE_BUFFER_SIZE SZ_1M
#elif IS_ENABLED(CONFIG_DRM_I915_DEBUG_GEM)
#define GUC_LOG_DEFAULT_CRASH_BUFFER_SIZE SZ_1M
#define GUC_LOG_DEFAULT_DEBUG_BUFFER_SIZE SZ_2M
#define GUC_LOG_DEFAULT_CAPTURE_BUFFER_SIZE SZ_1M
#else
#define GUC_LOG_DEFAULT_CRASH_BUFFER_SIZE SZ_8K
#define GUC_LOG_DEFAULT_DEBUG_BUFFER_SIZE SZ_64K
#define GUC_LOG_DEFAULT_CAPTURE_BUFFER_SIZE SZ_1M
#endif
static void guc_log_copy_debuglogs_for_relay(struct intel_guc_log *log);
struct guc_log_section {
u32 max;
u32 flag;
u32 default_val;
const char *name;
};
static void _guc_log_init_sizes(struct intel_guc_log *log)
{
struct intel_guc *guc = log_to_guc(log);
static const struct guc_log_section sections[GUC_LOG_SECTIONS_LIMIT] = {
{
GUC_LOG_CRASH_MASK >> GUC_LOG_CRASH_SHIFT,
GUC_LOG_LOG_ALLOC_UNITS,
GUC_LOG_DEFAULT_CRASH_BUFFER_SIZE,
"crash dump"
},
{
GUC_LOG_DEBUG_MASK >> GUC_LOG_DEBUG_SHIFT,
GUC_LOG_LOG_ALLOC_UNITS,
GUC_LOG_DEFAULT_DEBUG_BUFFER_SIZE,
"debug",
},
{
GUC_LOG_CAPTURE_MASK >> GUC_LOG_CAPTURE_SHIFT,
GUC_LOG_CAPTURE_ALLOC_UNITS,
GUC_LOG_DEFAULT_CAPTURE_BUFFER_SIZE,
"capture",
}
};
int i;
for (i = 0; i < GUC_LOG_SECTIONS_LIMIT; i++)
log->sizes[i].bytes = sections[i].default_val;
/* If debug size > 1MB then bump default crash size to keep the same units */
if (log->sizes[GUC_LOG_SECTIONS_DEBUG].bytes >= SZ_1M &&
GUC_LOG_DEFAULT_CRASH_BUFFER_SIZE < SZ_1M)
log->sizes[GUC_LOG_SECTIONS_CRASH].bytes = SZ_1M;
/* Prepare the GuC API structure fields: */
for (i = 0; i < GUC_LOG_SECTIONS_LIMIT; i++) {
/* Convert to correct units */
if ((log->sizes[i].bytes % SZ_1M) == 0) {
log->sizes[i].units = SZ_1M;
log->sizes[i].flag = sections[i].flag;
} else {
log->sizes[i].units = SZ_4K;
log->sizes[i].flag = 0;
}
if (!IS_ALIGNED(log->sizes[i].bytes, log->sizes[i].units))
guc_err(guc, "Mis-aligned log %s size: 0x%X vs 0x%X!\n",
sections[i].name, log->sizes[i].bytes, log->sizes[i].units);
log->sizes[i].count = log->sizes[i].bytes / log->sizes[i].units;
if (!log->sizes[i].count) {
guc_err(guc, "Zero log %s size!\n", sections[i].name);
} else {
/* Size is +1 unit */
log->sizes[i].count--;
}
/* Clip to field size */
if (log->sizes[i].count > sections[i].max) {
guc_err(guc, "log %s size too large: %d vs %d!\n",
sections[i].name, log->sizes[i].count + 1, sections[i].max + 1);
log->sizes[i].count = sections[i].max;
}
}
if (log->sizes[GUC_LOG_SECTIONS_CRASH].units != log->sizes[GUC_LOG_SECTIONS_DEBUG].units) {
guc_err(guc, "Unit mismatch for crash and debug sections: %d vs %d!\n",
log->sizes[GUC_LOG_SECTIONS_CRASH].units,
log->sizes[GUC_LOG_SECTIONS_DEBUG].units);
log->sizes[GUC_LOG_SECTIONS_CRASH].units = log->sizes[GUC_LOG_SECTIONS_DEBUG].units;
log->sizes[GUC_LOG_SECTIONS_CRASH].count = 0;
}
log->sizes_initialised = true;
}
static void guc_log_init_sizes(struct intel_guc_log *log)
{
if (log->sizes_initialised)
return;
_guc_log_init_sizes(log);
}
static u32 intel_guc_log_section_size_crash(struct intel_guc_log *log)
{
guc_log_init_sizes(log);
return log->sizes[GUC_LOG_SECTIONS_CRASH].bytes;
}
static u32 intel_guc_log_section_size_debug(struct intel_guc_log *log)
{
guc_log_init_sizes(log);
return log->sizes[GUC_LOG_SECTIONS_DEBUG].bytes;
}
u32 intel_guc_log_section_size_capture(struct intel_guc_log *log)
{
guc_log_init_sizes(log);
return log->sizes[GUC_LOG_SECTIONS_CAPTURE].bytes;
}
static u32 intel_guc_log_size(struct intel_guc_log *log)
{
/*
* GuC Log buffer Layout:
*
* NB: Ordering must follow "enum guc_log_buffer_type".
*
* +===============================+ 00B
* | Debug state header |
* +-------------------------------+ 32B
* | Crash dump state header |
* +-------------------------------+ 64B
* | Capture state header |
* +-------------------------------+ 96B
* | |
* +===============================+ PAGE_SIZE (4KB)
* | Debug logs |
* +===============================+ + DEBUG_SIZE
* | Crash Dump logs |
* +===============================+ + CRASH_SIZE
* | Capture logs |
* +===============================+ + CAPTURE_SIZE
*/
return PAGE_SIZE +
intel_guc_log_section_size_crash(log) +
intel_guc_log_section_size_debug(log) +
intel_guc_log_section_size_capture(log);
}
/**
* DOC: GuC firmware log
*
* Firmware log is enabled by setting i915.guc_log_level to the positive level.
* Log data is printed out via reading debugfs i915_guc_log_dump. Reading from
* i915_guc_load_status will print out firmware loading status and scratch
* registers value.
*/
static int guc_action_flush_log_complete(struct intel_guc *guc)
{
u32 action[] = {
INTEL_GUC_ACTION_LOG_BUFFER_FILE_FLUSH_COMPLETE,
GUC_DEBUG_LOG_BUFFER
};
return intel_guc_send_nb(guc, action, ARRAY_SIZE(action), 0);
}
static int guc_action_flush_log(struct intel_guc *guc)
{
u32 action[] = {
INTEL_GUC_ACTION_FORCE_LOG_BUFFER_FLUSH,
0
};
return intel_guc_send(guc, action, ARRAY_SIZE(action));
}
static int guc_action_control_log(struct intel_guc *guc, bool enable,
bool default_logging, u32 verbosity)
{
u32 action[] = {
INTEL_GUC_ACTION_UK_LOG_ENABLE_LOGGING,
(enable ? GUC_LOG_CONTROL_LOGGING_ENABLED : 0) |
(verbosity << GUC_LOG_CONTROL_VERBOSITY_SHIFT) |
(default_logging ? GUC_LOG_CONTROL_DEFAULT_LOGGING : 0)
};
GEM_BUG_ON(verbosity > GUC_LOG_VERBOSITY_MAX);
return intel_guc_send(guc, action, ARRAY_SIZE(action));
}
/*
* Sub buffer switch callback. Called whenever relay has to switch to a new
* sub buffer, relay stays on the same sub buffer if 0 is returned.
*/
static int subbuf_start_callback(struct rchan_buf *buf,
void *subbuf,
void *prev_subbuf)
{
/*
* Use no-overwrite mode by default, where relay will stop accepting
* new data if there are no empty sub buffers left.
* There is no strict synchronization enforced by relay between Consumer
* and Producer. In overwrite mode, there is a possibility of getting
* inconsistent/garbled data, the producer could be writing on to the
* same sub buffer from which Consumer is reading. This can't be avoided
* unless Consumer is fast enough and can always run in tandem with
* Producer.
*/
if (relay_buf_full(buf))
return 0;
return 1;
}
/*
* file_create() callback. Creates relay file in debugfs.
*/
static struct dentry *create_buf_file_callback(const char *filename,
struct dentry *parent,
umode_t mode,
struct rchan_buf *buf,
int *is_global)
{
struct dentry *buf_file;
/*
* This to enable the use of a single buffer for the relay channel and
* correspondingly have a single file exposed to User, through which
* it can collect the logs in order without any post-processing.
* Need to set 'is_global' even if parent is NULL for early logging.
*/
*is_global = 1;
if (!parent)
return NULL;
buf_file = debugfs_create_file(filename, mode,
parent, buf, &relay_file_operations);
if (IS_ERR(buf_file))
return NULL;
return buf_file;
}
/*
* file_remove() default callback. Removes relay file in debugfs.
*/
static int remove_buf_file_callback(struct dentry *dentry)
{
debugfs_remove(dentry);
return 0;
}
/* relay channel callbacks */
static const struct rchan_callbacks relay_callbacks = {
.subbuf_start = subbuf_start_callback,
.create_buf_file = create_buf_file_callback,
.remove_buf_file = remove_buf_file_callback,
};
static void guc_move_to_next_buf(struct intel_guc_log *log)
{
/*
* Make sure the updates made in the sub buffer are visible when
* Consumer sees the following update to offset inside the sub buffer.
*/
smp_wmb();
/* All data has been written, so now move the offset of sub buffer. */
relay_reserve(log->relay.channel, log->vma->obj->base.size -
intel_guc_log_section_size_capture(log));
/* Switch to the next sub buffer */
relay_flush(log->relay.channel);
}
static void *guc_get_write_buffer(struct intel_guc_log *log)
{
/*
* Just get the base address of a new sub buffer and copy data into it
* ourselves. NULL will be returned in no-overwrite mode, if all sub
* buffers are full. Could have used the relay_write() to indirectly
* copy the data, but that would have been bit convoluted, as we need to
* write to only certain locations inside a sub buffer which cannot be
* done without using relay_reserve() along with relay_write(). So its
* better to use relay_reserve() alone.
*/
return relay_reserve(log->relay.channel, 0);
}
bool intel_guc_check_log_buf_overflow(struct intel_guc_log *log,
enum guc_log_buffer_type type,
unsigned int full_cnt)
{
unsigned int prev_full_cnt = log->stats[type].sampled_overflow;
bool overflow = false;
if (full_cnt != prev_full_cnt) {
overflow = true;
log->stats[type].overflow = full_cnt;
log->stats[type].sampled_overflow += full_cnt - prev_full_cnt;
if (full_cnt < prev_full_cnt) {
/* buffer_full_cnt is a 4 bit counter */
log->stats[type].sampled_overflow += 16;
}
guc_notice_ratelimited(log_to_guc(log), "log buffer overflow\n");
}
return overflow;
}
unsigned int intel_guc_get_log_buffer_size(struct intel_guc_log *log,
enum guc_log_buffer_type type)
{
switch (type) {
case GUC_DEBUG_LOG_BUFFER:
return intel_guc_log_section_size_debug(log);
case GUC_CRASH_DUMP_LOG_BUFFER:
return intel_guc_log_section_size_crash(log);
case GUC_CAPTURE_LOG_BUFFER:
return intel_guc_log_section_size_capture(log);
default:
MISSING_CASE(type);
}
return 0;
}
size_t intel_guc_get_log_buffer_offset(struct intel_guc_log *log,
enum guc_log_buffer_type type)
{
enum guc_log_buffer_type i;
size_t offset = PAGE_SIZE;/* for the log_buffer_states */
for (i = GUC_DEBUG_LOG_BUFFER; i < GUC_MAX_LOG_BUFFER; ++i) {
if (i == type)
break;
offset += intel_guc_get_log_buffer_size(log, i);
}
return offset;
}
static void _guc_log_copy_debuglogs_for_relay(struct intel_guc_log *log)
{
struct intel_guc *guc = log_to_guc(log);
unsigned int buffer_size, read_offset, write_offset, bytes_to_copy, full_cnt;
struct guc_log_buffer_state *log_buf_state, *log_buf_snapshot_state;
struct guc_log_buffer_state log_buf_state_local;
enum guc_log_buffer_type type;
void *src_data, *dst_data;
bool new_overflow;
mutex_lock(&log->relay.lock);
if (guc_WARN_ON(guc, !intel_guc_log_relay_created(log)))
goto out_unlock;
/* Get the pointer to shared GuC log buffer */
src_data = log->buf_addr;
log_buf_state = src_data;
/* Get the pointer to local buffer to store the logs */
log_buf_snapshot_state = dst_data = guc_get_write_buffer(log);
if (unlikely(!log_buf_snapshot_state)) {
/*
* Used rate limited to avoid deluge of messages, logs might be
* getting consumed by User at a slow rate.
*/
guc_err_ratelimited(guc, "no sub-buffer to copy general logs\n");
log->relay.full_count++;
goto out_unlock;
}
/* Actual logs are present from the 2nd page */
src_data += PAGE_SIZE;
dst_data += PAGE_SIZE;
/* For relay logging, we exclude error state capture */
for (type = GUC_DEBUG_LOG_BUFFER; type <= GUC_CRASH_DUMP_LOG_BUFFER; type++) {
/*
* Make a copy of the state structure, inside GuC log buffer
* (which is uncached mapped), on the stack to avoid reading
* from it multiple times.
*/
memcpy(&log_buf_state_local, log_buf_state,
sizeof(struct guc_log_buffer_state));
buffer_size = intel_guc_get_log_buffer_size(log, type);
read_offset = log_buf_state_local.read_ptr;
write_offset = log_buf_state_local.sampled_write_ptr;
full_cnt = log_buf_state_local.buffer_full_cnt;
/* Bookkeeping stuff */
log->stats[type].flush += log_buf_state_local.flush_to_file;
new_overflow = intel_guc_check_log_buf_overflow(log, type, full_cnt);
/* Update the state of shared log buffer */
log_buf_state->read_ptr = write_offset;
log_buf_state->flush_to_file = 0;
log_buf_state++;
/* First copy the state structure in snapshot buffer */
memcpy(log_buf_snapshot_state, &log_buf_state_local,
sizeof(struct guc_log_buffer_state));
/*
* The write pointer could have been updated by GuC firmware,
* after sending the flush interrupt to Host, for consistency
* set write pointer value to same value of sampled_write_ptr
* in the snapshot buffer.
*/
log_buf_snapshot_state->write_ptr = write_offset;
log_buf_snapshot_state++;
/* Now copy the actual logs. */
if (unlikely(new_overflow)) {
/* copy the whole buffer in case of overflow */
read_offset = 0;
write_offset = buffer_size;
} else if (unlikely((read_offset > buffer_size) ||
(write_offset > buffer_size))) {
guc_err(guc, "invalid log buffer state\n");
/* copy whole buffer as offsets are unreliable */
read_offset = 0;
write_offset = buffer_size;
}
/* Just copy the newly written data */
if (read_offset > write_offset) {
i915_memcpy_from_wc(dst_data, src_data, write_offset);
bytes_to_copy = buffer_size - read_offset;
} else {
bytes_to_copy = write_offset - read_offset;
}
i915_memcpy_from_wc(dst_data + read_offset,
src_data + read_offset, bytes_to_copy);
src_data += buffer_size;
dst_data += buffer_size;
}
guc_move_to_next_buf(log);
out_unlock:
mutex_unlock(&log->relay.lock);
}
static void copy_debug_logs_work(struct work_struct *work)
{
struct intel_guc_log *log =
container_of(work, struct intel_guc_log, relay.flush_work);
guc_log_copy_debuglogs_for_relay(log);
}
static int guc_log_relay_map(struct intel_guc_log *log)
{
lockdep_assert_held(&log->relay.lock);
if (!log->vma || !log->buf_addr)
return -ENODEV;
/*
* WC vmalloc mapping of log buffer pages was done at
* GuC Log Init time, but lets keep a ref for book-keeping
*/
i915_gem_object_get(log->vma->obj);
log->relay.buf_in_use = true;
return 0;
}
static void guc_log_relay_unmap(struct intel_guc_log *log)
{
lockdep_assert_held(&log->relay.lock);
i915_gem_object_put(log->vma->obj);
log->relay.buf_in_use = false;
}
void intel_guc_log_init_early(struct intel_guc_log *log)
{
struct intel_guc *guc = log_to_guc(log);
struct drm_i915_private *i915 = guc_to_i915(guc);
drmm_mutex_init(&i915->drm, &log->relay.lock);
drmm_mutex_init(&i915->drm, &log->guc_lock);
INIT_WORK(&log->relay.flush_work, copy_debug_logs_work);
log->relay.started = false;
}
static int guc_log_relay_create(struct intel_guc_log *log)
{
struct intel_guc *guc = log_to_guc(log);
struct drm_i915_private *i915 = guc_to_i915(guc);
struct rchan *guc_log_relay_chan;
size_t n_subbufs, subbuf_size;
int ret;
lockdep_assert_held(&log->relay.lock);
GEM_BUG_ON(!log->vma);
/*
* Keep the size of sub buffers same as shared log buffer
* but GuC log-events excludes the error-state-capture logs
*/
subbuf_size = log->vma->size - intel_guc_log_section_size_capture(log);
/*
* Store up to 8 snapshots, which is large enough to buffer sufficient
* boot time logs and provides enough leeway to User, in terms of
* latency, for consuming the logs from relay. Also doesn't take
* up too much memory.
*/
n_subbufs = 8;
if (!guc->dbgfs_node)
return -ENOENT;
guc_log_relay_chan = relay_open("guc_log",
guc->dbgfs_node,
subbuf_size, n_subbufs,
&relay_callbacks, i915);
if (!guc_log_relay_chan) {
guc_err(guc, "Couldn't create relay channel for logging\n");
ret = -ENOMEM;
return ret;
}
GEM_BUG_ON(guc_log_relay_chan->subbuf_size < subbuf_size);
log->relay.channel = guc_log_relay_chan;
return 0;
}
static void guc_log_relay_destroy(struct intel_guc_log *log)
{
lockdep_assert_held(&log->relay.lock);
relay_close(log->relay.channel);
log->relay.channel = NULL;
}
static void guc_log_copy_debuglogs_for_relay(struct intel_guc_log *log)
{
struct intel_guc *guc = log_to_guc(log);
struct drm_i915_private *i915 = guc_to_i915(guc);
intel_wakeref_t wakeref;
_guc_log_copy_debuglogs_for_relay(log);
/*
* Generally device is expected to be active only at this
* time, so get/put should be really quick.
*/
with_intel_runtime_pm(&i915->runtime_pm, wakeref)
guc_action_flush_log_complete(guc);
}
static u32 __get_default_log_level(struct intel_guc_log *log)
{
struct intel_guc *guc = log_to_guc(log);
struct drm_i915_private *i915 = guc_to_i915(guc);
/* A negative value means "use platform/config default" */
if (i915->params.guc_log_level < 0) {
return (IS_ENABLED(CONFIG_DRM_I915_DEBUG) ||
IS_ENABLED(CONFIG_DRM_I915_DEBUG_GEM)) ?
GUC_LOG_LEVEL_MAX : GUC_LOG_LEVEL_NON_VERBOSE;
}
if (i915->params.guc_log_level > GUC_LOG_LEVEL_MAX) {
guc_warn(guc, "Log verbosity param out of range: %d > %d!\n",
i915->params.guc_log_level, GUC_LOG_LEVEL_MAX);
return (IS_ENABLED(CONFIG_DRM_I915_DEBUG) ||
IS_ENABLED(CONFIG_DRM_I915_DEBUG_GEM)) ?
GUC_LOG_LEVEL_MAX : GUC_LOG_LEVEL_DISABLED;
}
GEM_BUG_ON(i915->params.guc_log_level < GUC_LOG_LEVEL_DISABLED);
GEM_BUG_ON(i915->params.guc_log_level > GUC_LOG_LEVEL_MAX);
return i915->params.guc_log_level;
}
int intel_guc_log_create(struct intel_guc_log *log)
{
struct intel_guc *guc = log_to_guc(log);
struct i915_vma *vma;
void *vaddr;
u32 guc_log_size;
int ret;
GEM_BUG_ON(log->vma);
guc_log_size = intel_guc_log_size(log);
vma = intel_guc_allocate_vma(guc, guc_log_size);
if (IS_ERR(vma)) {
ret = PTR_ERR(vma);
goto err;
}
log->vma = vma;
/*
* Create a WC (Uncached for read) vmalloc mapping up front immediate access to
* data from memory during critical events such as error capture
*/
vaddr = i915_gem_object_pin_map_unlocked(log->vma->obj, I915_MAP_WC);
if (IS_ERR(vaddr)) {
ret = PTR_ERR(vaddr);
i915_vma_unpin_and_release(&log->vma, 0);
goto err;
}
log->buf_addr = vaddr;
log->level = __get_default_log_level(log);
guc_dbg(guc, "guc_log_level=%d (%s, verbose:%s, verbosity:%d)\n",
log->level, str_enabled_disabled(log->level),
str_yes_no(GUC_LOG_LEVEL_IS_VERBOSE(log->level)),
GUC_LOG_LEVEL_TO_VERBOSITY(log->level));
return 0;
err:
guc_err(guc, "Failed to allocate or map log buffer %pe\n", ERR_PTR(ret));
return ret;
}
void intel_guc_log_destroy(struct intel_guc_log *log)
{
log->buf_addr = NULL;
i915_vma_unpin_and_release(&log->vma, I915_VMA_RELEASE_MAP);
}
int intel_guc_log_set_level(struct intel_guc_log *log, u32 level)
{
struct intel_guc *guc = log_to_guc(log);
struct drm_i915_private *i915 = guc_to_i915(guc);
intel_wakeref_t wakeref;
int ret = 0;
BUILD_BUG_ON(GUC_LOG_VERBOSITY_MIN != 0);
GEM_BUG_ON(!log->vma);
/*
* GuC is recognizing log levels starting from 0 to max, we're using 0
* as indication that logging should be disabled.
*/
if (level < GUC_LOG_LEVEL_DISABLED || level > GUC_LOG_LEVEL_MAX)
return -EINVAL;
mutex_lock(&log->guc_lock);
if (log->level == level)
goto out_unlock;
with_intel_runtime_pm(&i915->runtime_pm, wakeref)
ret = guc_action_control_log(guc,
GUC_LOG_LEVEL_IS_VERBOSE(level),
GUC_LOG_LEVEL_IS_ENABLED(level),
GUC_LOG_LEVEL_TO_VERBOSITY(level));
if (ret) {
guc_dbg(guc, "guc_log_control action failed %pe\n", ERR_PTR(ret));
goto out_unlock;
}
log->level = level;
out_unlock:
mutex_unlock(&log->guc_lock);
return ret;
}
bool intel_guc_log_relay_created(const struct intel_guc_log *log)
{
return log->buf_addr;
}
int intel_guc_log_relay_open(struct intel_guc_log *log)
{
int ret;
if (!log->vma)
return -ENODEV;
mutex_lock(&log->relay.lock);
if (intel_guc_log_relay_created(log)) {
ret = -EEXIST;
goto out_unlock;
}
/*
* We require SSE 4.1 for fast reads from the GuC log buffer and
* it should be present on the chipsets supporting GuC based
* submissions.
*/
if (!i915_has_memcpy_from_wc()) {
ret = -ENXIO;
goto out_unlock;
}
ret = guc_log_relay_create(log);
if (ret)
goto out_unlock;
ret = guc_log_relay_map(log);
if (ret)
goto out_relay;
mutex_unlock(&log->relay.lock);
return 0;
out_relay:
guc_log_relay_destroy(log);
out_unlock:
mutex_unlock(&log->relay.lock);
return ret;
}
int intel_guc_log_relay_start(struct intel_guc_log *log)
{
if (log->relay.started)
return -EEXIST;
/*
* When GuC is logging without us relaying to userspace, we're ignoring
* the flush notification. This means that we need to unconditionally
* flush on relay enabling, since GuC only notifies us once.
*/
queue_work(system_highpri_wq, &log->relay.flush_work);
log->relay.started = true;
return 0;
}
void intel_guc_log_relay_flush(struct intel_guc_log *log)
{
struct intel_guc *guc = log_to_guc(log);
intel_wakeref_t wakeref;
if (!log->relay.started)
return;
/*
* Before initiating the forceful flush, wait for any pending/ongoing
* flush to complete otherwise forceful flush may not actually happen.
*/
flush_work(&log->relay.flush_work);
with_intel_runtime_pm(guc_to_gt(guc)->uncore->rpm, wakeref)
guc_action_flush_log(guc);
/* GuC would have updated log buffer by now, so copy it */
guc_log_copy_debuglogs_for_relay(log);
}
/*
* Stops the relay log. Called from intel_guc_log_relay_close(), so no
* possibility of race with start/flush since relay_write cannot race
* relay_close.
*/
static void guc_log_relay_stop(struct intel_guc_log *log)
{
struct intel_guc *guc = log_to_guc(log);
struct drm_i915_private *i915 = guc_to_i915(guc);
if (!log->relay.started)
return;
intel_synchronize_irq(i915);
flush_work(&log->relay.flush_work);
log->relay.started = false;
}
void intel_guc_log_relay_close(struct intel_guc_log *log)
{
guc_log_relay_stop(log);
mutex_lock(&log->relay.lock);
GEM_BUG_ON(!intel_guc_log_relay_created(log));
guc_log_relay_unmap(log);
guc_log_relay_destroy(log);
mutex_unlock(&log->relay.lock);
}
void intel_guc_log_handle_flush_event(struct intel_guc_log *log)
{
if (log->relay.started)
queue_work(system_highpri_wq, &log->relay.flush_work);
}
static const char *
stringify_guc_log_type(enum guc_log_buffer_type type)
{
switch (type) {
case GUC_DEBUG_LOG_BUFFER:
return "DEBUG";
case GUC_CRASH_DUMP_LOG_BUFFER:
return "CRASH";
case GUC_CAPTURE_LOG_BUFFER:
return "CAPTURE";
default:
MISSING_CASE(type);
}
return "";
}
/**
* intel_guc_log_info - dump information about GuC log relay
* @log: the GuC log
* @p: the &drm_printer
*
* Pretty printer for GuC log info
*/
void intel_guc_log_info(struct intel_guc_log *log, struct drm_printer *p)
{
enum guc_log_buffer_type type;
if (!intel_guc_log_relay_created(log)) {
drm_puts(p, "GuC log relay not created\n");
return;
}
drm_puts(p, "GuC logging stats:\n");
drm_printf(p, "\tRelay full count: %u\n", log->relay.full_count);
for (type = GUC_DEBUG_LOG_BUFFER; type < GUC_MAX_LOG_BUFFER; type++) {
drm_printf(p, "\t%s:\tflush count %10u, overflow count %10u\n",
stringify_guc_log_type(type),
log->stats[type].flush,
log->stats[type].sampled_overflow);
}
}
/**
* intel_guc_log_dump - dump the contents of the GuC log
* @log: the GuC log
* @p: the &drm_printer
* @dump_load_err: dump the log saved on GuC load error
*
* Pretty printer for the GuC log
*/
int intel_guc_log_dump(struct intel_guc_log *log, struct drm_printer *p,
bool dump_load_err)
{
struct intel_guc *guc = log_to_guc(log);
struct intel_uc *uc = container_of(guc, struct intel_uc, guc);
struct drm_i915_gem_object *obj = NULL;
void *map;
u32 *page;
int i, j;
if (!intel_guc_is_supported(guc))
return -ENODEV;
if (dump_load_err)
obj = uc->load_err_log;
else if (guc->log.vma)
obj = guc->log.vma->obj;
if (!obj)
return 0;
page = (u32 *)__get_free_page(GFP_KERNEL);
if (!page)
return -ENOMEM;
intel_guc_dump_time_info(guc, p);
map = i915_gem_object_pin_map_unlocked(obj, I915_MAP_WC);
if (IS_ERR(map)) {
guc_dbg(guc, "Failed to pin log object: %pe\n", map);
drm_puts(p, "(log data unaccessible)\n");
free_page((unsigned long)page);
return PTR_ERR(map);
}
for (i = 0; i < obj->base.size; i += PAGE_SIZE) {
if (!i915_memcpy_from_wc(page, map + i, PAGE_SIZE))
memcpy(page, map + i, PAGE_SIZE);
for (j = 0; j < PAGE_SIZE / sizeof(u32); j += 4)
drm_printf(p, "0x%08x 0x%08x 0x%08x 0x%08x\n",
*(page + j + 0), *(page + j + 1),
*(page + j + 2), *(page + j + 3));
}
drm_puts(p, "\n");
i915_gem_object_unpin_map(obj);
free_page((unsigned long)page);
return 0;
}
@@ -0,0 +1,113 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2014-2019 Intel Corporation
*/
#ifndef _INTEL_GUC_LOG_H_
#define _INTEL_GUC_LOG_H_
#include <linux/mutex.h>
#include <linux/relay.h>
#include <linux/workqueue.h>
#include "intel_guc_fwif.h"
#include "i915_gem.h"
struct intel_guc;
/*
* While we're using plain log level in i915, GuC controls are much more...
* "elaborate"? We have a couple of bits for verbosity, separate bit for actual
* log enabling, and separate bit for default logging - which "conveniently"
* ignores the enable bit.
*/
#define GUC_LOG_LEVEL_DISABLED 0
#define GUC_LOG_LEVEL_NON_VERBOSE 1
#define GUC_LOG_LEVEL_IS_ENABLED(x) ((x) > GUC_LOG_LEVEL_DISABLED)
#define GUC_LOG_LEVEL_IS_VERBOSE(x) ((x) > GUC_LOG_LEVEL_NON_VERBOSE)
#define GUC_LOG_LEVEL_TO_VERBOSITY(x) ({ \
typeof(x) _x = (x); \
GUC_LOG_LEVEL_IS_VERBOSE(_x) ? _x - 2 : 0; \
})
#define GUC_VERBOSITY_TO_LOG_LEVEL(x) ((x) + 2)
#define GUC_LOG_LEVEL_MAX GUC_VERBOSITY_TO_LOG_LEVEL(GUC_LOG_VERBOSITY_MAX)
enum {
GUC_LOG_SECTIONS_CRASH,
GUC_LOG_SECTIONS_DEBUG,
GUC_LOG_SECTIONS_CAPTURE,
GUC_LOG_SECTIONS_LIMIT
};
struct intel_guc_log {
u32 level;
/*
* Protects concurrent access and modification of intel_guc_log->level.
*
* This lock replaces the legacy struct_mutex usage in
* intel_guc_log system.
*/
struct mutex guc_lock;
/* Allocation settings */
struct {
s32 bytes; /* Size in bytes */
s32 units; /* GuC API units - 1MB or 4KB */
s32 count; /* Number of API units */
u32 flag; /* GuC API units flag */
} sizes[GUC_LOG_SECTIONS_LIMIT];
bool sizes_initialised;
/* Combined buffer allocation */
struct i915_vma *vma;
void *buf_addr;
/* RelayFS support */
struct {
bool buf_in_use;
bool started;
struct work_struct flush_work;
struct rchan *channel;
struct mutex lock;
u32 full_count;
} relay;
/* logging related stats */
struct {
u32 sampled_overflow;
u32 overflow;
u32 flush;
} stats[GUC_MAX_LOG_BUFFER];
};
void intel_guc_log_init_early(struct intel_guc_log *log);
bool intel_guc_check_log_buf_overflow(struct intel_guc_log *log, enum guc_log_buffer_type type,
unsigned int full_cnt);
unsigned int intel_guc_get_log_buffer_size(struct intel_guc_log *log,
enum guc_log_buffer_type type);
size_t intel_guc_get_log_buffer_offset(struct intel_guc_log *log, enum guc_log_buffer_type type);
int intel_guc_log_create(struct intel_guc_log *log);
void intel_guc_log_destroy(struct intel_guc_log *log);
int intel_guc_log_set_level(struct intel_guc_log *log, u32 level);
bool intel_guc_log_relay_created(const struct intel_guc_log *log);
int intel_guc_log_relay_open(struct intel_guc_log *log);
int intel_guc_log_relay_start(struct intel_guc_log *log);
void intel_guc_log_relay_flush(struct intel_guc_log *log);
void intel_guc_log_relay_close(struct intel_guc_log *log);
void intel_guc_log_handle_flush_event(struct intel_guc_log *log);
static inline u32 intel_guc_log_get_level(struct intel_guc_log *log)
{
return log->level;
}
void intel_guc_log_info(struct intel_guc_log *log, struct drm_printer *p);
int intel_guc_log_dump(struct intel_guc_log *log, struct drm_printer *p,
bool dump_load_err);
u32 intel_guc_log_section_size_capture(struct intel_guc_log *log);
#endif
@@ -0,0 +1,175 @@
// SPDX-License-Identifier: MIT
/*
* Copyright © 2020 Intel Corporation
*/
#include <linux/fs.h>
#include <drm/drm_print.h>
#include "gt/intel_gt_debugfs.h"
#include "intel_guc.h"
#include "intel_guc_log.h"
#include "intel_guc_log_debugfs.h"
#include "intel_uc.h"
static u32 obj_to_guc_log_dump_size(struct drm_i915_gem_object *obj)
{
u32 size;
if (!obj)
return PAGE_SIZE;
/* "0x%08x 0x%08x 0x%08x 0x%08x\n" => 16 bytes -> 44 chars => x2.75 */
size = ((obj->base.size * 11) + 3) / 4;
/* Add padding for final blank line, any extra header info, etc. */
size = PAGE_ALIGN(size + PAGE_SIZE);
return size;
}
static u32 guc_log_dump_size(struct intel_guc_log *log)
{
struct intel_guc *guc = log_to_guc(log);
if (!intel_guc_is_supported(guc))
return PAGE_SIZE;
if (!log->vma)
return PAGE_SIZE;
return obj_to_guc_log_dump_size(log->vma->obj);
}
static int guc_log_dump_show(struct seq_file *m, void *data)
{
struct drm_printer p = drm_seq_file_printer(m);
int ret;
ret = intel_guc_log_dump(m->private, &p, false);
if (IS_ENABLED(CONFIG_DRM_I915_DEBUG_GEM) && seq_has_overflowed(m))
pr_warn_once("preallocated size:%zx for %s exceeded\n",
m->size, __func__);
return ret;
}
DEFINE_INTEL_GT_DEBUGFS_ATTRIBUTE_WITH_SIZE(guc_log_dump, guc_log_dump_size);
static u32 guc_load_err_dump_size(struct intel_guc_log *log)
{
struct intel_guc *guc = log_to_guc(log);
struct intel_uc *uc = container_of(guc, struct intel_uc, guc);
if (!intel_guc_is_supported(guc))
return PAGE_SIZE;
return obj_to_guc_log_dump_size(uc->load_err_log);
}
static int guc_load_err_log_dump_show(struct seq_file *m, void *data)
{
struct drm_printer p = drm_seq_file_printer(m);
if (IS_ENABLED(CONFIG_DRM_I915_DEBUG_GEM) && seq_has_overflowed(m))
pr_warn_once("preallocated size:%zx for %s exceeded\n",
m->size, __func__);
return intel_guc_log_dump(m->private, &p, true);
}
DEFINE_INTEL_GT_DEBUGFS_ATTRIBUTE_WITH_SIZE(guc_load_err_log_dump, guc_load_err_dump_size);
static int guc_log_level_get(void *data, u64 *val)
{
struct intel_guc_log *log = data;
if (!log->vma)
return -ENODEV;
*val = intel_guc_log_get_level(log);
return 0;
}
static int guc_log_level_set(void *data, u64 val)
{
struct intel_guc_log *log = data;
if (!log->vma)
return -ENODEV;
return intel_guc_log_set_level(log, val);
}
DEFINE_SIMPLE_ATTRIBUTE(guc_log_level_fops,
guc_log_level_get, guc_log_level_set,
"%lld\n");
static int guc_log_relay_open(struct inode *inode, struct file *file)
{
struct intel_guc_log *log = inode->i_private;
if (!intel_guc_is_ready(log_to_guc(log)))
return -ENODEV;
file->private_data = log;
return intel_guc_log_relay_open(log);
}
static ssize_t
guc_log_relay_write(struct file *filp,
const char __user *ubuf,
size_t cnt,
loff_t *ppos)
{
struct intel_guc_log *log = filp->private_data;
int val;
int ret;
ret = kstrtoint_from_user(ubuf, cnt, 0, &val);
if (ret < 0)
return ret;
/*
* Enable and start the guc log relay on value of 1.
* Flush log relay for any other value.
*/
if (val == 1)
ret = intel_guc_log_relay_start(log);
else
intel_guc_log_relay_flush(log);
return ret ?: cnt;
}
static int guc_log_relay_release(struct inode *inode, struct file *file)
{
struct intel_guc_log *log = inode->i_private;
intel_guc_log_relay_close(log);
return 0;
}
static const struct file_operations guc_log_relay_fops = {
.owner = THIS_MODULE,
.open = guc_log_relay_open,
.write = guc_log_relay_write,
.release = guc_log_relay_release,
};
void intel_guc_log_debugfs_register(struct intel_guc_log *log,
struct dentry *root)
{
static const struct intel_gt_debugfs_file files[] = {
{ .name = "guc_log_dump", .fops = &guc_log_dump_fops },
{ .name = "guc_load_err_log_dump", .fops = &guc_load_err_log_dump_fops},
{ .name = "guc_log_level", .fops = &guc_log_level_fops },
{ .name = "guc_log_relay", .fops = &guc_log_relay_fops },
};
if (!intel_guc_is_supported(log_to_guc(log)))
return;
intel_gt_debugfs_register_files(root, files, ARRAY_SIZE(files), log);
}
@@ -0,0 +1,15 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2020 Intel Corporation
*/
#ifndef DEBUGFS_GUC_LOG_H
#define DEBUGFS_GUC_LOG_H
struct intel_guc_log;
struct dentry;
void intel_guc_log_debugfs_register(struct intel_guc_log *log,
struct dentry *root);
#endif /* DEBUGFS_GUC_LOG_H */
@@ -0,0 +1,51 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2023 Intel Corporation
*/
#ifndef __INTEL_GUC_PRINT__
#define __INTEL_GUC_PRINT__
#include "gt/intel_gt.h"
#include "gt/intel_gt_print.h"
#define guc_printk(_guc, _level, _fmt, ...) \
gt_##_level(guc_to_gt(_guc), "GUC: " _fmt, ##__VA_ARGS__)
#define guc_err(_guc, _fmt, ...) \
guc_printk((_guc), err, _fmt, ##__VA_ARGS__)
#define guc_warn(_guc, _fmt, ...) \
guc_printk((_guc), warn, _fmt, ##__VA_ARGS__)
#define guc_notice(_guc, _fmt, ...) \
guc_printk((_guc), notice, _fmt, ##__VA_ARGS__)
#define guc_info(_guc, _fmt, ...) \
guc_printk((_guc), info, _fmt, ##__VA_ARGS__)
#define guc_dbg(_guc, _fmt, ...) \
guc_printk((_guc), dbg, _fmt, ##__VA_ARGS__)
#define guc_err_ratelimited(_guc, _fmt, ...) \
guc_printk((_guc), err_ratelimited, _fmt, ##__VA_ARGS__)
#define guc_notice_ratelimited(_guc, _fmt, ...) \
guc_printk((_guc), notice_ratelimited, _fmt, ##__VA_ARGS__)
#define guc_probe_error(_guc, _fmt, ...) \
guc_printk((_guc), probe_error, _fmt, ##__VA_ARGS__)
#define guc_WARN(_guc, _cond, _fmt, ...) \
gt_WARN(guc_to_gt(_guc), _cond, "GUC: " _fmt, ##__VA_ARGS__)
#define guc_WARN_ONCE(_guc, _cond, _fmt, ...) \
gt_WARN_ONCE(guc_to_gt(_guc), _cond, "GUC: " _fmt, ##__VA_ARGS__)
#define guc_WARN_ON(_guc, _cond) \
gt_WARN(guc_to_gt(_guc), _cond, "%s(%s)", "guc_WARN_ON", __stringify(_cond))
#define guc_WARN_ON_ONCE(_guc, _cond) \
gt_WARN_ONCE(guc_to_gt(_guc), _cond, "%s(%s)", "guc_WARN_ON_ONCE", __stringify(_cond))
#endif /* __INTEL_GUC_PRINT__ */
@@ -0,0 +1,81 @@
// SPDX-License-Identifier: MIT
/*
* Copyright © 2021 Intel Corporation
*/
#include <linux/string_helpers.h>
#include "intel_guc_rc.h"
#include "intel_guc_print.h"
#include "gt/intel_gt.h"
#include "i915_drv.h"
static bool __guc_rc_supported(struct intel_guc *guc)
{
/* GuC RC is unavailable for pre-Gen12 */
return guc->submission_supported &&
GRAPHICS_VER(guc_to_i915(guc)) >= 12;
}
static bool __guc_rc_selected(struct intel_guc *guc)
{
if (!intel_guc_rc_is_supported(guc))
return false;
return guc->submission_selected;
}
void intel_guc_rc_init_early(struct intel_guc *guc)
{
guc->rc_supported = __guc_rc_supported(guc);
guc->rc_selected = __guc_rc_selected(guc);
}
static int guc_action_control_gucrc(struct intel_guc *guc, bool enable)
{
u32 rc_mode = enable ? INTEL_GUCRC_FIRMWARE_CONTROL :
INTEL_GUCRC_HOST_CONTROL;
u32 action[] = {
INTEL_GUC_ACTION_SETUP_PC_GUCRC,
rc_mode
};
int ret;
ret = intel_guc_send(guc, action, ARRAY_SIZE(action));
ret = ret > 0 ? -EPROTO : ret;
return ret;
}
static int __guc_rc_control(struct intel_guc *guc, bool enable)
{
struct intel_gt *gt = guc_to_gt(guc);
int ret;
if (!intel_uc_uses_guc_rc(&gt->uc))
return -EOPNOTSUPP;
if (!intel_guc_is_ready(guc))
return -EINVAL;
ret = guc_action_control_gucrc(guc, enable);
if (ret) {
guc_probe_error(guc, "Failed to %s RC (%pe)\n",
str_enable_disable(enable), ERR_PTR(ret));
return ret;
}
guc_info(guc, "RC %s\n", str_enabled_disabled(enable));
return 0;
}
int intel_guc_rc_enable(struct intel_guc *guc)
{
return __guc_rc_control(guc, true);
}
int intel_guc_rc_disable(struct intel_guc *guc)
{
return __guc_rc_control(guc, false);
}
@@ -0,0 +1,31 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2021 Intel Corporation
*/
#ifndef _INTEL_GUC_RC_H_
#define _INTEL_GUC_RC_H_
#include "intel_guc_submission.h"
void intel_guc_rc_init_early(struct intel_guc *guc);
static inline bool intel_guc_rc_is_supported(struct intel_guc *guc)
{
return guc->rc_supported;
}
static inline bool intel_guc_rc_is_wanted(struct intel_guc *guc)
{
return guc->submission_selected && intel_guc_rc_is_supported(guc);
}
static inline bool intel_guc_rc_is_used(struct intel_guc *guc)
{
return intel_guc_submission_is_used(guc) && intel_guc_rc_is_wanted(guc);
}
int intel_guc_rc_enable(struct intel_guc *guc);
int intel_guc_rc_disable(struct intel_guc *guc);
#endif
@@ -0,0 +1,158 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2014-2019 Intel Corporation
*/
#ifndef _INTEL_GUC_REG_H_
#define _INTEL_GUC_REG_H_
#include <linux/compiler.h>
#include <linux/types.h>
#include "i915_reg_defs.h"
/* Definitions of GuC H/W registers, bits, etc */
#define GUC_STATUS _MMIO(0xc000)
#define GS_RESET_SHIFT 0
#define GS_MIA_IN_RESET (0x01 << GS_RESET_SHIFT)
#define GS_BOOTROM_SHIFT 1
#define GS_BOOTROM_MASK (0x7F << GS_BOOTROM_SHIFT)
#define GS_UKERNEL_SHIFT 8
#define GS_UKERNEL_MASK (0xFF << GS_UKERNEL_SHIFT)
#define GS_MIA_SHIFT 16
#define GS_MIA_MASK (0x07 << GS_MIA_SHIFT)
#define GS_MIA_CORE_STATE (0x01 << GS_MIA_SHIFT)
#define GS_MIA_HALT_REQUESTED (0x02 << GS_MIA_SHIFT)
#define GS_MIA_ISR_ENTRY (0x04 << GS_MIA_SHIFT)
#define GS_AUTH_STATUS_SHIFT 30
#define GS_AUTH_STATUS_MASK (0x03U << GS_AUTH_STATUS_SHIFT)
#define GS_AUTH_STATUS_BAD (0x01 << GS_AUTH_STATUS_SHIFT)
#define GS_AUTH_STATUS_GOOD (0x02 << GS_AUTH_STATUS_SHIFT)
#define GUC_HEADER_INFO _MMIO(0xc014)
#define SOFT_SCRATCH(n) _MMIO(0xc180 + (n) * 4)
#define SOFT_SCRATCH_COUNT 16
#define GEN11_SOFT_SCRATCH(n) _MMIO(0x190240 + (n) * 4)
#define MEDIA_SOFT_SCRATCH(n) _MMIO(0x190310 + (n) * 4)
#define GEN11_SOFT_SCRATCH_COUNT 4
#define UOS_RSA_SCRATCH(i) _MMIO(0xc200 + (i) * 4)
#define UOS_RSA_SCRATCH_COUNT 64
#define DMA_ADDR_0_LOW _MMIO(0xc300)
#define DMA_ADDR_0_HIGH _MMIO(0xc304)
#define DMA_ADDR_1_LOW _MMIO(0xc308)
#define DMA_ADDR_1_HIGH _MMIO(0xc30c)
#define DMA_ADDRESS_SPACE_WOPCM (7 << 16)
#define DMA_ADDRESS_SPACE_GTT (8 << 16)
#define DMA_COPY_SIZE _MMIO(0xc310)
#define DMA_CTRL _MMIO(0xc314)
#define HUC_UKERNEL (1<<9)
#define UOS_MOVE (1<<4)
#define START_DMA (1<<0)
#define DMA_GUC_WOPCM_OFFSET _MMIO(0xc340)
#define GUC_WOPCM_OFFSET_VALID (1<<0)
#define HUC_LOADING_AGENT_VCR (0<<1)
#define HUC_LOADING_AGENT_GUC (1<<1)
#define GUC_WOPCM_OFFSET_SHIFT 14
#define GUC_WOPCM_OFFSET_MASK (0x3ffff << GUC_WOPCM_OFFSET_SHIFT)
#define GUC_MAX_IDLE_COUNT _MMIO(0xC3E4)
#define HUC_STATUS2 _MMIO(0xD3B0)
#define HUC_FW_VERIFIED (1<<7)
#define GEN11_HUC_KERNEL_LOAD_INFO _MMIO(0xC1DC)
#define HUC_LOAD_SUCCESSFUL (1 << 0)
#define GUC_WOPCM_SIZE _MMIO(0xc050)
#define GUC_WOPCM_SIZE_LOCKED (1<<0)
#define GUC_WOPCM_SIZE_SHIFT 12
#define GUC_WOPCM_SIZE_MASK (0xfffff << GUC_WOPCM_SIZE_SHIFT)
#define GEN8_GT_PM_CONFIG _MMIO(0x138140)
#define GEN9LP_GT_PM_CONFIG _MMIO(0x138140)
#define GEN9_GT_PM_CONFIG _MMIO(0x13816c)
#define GT_DOORBELL_ENABLE (1<<0)
#define GEN8_GTCR _MMIO(0x4274)
#define GEN8_GTCR_INVALIDATE (1<<0)
#define GEN12_GUC_TLB_INV_CR _MMIO(0xcee8)
#define GEN12_GUC_TLB_INV_CR_INVALIDATE (1 << 0)
#define GUC_ARAT_C6DIS _MMIO(0xA178)
#define GUC_SHIM_CONTROL _MMIO(0xc064)
#define GUC_DISABLE_SRAM_INIT_TO_ZEROES (1<<0)
#define GUC_ENABLE_READ_CACHE_LOGIC (1<<1)
#define GUC_ENABLE_MIA_CACHING (1<<2)
#define GUC_GEN10_MSGCH_ENABLE (1<<4)
#define GUC_ENABLE_READ_CACHE_FOR_SRAM_DATA (1<<9)
#define GUC_ENABLE_READ_CACHE_FOR_WOPCM_DATA (1<<10)
#define GUC_ENABLE_MIA_CLOCK_GATING (1<<15)
#define GUC_GEN10_SHIM_WC_ENABLE (1<<21)
#define GUC_SHIM_CONTROL2 _MMIO(0xc068)
#define GUC_ENABLE_DEBUG_REG (1<<11)
#define GUC_IS_PRIVILEGED (1<<29)
#define GSC_LOADS_HUC (1<<30)
#define GUC_SEND_INTERRUPT _MMIO(0xc4c8)
#define GUC_SEND_TRIGGER (1<<0)
#define GEN11_GUC_HOST_INTERRUPT _MMIO(0x1901f0)
#define MEDIA_GUC_HOST_INTERRUPT _MMIO(0x190304)
#define GEN12_GUC_SEM_INTR_ENABLES _MMIO(0xc71c)
#define GUC_SEM_INTR_ROUTE_TO_GUC BIT(31)
#define GUC_SEM_INTR_ENABLE_ALL (0xff)
#define GUC_NUM_DOORBELLS 256
/* format of the HW-monitored doorbell cacheline */
struct guc_doorbell_info {
u32 db_status;
#define GUC_DOORBELL_DISABLED 0
#define GUC_DOORBELL_ENABLED 1
u32 cookie;
u32 reserved[14];
} __packed;
#define GEN8_DRBREGL(x) _MMIO(0x1000 + (x) * 8)
#define GEN8_DRB_VALID (1<<0)
#define GEN8_DRBREGU(x) _MMIO(0x1000 + (x) * 8 + 4)
#define GEN12_DIST_DBS_POPULATED _MMIO(0xd08)
#define GEN12_DOORBELLS_PER_SQIDI_SHIFT 16
#define GEN12_DOORBELLS_PER_SQIDI (0xff)
#define GEN12_SQIDIS_DOORBELL_EXIST (0xffff)
#define DE_GUCRMR _MMIO(0x44054)
#define GUC_BCS_RCS_IER _MMIO(0xC550)
#define GUC_VCS2_VCS1_IER _MMIO(0xC554)
#define GUC_WD_VECS_IER _MMIO(0xC558)
#define GUC_PM_P24C_IER _MMIO(0xC55C)
/* GuC Interrupt Vector */
#define GUC_INTR_GUC2HOST BIT(15)
#define GUC_INTR_EXEC_ERROR BIT(14)
#define GUC_INTR_DISPLAY_EVENT BIT(13)
#define GUC_INTR_SEM_SIG BIT(12)
#define GUC_INTR_IOMMU2GUC BIT(11)
#define GUC_INTR_DOORBELL_RANG BIT(10)
#define GUC_INTR_DMA_DONE BIT(9)
#define GUC_INTR_FATAL_ERROR BIT(8)
#define GUC_INTR_NOTIF_ERROR BIT(7)
#define GUC_INTR_SW_INT_6 BIT(6)
#define GUC_INTR_SW_INT_5 BIT(5)
#define GUC_INTR_SW_INT_4 BIT(4)
#define GUC_INTR_SW_INT_3 BIT(3)
#define GUC_INTR_SW_INT_2 BIT(2)
#define GUC_INTR_SW_INT_1 BIT(1)
#define GUC_INTR_SW_INT_0 BIT(0)
#endif
@@ -0,0 +1,906 @@
// SPDX-License-Identifier: MIT
/*
* Copyright © 2021 Intel Corporation
*/
#include <linux/string_helpers.h>
#include <drm/drm_cache.h>
#include "gt/intel_gt.h"
#include "gt/intel_gt_regs.h"
#include "gt/intel_rps.h"
#include "i915_drv.h"
#include "i915_reg.h"
#include "i915_wait_util.h"
#include "intel_guc_print.h"
#include "intel_guc_slpc.h"
/**
* DOC: SLPC - Dynamic Frequency management
*
* Single Loop Power Control (SLPC) is a GuC algorithm that manages
* GT frequency based on busyness and how KMD initializes it. SLPC is
* almost completely in control after initialization except for a few
* scenarios mentioned below.
*
* KMD uses the concept of waitboost to ramp frequency to RP0 when there
* are pending submissions for a context. It achieves this by sending GuC a
* request to update the min frequency to RP0. Waitboost is disabled
* when the request retires.
*
* Another form of frequency control happens through per-context hints.
* A context can be marked as low latency during creation. That will ensure
* that SLPC uses an aggressive frequency ramp when that context is active.
*
* Power profiles add another level of control to these mechanisms.
* When power saving profile is chosen, SLPC will use conservative
* thresholds to ramp frequency, thus saving power. KMD will disable
* waitboosts as well, which achieves further power savings. Base profile
* is default and ensures balanced performance for any workload.
*
* Lastly, users have some level of control through sysfs, where min/max
* frequency values can be altered and the use of efficient freq
* can be toggled.
*/
static inline struct intel_guc *slpc_to_guc(struct intel_guc_slpc *slpc)
{
return container_of(slpc, struct intel_guc, slpc);
}
static inline struct intel_gt *slpc_to_gt(struct intel_guc_slpc *slpc)
{
return guc_to_gt(slpc_to_guc(slpc));
}
static inline struct drm_i915_private *slpc_to_i915(struct intel_guc_slpc *slpc)
{
return slpc_to_gt(slpc)->i915;
}
static bool __detect_slpc_supported(struct intel_guc *guc)
{
/* GuC SLPC is unavailable for pre-Gen12 */
return guc->submission_supported &&
GRAPHICS_VER(guc_to_i915(guc)) >= 12;
}
static bool __guc_slpc_selected(struct intel_guc *guc)
{
if (!intel_guc_slpc_is_supported(guc))
return false;
return guc->submission_selected;
}
void intel_guc_slpc_init_early(struct intel_guc_slpc *slpc)
{
struct intel_guc *guc = slpc_to_guc(slpc);
slpc->supported = __detect_slpc_supported(guc);
slpc->selected = __guc_slpc_selected(guc);
}
static void slpc_mem_set_param(struct slpc_shared_data *data,
u32 id, u32 value)
{
GEM_BUG_ON(id >= SLPC_MAX_OVERRIDE_PARAMETERS);
/*
* When the flag bit is set, corresponding value will be read
* and applied by SLPC.
*/
data->override_params.bits[id >> 5] |= (1 << (id % 32));
data->override_params.values[id] = value;
}
static void slpc_mem_set_enabled(struct slpc_shared_data *data,
u8 enable_id, u8 disable_id)
{
/*
* Enabling a param involves setting the enable_id
* to 1 and disable_id to 0.
*/
slpc_mem_set_param(data, enable_id, 1);
slpc_mem_set_param(data, disable_id, 0);
}
static void slpc_mem_set_disabled(struct slpc_shared_data *data,
u8 enable_id, u8 disable_id)
{
/*
* Disabling a param involves setting the enable_id
* to 0 and disable_id to 1.
*/
slpc_mem_set_param(data, disable_id, 1);
slpc_mem_set_param(data, enable_id, 0);
}
static u32 slpc_get_state(struct intel_guc_slpc *slpc)
{
struct slpc_shared_data *data;
GEM_BUG_ON(!slpc->vma);
drm_clflush_virt_range(slpc->vaddr, sizeof(u32));
data = slpc->vaddr;
return data->header.global_state;
}
static int guc_action_slpc_set_param_nb(struct intel_guc *guc, u8 id, u32 value)
{
u32 request[] = {
GUC_ACTION_HOST2GUC_PC_SLPC_REQUEST,
SLPC_EVENT(SLPC_EVENT_PARAMETER_SET, 2),
id,
value,
};
int ret;
ret = intel_guc_send_nb(guc, request, ARRAY_SIZE(request), 0);
return ret > 0 ? -EPROTO : ret;
}
static int slpc_set_param_nb(struct intel_guc_slpc *slpc, u8 id, u32 value)
{
struct intel_guc *guc = slpc_to_guc(slpc);
GEM_BUG_ON(id >= SLPC_MAX_PARAM);
return guc_action_slpc_set_param_nb(guc, id, value);
}
static int guc_action_slpc_set_param(struct intel_guc *guc, u8 id, u32 value)
{
u32 request[] = {
GUC_ACTION_HOST2GUC_PC_SLPC_REQUEST,
SLPC_EVENT(SLPC_EVENT_PARAMETER_SET, 2),
id,
value,
};
int ret;
ret = intel_guc_send(guc, request, ARRAY_SIZE(request));
return ret > 0 ? -EPROTO : ret;
}
static bool slpc_is_running(struct intel_guc_slpc *slpc)
{
return slpc_get_state(slpc) == SLPC_GLOBAL_STATE_RUNNING;
}
static int guc_action_slpc_query(struct intel_guc *guc, u32 offset)
{
u32 request[] = {
GUC_ACTION_HOST2GUC_PC_SLPC_REQUEST,
SLPC_EVENT(SLPC_EVENT_QUERY_TASK_STATE, 2),
offset,
0,
};
int ret;
ret = intel_guc_send(guc, request, ARRAY_SIZE(request));
return ret > 0 ? -EPROTO : ret;
}
static int slpc_query_task_state(struct intel_guc_slpc *slpc)
{
struct intel_guc *guc = slpc_to_guc(slpc);
u32 offset = intel_guc_ggtt_offset(guc, slpc->vma);
int ret;
ret = guc_action_slpc_query(guc, offset);
if (unlikely(ret))
guc_probe_error(guc, "Failed to query task state: %pe\n", ERR_PTR(ret));
drm_clflush_virt_range(slpc->vaddr, SLPC_PAGE_SIZE_BYTES);
return ret;
}
static int slpc_set_param(struct intel_guc_slpc *slpc, u8 id, u32 value)
{
struct intel_guc *guc = slpc_to_guc(slpc);
int ret;
GEM_BUG_ON(id >= SLPC_MAX_PARAM);
ret = guc_action_slpc_set_param(guc, id, value);
if (ret)
guc_probe_error(guc, "Failed to set param %d to %u: %pe\n",
id, value, ERR_PTR(ret));
return ret;
}
static int slpc_force_min_freq(struct intel_guc_slpc *slpc, u32 freq)
{
struct intel_guc *guc = slpc_to_guc(slpc);
struct drm_i915_private *i915 = slpc_to_i915(slpc);
intel_wakeref_t wakeref;
int ret = 0;
lockdep_assert_held(&slpc->lock);
if (!intel_guc_is_ready(guc))
return -ENODEV;
/*
* This function is a little different as compared to
* intel_guc_slpc_set_min_freq(). Softlimit will not be updated
* here since this is used to temporarily change min freq,
* for example, during a waitboost. Caller is responsible for
* checking bounds.
*/
with_intel_runtime_pm(&i915->runtime_pm, wakeref) {
/* Non-blocking request will avoid stalls */
ret = slpc_set_param_nb(slpc,
SLPC_PARAM_GLOBAL_MIN_GT_UNSLICE_FREQ_MHZ,
freq);
if (ret)
guc_notice(guc, "Failed to send set_param for min freq(%d): %pe\n",
freq, ERR_PTR(ret));
}
return ret;
}
static void slpc_boost_work(struct work_struct *work)
{
struct intel_guc_slpc *slpc = container_of(work, typeof(*slpc), boost_work);
int err;
/*
* Raise min freq to boost. It's possible that
* this is greater than current max. But it will
* certainly be limited by RP0. An error setting
* the min param is not fatal.
*/
mutex_lock(&slpc->lock);
if (atomic_read(&slpc->num_waiters)) {
err = slpc_force_min_freq(slpc, slpc->boost_freq);
if (!err)
slpc->num_boosts++;
}
mutex_unlock(&slpc->lock);
}
int intel_guc_slpc_init(struct intel_guc_slpc *slpc)
{
struct intel_guc *guc = slpc_to_guc(slpc);
u32 size = PAGE_ALIGN(sizeof(struct slpc_shared_data));
int err;
GEM_BUG_ON(slpc->vma);
err = intel_guc_allocate_and_map_vma(guc, size, &slpc->vma, (void **)&slpc->vaddr);
if (unlikely(err)) {
guc_probe_error(guc, "Failed to allocate SLPC struct: %pe\n", ERR_PTR(err));
return err;
}
slpc->max_freq_softlimit = 0;
slpc->min_freq_softlimit = 0;
slpc->ignore_eff_freq = false;
slpc->min_is_rpmax = false;
slpc->boost_freq = 0;
atomic_set(&slpc->num_waiters, 0);
slpc->num_boosts = 0;
slpc->media_ratio_mode = SLPC_MEDIA_RATIO_MODE_DYNAMIC_CONTROL;
slpc->power_profile = SLPC_POWER_PROFILES_BASE;
mutex_init(&slpc->lock);
INIT_WORK(&slpc->boost_work, slpc_boost_work);
return err;
}
static const char *slpc_global_state_to_string(enum slpc_global_state state)
{
switch (state) {
case SLPC_GLOBAL_STATE_NOT_RUNNING:
return "not running";
case SLPC_GLOBAL_STATE_INITIALIZING:
return "initializing";
case SLPC_GLOBAL_STATE_RESETTING:
return "resetting";
case SLPC_GLOBAL_STATE_RUNNING:
return "running";
case SLPC_GLOBAL_STATE_SHUTTING_DOWN:
return "shutting down";
case SLPC_GLOBAL_STATE_ERROR:
return "error";
default:
return "unknown";
}
}
static const char *slpc_get_state_string(struct intel_guc_slpc *slpc)
{
return slpc_global_state_to_string(slpc_get_state(slpc));
}
static int guc_action_slpc_reset(struct intel_guc *guc, u32 offset)
{
u32 request[] = {
GUC_ACTION_HOST2GUC_PC_SLPC_REQUEST,
SLPC_EVENT(SLPC_EVENT_RESET, 2),
offset,
0,
};
int ret;
ret = intel_guc_send(guc, request, ARRAY_SIZE(request));
return ret > 0 ? -EPROTO : ret;
}
static int slpc_reset(struct intel_guc_slpc *slpc)
{
struct intel_guc *guc = slpc_to_guc(slpc);
u32 offset = intel_guc_ggtt_offset(guc, slpc->vma);
int ret;
ret = guc_action_slpc_reset(guc, offset);
if (unlikely(ret < 0)) {
guc_probe_error(guc, "SLPC reset action failed: %pe\n", ERR_PTR(ret));
return ret;
}
if (!ret) {
if (wait_for(slpc_is_running(slpc), SLPC_RESET_TIMEOUT_MS)) {
guc_probe_error(guc, "SLPC not enabled! State = %s\n",
slpc_get_state_string(slpc));
return -EIO;
}
}
return 0;
}
static u32 slpc_decode_min_freq(struct intel_guc_slpc *slpc)
{
struct slpc_shared_data *data = slpc->vaddr;
GEM_BUG_ON(!slpc->vma);
return DIV_ROUND_CLOSEST(REG_FIELD_GET(SLPC_MIN_UNSLICE_FREQ_MASK,
data->task_state_data.freq) *
GT_FREQUENCY_MULTIPLIER, GEN9_FREQ_SCALER);
}
static u32 slpc_decode_max_freq(struct intel_guc_slpc *slpc)
{
struct slpc_shared_data *data = slpc->vaddr;
GEM_BUG_ON(!slpc->vma);
return DIV_ROUND_CLOSEST(REG_FIELD_GET(SLPC_MAX_UNSLICE_FREQ_MASK,
data->task_state_data.freq) *
GT_FREQUENCY_MULTIPLIER, GEN9_FREQ_SCALER);
}
static void slpc_shared_data_reset(struct intel_guc_slpc *slpc)
{
struct drm_i915_private *i915 = slpc_to_i915(slpc);
struct slpc_shared_data *data = slpc->vaddr;
memset(data, 0, sizeof(struct slpc_shared_data));
data->header.size = sizeof(struct slpc_shared_data);
/* Enable only GTPERF task, disable others */
slpc_mem_set_enabled(data, SLPC_PARAM_TASK_ENABLE_GTPERF,
SLPC_PARAM_TASK_DISABLE_GTPERF);
/*
* Don't allow balancer related algorithms on platforms before
* Xe_LPG, where GuC started to restrict it to TDP limited scenarios.
*/
if (GRAPHICS_VER_FULL(i915) < IP_VER(12, 70)) {
slpc_mem_set_disabled(data, SLPC_PARAM_TASK_ENABLE_BALANCER,
SLPC_PARAM_TASK_DISABLE_BALANCER);
slpc_mem_set_disabled(data, SLPC_PARAM_TASK_ENABLE_DCC,
SLPC_PARAM_TASK_DISABLE_DCC);
}
}
/**
* intel_guc_slpc_set_max_freq() - Set max frequency limit for SLPC.
* @slpc: pointer to intel_guc_slpc.
* @val: frequency (MHz)
*
* This function will invoke GuC SLPC action to update the max frequency
* limit for unslice.
*
* Return: 0 on success, non-zero error code on failure.
*/
int intel_guc_slpc_set_max_freq(struct intel_guc_slpc *slpc, u32 val)
{
struct drm_i915_private *i915 = slpc_to_i915(slpc);
intel_wakeref_t wakeref;
int ret;
if (val < slpc->min_freq ||
val > slpc->rp0_freq ||
val < slpc->min_freq_softlimit)
return -EINVAL;
with_intel_runtime_pm(&i915->runtime_pm, wakeref) {
ret = slpc_set_param(slpc,
SLPC_PARAM_GLOBAL_MAX_GT_UNSLICE_FREQ_MHZ,
val);
/* Return standardized err code for sysfs calls */
if (ret)
ret = -EIO;
}
if (!ret)
slpc->max_freq_softlimit = val;
return ret;
}
/**
* intel_guc_slpc_get_max_freq() - Get max frequency limit for SLPC.
* @slpc: pointer to intel_guc_slpc.
* @val: pointer to val which will hold max frequency (MHz)
*
* This function will invoke GuC SLPC action to read the max frequency
* limit for unslice.
*
* Return: 0 on success, non-zero error code on failure.
*/
int intel_guc_slpc_get_max_freq(struct intel_guc_slpc *slpc, u32 *val)
{
struct drm_i915_private *i915 = slpc_to_i915(slpc);
intel_wakeref_t wakeref;
int ret = 0;
with_intel_runtime_pm(&i915->runtime_pm, wakeref) {
/* Force GuC to update task data */
ret = slpc_query_task_state(slpc);
if (!ret)
*val = slpc_decode_max_freq(slpc);
}
return ret;
}
int intel_guc_slpc_set_ignore_eff_freq(struct intel_guc_slpc *slpc, bool val)
{
struct drm_i915_private *i915 = slpc_to_i915(slpc);
intel_wakeref_t wakeref;
int ret;
mutex_lock(&slpc->lock);
wakeref = intel_runtime_pm_get(&i915->runtime_pm);
ret = slpc_set_param(slpc,
SLPC_PARAM_IGNORE_EFFICIENT_FREQUENCY,
val);
if (ret) {
guc_probe_error(slpc_to_guc(slpc), "Failed to set efficient freq(%d): %pe\n",
val, ERR_PTR(ret));
} else {
slpc->ignore_eff_freq = val;
/* Set min to RPn when we disable efficient freq */
if (val)
ret = slpc_set_param(slpc,
SLPC_PARAM_GLOBAL_MIN_GT_UNSLICE_FREQ_MHZ,
slpc->min_freq);
}
intel_runtime_pm_put(&i915->runtime_pm, wakeref);
mutex_unlock(&slpc->lock);
return ret;
}
/**
* intel_guc_slpc_set_min_freq() - Set min frequency limit for SLPC.
* @slpc: pointer to intel_guc_slpc.
* @val: frequency (MHz)
*
* This function will invoke GuC SLPC action to update the min unslice
* frequency.
*
* Return: 0 on success, non-zero error code on failure.
*/
int intel_guc_slpc_set_min_freq(struct intel_guc_slpc *slpc, u32 val)
{
struct drm_i915_private *i915 = slpc_to_i915(slpc);
intel_wakeref_t wakeref;
int ret;
if (val < slpc->min_freq ||
val > slpc->rp0_freq ||
val > slpc->max_freq_softlimit)
return -EINVAL;
/* Need a lock now since waitboost can be modifying min as well */
mutex_lock(&slpc->lock);
wakeref = intel_runtime_pm_get(&i915->runtime_pm);
ret = slpc_set_param(slpc,
SLPC_PARAM_GLOBAL_MIN_GT_UNSLICE_FREQ_MHZ,
val);
if (!ret)
slpc->min_freq_softlimit = val;
intel_runtime_pm_put(&i915->runtime_pm, wakeref);
mutex_unlock(&slpc->lock);
/* Return standardized err code for sysfs calls */
if (ret)
ret = -EIO;
return ret;
}
/**
* intel_guc_slpc_get_min_freq() - Get min frequency limit for SLPC.
* @slpc: pointer to intel_guc_slpc.
* @val: pointer to val which will hold min frequency (MHz)
*
* This function will invoke GuC SLPC action to read the min frequency
* limit for unslice.
*
* Return: 0 on success, non-zero error code on failure.
*/
int intel_guc_slpc_get_min_freq(struct intel_guc_slpc *slpc, u32 *val)
{
struct drm_i915_private *i915 = slpc_to_i915(slpc);
intel_wakeref_t wakeref;
int ret = 0;
with_intel_runtime_pm(&i915->runtime_pm, wakeref) {
/* Force GuC to update task data */
ret = slpc_query_task_state(slpc);
if (!ret)
*val = slpc_decode_min_freq(slpc);
}
return ret;
}
int intel_guc_slpc_set_strategy(struct intel_guc_slpc *slpc, u32 val)
{
struct drm_i915_private *i915 = slpc_to_i915(slpc);
intel_wakeref_t wakeref;
int ret = 0;
with_intel_runtime_pm(&i915->runtime_pm, wakeref)
ret = slpc_set_param(slpc,
SLPC_PARAM_STRATEGIES,
val);
return ret;
}
int intel_guc_slpc_set_media_ratio_mode(struct intel_guc_slpc *slpc, u32 val)
{
struct drm_i915_private *i915 = slpc_to_i915(slpc);
intel_wakeref_t wakeref;
int ret = 0;
if (!HAS_MEDIA_RATIO_MODE(i915))
return -ENODEV;
with_intel_runtime_pm(&i915->runtime_pm, wakeref)
ret = slpc_set_param(slpc,
SLPC_PARAM_MEDIA_FF_RATIO_MODE,
val);
return ret;
}
int intel_guc_slpc_set_power_profile(struct intel_guc_slpc *slpc, u32 val)
{
struct drm_i915_private *i915 = slpc_to_i915(slpc);
intel_wakeref_t wakeref;
int ret = 0;
if (val > SLPC_POWER_PROFILES_POWER_SAVING)
return -EINVAL;
mutex_lock(&slpc->lock);
wakeref = intel_runtime_pm_get(&i915->runtime_pm);
ret = slpc_set_param(slpc,
SLPC_PARAM_POWER_PROFILE,
val);
if (ret)
guc_err(slpc_to_guc(slpc),
"Failed to set power profile to %d: %pe\n",
val, ERR_PTR(ret));
else
slpc->power_profile = val;
intel_runtime_pm_put(&i915->runtime_pm, wakeref);
mutex_unlock(&slpc->lock);
return ret;
}
void intel_guc_pm_intrmsk_enable(struct intel_gt *gt)
{
u32 pm_intrmsk_mbz = 0;
/*
* Allow GuC to receive ARAT timer expiry event.
* This interrupt register is setup by RPS code
* when host based Turbo is enabled.
*/
pm_intrmsk_mbz |= ARAT_EXPIRED_INTRMSK;
intel_uncore_rmw(gt->uncore,
GEN6_PMINTRMSK, pm_intrmsk_mbz, 0);
}
static int slpc_set_softlimits(struct intel_guc_slpc *slpc)
{
int ret = 0;
/*
* Softlimits are initially equivalent to platform limits
* unless they have deviated from defaults, in which case,
* we retain the values and set min/max accordingly.
*/
if (!slpc->max_freq_softlimit) {
slpc->max_freq_softlimit = slpc->rp0_freq;
slpc_to_gt(slpc)->defaults.max_freq = slpc->max_freq_softlimit;
} else if (slpc->max_freq_softlimit != slpc->rp0_freq) {
ret = intel_guc_slpc_set_max_freq(slpc,
slpc->max_freq_softlimit);
}
if (unlikely(ret))
return ret;
if (!slpc->min_freq_softlimit) {
/* Min softlimit is initialized to RPn */
slpc->min_freq_softlimit = slpc->min_freq;
slpc_to_gt(slpc)->defaults.min_freq = slpc->min_freq_softlimit;
} else {
return intel_guc_slpc_set_min_freq(slpc,
slpc->min_freq_softlimit);
}
return 0;
}
static bool is_slpc_min_freq_rpmax(struct intel_guc_slpc *slpc)
{
int slpc_min_freq;
int ret;
ret = intel_guc_slpc_get_min_freq(slpc, &slpc_min_freq);
if (ret) {
guc_err(slpc_to_guc(slpc), "Failed to get min freq: %pe\n", ERR_PTR(ret));
return false;
}
if (slpc_min_freq == SLPC_MAX_FREQ_MHZ)
return true;
else
return false;
}
static void update_server_min_softlimit(struct intel_guc_slpc *slpc)
{
/* For server parts, SLPC min will be at RPMax.
* Use min softlimit to clamp it to RP0 instead.
*/
if (!slpc->min_freq_softlimit &&
is_slpc_min_freq_rpmax(slpc)) {
slpc->min_is_rpmax = true;
slpc->min_freq_softlimit = slpc->rp0_freq;
(slpc_to_gt(slpc))->defaults.min_freq = slpc->min_freq_softlimit;
}
}
static int slpc_use_fused_rp0(struct intel_guc_slpc *slpc)
{
/* Force SLPC to used platform rp0 */
return slpc_set_param(slpc,
SLPC_PARAM_GLOBAL_MAX_GT_UNSLICE_FREQ_MHZ,
slpc->rp0_freq);
}
static void slpc_get_rp_values(struct intel_guc_slpc *slpc)
{
struct intel_rps *rps = &slpc_to_gt(slpc)->rps;
struct intel_rps_freq_caps caps;
gen6_rps_get_freq_caps(rps, &caps);
slpc->rp0_freq = intel_gpu_freq(rps, caps.rp0_freq);
slpc->rp1_freq = intel_gpu_freq(rps, caps.rp1_freq);
slpc->min_freq = intel_gpu_freq(rps, caps.min_freq);
if (!slpc->boost_freq)
slpc->boost_freq = slpc->rp0_freq;
}
/*
* intel_guc_slpc_enable() - Start SLPC
* @slpc: pointer to intel_guc_slpc.
*
* SLPC is enabled by setting up the shared data structure and
* sending reset event to GuC SLPC. Initial data is setup in
* intel_guc_slpc_init. Here we send the reset event. We do
* not currently need a slpc_disable since this is taken care
* of automatically when a reset/suspend occurs and the GuC
* CTB is destroyed.
*
* Return: 0 on success, non-zero error code on failure.
*/
int intel_guc_slpc_enable(struct intel_guc_slpc *slpc)
{
struct intel_guc *guc = slpc_to_guc(slpc);
int ret;
GEM_BUG_ON(!slpc->vma);
slpc_shared_data_reset(slpc);
ret = slpc_reset(slpc);
if (unlikely(ret < 0)) {
guc_probe_error(guc, "SLPC Reset event returned: %pe\n", ERR_PTR(ret));
return ret;
}
ret = slpc_query_task_state(slpc);
if (unlikely(ret < 0))
return ret;
intel_guc_pm_intrmsk_enable(slpc_to_gt(slpc));
slpc_get_rp_values(slpc);
/* Handle the case where min=max=RPmax */
update_server_min_softlimit(slpc);
/* Set SLPC max limit to RP0 */
ret = slpc_use_fused_rp0(slpc);
if (unlikely(ret)) {
guc_probe_error(guc, "Failed to set SLPC max to RP0: %pe\n", ERR_PTR(ret));
return ret;
}
/* Set cached value of ignore efficient freq */
intel_guc_slpc_set_ignore_eff_freq(slpc, slpc->ignore_eff_freq);
/* Revert SLPC min/max to softlimits if necessary */
ret = slpc_set_softlimits(slpc);
if (unlikely(ret)) {
guc_probe_error(guc, "Failed to set SLPC softlimits: %pe\n", ERR_PTR(ret));
return ret;
}
/* Set cached media freq ratio mode */
intel_guc_slpc_set_media_ratio_mode(slpc, slpc->media_ratio_mode);
/* Enable SLPC Optimized Strategy for compute */
intel_guc_slpc_set_strategy(slpc, SLPC_OPTIMIZED_STRATEGY_COMPUTE);
/* Set cached value of power_profile */
ret = intel_guc_slpc_set_power_profile(slpc, slpc->power_profile);
if (unlikely(ret)) {
guc_probe_error(guc, "Failed to set SLPC power profile: %pe\n", ERR_PTR(ret));
return ret;
}
return 0;
}
int intel_guc_slpc_set_boost_freq(struct intel_guc_slpc *slpc, u32 val)
{
int ret = 0;
if (val < slpc->min_freq || val > slpc->rp0_freq)
return -EINVAL;
mutex_lock(&slpc->lock);
if (slpc->boost_freq != val) {
/* Apply only if there are active waiters */
if (atomic_read(&slpc->num_waiters)) {
ret = slpc_force_min_freq(slpc, val);
if (ret) {
ret = -EIO;
goto done;
}
}
slpc->boost_freq = val;
}
done:
mutex_unlock(&slpc->lock);
return ret;
}
void intel_guc_slpc_dec_waiters(struct intel_guc_slpc *slpc)
{
/*
* Return min back to the softlimit.
* This is called during request retire,
* so we don't need to fail that if the
* set_param fails.
*/
mutex_lock(&slpc->lock);
if (atomic_dec_and_test(&slpc->num_waiters))
slpc_force_min_freq(slpc, slpc->min_freq_softlimit);
mutex_unlock(&slpc->lock);
}
int intel_guc_slpc_print_info(struct intel_guc_slpc *slpc, struct drm_printer *p)
{
struct drm_i915_private *i915 = slpc_to_i915(slpc);
struct slpc_shared_data *data = slpc->vaddr;
struct slpc_task_state_data *slpc_tasks;
intel_wakeref_t wakeref;
int ret = 0;
GEM_BUG_ON(!slpc->vma);
with_intel_runtime_pm(&i915->runtime_pm, wakeref) {
ret = slpc_query_task_state(slpc);
if (!ret) {
slpc_tasks = &data->task_state_data;
drm_printf(p, "\tSLPC state: %s\n", slpc_get_state_string(slpc));
drm_printf(p, "\tGTPERF task active: %s\n",
str_yes_no(slpc_tasks->status & SLPC_GTPERF_TASK_ENABLED));
drm_printf(p, "\tDCC enabled: %s\n",
str_yes_no(slpc_tasks->status &
SLPC_DCC_TASK_ENABLED));
drm_printf(p, "\tDCC in: %s\n",
str_yes_no(slpc_tasks->status & SLPC_IN_DCC));
drm_printf(p, "\tBalancer enabled: %s\n",
str_yes_no(slpc_tasks->status &
SLPC_BALANCER_ENABLED));
drm_printf(p, "\tIBC enabled: %s\n",
str_yes_no(slpc_tasks->status &
SLPC_IBC_TASK_ENABLED));
drm_printf(p, "\tBalancer IA LMT enabled: %s\n",
str_yes_no(slpc_tasks->status &
SLPC_BALANCER_IA_LMT_ENABLED));
drm_printf(p, "\tBalancer IA LMT active: %s\n",
str_yes_no(slpc_tasks->status &
SLPC_BALANCER_IA_LMT_ACTIVE));
drm_printf(p, "\tMax freq: %u MHz\n",
slpc_decode_max_freq(slpc));
drm_printf(p, "\tMin freq: %u MHz\n",
slpc_decode_min_freq(slpc));
drm_printf(p, "\twaitboosts: %u\n",
slpc->num_boosts);
drm_printf(p, "\tBoosts outstanding: %u\n",
atomic_read(&slpc->num_waiters));
}
}
return ret;
}
void intel_guc_slpc_fini(struct intel_guc_slpc *slpc)
{
if (!slpc->vma)
return;
i915_vma_unpin_and_release(&slpc->vma, I915_VMA_RELEASE_MAP);
}
@@ -0,0 +1,51 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2021 Intel Corporation
*/
#ifndef _INTEL_GUC_SLPC_H_
#define _INTEL_GUC_SLPC_H_
#include "intel_guc_submission.h"
#include "intel_guc_slpc_types.h"
#define SLPC_MAX_FREQ_MHZ 4250
struct intel_gt;
struct drm_printer;
static inline bool intel_guc_slpc_is_supported(struct intel_guc *guc)
{
return guc->slpc.supported;
}
static inline bool intel_guc_slpc_is_wanted(struct intel_guc *guc)
{
return guc->slpc.selected;
}
static inline bool intel_guc_slpc_is_used(struct intel_guc *guc)
{
return intel_guc_submission_is_used(guc) && intel_guc_slpc_is_wanted(guc);
}
void intel_guc_slpc_init_early(struct intel_guc_slpc *slpc);
int intel_guc_slpc_init(struct intel_guc_slpc *slpc);
int intel_guc_slpc_enable(struct intel_guc_slpc *slpc);
void intel_guc_slpc_fini(struct intel_guc_slpc *slpc);
int intel_guc_slpc_set_max_freq(struct intel_guc_slpc *slpc, u32 val);
int intel_guc_slpc_set_min_freq(struct intel_guc_slpc *slpc, u32 val);
int intel_guc_slpc_set_boost_freq(struct intel_guc_slpc *slpc, u32 val);
int intel_guc_slpc_get_max_freq(struct intel_guc_slpc *slpc, u32 *val);
int intel_guc_slpc_get_min_freq(struct intel_guc_slpc *slpc, u32 *val);
int intel_guc_slpc_print_info(struct intel_guc_slpc *slpc, struct drm_printer *p);
int intel_guc_slpc_set_media_ratio_mode(struct intel_guc_slpc *slpc, u32 val);
void intel_guc_pm_intrmsk_enable(struct intel_gt *gt);
void intel_guc_slpc_boost(struct intel_guc_slpc *slpc);
void intel_guc_slpc_dec_waiters(struct intel_guc_slpc *slpc);
int intel_guc_slpc_set_ignore_eff_freq(struct intel_guc_slpc *slpc, bool val);
int intel_guc_slpc_set_strategy(struct intel_guc_slpc *slpc, u32 val);
int intel_guc_slpc_set_power_profile(struct intel_guc_slpc *slpc, u32 val);
#endif
@@ -0,0 +1,52 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2021 Intel Corporation
*/
#ifndef _INTEL_GUC_SLPC_TYPES_H_
#define _INTEL_GUC_SLPC_TYPES_H_
#include <linux/atomic.h>
#include <linux/workqueue.h>
#include <linux/mutex.h>
#include <linux/types.h>
#define SLPC_RESET_TIMEOUT_MS 5
struct intel_guc_slpc {
struct i915_vma *vma;
struct slpc_shared_data *vaddr;
bool supported;
bool selected;
/* Indicates this is a server part */
bool min_is_rpmax;
/* platform frequency limits */
u32 min_freq;
u32 rp0_freq;
u32 rp1_freq;
u32 boost_freq;
/* frequency softlimits */
u32 min_freq_softlimit;
u32 max_freq_softlimit;
bool ignore_eff_freq;
/* Base or power saving */
u32 power_profile;
/* cached media ratio mode */
u32 media_ratio_mode;
/* Protects set/reset of boost freq
* and value of num_waiters
*/
struct mutex lock;
struct work_struct boost_work;
atomic_t num_waiters;
u32 num_boosts;
};
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,58 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2014-2019 Intel Corporation
*/
#ifndef _INTEL_GUC_SUBMISSION_H_
#define _INTEL_GUC_SUBMISSION_H_
#include <linux/types.h>
#include "intel_guc.h"
struct drm_printer;
struct intel_engine_cs;
void intel_guc_submission_init_early(struct intel_guc *guc);
int intel_guc_submission_init(struct intel_guc *guc);
int intel_guc_submission_enable(struct intel_guc *guc);
void intel_guc_submission_disable(struct intel_guc *guc);
void intel_guc_submission_fini(struct intel_guc *guc);
int intel_guc_preempt_work_create(struct intel_guc *guc);
void intel_guc_preempt_work_destroy(struct intel_guc *guc);
int intel_guc_submission_setup(struct intel_engine_cs *engine);
void intel_guc_submission_print_info(struct intel_guc *guc,
struct drm_printer *p);
void intel_guc_submission_print_context_info(struct intel_guc *guc,
struct drm_printer *p);
void intel_guc_dump_active_requests(struct intel_engine_cs *engine,
struct i915_request *hung_rq,
struct drm_printer *m);
void intel_guc_busyness_park(struct intel_gt *gt);
void intel_guc_busyness_unpark(struct intel_gt *gt);
bool intel_guc_virtual_engine_has_heartbeat(const struct intel_engine_cs *ve);
int intel_guc_wait_for_pending_msg(struct intel_guc *guc,
atomic_t *wait_var,
bool interruptible,
long timeout);
void intel_guc_submission_flush_work(struct intel_guc *guc);
static inline bool intel_guc_submission_is_supported(struct intel_guc *guc)
{
return guc->submission_supported;
}
static inline bool intel_guc_submission_is_wanted(struct intel_guc *guc)
{
return guc->submission_selected;
}
static inline bool intel_guc_submission_is_used(struct intel_guc *guc)
{
return intel_guc_is_used(guc) && intel_guc_submission_is_wanted(guc);
}
#endif
@@ -0,0 +1,688 @@
// SPDX-License-Identifier: MIT
/*
* Copyright © 2016-2019 Intel Corporation
*/
#include <linux/types.h>
#include "gt/intel_gt.h"
#include "gt/intel_rps.h"
#include "intel_guc_reg.h"
#include "intel_huc.h"
#include "intel_huc_print.h"
#include "i915_drv.h"
#include "i915_reg.h"
#include "pxp/intel_pxp_cmd_interface_43.h"
#include <linux/device/bus.h>
#include <linux/mei_aux.h>
/**
* DOC: HuC
*
* The HuC is a dedicated microcontroller for usage in media HEVC (High
* Efficiency Video Coding) operations. Userspace can directly use the firmware
* capabilities by adding HuC specific commands to batch buffers.
*
* The kernel driver is only responsible for loading the HuC firmware and
* triggering its security authentication. This is done differently depending
* on the platform:
*
* - older platforms (from Gen9 to most Gen12s): the load is performed via DMA
* and the authentication via GuC
* - DG2: load and authentication are both performed via GSC.
* - MTL and newer platforms: the load is performed via DMA (same as with
* not-DG2 older platforms), while the authentication is done in 2-steps,
* a first auth for clear-media workloads via GuC and a second one for all
* workloads via GSC.
*
* On platforms where the GuC does the authentication, to correctly do so the
* HuC binary must be loaded before the GuC one.
* Loading the HuC is optional; however, not using the HuC might negatively
* impact power usage and/or performance of media workloads, depending on the
* use-cases.
* HuC must be reloaded on events that cause the WOPCM to lose its contents
* (S3/S4, FLR); on older platforms the HuC must also be reloaded on GuC/GT
* reset, while on newer ones it will survive that.
*
* See https://github.com/intel/media-driver for the latest details on HuC
* functionality.
*/
/**
* DOC: HuC Memory Management
*
* Similarly to the GuC, the HuC can't do any memory allocations on its own,
* with the difference being that the allocations for HuC usage are handled by
* the userspace driver instead of the kernel one. The HuC accesses the memory
* via the PPGTT belonging to the context loaded on the VCS executing the
* HuC-specific commands.
*/
/*
* MEI-GSC load is an async process. The probing of the exposed aux device
* (see intel_gsc.c) usually happens a few seconds after i915 probe, depending
* on when the kernel schedules it. Unless something goes terribly wrong, we're
* guaranteed for this to happen during boot, so the big timeout is a safety net
* that we never expect to need.
* MEI-PXP + HuC load usually takes ~300ms, but if the GSC needs to be resumed
* and/or reset, this can take longer. Note that the kernel might schedule
* other work between the i915 init/resume and the MEI one, which can add to
* the delay.
*/
#define GSC_INIT_TIMEOUT_MS 10000
#define PXP_INIT_TIMEOUT_MS 5000
static int sw_fence_dummy_notify(struct i915_sw_fence *sf,
enum i915_sw_fence_notify state)
{
return NOTIFY_DONE;
}
static void __delayed_huc_load_complete(struct intel_huc *huc)
{
if (!i915_sw_fence_done(&huc->delayed_load.fence))
i915_sw_fence_complete(&huc->delayed_load.fence);
}
static void delayed_huc_load_complete(struct intel_huc *huc)
{
hrtimer_cancel(&huc->delayed_load.timer);
__delayed_huc_load_complete(huc);
}
static void __gsc_init_error(struct intel_huc *huc)
{
huc->delayed_load.status = INTEL_HUC_DELAYED_LOAD_ERROR;
__delayed_huc_load_complete(huc);
}
static void gsc_init_error(struct intel_huc *huc)
{
hrtimer_cancel(&huc->delayed_load.timer);
__gsc_init_error(huc);
}
static void gsc_init_done(struct intel_huc *huc)
{
hrtimer_cancel(&huc->delayed_load.timer);
/* MEI-GSC init is done, now we wait for MEI-PXP to bind */
huc->delayed_load.status = INTEL_HUC_WAITING_ON_PXP;
if (!i915_sw_fence_done(&huc->delayed_load.fence))
hrtimer_start(&huc->delayed_load.timer,
ms_to_ktime(PXP_INIT_TIMEOUT_MS),
HRTIMER_MODE_REL);
}
static enum hrtimer_restart huc_delayed_load_timer_callback(struct hrtimer *hrtimer)
{
struct intel_huc *huc = container_of(hrtimer, struct intel_huc, delayed_load.timer);
if (!intel_huc_is_authenticated(huc, INTEL_HUC_AUTH_BY_GSC)) {
if (huc->delayed_load.status == INTEL_HUC_WAITING_ON_GSC)
huc_notice(huc, "timed out waiting for MEI GSC\n");
else if (huc->delayed_load.status == INTEL_HUC_WAITING_ON_PXP)
huc_notice(huc, "timed out waiting for MEI PXP\n");
else
MISSING_CASE(huc->delayed_load.status);
__gsc_init_error(huc);
}
return HRTIMER_NORESTART;
}
static void huc_delayed_load_start(struct intel_huc *huc)
{
ktime_t delay;
GEM_BUG_ON(intel_huc_is_authenticated(huc, INTEL_HUC_AUTH_BY_GSC));
/*
* On resume we don't have to wait for MEI-GSC to be re-probed, but we
* do need to wait for MEI-PXP to reset & re-bind
*/
switch (huc->delayed_load.status) {
case INTEL_HUC_WAITING_ON_GSC:
delay = ms_to_ktime(GSC_INIT_TIMEOUT_MS);
break;
case INTEL_HUC_WAITING_ON_PXP:
delay = ms_to_ktime(PXP_INIT_TIMEOUT_MS);
break;
default:
gsc_init_error(huc);
return;
}
/*
* This fence is always complete unless we're waiting for the
* GSC device to come up to load the HuC. We arm the fence here
* and complete it when we confirm that the HuC is loaded from
* the PXP bind callback.
*/
GEM_BUG_ON(!i915_sw_fence_done(&huc->delayed_load.fence));
i915_sw_fence_fini(&huc->delayed_load.fence);
i915_sw_fence_reinit(&huc->delayed_load.fence);
i915_sw_fence_await(&huc->delayed_load.fence);
i915_sw_fence_commit(&huc->delayed_load.fence);
hrtimer_start(&huc->delayed_load.timer, delay, HRTIMER_MODE_REL);
}
static int gsc_notifier(struct notifier_block *nb, unsigned long action, void *data)
{
struct device *dev = data;
struct intel_huc *huc = container_of(nb, struct intel_huc, delayed_load.nb);
struct intel_gsc_intf *intf = &huc_to_gt(huc)->gsc.intf[0];
if (!intf->adev || &intf->adev->aux_dev.dev != dev)
return 0;
switch (action) {
case BUS_NOTIFY_BOUND_DRIVER: /* mei driver bound to aux device */
gsc_init_done(huc);
break;
case BUS_NOTIFY_DRIVER_NOT_BOUND: /* mei driver fails to be bound */
case BUS_NOTIFY_UNBIND_DRIVER: /* mei driver about to be unbound */
huc_info(huc, "MEI driver not bound, disabling load\n");
gsc_init_error(huc);
break;
}
return 0;
}
void intel_huc_register_gsc_notifier(struct intel_huc *huc, const struct bus_type *bus)
{
int ret;
if (!intel_huc_is_loaded_by_gsc(huc))
return;
huc->delayed_load.nb.notifier_call = gsc_notifier;
ret = bus_register_notifier(bus, &huc->delayed_load.nb);
if (ret) {
huc_err(huc, "failed to register GSC notifier %pe\n", ERR_PTR(ret));
huc->delayed_load.nb.notifier_call = NULL;
gsc_init_error(huc);
}
}
void intel_huc_unregister_gsc_notifier(struct intel_huc *huc, const struct bus_type *bus)
{
if (!huc->delayed_load.nb.notifier_call)
return;
delayed_huc_load_complete(huc);
bus_unregister_notifier(bus, &huc->delayed_load.nb);
huc->delayed_load.nb.notifier_call = NULL;
}
static void delayed_huc_load_init(struct intel_huc *huc)
{
/*
* Initialize fence to be complete as this is expected to be complete
* unless there is a delayed HuC load in progress.
*/
i915_sw_fence_init(&huc->delayed_load.fence,
sw_fence_dummy_notify);
i915_sw_fence_commit(&huc->delayed_load.fence);
hrtimer_setup(&huc->delayed_load.timer, huc_delayed_load_timer_callback, CLOCK_MONOTONIC,
HRTIMER_MODE_REL);
}
static void delayed_huc_load_fini(struct intel_huc *huc)
{
/*
* the fence is initialized in init_early, so we need to clean it up
* even if HuC loading is off.
*/
delayed_huc_load_complete(huc);
i915_sw_fence_fini(&huc->delayed_load.fence);
}
int intel_huc_sanitize(struct intel_huc *huc)
{
delayed_huc_load_complete(huc);
intel_uc_fw_sanitize(&huc->fw);
return 0;
}
static bool vcs_supported(struct intel_gt *gt)
{
intel_engine_mask_t mask = gt->info.engine_mask;
/*
* We reach here from i915_driver_early_probe for the primary GT before
* its engine mask is set, so we use the device info engine mask for it;
* this means we're not taking VCS fusing into account, but if the
* primary GT supports VCS engines we expect at least one of them to
* remain unfused so we're fine.
* For other GTs we expect the GT-specific mask to be set before we
* call this function.
*/
GEM_BUG_ON(!gt_is_root(gt) && !gt->info.engine_mask);
if (gt_is_root(gt))
mask = INTEL_INFO(gt->i915)->platform_engine_mask;
else
mask = gt->info.engine_mask;
return __ENGINE_INSTANCES_MASK(mask, VCS0, I915_MAX_VCS);
}
void intel_huc_init_early(struct intel_huc *huc)
{
struct drm_i915_private *i915 = huc_to_gt(huc)->i915;
struct intel_gt *gt = huc_to_gt(huc);
intel_uc_fw_init_early(&huc->fw, INTEL_UC_FW_TYPE_HUC, true);
/*
* we always init the fence as already completed, even if HuC is not
* supported. This way we don't have to distinguish between HuC not
* supported/disabled or already loaded, and can focus on if the load
* is currently in progress (fence not complete) or not, which is what
* we care about for stalling userspace submissions.
*/
delayed_huc_load_init(huc);
if (!vcs_supported(gt)) {
intel_uc_fw_change_status(&huc->fw, INTEL_UC_FIRMWARE_NOT_SUPPORTED);
return;
}
if (GRAPHICS_VER(i915) >= 11) {
huc->status[INTEL_HUC_AUTH_BY_GUC].reg = GEN11_HUC_KERNEL_LOAD_INFO;
huc->status[INTEL_HUC_AUTH_BY_GUC].mask = HUC_LOAD_SUCCESSFUL;
huc->status[INTEL_HUC_AUTH_BY_GUC].value = HUC_LOAD_SUCCESSFUL;
} else {
huc->status[INTEL_HUC_AUTH_BY_GUC].reg = HUC_STATUS2;
huc->status[INTEL_HUC_AUTH_BY_GUC].mask = HUC_FW_VERIFIED;
huc->status[INTEL_HUC_AUTH_BY_GUC].value = HUC_FW_VERIFIED;
}
if (IS_DG2(i915)) {
huc->status[INTEL_HUC_AUTH_BY_GSC].reg = GEN11_HUC_KERNEL_LOAD_INFO;
huc->status[INTEL_HUC_AUTH_BY_GSC].mask = HUC_LOAD_SUCCESSFUL;
huc->status[INTEL_HUC_AUTH_BY_GSC].value = HUC_LOAD_SUCCESSFUL;
} else {
huc->status[INTEL_HUC_AUTH_BY_GSC].reg = HECI_FWSTS(MTL_GSC_HECI1_BASE, 5);
huc->status[INTEL_HUC_AUTH_BY_GSC].mask = HECI1_FWSTS5_HUC_AUTH_DONE;
huc->status[INTEL_HUC_AUTH_BY_GSC].value = HECI1_FWSTS5_HUC_AUTH_DONE;
}
}
void intel_huc_fini_late(struct intel_huc *huc)
{
delayed_huc_load_fini(huc);
}
#define HUC_LOAD_MODE_STRING(x) (x ? "GSC" : "legacy")
static int check_huc_loading_mode(struct intel_huc *huc)
{
struct intel_gt *gt = huc_to_gt(huc);
bool gsc_enabled = huc->fw.has_gsc_headers;
/*
* The fuse for HuC load via GSC is only valid on platforms that have
* GuC deprivilege.
*/
if (HAS_GUC_DEPRIVILEGE(gt->i915))
huc->loaded_via_gsc = intel_uncore_read(gt->uncore, GUC_SHIM_CONTROL2) &
GSC_LOADS_HUC;
if (huc->loaded_via_gsc && !gsc_enabled) {
huc_err(huc, "HW requires a GSC-enabled blob, but we found a legacy one\n");
return -ENOEXEC;
}
/*
* On newer platforms we have GSC-enabled binaries but we load the HuC
* via DMA. To do so we need to find the location of the legacy-style
* binary inside the GSC-enabled one, which we do at fetch time. Make
* sure that we were able to do so if the fuse says we need to load via
* DMA and the binary is GSC-enabled.
*/
if (!huc->loaded_via_gsc && gsc_enabled && !huc->fw.dma_start_offset) {
huc_err(huc, "HW in DMA mode, but we have an incompatible GSC-enabled blob\n");
return -ENOEXEC;
}
/*
* If the HuC is loaded via GSC, we need to be able to access the GSC.
* On DG2 this is done via the mei components, while on newer platforms
* it is done via the GSCCS,
*/
if (huc->loaded_via_gsc) {
if (IS_DG2(gt->i915)) {
if (!IS_ENABLED(CONFIG_INTEL_MEI_PXP) ||
!IS_ENABLED(CONFIG_INTEL_MEI_GSC)) {
huc_info(huc, "can't load due to missing mei modules\n");
return -EIO;
}
} else {
if (!HAS_ENGINE(gt, GSC0)) {
huc_info(huc, "can't load due to missing GSCCS\n");
return -EIO;
}
}
}
huc_dbg(huc, "loaded by GSC = %s\n", str_yes_no(huc->loaded_via_gsc));
return 0;
}
int intel_huc_init(struct intel_huc *huc)
{
struct intel_gt *gt = huc_to_gt(huc);
int err;
err = check_huc_loading_mode(huc);
if (err)
goto out;
if (HAS_ENGINE(gt, GSC0)) {
struct i915_vma *vma;
vma = intel_guc_allocate_vma(gt_to_guc(gt), PXP43_HUC_AUTH_INOUT_SIZE * 2);
if (IS_ERR(vma)) {
err = PTR_ERR(vma);
huc_info(huc, "Failed to allocate heci pkt\n");
goto out;
}
huc->heci_pkt = vma;
}
err = intel_uc_fw_init(&huc->fw);
if (err)
goto out_pkt;
intel_uc_fw_change_status(&huc->fw, INTEL_UC_FIRMWARE_LOADABLE);
return 0;
out_pkt:
if (huc->heci_pkt)
i915_vma_unpin_and_release(&huc->heci_pkt, 0);
out:
intel_uc_fw_change_status(&huc->fw, INTEL_UC_FIRMWARE_INIT_FAIL);
huc_info(huc, "initialization failed %pe\n", ERR_PTR(err));
return err;
}
void intel_huc_fini(struct intel_huc *huc)
{
if (huc->heci_pkt)
i915_vma_unpin_and_release(&huc->heci_pkt, 0);
if (intel_uc_fw_is_loadable(&huc->fw))
intel_uc_fw_fini(&huc->fw);
}
static const char *auth_mode_string(struct intel_huc *huc,
enum intel_huc_authentication_type type)
{
bool partial = huc->fw.has_gsc_headers && type == INTEL_HUC_AUTH_BY_GUC;
return partial ? "clear media" : "all workloads";
}
/*
* Use a longer timeout for debug builds so that problems can be detected
* and analysed. But a shorter timeout for releases so that user's don't
* wait forever to find out there is a problem. Note that the only reason
* an end user should hit the timeout is in case of extreme thermal throttling.
* And a system that is that hot during boot is probably dead anyway!
*/
#if IS_ENABLED(CONFIG_DRM_I915_DEBUG_GEM)
#define HUC_LOAD_RETRY_LIMIT 20
#else
#define HUC_LOAD_RETRY_LIMIT 3
#endif
int intel_huc_wait_for_auth_complete(struct intel_huc *huc,
enum intel_huc_authentication_type type)
{
struct intel_gt *gt = huc_to_gt(huc);
struct intel_uncore *uncore = gt->uncore;
ktime_t before, after, delta;
int ret, count;
u64 delta_ms;
u32 before_freq;
/*
* The KMD requests maximum frequency during driver load, however thermal
* throttling can force the frequency down to minimum (although the board
* really should never get that hot in real life!). IFWI issues have been
* seen to cause sporadic failures to grant the higher frequency. And at
* minimum frequency, the authentication time can be in the seconds range.
* Note that there is a limit on how long an individual wait_for() can wait.
* So wrap it in a loop.
*/
before_freq = intel_rps_read_actual_frequency(&gt->rps);
before = ktime_get();
for (count = 0; count < HUC_LOAD_RETRY_LIMIT; count++) {
ret = __intel_wait_for_register(gt->uncore,
huc->status[type].reg,
huc->status[type].mask,
huc->status[type].value,
2, 1000, NULL);
if (!ret)
break;
huc_dbg(huc, "auth still in progress, count = %d, freq = %dMHz, status = 0x%08X\n",
count, intel_rps_read_actual_frequency(&gt->rps),
huc->status[type].reg.reg);
}
after = ktime_get();
delta = ktime_sub(after, before);
delta_ms = ktime_to_ms(delta);
if (delta_ms > 50) {
huc_warn(huc, "excessive auth time: %lldms! [status = 0x%08X, count = %d, ret = %d]\n",
delta_ms, huc->status[type].reg.reg, count, ret);
huc_warn(huc, "excessive auth time: [freq = %dMHz -> %dMHz vs %dMHz, perf_limit_reasons = 0x%08X]\n",
before_freq, intel_rps_read_actual_frequency(&gt->rps),
intel_rps_get_requested_frequency(&gt->rps),
intel_uncore_read(uncore, intel_gt_perf_limit_reasons_reg(gt)));
} else {
huc_dbg(huc, "auth took %lldms, freq = %dMHz -> %dMHz vs %dMHz, status = 0x%08X, count = %d, ret = %d\n",
delta_ms, before_freq, intel_rps_read_actual_frequency(&gt->rps),
intel_rps_get_requested_frequency(&gt->rps),
huc->status[type].reg.reg, count, ret);
}
/* mark the load process as complete even if the wait failed */
delayed_huc_load_complete(huc);
if (ret) {
huc_err(huc, "firmware not verified for %s: %pe\n",
auth_mode_string(huc, type), ERR_PTR(ret));
intel_uc_fw_change_status(&huc->fw, INTEL_UC_FIRMWARE_LOAD_FAIL);
return ret;
}
intel_uc_fw_change_status(&huc->fw, INTEL_UC_FIRMWARE_RUNNING);
huc_info(huc, "authenticated for %s\n", auth_mode_string(huc, type));
return 0;
}
/**
* intel_huc_auth() - Authenticate HuC uCode
* @huc: intel_huc structure
* @type: authentication type (via GuC or via GSC)
*
* Called after HuC and GuC firmware loading during intel_uc_init_hw().
*
* This function invokes the GuC action to authenticate the HuC firmware,
* passing the offset of the RSA signature to intel_guc_auth_huc(). It then
* waits for up to 50ms for firmware verification ACK.
*/
int intel_huc_auth(struct intel_huc *huc, enum intel_huc_authentication_type type)
{
struct intel_gt *gt = huc_to_gt(huc);
struct intel_guc *guc = gt_to_guc(gt);
int ret;
if (!intel_uc_fw_is_loaded(&huc->fw))
return -ENOEXEC;
/* GSC will do the auth with the load */
if (intel_huc_is_loaded_by_gsc(huc))
return -ENODEV;
if (intel_huc_is_authenticated(huc, type))
return -EEXIST;
switch (type) {
case INTEL_HUC_AUTH_BY_GUC:
ret = intel_guc_auth_huc(guc, intel_guc_ggtt_offset(guc, huc->fw.rsa_data));
break;
case INTEL_HUC_AUTH_BY_GSC:
ret = intel_huc_fw_auth_via_gsccs(huc);
break;
default:
MISSING_CASE(type);
ret = -EINVAL;
}
if (ret)
goto fail;
/* Check authentication status, it should be done by now */
ret = intel_huc_wait_for_auth_complete(huc, type);
if (ret)
goto fail;
return 0;
fail:
huc_probe_error(huc, "%s authentication failed %pe\n",
auth_mode_string(huc, type), ERR_PTR(ret));
return ret;
}
bool intel_huc_is_authenticated(struct intel_huc *huc,
enum intel_huc_authentication_type type)
{
struct intel_gt *gt = huc_to_gt(huc);
intel_wakeref_t wakeref;
u32 status = 0;
with_intel_runtime_pm(gt->uncore->rpm, wakeref)
status = intel_uncore_read(gt->uncore, huc->status[type].reg);
return (status & huc->status[type].mask) == huc->status[type].value;
}
static bool huc_is_fully_authenticated(struct intel_huc *huc)
{
struct intel_uc_fw *huc_fw = &huc->fw;
if (!huc_fw->has_gsc_headers)
return intel_huc_is_authenticated(huc, INTEL_HUC_AUTH_BY_GUC);
else if (intel_huc_is_loaded_by_gsc(huc) || HAS_ENGINE(huc_to_gt(huc), GSC0))
return intel_huc_is_authenticated(huc, INTEL_HUC_AUTH_BY_GSC);
else
return false;
}
/**
* intel_huc_check_status() - check HuC status
* @huc: intel_huc structure
*
* This function reads status register to verify if HuC
* firmware was successfully loaded.
*
* The return values match what is expected for the I915_PARAM_HUC_STATUS
* getparam.
*/
int intel_huc_check_status(struct intel_huc *huc)
{
struct intel_uc_fw *huc_fw = &huc->fw;
switch (__intel_uc_fw_status(huc_fw)) {
case INTEL_UC_FIRMWARE_NOT_SUPPORTED:
return -ENODEV;
case INTEL_UC_FIRMWARE_DISABLED:
return -EOPNOTSUPP;
case INTEL_UC_FIRMWARE_MISSING:
return -ENOPKG;
case INTEL_UC_FIRMWARE_ERROR:
return -ENOEXEC;
case INTEL_UC_FIRMWARE_INIT_FAIL:
return -ENOMEM;
case INTEL_UC_FIRMWARE_LOAD_FAIL:
return -EIO;
default:
break;
}
/*
* GSC-enabled binaries loaded via DMA are first partially
* authenticated by GuC and then fully authenticated by GSC
*/
if (huc_is_fully_authenticated(huc))
return 1; /* full auth */
else if (huc_fw->has_gsc_headers && !intel_huc_is_loaded_by_gsc(huc) &&
intel_huc_is_authenticated(huc, INTEL_HUC_AUTH_BY_GUC))
return 2; /* clear media only */
else
return 0;
}
static bool huc_has_delayed_load(struct intel_huc *huc)
{
return intel_huc_is_loaded_by_gsc(huc) &&
(huc->delayed_load.status != INTEL_HUC_DELAYED_LOAD_ERROR);
}
void intel_huc_update_auth_status(struct intel_huc *huc)
{
if (!intel_uc_fw_is_loadable(&huc->fw))
return;
if (!huc->fw.has_gsc_headers)
return;
if (huc_is_fully_authenticated(huc))
intel_uc_fw_change_status(&huc->fw,
INTEL_UC_FIRMWARE_RUNNING);
else if (huc_has_delayed_load(huc))
huc_delayed_load_start(huc);
}
/**
* intel_huc_load_status - dump information about HuC load status
* @huc: the HuC
* @p: the &drm_printer
*
* Pretty printer for HuC load status.
*/
void intel_huc_load_status(struct intel_huc *huc, struct drm_printer *p)
{
struct intel_gt *gt = huc_to_gt(huc);
intel_wakeref_t wakeref;
if (!intel_huc_is_supported(huc)) {
drm_printf(p, "HuC not supported\n");
return;
}
if (!intel_huc_is_wanted(huc)) {
drm_printf(p, "HuC disabled\n");
return;
}
intel_uc_fw_dump(&huc->fw, p);
with_intel_runtime_pm(gt->uncore->rpm, wakeref)
drm_printf(p, "HuC status: 0x%08x\n",
intel_uncore_read(gt->uncore, huc->status[INTEL_HUC_AUTH_BY_GUC].reg));
}
@@ -0,0 +1,101 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2014-2019 Intel Corporation
*/
#ifndef _INTEL_HUC_H_
#define _INTEL_HUC_H_
#include "i915_reg_defs.h"
#include "i915_sw_fence.h"
#include "intel_uc_fw.h"
#include "intel_huc_fw.h"
#include <linux/notifier.h>
#include <linux/hrtimer.h>
struct bus_type;
struct i915_vma;
enum intel_huc_delayed_load_status {
INTEL_HUC_WAITING_ON_GSC = 0,
INTEL_HUC_WAITING_ON_PXP,
INTEL_HUC_DELAYED_LOAD_ERROR,
};
enum intel_huc_authentication_type {
INTEL_HUC_AUTH_BY_GUC = 0,
INTEL_HUC_AUTH_BY_GSC,
INTEL_HUC_AUTH_MAX_MODES
};
struct intel_huc {
/* Generic uC firmware management */
struct intel_uc_fw fw;
/* HuC-specific additions */
struct {
i915_reg_t reg;
u32 mask;
u32 value;
} status[INTEL_HUC_AUTH_MAX_MODES];
struct {
struct i915_sw_fence fence;
struct hrtimer timer;
struct notifier_block nb;
enum intel_huc_delayed_load_status status;
} delayed_load;
/* for load via GSCCS */
struct i915_vma *heci_pkt;
bool loaded_via_gsc;
};
int intel_huc_sanitize(struct intel_huc *huc);
void intel_huc_init_early(struct intel_huc *huc);
void intel_huc_fini_late(struct intel_huc *huc);
int intel_huc_init(struct intel_huc *huc);
void intel_huc_fini(struct intel_huc *huc);
int intel_huc_auth(struct intel_huc *huc, enum intel_huc_authentication_type type);
int intel_huc_wait_for_auth_complete(struct intel_huc *huc,
enum intel_huc_authentication_type type);
bool intel_huc_is_authenticated(struct intel_huc *huc,
enum intel_huc_authentication_type type);
int intel_huc_check_status(struct intel_huc *huc);
void intel_huc_update_auth_status(struct intel_huc *huc);
void intel_huc_register_gsc_notifier(struct intel_huc *huc, const struct bus_type *bus);
void intel_huc_unregister_gsc_notifier(struct intel_huc *huc, const struct bus_type *bus);
static inline bool intel_huc_is_supported(struct intel_huc *huc)
{
return intel_uc_fw_is_supported(&huc->fw);
}
static inline bool intel_huc_is_wanted(struct intel_huc *huc)
{
return intel_uc_fw_is_enabled(&huc->fw);
}
static inline bool intel_huc_is_used(struct intel_huc *huc)
{
GEM_BUG_ON(__intel_uc_fw_status(&huc->fw) == INTEL_UC_FIRMWARE_SELECTED);
return intel_uc_fw_is_available(&huc->fw);
}
static inline bool intel_huc_is_loaded_by_gsc(const struct intel_huc *huc)
{
return huc->loaded_via_gsc;
}
static inline bool intel_huc_wait_required(struct intel_huc *huc)
{
return intel_huc_is_used(huc) && intel_huc_is_loaded_by_gsc(huc) &&
!intel_huc_is_authenticated(huc, INTEL_HUC_AUTH_BY_GSC);
}
void intel_huc_load_status(struct intel_huc *huc, struct drm_printer *p);
#endif
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: MIT
/*
* Copyright © 2020 Intel Corporation
*/
#include <drm/drm_print.h>
#include "gt/intel_gt_debugfs.h"
#include "intel_huc.h"
#include "intel_huc_debugfs.h"
static int huc_info_show(struct seq_file *m, void *data)
{
struct intel_huc *huc = m->private;
struct drm_printer p = drm_seq_file_printer(m);
if (!intel_huc_is_supported(huc))
return -ENODEV;
intel_huc_load_status(huc, &p);
return 0;
}
DEFINE_INTEL_GT_DEBUGFS_ATTRIBUTE(huc_info);
void intel_huc_debugfs_register(struct intel_huc *huc, struct dentry *root)
{
static const struct intel_gt_debugfs_file files[] = {
{ .name = "huc_info", .fops = &huc_info_fops },
};
if (!intel_huc_is_supported(huc))
return;
intel_gt_debugfs_register_files(root, files, ARRAY_SIZE(files), huc);
}
@@ -0,0 +1,14 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2020 Intel Corporation
*/
#ifndef DEBUGFS_HUC_H
#define DEBUGFS_HUC_H
struct intel_huc;
struct dentry;
void intel_huc_debugfs_register(struct intel_huc *huc, struct dentry *root);
#endif /* DEBUGFS_HUC_H */
@@ -0,0 +1,284 @@
// SPDX-License-Identifier: MIT
/*
* Copyright © 2014-2019 Intel Corporation
*/
#include "gt/intel_gsc.h"
#include "gt/intel_gt.h"
#include "intel_gsc_binary_headers.h"
#include "intel_gsc_uc_heci_cmd_submit.h"
#include "intel_huc.h"
#include "intel_huc_fw.h"
#include "intel_huc_print.h"
#include "i915_drv.h"
#include "pxp/intel_pxp_huc.h"
#include "pxp/intel_pxp_cmd_interface_43.h"
struct mtl_huc_auth_msg_in {
struct intel_gsc_mtl_header header;
struct pxp43_new_huc_auth_in huc_in;
} __packed;
struct mtl_huc_auth_msg_out {
struct intel_gsc_mtl_header header;
struct pxp43_huc_auth_out huc_out;
} __packed;
int intel_huc_fw_auth_via_gsccs(struct intel_huc *huc)
{
struct intel_gt *gt = huc_to_gt(huc);
struct drm_i915_gem_object *obj;
struct mtl_huc_auth_msg_in *msg_in;
struct mtl_huc_auth_msg_out *msg_out;
void *pkt_vaddr;
u64 pkt_offset;
int retry = 5;
int err = 0;
if (!huc->heci_pkt)
return -ENODEV;
obj = huc->heci_pkt->obj;
pkt_offset = i915_ggtt_offset(huc->heci_pkt);
pkt_vaddr = i915_gem_object_pin_map_unlocked(obj,
intel_gt_coherent_map_type(gt, obj, true));
if (IS_ERR(pkt_vaddr))
return PTR_ERR(pkt_vaddr);
msg_in = pkt_vaddr;
msg_out = pkt_vaddr + PXP43_HUC_AUTH_INOUT_SIZE;
intel_gsc_uc_heci_cmd_emit_mtl_header(&msg_in->header,
HECI_MEADDRESS_PXP,
sizeof(*msg_in), 0);
msg_in->huc_in.header.api_version = PXP_APIVER(4, 3);
msg_in->huc_in.header.command_id = PXP43_CMDID_NEW_HUC_AUTH;
msg_in->huc_in.header.status = 0;
msg_in->huc_in.header.buffer_len = sizeof(msg_in->huc_in) -
sizeof(msg_in->huc_in.header);
msg_in->huc_in.huc_base_address = huc->fw.vma_res.start;
msg_in->huc_in.huc_size = huc->fw.obj->base.size;
do {
err = intel_gsc_uc_heci_cmd_submit_packet(&gt->uc.gsc,
pkt_offset, sizeof(*msg_in),
pkt_offset + PXP43_HUC_AUTH_INOUT_SIZE,
PXP43_HUC_AUTH_INOUT_SIZE);
if (err) {
huc_err(huc, "failed to submit GSC request to auth: %d\n", err);
goto out_unpin;
}
if (msg_out->header.flags & GSC_OUTFLAG_MSG_PENDING) {
msg_in->header.gsc_message_handle = msg_out->header.gsc_message_handle;
err = -EBUSY;
msleep(50);
}
} while (--retry && err == -EBUSY);
if (err)
goto out_unpin;
if (msg_out->header.message_size != sizeof(*msg_out)) {
huc_err(huc, "invalid GSC reply length %u [expected %zu]\n",
msg_out->header.message_size, sizeof(*msg_out));
err = -EPROTO;
goto out_unpin;
}
/*
* The GSC will return PXP_STATUS_OP_NOT_PERMITTED if the HuC is already
* loaded. If the same error is ever returned with HuC not loaded we'll
* still catch it when we check the authentication bit later.
*/
if (msg_out->huc_out.header.status != PXP_STATUS_SUCCESS &&
msg_out->huc_out.header.status != PXP_STATUS_OP_NOT_PERMITTED) {
huc_err(huc, "auth failed with GSC error = 0x%x\n",
msg_out->huc_out.header.status);
err = -EIO;
goto out_unpin;
}
out_unpin:
i915_gem_object_unpin_map(obj);
return err;
}
static bool css_valid(const void *data, size_t size)
{
const struct uc_css_header *css = data;
if (unlikely(size < sizeof(struct uc_css_header)))
return false;
if (css->module_type != 0x6)
return false;
if (css->module_vendor != PCI_VENDOR_ID_INTEL)
return false;
return true;
}
static inline u32 entry_offset(const struct intel_gsc_cpd_entry *entry)
{
return entry->offset & INTEL_GSC_CPD_ENTRY_OFFSET_MASK;
}
int intel_huc_fw_get_binary_info(struct intel_uc_fw *huc_fw, const void *data, size_t size)
{
struct intel_huc *huc = container_of(huc_fw, struct intel_huc, fw);
const struct intel_gsc_cpd_header_v2 *header = data;
const struct intel_gsc_cpd_entry *entry;
size_t min_size = sizeof(*header);
int i;
if (!huc_fw->has_gsc_headers) {
huc_err(huc, "Invalid FW type for GSC header parsing!\n");
return -EINVAL;
}
if (size < sizeof(*header)) {
huc_err(huc, "FW too small! %zu < %zu\n", size, min_size);
return -ENODATA;
}
/*
* The GSC-enabled HuC binary starts with a directory header, followed
* by a series of entries. Each entry is identified by a name and
* points to a specific section of the binary containing the relevant
* data. The entries we're interested in are:
* - "HUCP.man": points to the GSC manifest header for the HuC, which
* contains the version info.
* - "huc_fw": points to the legacy-style binary that can be used for
* load via the DMA. This entry only contains a valid CSS
* on binaries for platforms that support 2-step HuC load
* via dma and auth via GSC (like MTL).
*
* --------------------------------------------------
* [ intel_gsc_cpd_header_v2 ]
* --------------------------------------------------
* [ intel_gsc_cpd_entry[] ]
* [ entry1 ]
* [ ... ]
* [ entryX ]
* [ "HUCP.man" ]
* [ ... ]
* [ offset >----------------------------]------o
* [ ... ] |
* [ entryY ] |
* [ "huc_fw" ] |
* [ ... ] |
* [ offset >----------------------------]----------o
* -------------------------------------------------- | |
* | |
* -------------------------------------------------- | |
* [ intel_gsc_manifest_header ]<-----o |
* [ ... ] |
* [ intel_gsc_version fw_version ] |
* [ ... ] |
* -------------------------------------------------- |
* |
* -------------------------------------------------- |
* [ data[] ]<---------o
* [ ... ]
* [ ... ]
* --------------------------------------------------
*/
if (header->header_marker != INTEL_GSC_CPD_HEADER_MARKER) {
huc_err(huc, "invalid marker for CPD header: 0x%08x!\n",
header->header_marker);
return -EINVAL;
}
/* we only have binaries with header v2 and entry v1 for now */
if (header->header_version != 2 || header->entry_version != 1) {
huc_err(huc, "invalid CPD header/entry version %u:%u!\n",
header->header_version, header->entry_version);
return -EINVAL;
}
if (header->header_length < sizeof(struct intel_gsc_cpd_header_v2)) {
huc_err(huc, "invalid CPD header length %u!\n",
header->header_length);
return -EINVAL;
}
min_size = header->header_length + sizeof(*entry) * header->num_of_entries;
if (size < min_size) {
huc_err(huc, "FW too small! %zu < %zu\n", size, min_size);
return -ENODATA;
}
entry = data + header->header_length;
for (i = 0; i < header->num_of_entries; i++, entry++) {
if (strcmp(entry->name, "HUCP.man") == 0)
intel_uc_fw_version_from_gsc_manifest(&huc_fw->file_selected.ver,
data + entry_offset(entry));
if (strcmp(entry->name, "huc_fw") == 0) {
u32 offset = entry_offset(entry);
if (offset < size && css_valid(data + offset, size - offset))
huc_fw->dma_start_offset = offset;
}
}
return 0;
}
int intel_huc_fw_load_and_auth_via_gsc(struct intel_huc *huc)
{
int ret;
if (!intel_huc_is_loaded_by_gsc(huc))
return -ENODEV;
if (!intel_uc_fw_is_loadable(&huc->fw))
return -ENOEXEC;
/*
* If we abort a suspend, HuC might still be loaded when the mei
* component gets re-bound and this function called again. If so, just
* mark the HuC as loaded.
*/
if (intel_huc_is_authenticated(huc, INTEL_HUC_AUTH_BY_GSC)) {
intel_uc_fw_change_status(&huc->fw, INTEL_UC_FIRMWARE_RUNNING);
return 0;
}
GEM_WARN_ON(intel_uc_fw_is_loaded(&huc->fw));
ret = intel_pxp_huc_load_and_auth(huc_to_gt(huc)->i915->pxp);
if (ret)
return ret;
intel_uc_fw_change_status(&huc->fw, INTEL_UC_FIRMWARE_TRANSFERRED);
return intel_huc_wait_for_auth_complete(huc, INTEL_HUC_AUTH_BY_GSC);
}
/**
* intel_huc_fw_upload() - load HuC uCode to device via DMA transfer
* @huc: intel_huc structure
*
* Called from intel_uc_init_hw() during driver load, resume from sleep and
* after a GPU reset. Note that HuC must be loaded before GuC.
*
* The firmware image should have already been fetched into memory, so only
* check that fetch succeeded, and then transfer the image to the h/w.
*
* Return: non-zero code on error
*/
int intel_huc_fw_upload(struct intel_huc *huc)
{
if (intel_huc_is_loaded_by_gsc(huc))
return -ENODEV;
/* HW doesn't look at destination address for HuC, so set it to 0 */
return intel_uc_fw_upload(&huc->fw, 0, HUC_UKERNEL);
}
@@ -0,0 +1,18 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2014-2019 Intel Corporation
*/
#ifndef _INTEL_HUC_FW_H_
#define _INTEL_HUC_FW_H_
struct intel_huc;
struct intel_uc_fw;
#include <linux/types.h>
int intel_huc_fw_load_and_auth_via_gsc(struct intel_huc *huc);
int intel_huc_fw_auth_via_gsccs(struct intel_huc *huc);
int intel_huc_fw_upload(struct intel_huc *huc);
int intel_huc_fw_get_binary_info(struct intel_uc_fw *huc_fw, const void *data, size_t size);
#endif
@@ -0,0 +1,21 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2023 Intel Corporation
*/
#ifndef __INTEL_HUC_PRINT__
#define __INTEL_HUC_PRINT__
#include "gt/intel_gt.h"
#include "gt/intel_gt_print.h"
#define huc_printk(_huc, _level, _fmt, ...) \
gt_##_level(huc_to_gt(_huc), "HuC: " _fmt, ##__VA_ARGS__)
#define huc_err(_huc, _fmt, ...) huc_printk((_huc), err, _fmt, ##__VA_ARGS__)
#define huc_warn(_huc, _fmt, ...) huc_printk((_huc), warn, _fmt, ##__VA_ARGS__)
#define huc_notice(_huc, _fmt, ...) huc_printk((_huc), notice, _fmt, ##__VA_ARGS__)
#define huc_info(_huc, _fmt, ...) huc_printk((_huc), info, _fmt, ##__VA_ARGS__)
#define huc_dbg(_huc, _fmt, ...) huc_printk((_huc), dbg, _fmt, ##__VA_ARGS__)
#define huc_probe_error(_huc, _fmt, ...) huc_printk((_huc), probe_error, _fmt, ##__VA_ARGS__)
#endif /* __INTEL_HUC_PRINT__ */
@@ -0,0 +1,768 @@
// SPDX-License-Identifier: MIT
/*
* Copyright © 2016-2019 Intel Corporation
*/
#include <linux/string_helpers.h>
#include "gt/intel_gt.h"
#include "gt/intel_gt_print.h"
#include "gt/intel_reset.h"
#include "intel_gsc_fw.h"
#include "intel_gsc_uc.h"
#include "intel_guc.h"
#include "intel_guc_ads.h"
#include "intel_guc_print.h"
#include "intel_guc_submission.h"
#include "gt/intel_rps.h"
#include "intel_uc.h"
#include "i915_drv.h"
#include "i915_hwmon.h"
static const struct intel_uc_ops uc_ops_off;
static const struct intel_uc_ops uc_ops_on;
static void uc_expand_default_options(struct intel_uc *uc)
{
struct drm_i915_private *i915 = uc_to_gt(uc)->i915;
if (i915->params.enable_guc != -1)
return;
/* Don't enable GuC/HuC on pre-Gen12 */
if (GRAPHICS_VER(i915) < 12) {
i915->params.enable_guc = 0;
return;
}
/* Don't enable GuC/HuC on older Gen12 platforms */
if (IS_TIGERLAKE(i915) || IS_ROCKETLAKE(i915)) {
i915->params.enable_guc = 0;
return;
}
/* Intermediate platforms are HuC authentication only */
if (IS_ALDERLAKE_S(i915) && !IS_RAPTORLAKE_S(i915)) {
i915->params.enable_guc = ENABLE_GUC_LOAD_HUC;
return;
}
/* Default: enable HuC authentication and GuC submission */
i915->params.enable_guc = ENABLE_GUC_LOAD_HUC | ENABLE_GUC_SUBMISSION;
}
/* Reset GuC providing us with fresh state for both GuC and HuC.
*/
static int __intel_uc_reset_hw(struct intel_uc *uc)
{
struct intel_gt *gt = uc_to_gt(uc);
int ret;
u32 guc_status;
ret = intel_reset_guc(gt);
if (ret) {
gt_err(gt, "Failed to reset GuC, ret = %d\n", ret);
return ret;
}
guc_status = intel_uncore_read(gt->uncore, GUC_STATUS);
gt_WARN(gt, !(guc_status & GS_MIA_IN_RESET),
"GuC status: 0x%x, MIA core expected to be in reset\n",
guc_status);
return ret;
}
static void __confirm_options(struct intel_uc *uc)
{
struct intel_gt *gt = uc_to_gt(uc);
struct drm_i915_private *i915 = gt->i915;
gt_dbg(gt, "enable_guc=%d (guc:%s submission:%s huc:%s slpc:%s)\n",
i915->params.enable_guc,
str_yes_no(intel_uc_wants_guc(uc)),
str_yes_no(intel_uc_wants_guc_submission(uc)),
str_yes_no(intel_uc_wants_huc(uc)),
str_yes_no(intel_uc_wants_guc_slpc(uc)));
if (i915->params.enable_guc == 0) {
GEM_BUG_ON(intel_uc_wants_guc(uc));
GEM_BUG_ON(intel_uc_wants_guc_submission(uc));
GEM_BUG_ON(intel_uc_wants_huc(uc));
GEM_BUG_ON(intel_uc_wants_guc_slpc(uc));
return;
}
if (!intel_uc_supports_guc(uc))
gt_info(gt, "Incompatible option enable_guc=%d - %s\n",
i915->params.enable_guc, "GuC is not supported!");
if (i915->params.enable_guc & ENABLE_GUC_SUBMISSION &&
!intel_uc_supports_guc_submission(uc))
gt_info(gt, "Incompatible option enable_guc=%d - %s\n",
i915->params.enable_guc, "GuC submission is N/A");
if (i915->params.enable_guc & ~ENABLE_GUC_MASK)
gt_info(gt, "Incompatible option enable_guc=%d - %s\n",
i915->params.enable_guc, "undocumented flag");
}
void intel_uc_init_early(struct intel_uc *uc)
{
uc_expand_default_options(uc);
intel_guc_init_early(&uc->guc);
intel_huc_init_early(&uc->huc);
intel_gsc_uc_init_early(&uc->gsc);
__confirm_options(uc);
if (intel_uc_wants_guc(uc))
uc->ops = &uc_ops_on;
else
uc->ops = &uc_ops_off;
}
void intel_uc_init_late(struct intel_uc *uc)
{
intel_guc_init_late(&uc->guc);
intel_gsc_uc_load_start(&uc->gsc);
}
void intel_uc_driver_late_release(struct intel_uc *uc)
{
intel_huc_fini_late(&uc->huc);
}
/**
* intel_uc_init_mmio - setup uC MMIO access
* @uc: the intel_uc structure
*
* Setup minimal state necessary for MMIO accesses later in the
* initialization sequence.
*/
void intel_uc_init_mmio(struct intel_uc *uc)
{
intel_guc_init_send_regs(&uc->guc);
}
static void __uc_capture_load_err_log(struct intel_uc *uc)
{
struct intel_guc *guc = &uc->guc;
if (guc->log.vma && !uc->load_err_log)
uc->load_err_log = i915_gem_object_get(guc->log.vma->obj);
}
static void __uc_free_load_err_log(struct intel_uc *uc)
{
struct drm_i915_gem_object *log = fetch_and_zero(&uc->load_err_log);
if (log)
i915_gem_object_put(log);
}
void intel_uc_driver_remove(struct intel_uc *uc)
{
intel_uc_fini_hw(uc);
intel_uc_fini(uc);
__uc_free_load_err_log(uc);
}
/*
* Events triggered while CT buffers are disabled are logged in the SCRATCH_15
* register using the same bits used in the CT message payload. Since our
* communication channel with guc is turned off at this point, we can save the
* message and handle it after we turn it back on.
*/
static void guc_clear_mmio_msg(struct intel_guc *guc)
{
intel_uncore_write(guc_to_gt(guc)->uncore, SOFT_SCRATCH(15), 0);
}
static void guc_get_mmio_msg(struct intel_guc *guc)
{
u32 val;
spin_lock_irq(&guc->irq_lock);
val = intel_uncore_read(guc_to_gt(guc)->uncore, SOFT_SCRATCH(15));
guc->mmio_msg |= val & guc->msg_enabled_mask;
/*
* clear all events, including the ones we're not currently servicing,
* to make sure we don't try to process a stale message if we enable
* handling of more events later.
*/
guc_clear_mmio_msg(guc);
spin_unlock_irq(&guc->irq_lock);
}
static void guc_handle_mmio_msg(struct intel_guc *guc)
{
/* we need communication to be enabled to reply to GuC */
GEM_BUG_ON(!intel_guc_ct_enabled(&guc->ct));
spin_lock_irq(&guc->irq_lock);
if (guc->mmio_msg) {
intel_guc_to_host_process_recv_msg(guc, &guc->mmio_msg, 1);
guc->mmio_msg = 0;
}
spin_unlock_irq(&guc->irq_lock);
}
static int guc_enable_communication(struct intel_guc *guc)
{
struct intel_gt *gt = guc_to_gt(guc);
int ret;
GEM_BUG_ON(intel_guc_ct_enabled(&guc->ct));
ret = intel_guc_ct_enable(&guc->ct);
if (ret)
return ret;
/* check for mmio messages received before/during the CT enable */
guc_get_mmio_msg(guc);
guc_handle_mmio_msg(guc);
intel_guc_enable_interrupts(guc);
/* check for CT messages received before we enabled interrupts */
spin_lock_irq(gt->irq_lock);
intel_guc_ct_event_handler(&guc->ct);
spin_unlock_irq(gt->irq_lock);
guc_dbg(guc, "communication enabled\n");
return 0;
}
static void guc_disable_communication(struct intel_guc *guc)
{
/*
* Events generated during or after CT disable are logged by guc in
* via mmio. Make sure the register is clear before disabling CT since
* all events we cared about have already been processed via CT.
*/
guc_clear_mmio_msg(guc);
intel_guc_disable_interrupts(guc);
intel_guc_ct_disable(&guc->ct);
/*
* Check for messages received during/after the CT disable. We do not
* expect any messages to have arrived via CT between the interrupt
* disable and the CT disable because GuC should've been idle until we
* triggered the CT disable protocol.
*/
guc_get_mmio_msg(guc);
guc_dbg(guc, "communication disabled\n");
}
static void __uc_fetch_firmwares(struct intel_uc *uc)
{
struct intel_gt *gt = uc_to_gt(uc);
int err;
GEM_BUG_ON(!intel_uc_wants_guc(uc));
err = intel_uc_fw_fetch(&uc->guc.fw);
if (err) {
/* Make sure we transition out of transient "SELECTED" state */
if (intel_uc_wants_huc(uc)) {
gt_dbg(gt, "Failed to fetch GuC fw (%pe) disabling HuC\n", ERR_PTR(err));
intel_uc_fw_change_status(&uc->huc.fw,
INTEL_UC_FIRMWARE_ERROR);
}
if (intel_uc_wants_gsc_uc(uc)) {
gt_dbg(gt, "Failed to fetch GuC fw (%pe) disabling GSC\n", ERR_PTR(err));
intel_uc_fw_change_status(&uc->gsc.fw,
INTEL_UC_FIRMWARE_ERROR);
}
return;
}
if (intel_uc_wants_huc(uc))
intel_uc_fw_fetch(&uc->huc.fw);
if (intel_uc_wants_gsc_uc(uc))
intel_uc_fw_fetch(&uc->gsc.fw);
}
static void __uc_cleanup_firmwares(struct intel_uc *uc)
{
intel_uc_fw_cleanup_fetch(&uc->gsc.fw);
intel_uc_fw_cleanup_fetch(&uc->huc.fw);
intel_uc_fw_cleanup_fetch(&uc->guc.fw);
}
static int __uc_init(struct intel_uc *uc)
{
struct intel_guc *guc = &uc->guc;
struct intel_huc *huc = &uc->huc;
int ret;
GEM_BUG_ON(!intel_uc_wants_guc(uc));
if (!intel_uc_uses_guc(uc))
return 0;
ret = intel_guc_init(guc);
if (ret)
return ret;
if (intel_uc_uses_huc(uc))
intel_huc_init(huc);
if (intel_uc_uses_gsc_uc(uc))
intel_gsc_uc_init(&uc->gsc);
return 0;
}
ALLOW_ERROR_INJECTION(__uc_init, ERRNO);
static void __uc_fini(struct intel_uc *uc)
{
intel_gsc_uc_fini(&uc->gsc);
intel_huc_fini(&uc->huc);
intel_guc_fini(&uc->guc);
}
static int __uc_sanitize(struct intel_uc *uc)
{
struct intel_guc *guc = &uc->guc;
struct intel_huc *huc = &uc->huc;
GEM_BUG_ON(!intel_uc_supports_guc(uc));
intel_huc_sanitize(huc);
intel_guc_sanitize(guc);
return __intel_uc_reset_hw(uc);
}
/* Initialize and verify the uC regs related to uC positioning in WOPCM */
static int uc_init_wopcm(struct intel_uc *uc)
{
struct intel_gt *gt = uc_to_gt(uc);
struct intel_uncore *uncore = gt->uncore;
u32 base = intel_wopcm_guc_base(&gt->wopcm);
u32 size = intel_wopcm_guc_size(&gt->wopcm);
u32 huc_agent = intel_uc_uses_huc(uc) ? HUC_LOADING_AGENT_GUC : 0;
u32 mask;
int err;
if (unlikely(!base || !size)) {
gt_probe_error(gt, "Unsuccessful WOPCM partitioning\n");
return -E2BIG;
}
GEM_BUG_ON(!intel_uc_supports_guc(uc));
GEM_BUG_ON(!(base & GUC_WOPCM_OFFSET_MASK));
GEM_BUG_ON(base & ~GUC_WOPCM_OFFSET_MASK);
GEM_BUG_ON(!(size & GUC_WOPCM_SIZE_MASK));
GEM_BUG_ON(size & ~GUC_WOPCM_SIZE_MASK);
mask = GUC_WOPCM_SIZE_MASK | GUC_WOPCM_SIZE_LOCKED;
err = intel_uncore_write_and_verify(uncore, GUC_WOPCM_SIZE, size, mask,
size | GUC_WOPCM_SIZE_LOCKED);
if (err)
goto err_out;
mask = GUC_WOPCM_OFFSET_MASK | GUC_WOPCM_OFFSET_VALID | huc_agent;
err = intel_uncore_write_and_verify(uncore, DMA_GUC_WOPCM_OFFSET,
base | huc_agent, mask,
base | huc_agent |
GUC_WOPCM_OFFSET_VALID);
if (err)
goto err_out;
return 0;
err_out:
gt_probe_error(gt, "Failed to init uC WOPCM registers!\n");
gt_probe_error(gt, "%s(%#x)=%#x\n", "DMA_GUC_WOPCM_OFFSET",
i915_mmio_reg_offset(DMA_GUC_WOPCM_OFFSET),
intel_uncore_read(uncore, DMA_GUC_WOPCM_OFFSET));
gt_probe_error(gt, "%s(%#x)=%#x\n", "GUC_WOPCM_SIZE",
i915_mmio_reg_offset(GUC_WOPCM_SIZE),
intel_uncore_read(uncore, GUC_WOPCM_SIZE));
return err;
}
static bool uc_is_wopcm_locked(struct intel_uc *uc)
{
struct intel_gt *gt = uc_to_gt(uc);
struct intel_uncore *uncore = gt->uncore;
return (intel_uncore_read(uncore, GUC_WOPCM_SIZE) & GUC_WOPCM_SIZE_LOCKED) ||
(intel_uncore_read(uncore, DMA_GUC_WOPCM_OFFSET) & GUC_WOPCM_OFFSET_VALID);
}
static int __uc_check_hw(struct intel_uc *uc)
{
if (uc->fw_table_invalid)
return -EIO;
if (!intel_uc_supports_guc(uc))
return 0;
/*
* We can silently continue without GuC only if it was never enabled
* before on this system after reboot, otherwise we risk GPU hangs.
* To check if GuC was loaded before we look at WOPCM registers.
*/
if (uc_is_wopcm_locked(uc))
return -EIO;
return 0;
}
static void print_fw_ver(struct intel_gt *gt, struct intel_uc_fw *fw)
{
gt_info(gt, "%s firmware %s version %u.%u.%u\n",
intel_uc_fw_type_repr(fw->type), fw->file_selected.path,
fw->file_selected.ver.major,
fw->file_selected.ver.minor,
fw->file_selected.ver.patch);
}
static int __uc_init_hw(struct intel_uc *uc)
{
struct intel_gt *gt = uc_to_gt(uc);
struct drm_i915_private *i915 = gt->i915;
struct intel_guc *guc = &uc->guc;
struct intel_huc *huc = &uc->huc;
int ret, attempts;
bool pl1en = false;
GEM_BUG_ON(!intel_uc_supports_guc(uc));
GEM_BUG_ON(!intel_uc_wants_guc(uc));
print_fw_ver(gt, &guc->fw);
if (intel_uc_uses_huc(uc))
print_fw_ver(gt, &huc->fw);
if (!intel_uc_fw_is_loadable(&guc->fw)) {
ret = __uc_check_hw(uc) ||
intel_uc_fw_is_overridden(&guc->fw) ||
intel_uc_wants_guc_submission(uc) ?
intel_uc_fw_status_to_error(guc->fw.status) : 0;
goto err_out;
}
ret = uc_init_wopcm(uc);
if (ret)
goto err_out;
intel_guc_reset_interrupts(guc);
/* WaEnableuKernelHeaderValidFix:skl */
/* WaEnableGuCBootHashCheckNotSet:skl,bxt,kbl */
if (GRAPHICS_VER(i915) == 9)
attempts = 3;
else
attempts = 1;
/* Disable a potentially low PL1 power limit to allow freq to be raised */
i915_hwmon_power_max_disable(gt->i915, &pl1en);
intel_rps_raise_unslice(&uc_to_gt(uc)->rps);
while (attempts--) {
/*
* Always reset the GuC just before (re)loading, so
* that the state and timing are fairly predictable
*/
ret = __uc_sanitize(uc);
if (ret)
goto err_rps;
intel_huc_fw_upload(huc);
intel_guc_ads_reset(guc);
intel_guc_write_params(guc);
ret = intel_guc_fw_upload(guc);
if (ret == 0)
break;
gt_dbg(gt, "GuC fw load failed (%pe) will reset and retry %d more time(s)\n",
ERR_PTR(ret), attempts);
}
/* Did we succeed or run out of retries? */
if (ret)
goto err_log_capture;
ret = guc_enable_communication(guc);
if (ret)
goto err_log_capture;
/*
* GSC-loaded HuC is authenticated by the GSC, so we don't need to
* trigger the auth here. However, given that the HuC loaded this way
* survive GT reset, we still need to update our SW bookkeeping to make
* sure it reflects the correct HW status.
*/
if (intel_huc_is_loaded_by_gsc(huc))
intel_huc_update_auth_status(huc);
else
intel_huc_auth(huc, INTEL_HUC_AUTH_BY_GUC);
if (intel_uc_uses_guc_submission(uc)) {
ret = intel_guc_submission_enable(guc);
if (ret)
goto err_log_capture;
}
if (intel_uc_uses_guc_slpc(uc)) {
ret = intel_guc_slpc_enable(&guc->slpc);
if (ret)
goto err_submission;
} else {
/* Restore GT back to RPn for non-SLPC path */
intel_rps_lower_unslice(&uc_to_gt(uc)->rps);
}
i915_hwmon_power_max_restore(gt->i915, pl1en);
guc_info(guc, "submission %s\n", str_enabled_disabled(intel_uc_uses_guc_submission(uc)));
guc_info(guc, "SLPC %s\n", str_enabled_disabled(intel_uc_uses_guc_slpc(uc)));
return 0;
/*
* We've failed to load the firmware :(
*/
err_submission:
intel_guc_submission_disable(guc);
err_log_capture:
__uc_capture_load_err_log(uc);
err_rps:
/* Return GT back to RPn */
intel_rps_lower_unslice(&uc_to_gt(uc)->rps);
i915_hwmon_power_max_restore(gt->i915, pl1en);
err_out:
__uc_sanitize(uc);
if (!ret) {
gt_notice(gt, "GuC is uninitialized\n");
/* We want to run without GuC submission */
return 0;
}
gt_probe_error(gt, "GuC initialization failed %pe\n", ERR_PTR(ret));
/* We want to keep KMS alive */
return -EIO;
}
static void __uc_fini_hw(struct intel_uc *uc)
{
struct intel_guc *guc = &uc->guc;
if (!intel_guc_is_fw_running(guc))
return;
if (intel_uc_uses_guc_submission(uc))
intel_guc_submission_disable(guc);
__uc_sanitize(uc);
}
/**
* intel_uc_reset_prepare - Prepare for reset
* @uc: the intel_uc structure
*
* Preparing for full gpu reset.
*/
void intel_uc_reset_prepare(struct intel_uc *uc)
{
struct intel_guc *guc = &uc->guc;
uc->reset_in_progress = true;
/* Nothing to do if GuC isn't supported */
if (!intel_uc_supports_guc(uc))
return;
/* Firmware expected to be running when this function is called */
if (!intel_guc_is_ready(guc))
goto sanitize;
if (intel_uc_uses_guc_submission(uc))
intel_guc_submission_reset_prepare(guc);
sanitize:
__uc_sanitize(uc);
}
void intel_uc_reset(struct intel_uc *uc, intel_engine_mask_t stalled)
{
struct intel_guc *guc = &uc->guc;
/* Firmware can not be running when this function is called */
if (intel_uc_uses_guc_submission(uc))
intel_guc_submission_reset(guc, stalled);
}
void intel_uc_reset_finish(struct intel_uc *uc)
{
struct intel_guc *guc = &uc->guc;
/*
* NB: The wedge code path results in prepare -> prepare -> finish -> finish.
* So this function is sometimes called with the in-progress flag not set.
*/
uc->reset_in_progress = false;
/* Firmware expected to be running when this function is called */
if (intel_uc_uses_guc_submission(uc))
intel_guc_submission_reset_finish(guc);
}
void intel_uc_cancel_requests(struct intel_uc *uc)
{
struct intel_guc *guc = &uc->guc;
/* Firmware can not be running when this function is called */
if (intel_uc_uses_guc_submission(uc))
intel_guc_submission_cancel_requests(guc);
}
void intel_uc_runtime_suspend(struct intel_uc *uc)
{
struct intel_guc *guc = &uc->guc;
if (!intel_guc_is_ready(guc)) {
guc->interrupts.enabled = false;
return;
}
/*
* Wait for any outstanding CTB before tearing down communication /w the
* GuC.
*/
#define OUTSTANDING_CTB_TIMEOUT_PERIOD (HZ / 5)
intel_guc_wait_for_pending_msg(guc, &guc->outstanding_submission_g2h,
false, OUTSTANDING_CTB_TIMEOUT_PERIOD);
GEM_WARN_ON(atomic_read(&guc->outstanding_submission_g2h));
guc_disable_communication(guc);
}
void intel_uc_suspend(struct intel_uc *uc)
{
struct intel_guc *guc = &uc->guc;
intel_wakeref_t wakeref;
int err;
/* flush the GSC worker */
intel_gsc_uc_flush_work(&uc->gsc);
wake_up_all_tlb_invalidate(guc);
if (!intel_guc_is_ready(guc)) {
guc->interrupts.enabled = false;
return;
}
intel_guc_submission_flush_work(guc);
with_intel_runtime_pm(&uc_to_gt(uc)->i915->runtime_pm, wakeref) {
err = intel_guc_suspend(guc);
if (err)
guc_dbg(guc, "Failed to suspend, %pe", ERR_PTR(err));
}
}
static void __uc_resume_mappings(struct intel_uc *uc)
{
intel_uc_fw_resume_mapping(&uc->guc.fw);
intel_uc_fw_resume_mapping(&uc->huc.fw);
}
static int __uc_resume(struct intel_uc *uc, bool enable_communication)
{
struct intel_guc *guc = &uc->guc;
struct intel_gt *gt = guc_to_gt(guc);
int err;
if (!intel_guc_is_fw_running(guc))
return 0;
/* Make sure we enable communication if and only if it's disabled */
GEM_BUG_ON(enable_communication == intel_guc_ct_enabled(&guc->ct));
if (enable_communication)
guc_enable_communication(guc);
/* If we are only resuming GuC communication but not reloading
* GuC, we need to ensure the ARAT timer interrupt is enabled
* again. In case of GuC reload, it is enabled during SLPC enable.
*/
if (enable_communication && intel_uc_uses_guc_slpc(uc))
intel_guc_pm_intrmsk_enable(gt);
err = intel_guc_resume(guc);
if (err) {
guc_dbg(guc, "Failed to resume, %pe", ERR_PTR(err));
return err;
}
intel_gsc_uc_resume(&uc->gsc);
if (intel_guc_tlb_invalidation_is_available(guc)) {
intel_guc_invalidate_tlb_engines(guc);
intel_guc_invalidate_tlb_guc(guc);
}
return 0;
}
int intel_uc_resume(struct intel_uc *uc)
{
/*
* When coming out of S3/S4 we sanitize and re-init the HW, so
* communication is already re-enabled at this point.
*/
return __uc_resume(uc, false);
}
int intel_uc_runtime_resume(struct intel_uc *uc)
{
/*
* During runtime resume we don't sanitize, so we need to re-init
* communication as well.
*/
return __uc_resume(uc, true);
}
static const struct intel_uc_ops uc_ops_off = {
.init_hw = __uc_check_hw,
.fini = __uc_fini, /* to clean-up the init_early initialization */
};
static const struct intel_uc_ops uc_ops_on = {
.sanitize = __uc_sanitize,
.init_fw = __uc_fetch_firmwares,
.fini_fw = __uc_cleanup_firmwares,
.init = __uc_init,
.fini = __uc_fini,
.init_hw = __uc_init_hw,
.fini_hw = __uc_fini_hw,
.resume_mappings = __uc_resume_mappings,
};
@@ -0,0 +1,121 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2014-2019 Intel Corporation
*/
#ifndef _INTEL_UC_H_
#define _INTEL_UC_H_
#include "intel_gsc_uc.h"
#include "intel_guc.h"
#include "intel_guc_rc.h"
#include "intel_guc_submission.h"
#include "intel_guc_slpc.h"
#include "intel_huc.h"
#include "i915_params.h"
struct intel_uc;
struct intel_uc_ops {
int (*sanitize)(struct intel_uc *uc);
void (*init_fw)(struct intel_uc *uc);
void (*fini_fw)(struct intel_uc *uc);
int (*init)(struct intel_uc *uc);
void (*fini)(struct intel_uc *uc);
int (*init_hw)(struct intel_uc *uc);
void (*fini_hw)(struct intel_uc *uc);
void (*resume_mappings)(struct intel_uc *uc);
};
struct intel_uc {
struct intel_uc_ops const *ops;
struct intel_gsc_uc gsc;
struct intel_guc guc;
struct intel_huc huc;
/* Snapshot of GuC log from last failed load */
struct drm_i915_gem_object *load_err_log;
bool reset_in_progress;
bool fw_table_invalid;
};
void intel_uc_init_early(struct intel_uc *uc);
void intel_uc_init_late(struct intel_uc *uc);
void intel_uc_driver_late_release(struct intel_uc *uc);
void intel_uc_driver_remove(struct intel_uc *uc);
void intel_uc_init_mmio(struct intel_uc *uc);
void intel_uc_reset_prepare(struct intel_uc *uc);
void intel_uc_reset(struct intel_uc *uc, intel_engine_mask_t stalled);
void intel_uc_reset_finish(struct intel_uc *uc);
void intel_uc_cancel_requests(struct intel_uc *uc);
void intel_uc_suspend(struct intel_uc *uc);
void intel_uc_runtime_suspend(struct intel_uc *uc);
int intel_uc_resume(struct intel_uc *uc);
int intel_uc_runtime_resume(struct intel_uc *uc);
/*
* We need to know as early as possible if we're going to use GuC or not to
* take the correct setup paths. Additionally, once we've started loading the
* GuC, it is unsafe to keep executing without it because some parts of the HW,
* a subset of which is not cleaned on GT reset, will start expecting the GuC FW
* to be running.
* To solve both these requirements, we commit to using the microcontrollers if
* the relevant modparam is set and the blobs are found on the system. At this
* stage, the only thing that can stop us from attempting to load the blobs on
* the HW and use them is a fundamental issue (e.g. no memory for our
* structures); if we hit such a problem during driver load we're broken even
* without GuC, so there is no point in trying to fall back.
*
* Given the above, we can be in one of 4 states, with the last one implying
* we're committed to using the microcontroller:
* - Not supported: not available in HW and/or firmware not defined.
* - Supported: available in HW and firmware defined.
* - Wanted: supported + enabled in modparam.
* - In use: wanted + firmware found on the system and successfully fetched.
*/
#define __uc_state_checker(x, func, state, required) \
static inline bool intel_uc_##state##_##func(struct intel_uc *uc) \
{ \
return intel_##func##_is_##required(&uc->x); \
}
#define uc_state_checkers(x, func) \
__uc_state_checker(x, func, supports, supported) \
__uc_state_checker(x, func, wants, wanted) \
__uc_state_checker(x, func, uses, used)
uc_state_checkers(guc, guc);
uc_state_checkers(huc, huc);
uc_state_checkers(guc, guc_submission);
uc_state_checkers(guc, guc_slpc);
uc_state_checkers(guc, guc_rc);
uc_state_checkers(gsc, gsc_uc);
#undef uc_state_checkers
#undef __uc_state_checker
static inline int intel_uc_wait_for_idle(struct intel_uc *uc, long timeout)
{
return intel_guc_wait_for_idle(&uc->guc, timeout);
}
#define intel_uc_ops_function(_NAME, _OPS, _TYPE, _RET) \
static inline _TYPE intel_uc_##_NAME(struct intel_uc *uc) \
{ \
if (uc->ops->_OPS) \
return uc->ops->_OPS(uc); \
return _RET; \
}
intel_uc_ops_function(sanitize, sanitize, int, 0);
intel_uc_ops_function(fetch_firmwares, init_fw, void, );
intel_uc_ops_function(cleanup_firmwares, fini_fw, void, );
intel_uc_ops_function(init, init, int, 0);
intel_uc_ops_function(fini, fini, void, );
intel_uc_ops_function(init_hw, init_hw, int, 0);
intel_uc_ops_function(fini_hw, fini_hw, void, );
intel_uc_ops_function(resume_mappings, resume_mappings, void, );
#undef intel_uc_ops_function
#endif
@@ -0,0 +1,65 @@
// SPDX-License-Identifier: MIT
/*
* Copyright © 2020 Intel Corporation
*/
#include <linux/debugfs.h>
#include <linux/string_helpers.h>
#include <drm/drm_print.h>
#include "gt/intel_gt_debugfs.h"
#include "intel_guc_debugfs.h"
#include "intel_gsc_uc_debugfs.h"
#include "intel_huc_debugfs.h"
#include "intel_uc.h"
#include "intel_uc_debugfs.h"
static int uc_usage_show(struct seq_file *m, void *data)
{
struct intel_uc *uc = m->private;
struct drm_printer p = drm_seq_file_printer(m);
drm_printf(&p, "[guc] supported:%s wanted:%s used:%s\n",
str_yes_no(intel_uc_supports_guc(uc)),
str_yes_no(intel_uc_wants_guc(uc)),
str_yes_no(intel_uc_uses_guc(uc)));
drm_printf(&p, "[huc] supported:%s wanted:%s used:%s\n",
str_yes_no(intel_uc_supports_huc(uc)),
str_yes_no(intel_uc_wants_huc(uc)),
str_yes_no(intel_uc_uses_huc(uc)));
drm_printf(&p, "[submission] supported:%s wanted:%s used:%s\n",
str_yes_no(intel_uc_supports_guc_submission(uc)),
str_yes_no(intel_uc_wants_guc_submission(uc)),
str_yes_no(intel_uc_uses_guc_submission(uc)));
return 0;
}
DEFINE_INTEL_GT_DEBUGFS_ATTRIBUTE(uc_usage);
void intel_uc_debugfs_register(struct intel_uc *uc, struct dentry *gt_root)
{
static const struct intel_gt_debugfs_file files[] = {
{ .name = "usage", .fops = &uc_usage_fops },
};
struct dentry *root;
if (!gt_root)
return;
/* GuC and HuC go always in pair, no need to check both */
if (!intel_uc_supports_guc(uc))
return;
root = debugfs_create_dir("uc", gt_root);
if (IS_ERR(root))
return;
uc->guc.dbgfs_node = root;
intel_gt_debugfs_register_files(root, files, ARRAY_SIZE(files), uc);
intel_gsc_uc_debugfs_register(&uc->gsc, root);
intel_guc_debugfs_register(&uc->guc, root);
intel_huc_debugfs_register(&uc->huc, root);
}
@@ -0,0 +1,14 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2020 Intel Corporation
*/
#ifndef DEBUGFS_UC_H
#define DEBUGFS_UC_H
struct intel_uc;
struct dentry;
void intel_uc_debugfs_register(struct intel_uc *uc, struct dentry *gt_root);
#endif /* DEBUGFS_UC_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,314 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2014-2019 Intel Corporation
*/
#ifndef _INTEL_UC_FW_H_
#define _INTEL_UC_FW_H_
#include <linux/sizes.h>
#include <linux/types.h>
#include "intel_uc_fw_abi.h"
#include "intel_device_info.h"
#include "i915_gem.h"
#include "i915_vma.h"
struct drm_printer;
struct drm_i915_private;
struct intel_gt;
/* Home of GuC, HuC and DMC firmwares */
#define INTEL_UC_FIRMWARE_URL "https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git/tree/i915"
/*
* +------------+---------------------------------------------------+
* | PHASE | FIRMWARE STATUS TRANSITIONS |
* +============+===================================================+
* | | UNINITIALIZED |
* +------------+- / | \ -+
* | | DISABLED <--/ | \--> NOT_SUPPORTED |
* | init_early | V |
* | | SELECTED |
* +------------+- / | \ -+
* | | MISSING <--/ | \--> ERROR |
* | fetch | V |
* | | AVAILABLE |
* +------------+- | \ -+
* | | | \--> INIT FAIL |
* | init | V |
* | | /------> LOADABLE <----<-----------\ |
* +------------+- \ / \ \ \ -+
* | | LOAD FAIL <--< \--> TRANSFERRED \ |
* | upload | \ / \ / |
* | | \---------/ \--> RUNNING |
* +------------+---------------------------------------------------+
*/
enum intel_uc_fw_status {
INTEL_UC_FIRMWARE_NOT_SUPPORTED = -1, /* no uc HW */
INTEL_UC_FIRMWARE_UNINITIALIZED = 0, /* used to catch checks done too early */
INTEL_UC_FIRMWARE_DISABLED, /* disabled */
INTEL_UC_FIRMWARE_SELECTED, /* selected the blob we want to load */
INTEL_UC_FIRMWARE_MISSING, /* blob not found on the system */
INTEL_UC_FIRMWARE_ERROR, /* invalid format or version */
INTEL_UC_FIRMWARE_AVAILABLE, /* blob found and copied in mem */
INTEL_UC_FIRMWARE_INIT_FAIL, /* failed to prepare fw objects for load */
INTEL_UC_FIRMWARE_LOADABLE, /* all fw-required objects are ready */
INTEL_UC_FIRMWARE_LOAD_FAIL, /* failed to xfer or init/auth the fw */
INTEL_UC_FIRMWARE_TRANSFERRED, /* dma xfer done */
INTEL_UC_FIRMWARE_RUNNING /* init/auth done */
};
enum intel_uc_fw_type {
INTEL_UC_FW_TYPE_GUC = 0,
INTEL_UC_FW_TYPE_HUC,
INTEL_UC_FW_TYPE_GSC,
};
#define INTEL_UC_FW_NUM_TYPES 3
struct intel_uc_fw_ver {
u32 major;
u32 minor;
u32 patch;
u32 build;
};
/*
* The firmware build process will generate a version header file with major and
* minor version defined. The versions are built into CSS header of firmware.
* i915 kernel driver set the minimal firmware version required per platform.
*/
struct intel_uc_fw_file {
const char *path;
struct intel_uc_fw_ver ver;
};
/*
* This structure encapsulates all the data needed during the process
* of fetching, caching, and loading the firmware image into the uC.
*/
struct intel_uc_fw {
enum intel_uc_fw_type type;
union {
const enum intel_uc_fw_status status;
enum intel_uc_fw_status __status; /* no accidental overwrites */
};
struct intel_uc_fw_file file_wanted;
struct intel_uc_fw_file file_selected;
bool user_overridden;
size_t size;
struct drm_i915_gem_object *obj;
/**
* @needs_ggtt_mapping: indicates whether the fw object needs to be
* pinned to ggtt. If true, the fw is pinned at init time and unpinned
* during driver unload.
*/
bool needs_ggtt_mapping;
/**
* @vma_res: A vma resource used in binding the uc fw to ggtt. The fw is
* pinned in a reserved area of the ggtt (above the maximum address
* usable by GuC); therefore, we can't use the normal vma functions to
* do the pinning and we instead use this resource to do so.
*/
struct i915_vma_resource vma_res;
struct i915_vma *rsa_data;
u32 rsa_size;
u32 ucode_size;
u32 private_data_size;
u32 dma_start_offset;
bool has_gsc_headers;
};
/*
* When we load the uC binaries, we pin them in a reserved section at the top of
* the GGTT, which is ~18 MBs. On multi-GT systems where the GTs share the GGTT,
* we also need to make sure that each binary is pinned to a unique location
* during load, because the different GT can go through the FW load at the same
* time (see uc_fw_ggtt_offset() for details).
* Given that the available space is much greater than what is required by the
* binaries, to keep things simple instead of dynamically partitioning the
* reserved section to make space for all the blobs we can just reserve a static
* chunk for each binary.
*/
#define INTEL_UC_RSVD_GGTT_PER_FW SZ_2M
#ifdef CONFIG_DRM_I915_DEBUG_GUC
void intel_uc_fw_change_status(struct intel_uc_fw *uc_fw,
enum intel_uc_fw_status status);
#else
static inline void intel_uc_fw_change_status(struct intel_uc_fw *uc_fw,
enum intel_uc_fw_status status)
{
uc_fw->__status = status;
}
#endif
static inline
const char *intel_uc_fw_status_repr(enum intel_uc_fw_status status)
{
switch (status) {
case INTEL_UC_FIRMWARE_NOT_SUPPORTED:
return "N/A";
case INTEL_UC_FIRMWARE_UNINITIALIZED:
return "UNINITIALIZED";
case INTEL_UC_FIRMWARE_DISABLED:
return "DISABLED";
case INTEL_UC_FIRMWARE_SELECTED:
return "SELECTED";
case INTEL_UC_FIRMWARE_MISSING:
return "MISSING";
case INTEL_UC_FIRMWARE_ERROR:
return "ERROR";
case INTEL_UC_FIRMWARE_AVAILABLE:
return "AVAILABLE";
case INTEL_UC_FIRMWARE_INIT_FAIL:
return "INIT FAIL";
case INTEL_UC_FIRMWARE_LOADABLE:
return "LOADABLE";
case INTEL_UC_FIRMWARE_LOAD_FAIL:
return "LOAD FAIL";
case INTEL_UC_FIRMWARE_TRANSFERRED:
return "TRANSFERRED";
case INTEL_UC_FIRMWARE_RUNNING:
return "RUNNING";
}
return "<invalid>";
}
static inline int intel_uc_fw_status_to_error(enum intel_uc_fw_status status)
{
switch (status) {
case INTEL_UC_FIRMWARE_NOT_SUPPORTED:
return -ENODEV;
case INTEL_UC_FIRMWARE_UNINITIALIZED:
return -EACCES;
case INTEL_UC_FIRMWARE_DISABLED:
return -EPERM;
case INTEL_UC_FIRMWARE_MISSING:
return -ENOENT;
case INTEL_UC_FIRMWARE_ERROR:
return -ENOEXEC;
case INTEL_UC_FIRMWARE_INIT_FAIL:
case INTEL_UC_FIRMWARE_LOAD_FAIL:
return -EIO;
case INTEL_UC_FIRMWARE_SELECTED:
return -ESTALE;
case INTEL_UC_FIRMWARE_AVAILABLE:
case INTEL_UC_FIRMWARE_LOADABLE:
case INTEL_UC_FIRMWARE_TRANSFERRED:
case INTEL_UC_FIRMWARE_RUNNING:
return 0;
}
return -EINVAL;
}
static inline const char *intel_uc_fw_type_repr(enum intel_uc_fw_type type)
{
switch (type) {
case INTEL_UC_FW_TYPE_GUC:
return "GuC";
case INTEL_UC_FW_TYPE_HUC:
return "HuC";
case INTEL_UC_FW_TYPE_GSC:
return "GSC";
}
return "uC";
}
static inline enum intel_uc_fw_status
__intel_uc_fw_status(struct intel_uc_fw *uc_fw)
{
/* shouldn't call this before checking hw/blob availability */
GEM_BUG_ON(uc_fw->status == INTEL_UC_FIRMWARE_UNINITIALIZED);
return uc_fw->status;
}
static inline bool intel_uc_fw_is_supported(struct intel_uc_fw *uc_fw)
{
return __intel_uc_fw_status(uc_fw) != INTEL_UC_FIRMWARE_NOT_SUPPORTED;
}
static inline bool intel_uc_fw_is_enabled(struct intel_uc_fw *uc_fw)
{
return __intel_uc_fw_status(uc_fw) > INTEL_UC_FIRMWARE_DISABLED;
}
static inline bool intel_uc_fw_is_available(struct intel_uc_fw *uc_fw)
{
return __intel_uc_fw_status(uc_fw) >= INTEL_UC_FIRMWARE_AVAILABLE;
}
static inline bool intel_uc_fw_is_loadable(struct intel_uc_fw *uc_fw)
{
return __intel_uc_fw_status(uc_fw) >= INTEL_UC_FIRMWARE_LOADABLE;
}
static inline bool intel_uc_fw_is_loaded(struct intel_uc_fw *uc_fw)
{
return __intel_uc_fw_status(uc_fw) >= INTEL_UC_FIRMWARE_TRANSFERRED;
}
static inline bool intel_uc_fw_is_running(struct intel_uc_fw *uc_fw)
{
return __intel_uc_fw_status(uc_fw) == INTEL_UC_FIRMWARE_RUNNING;
}
static inline bool intel_uc_fw_is_in_error(struct intel_uc_fw *uc_fw)
{
return intel_uc_fw_status_to_error(__intel_uc_fw_status(uc_fw)) != 0;
}
static inline bool intel_uc_fw_is_overridden(const struct intel_uc_fw *uc_fw)
{
return uc_fw->user_overridden;
}
static inline void intel_uc_fw_sanitize(struct intel_uc_fw *uc_fw)
{
if (intel_uc_fw_is_loaded(uc_fw))
intel_uc_fw_change_status(uc_fw, INTEL_UC_FIRMWARE_LOADABLE);
}
static inline u32 __intel_uc_fw_get_upload_size(struct intel_uc_fw *uc_fw)
{
return sizeof(struct uc_css_header) + uc_fw->ucode_size;
}
/**
* intel_uc_fw_get_upload_size() - Get size of firmware needed to be uploaded.
* @uc_fw: uC firmware.
*
* Get the size of the firmware and header that will be uploaded to WOPCM.
*
* Return: Upload firmware size, or zero on firmware fetch failure.
*/
static inline u32 intel_uc_fw_get_upload_size(struct intel_uc_fw *uc_fw)
{
if (!intel_uc_fw_is_available(uc_fw))
return 0;
return __intel_uc_fw_get_upload_size(uc_fw);
}
void intel_uc_fw_version_from_gsc_manifest(struct intel_uc_fw_ver *ver,
const void *data);
int intel_uc_check_file_version(struct intel_uc_fw *uc_fw, bool *old_ver);
void intel_uc_fw_init_early(struct intel_uc_fw *uc_fw,
enum intel_uc_fw_type type,
bool needs_ggtt_mapping);
int intel_uc_fw_fetch(struct intel_uc_fw *uc_fw);
void intel_uc_fw_cleanup_fetch(struct intel_uc_fw *uc_fw);
int intel_uc_fw_upload(struct intel_uc_fw *uc_fw, u32 offset, u32 dma_flags);
int intel_uc_fw_init(struct intel_uc_fw *uc_fw);
void intel_uc_fw_fini(struct intel_uc_fw *uc_fw);
void intel_uc_fw_resume_mapping(struct intel_uc_fw *uc_fw);
size_t intel_uc_fw_copy_rsa(struct intel_uc_fw *uc_fw, void *dst, u32 max_len);
int intel_uc_fw_mark_load_failed(struct intel_uc_fw *uc_fw, int err);
void intel_uc_fw_dump(const struct intel_uc_fw *uc_fw, struct drm_printer *p);
#endif
@@ -0,0 +1,87 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2019 Intel Corporation
*/
#ifndef _INTEL_UC_FW_ABI_H
#define _INTEL_UC_FW_ABI_H
#include <linux/types.h>
#include <linux/build_bug.h>
/**
* DOC: Firmware Layout
*
* The GuC/HuC firmware layout looks like this::
*
* +======================================================================+
* | Firmware blob |
* +===============+===============+============+============+============+
* | CSS header | uCode | RSA key | modulus | exponent |
* +===============+===============+============+============+============+
* <-header size-> <---header size continued ----------->
* <--- size ----------------------------------------------------------->
* <-key size->
* <-mod size->
* <-exp size->
*
* The firmware may or may not have modulus key and exponent data. The header,
* uCode and RSA signature are must-have components that will be used by driver.
* Length of each components, which is all in dwords, can be found in header.
* In the case that modulus and exponent are not present in fw, a.k.a truncated
* image, the length value still appears in header.
*
* Driver will do some basic fw size validation based on the following rules:
*
* 1. Header, uCode and RSA are must-have components.
* 2. All firmware components, if they present, are in the sequence illustrated
* in the layout table above.
* 3. Length info of each component can be found in header, in dwords.
* 4. Modulus and exponent key are not required by driver. They may not appear
* in fw. So driver will load a truncated firmware in this case.
*
* Starting from DG2, the HuC is loaded by the GSC instead of i915. The GSC
* firmware performs all the required integrity checks, we just need to check
* the version. Note that the header for GSC-managed blobs is different from the
* CSS used for dma-loaded firmwares.
*/
struct uc_css_header {
u32 module_type;
/*
* header_size includes all non-uCode bits, including css_header, rsa
* key, modulus key and exponent data.
*/
u32 header_size_dw;
u32 header_version;
u32 module_id;
u32 module_vendor;
u32 date;
#define CSS_DATE_DAY (0xFF << 0)
#define CSS_DATE_MONTH (0xFF << 8)
#define CSS_DATE_YEAR (0xFFFF << 16)
u32 size_dw; /* uCode plus header_size_dw */
u32 key_size_dw;
u32 modulus_size_dw;
u32 exponent_size_dw;
u32 time;
#define CSS_TIME_HOUR (0xFF << 0)
#define CSS_DATE_MIN (0xFF << 8)
#define CSS_DATE_SEC (0xFFFF << 16)
char username[8];
char buildnumber[12];
u32 sw_version;
#define CSS_SW_VERSION_UC_MAJOR (0xFF << 16)
#define CSS_SW_VERSION_UC_MINOR (0xFF << 8)
#define CSS_SW_VERSION_UC_PATCH (0xFF << 0)
u32 vf_version;
u32 reserved0[12];
union {
u32 private_data_size; /* only applies to GuC */
u32 reserved1;
};
u32 header_info;
} __packed;
static_assert(sizeof(struct uc_css_header) == 128);
#endif /* _INTEL_UC_FW_ABI_H */
@@ -0,0 +1,419 @@
// SPDX-License-Identifier: MIT
/*
* Copyright © 2021 Intel Corporation
*/
#include "gt/intel_gt_print.h"
#include "intel_guc_print.h"
#include "selftests/igt_spinner.h"
#include "selftests/intel_scheduler_helpers.h"
static int request_add_spin(struct i915_request *rq, struct igt_spinner *spin)
{
int err = 0;
i915_request_get(rq);
i915_request_add(rq);
if (spin && !igt_wait_for_spinner(spin, rq))
err = -ETIMEDOUT;
return err;
}
static struct i915_request *nop_user_request(struct intel_context *ce,
struct i915_request *from)
{
struct i915_request *rq;
int ret;
rq = intel_context_create_request(ce);
if (IS_ERR(rq))
return rq;
if (from) {
ret = i915_sw_fence_await_dma_fence(&rq->submit,
&from->fence, 0,
I915_FENCE_GFP);
if (ret < 0) {
i915_request_put(rq);
return ERR_PTR(ret);
}
}
i915_request_get(rq);
i915_request_add(rq);
return rq;
}
static int intel_guc_scrub_ctbs(void *arg)
{
struct intel_gt *gt = arg;
int ret = 0;
int i;
struct i915_request *last[3] = {NULL, NULL, NULL}, *rq;
intel_wakeref_t wakeref;
struct intel_engine_cs *engine;
struct intel_context *ce;
if (!intel_has_gpu_reset(gt))
return 0;
wakeref = intel_runtime_pm_get(gt->uncore->rpm);
engine = intel_selftest_find_any_engine(gt);
/* Submit requests and inject errors forcing G2H to be dropped */
for (i = 0; i < 3; ++i) {
ce = intel_context_create(engine);
if (IS_ERR(ce)) {
ret = PTR_ERR(ce);
gt_err(gt, "Failed to create context %d: %pe\n", i, ce);
goto err;
}
switch (i) {
case 0:
ce->drop_schedule_enable = true;
break;
case 1:
ce->drop_schedule_disable = true;
break;
case 2:
ce->drop_deregister = true;
break;
}
rq = nop_user_request(ce, NULL);
intel_context_put(ce);
if (IS_ERR(rq)) {
ret = PTR_ERR(rq);
gt_err(gt, "Failed to create request %d: %pe\n", i, rq);
goto err;
}
last[i] = rq;
}
for (i = 0; i < 3; ++i) {
ret = i915_request_wait(last[i], 0, HZ);
if (ret < 0) {
gt_err(gt, "Last request failed to complete: %pe\n", ERR_PTR(ret));
goto err;
}
i915_request_put(last[i]);
last[i] = NULL;
}
/* Force all H2G / G2H to be submitted / processed */
intel_gt_retire_requests(gt);
msleep(500);
/* Scrub missing G2H */
intel_gt_handle_error(engine->gt, -1, 0, "selftest reset");
/* GT will not idle if G2H are lost */
ret = intel_gt_wait_for_idle(gt, HZ);
if (ret < 0) {
gt_err(gt, "GT failed to idle: %pe\n", ERR_PTR(ret));
goto err;
}
err:
for (i = 0; i < 3; ++i)
if (last[i])
i915_request_put(last[i]);
intel_runtime_pm_put(gt->uncore->rpm, wakeref);
return ret;
}
/*
* intel_guc_steal_guc_ids - Test to exhaust all guc_ids and then steal one
*
* This test creates a spinner which is used to block all subsequent submissions
* until it completes. Next, a loop creates a context and a NOP request each
* iteration until the guc_ids are exhausted (request creation returns -EAGAIN).
* The spinner is ended, unblocking all requests created in the loop. At this
* point all guc_ids are exhausted but are available to steal. Try to create
* another request which should successfully steal a guc_id. Wait on last
* request to complete, idle GPU, verify a guc_id was stolen via a counter, and
* exit the test. Test also artificially reduces the number of guc_ids so the
* test runs in a timely manner.
*/
static int intel_guc_steal_guc_ids(void *arg)
{
struct intel_gt *gt = arg;
struct intel_guc *guc = gt_to_guc(gt);
int ret, sv, context_index = 0;
intel_wakeref_t wakeref;
struct intel_engine_cs *engine;
struct intel_context **ce;
struct igt_spinner spin;
struct i915_request *spin_rq = NULL, *rq, *last = NULL;
int number_guc_id_stolen = guc->number_guc_id_stolen;
ce = kzalloc_objs(*ce, GUC_MAX_CONTEXT_ID);
if (!ce) {
guc_err(guc, "Context array allocation failed\n");
return -ENOMEM;
}
wakeref = intel_runtime_pm_get(gt->uncore->rpm);
engine = intel_selftest_find_any_engine(gt);
sv = guc->submission_state.num_guc_ids;
guc->submission_state.num_guc_ids = 512;
/* Create spinner to block requests in below loop */
ce[context_index] = intel_context_create(engine);
if (IS_ERR(ce[context_index])) {
ret = PTR_ERR(ce[context_index]);
guc_err(guc, "Failed to create context: %pe\n", ce[context_index]);
ce[context_index] = NULL;
goto err_wakeref;
}
ret = igt_spinner_init(&spin, engine->gt);
if (ret) {
guc_err(guc, "Failed to create spinner: %pe\n", ERR_PTR(ret));
goto err_contexts;
}
spin_rq = igt_spinner_create_request(&spin, ce[context_index],
MI_ARB_CHECK);
if (IS_ERR(spin_rq)) {
ret = PTR_ERR(spin_rq);
guc_err(guc, "Failed to create spinner request: %pe\n", spin_rq);
goto err_contexts;
}
ret = request_add_spin(spin_rq, &spin);
if (ret) {
guc_err(guc, "Failed to add Spinner request: %pe\n", ERR_PTR(ret));
goto err_spin_rq;
}
/* Use all guc_ids */
while (ret != -EAGAIN) {
ce[++context_index] = intel_context_create(engine);
if (IS_ERR(ce[context_index])) {
ret = PTR_ERR(ce[context_index]);
guc_err(guc, "Failed to create context: %pe\n", ce[context_index]);
ce[context_index--] = NULL;
goto err_spin_rq;
}
rq = nop_user_request(ce[context_index], spin_rq);
if (IS_ERR(rq)) {
ret = PTR_ERR(rq);
rq = NULL;
if ((ret != -EAGAIN) || !last) {
guc_err(guc, "Failed to create %srequest %d: %pe\n",
last ? "" : "first ", context_index, ERR_PTR(ret));
goto err_spin_rq;
}
} else {
if (last)
i915_request_put(last);
last = rq;
}
}
/* Release blocked requests */
igt_spinner_end(&spin);
ret = intel_selftest_wait_for_rq(spin_rq);
if (ret) {
guc_err(guc, "Spin request failed to complete: %pe\n", ERR_PTR(ret));
i915_request_put(last);
goto err_spin_rq;
}
i915_request_put(spin_rq);
igt_spinner_fini(&spin);
spin_rq = NULL;
/* Wait for last request */
ret = i915_request_wait(last, 0, HZ * 30);
i915_request_put(last);
if (ret < 0) {
guc_err(guc, "Last request failed to complete: %pe\n", ERR_PTR(ret));
goto err_spin_rq;
}
/* Try to steal guc_id */
rq = nop_user_request(ce[context_index], NULL);
if (IS_ERR(rq)) {
ret = PTR_ERR(rq);
guc_err(guc, "Failed to steal guc_id %d: %pe\n", context_index, rq);
goto err_spin_rq;
}
/* Wait for request with stolen guc_id */
ret = i915_request_wait(rq, 0, HZ);
i915_request_put(rq);
if (ret < 0) {
guc_err(guc, "Request with stolen guc_id failed to complete: %pe\n", ERR_PTR(ret));
goto err_spin_rq;
}
/* Wait for idle */
ret = intel_gt_wait_for_idle(gt, HZ * 30);
if (ret < 0) {
guc_err(guc, "GT failed to idle: %pe\n", ERR_PTR(ret));
goto err_spin_rq;
}
/* Verify a guc_id was stolen */
if (guc->number_guc_id_stolen == number_guc_id_stolen) {
guc_err(guc, "No guc_id was stolen");
ret = -EINVAL;
} else {
ret = 0;
}
err_spin_rq:
if (spin_rq) {
igt_spinner_end(&spin);
intel_selftest_wait_for_rq(spin_rq);
i915_request_put(spin_rq);
igt_spinner_fini(&spin);
intel_gt_wait_for_idle(gt, HZ * 30);
}
err_contexts:
for (; context_index >= 0 && ce[context_index]; --context_index)
intel_context_put(ce[context_index]);
err_wakeref:
intel_runtime_pm_put(gt->uncore->rpm, wakeref);
kfree(ce);
guc->submission_state.num_guc_ids = sv;
return ret;
}
/*
* Send a context schedule H2G message with an invalid context id.
* This should generate a GUC_RESULT_INVALID_CONTEXT response.
*/
static int bad_h2g(struct intel_guc *guc)
{
u32 action[] = {
INTEL_GUC_ACTION_SCHED_CONTEXT,
0x12345678,
};
return intel_guc_send_nb(guc, action, ARRAY_SIZE(action), 0);
}
/*
* Set a spinner running to make sure the system is alive and active,
* then send a bad but asynchronous H2G command and wait to see if an
* error response is returned. If no response is received or if the
* spinner dies then the test will fail.
*/
#define FAST_RESPONSE_TIMEOUT_MS 1000
static int intel_guc_fast_request(void *arg)
{
struct intel_gt *gt = arg;
struct intel_context *ce;
struct igt_spinner spin;
struct i915_request *rq;
intel_wakeref_t wakeref;
struct intel_engine_cs *engine = intel_selftest_find_any_engine(gt);
bool spinning = false;
int ret = 0;
if (!engine)
return 0;
wakeref = intel_runtime_pm_get(gt->uncore->rpm);
ce = intel_context_create(engine);
if (IS_ERR(ce)) {
ret = PTR_ERR(ce);
gt_err(gt, "Failed to create spinner request: %pe\n", ce);
goto err_pm;
}
ret = igt_spinner_init(&spin, engine->gt);
if (ret) {
gt_err(gt, "Failed to create spinner: %pe\n", ERR_PTR(ret));
goto err_pm;
}
spinning = true;
rq = igt_spinner_create_request(&spin, ce, MI_ARB_CHECK);
intel_context_put(ce);
if (IS_ERR(rq)) {
ret = PTR_ERR(rq);
gt_err(gt, "Failed to create spinner request: %pe\n", rq);
goto err_spin;
}
ret = request_add_spin(rq, &spin);
if (ret) {
gt_err(gt, "Failed to add Spinner request: %pe\n", ERR_PTR(ret));
goto err_rq;
}
gt->uc.guc.fast_response_selftest = 1;
ret = bad_h2g(&gt->uc.guc);
if (ret) {
gt_err(gt, "Failed to send H2G: %pe\n", ERR_PTR(ret));
goto err_rq;
}
ret = wait_for(gt->uc.guc.fast_response_selftest != 1 || i915_request_completed(rq),
FAST_RESPONSE_TIMEOUT_MS);
if (ret) {
gt_err(gt, "Request wait failed: %pe\n", ERR_PTR(ret));
goto err_rq;
}
if (i915_request_completed(rq)) {
gt_err(gt, "Spinner died waiting for fast request error!\n");
ret = -EIO;
goto err_rq;
}
if (gt->uc.guc.fast_response_selftest != 2) {
gt_err(gt, "Unexpected fast response count: %d\n",
gt->uc.guc.fast_response_selftest);
goto err_rq;
}
igt_spinner_end(&spin);
spinning = false;
ret = intel_selftest_wait_for_rq(rq);
if (ret) {
gt_err(gt, "Request failed to complete: %pe\n", ERR_PTR(ret));
goto err_rq;
}
err_rq:
i915_request_put(rq);
err_spin:
if (spinning)
igt_spinner_end(&spin);
igt_spinner_fini(&spin);
err_pm:
intel_runtime_pm_put(gt->uncore->rpm, wakeref);
return ret;
}
int intel_guc_live_selftests(struct drm_i915_private *i915)
{
static const struct i915_subtest tests[] = {
SUBTEST(intel_guc_scrub_ctbs),
SUBTEST(intel_guc_steal_guc_ids),
SUBTEST(intel_guc_fast_request),
};
struct intel_gt *gt = to_gt(i915);
if (intel_gt_is_wedged(gt))
return 0;
if (!intel_uc_uses_guc_submission(&gt->uc))
return 0;
return intel_gt_live_subtests(tests, gt);
}
@@ -0,0 +1,162 @@
// SPDX-License-Identifier: MIT
/*
* Copyright © 2022 Intel Corporation
*/
#include "gt/intel_gt_print.h"
#include "selftests/igt_spinner.h"
#include "selftests/igt_reset.h"
#include "selftests/intel_scheduler_helpers.h"
#include "gt/intel_engine_heartbeat.h"
#include "gem/selftests/mock_context.h"
#define BEAT_INTERVAL 100
static struct i915_request *nop_request(struct intel_engine_cs *engine)
{
struct i915_request *rq;
rq = intel_engine_create_kernel_request(engine);
if (IS_ERR(rq))
return rq;
i915_request_get(rq);
i915_request_add(rq);
return rq;
}
static int intel_hang_guc(void *arg)
{
struct intel_gt *gt = arg;
int ret = 0;
struct i915_gem_context *ctx;
struct intel_context *ce;
struct igt_spinner spin;
struct i915_request *rq;
intel_wakeref_t wakeref;
struct i915_gpu_error *global = &gt->i915->gpu_error;
struct intel_engine_cs *engine = intel_selftest_find_any_engine(gt);
unsigned int reset_count;
u32 guc_status;
u32 old_beat;
if (!engine)
return 0;
ctx = kernel_context(gt->i915, NULL);
if (IS_ERR(ctx)) {
gt_err(gt, "Failed get kernel context: %pe\n", ctx);
return PTR_ERR(ctx);
}
wakeref = intel_runtime_pm_get(gt->uncore->rpm);
ce = intel_context_create(engine);
if (IS_ERR(ce)) {
ret = PTR_ERR(ce);
gt_err(gt, "Failed to create spinner request: %pe\n", ce);
goto err;
}
reset_count = i915_reset_count(global);
old_beat = engine->props.heartbeat_interval_ms;
ret = intel_engine_set_heartbeat(engine, BEAT_INTERVAL);
if (ret) {
gt_err(gt, "Failed to boost heartbeat interval: %pe\n", ERR_PTR(ret));
goto err;
}
ret = igt_spinner_init(&spin, engine->gt);
if (ret) {
gt_err(gt, "Failed to create spinner: %pe\n", ERR_PTR(ret));
goto err;
}
rq = igt_spinner_create_request(&spin, ce, MI_ARB_CHECK);
intel_context_put(ce);
if (IS_ERR(rq)) {
ret = PTR_ERR(rq);
gt_err(gt, "Failed to create spinner request: %pe\n", rq);
goto err_spin;
}
ret = request_add_spin(rq, &spin);
if (ret) {
i915_request_put(rq);
gt_err(gt, "Failed to add Spinner request: %pe\n", ERR_PTR(ret));
goto err_spin;
}
ret = intel_reset_guc(gt);
if (ret) {
i915_request_put(rq);
gt_err(gt, "Failed to reset GuC: %pe\n", ERR_PTR(ret));
goto err_spin;
}
guc_status = intel_uncore_read(gt->uncore, GUC_STATUS);
if (!(guc_status & GS_MIA_IN_RESET)) {
i915_request_put(rq);
gt_err(gt, "Failed to reset GuC: status = 0x%08X\n", guc_status);
ret = -EIO;
goto err_spin;
}
/* Wait for the heartbeat to cause a reset */
ret = intel_selftest_wait_for_rq(rq);
i915_request_put(rq);
if (ret) {
gt_err(gt, "Request failed to complete: %pe\n", ERR_PTR(ret));
goto err_spin;
}
if (i915_reset_count(global) == reset_count) {
gt_err(gt, "Failed to record a GPU reset\n");
ret = -EINVAL;
goto err_spin;
}
err_spin:
igt_spinner_end(&spin);
igt_spinner_fini(&spin);
intel_engine_set_heartbeat(engine, old_beat);
if (ret == 0) {
rq = nop_request(engine);
if (IS_ERR(rq)) {
ret = PTR_ERR(rq);
goto err;
}
ret = intel_selftest_wait_for_rq(rq);
i915_request_put(rq);
if (ret) {
gt_err(gt, "No-op failed to complete: %pe\n", ERR_PTR(ret));
goto err;
}
}
err:
intel_runtime_pm_put(gt->uncore->rpm, wakeref);
kernel_context_close(ctx);
return ret;
}
int intel_guc_hang_check(struct drm_i915_private *i915)
{
static const struct i915_subtest tests[] = {
SUBTEST(intel_hang_guc),
};
struct intel_gt *gt = to_gt(i915);
if (intel_gt_is_wedged(gt))
return 0;
if (!intel_uc_uses_guc_submission(&gt->uc))
return 0;
return intel_gt_live_subtests(tests, gt);
}
@@ -0,0 +1,184 @@
// SPDX-License-Identifier: MIT
/*
* Copyright © 2019 Intel Corporation
*/
#include "gt/intel_gt_print.h"
#include "selftests/igt_spinner.h"
#include "selftests/igt_reset.h"
#include "selftests/intel_scheduler_helpers.h"
#include "gt/intel_engine_heartbeat.h"
#include "gem/selftests/mock_context.h"
static void logical_sort(struct intel_engine_cs **engines, int num_engines)
{
struct intel_engine_cs *sorted[MAX_ENGINE_INSTANCE + 1];
int i, j;
for (i = 0; i < num_engines; ++i)
for (j = 0; j < MAX_ENGINE_INSTANCE + 1; ++j) {
if (engines[j]->logical_mask & BIT(i)) {
sorted[i] = engines[j];
break;
}
}
memcpy(*engines, *sorted,
sizeof(struct intel_engine_cs *) * num_engines);
}
static struct intel_context *
multi_lrc_create_parent(struct intel_gt *gt, u8 class,
unsigned long flags)
{
struct intel_engine_cs *siblings[MAX_ENGINE_INSTANCE + 1];
struct intel_engine_cs *engine;
enum intel_engine_id id;
int i = 0;
for_each_engine(engine, gt, id) {
if (engine->class != class)
continue;
siblings[i++] = engine;
}
if (i <= 1)
return ERR_PTR(0);
logical_sort(siblings, i);
return intel_engine_create_parallel(siblings, 1, i);
}
static void multi_lrc_context_unpin(struct intel_context *ce)
{
struct intel_context *child;
GEM_BUG_ON(!intel_context_is_parent(ce));
for_each_child(ce, child)
intel_context_unpin(child);
intel_context_unpin(ce);
}
static void multi_lrc_context_put(struct intel_context *ce)
{
GEM_BUG_ON(!intel_context_is_parent(ce));
/*
* Only the parent gets the creation ref put in the uAPI, the parent
* itself is responsible for creation ref put on the children.
*/
intel_context_put(ce);
}
static struct i915_request *
multi_lrc_nop_request(struct intel_context *ce)
{
struct intel_context *child;
struct i915_request *rq, *child_rq;
int i = 0;
GEM_BUG_ON(!intel_context_is_parent(ce));
rq = intel_context_create_request(ce);
if (IS_ERR(rq))
return rq;
i915_request_get(rq);
i915_request_add(rq);
for_each_child(ce, child) {
child_rq = intel_context_create_request(child);
if (IS_ERR(child_rq))
goto child_error;
if (++i == ce->parallel.number_children)
set_bit(I915_FENCE_FLAG_SUBMIT_PARALLEL,
&child_rq->fence.flags);
i915_request_add(child_rq);
}
return rq;
child_error:
i915_request_put(rq);
return ERR_PTR(-ENOMEM);
}
static int __intel_guc_multi_lrc_basic(struct intel_gt *gt, unsigned int class)
{
struct intel_context *parent;
struct i915_request *rq;
int ret;
parent = multi_lrc_create_parent(gt, class, 0);
if (IS_ERR(parent)) {
gt_err(gt, "Failed creating contexts: %pe\n", parent);
return PTR_ERR(parent);
} else if (!parent) {
gt_dbg(gt, "Not enough engines in class: %d\n", class);
return 0;
}
rq = multi_lrc_nop_request(parent);
if (IS_ERR(rq)) {
ret = PTR_ERR(rq);
gt_err(gt, "Failed creating requests: %pe\n", rq);
goto out;
}
ret = intel_selftest_wait_for_rq(rq);
if (ret)
gt_err(gt, "Failed waiting on request: %pe\n", ERR_PTR(ret));
i915_request_put(rq);
if (ret >= 0) {
ret = intel_gt_wait_for_idle(gt, HZ * 5);
if (ret < 0)
gt_err(gt, "GT failed to idle: %pe\n", ERR_PTR(ret));
}
out:
multi_lrc_context_unpin(parent);
multi_lrc_context_put(parent);
return ret;
}
static int intel_guc_multi_lrc_basic(void *arg)
{
struct intel_gt *gt = arg;
unsigned int class;
int ret;
for (class = 0; class < MAX_ENGINE_CLASS + 1; ++class) {
/* We don't support breadcrumb handshake on these classes */
if (class == COMPUTE_CLASS || class == RENDER_CLASS)
continue;
ret = __intel_guc_multi_lrc_basic(gt, class);
if (ret)
return ret;
}
return 0;
}
int intel_guc_multi_lrc_live_selftests(struct drm_i915_private *i915)
{
static const struct i915_subtest tests[] = {
SUBTEST(intel_guc_multi_lrc_basic),
};
struct intel_gt *gt = to_gt(i915);
if (intel_gt_is_wedged(gt))
return 0;
if (!intel_uc_uses_guc_submission(&gt->uc))
return 0;
return intel_gt_live_subtests(tests, gt);
}