blob: 8d813c68450bf25e39716da439e057a729bdc853 [file]
/*
* Copyright 2019 Advanced Micro Devices, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*/
#include <linux/firmware.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/sizes.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <drm/drm_exec.h>
#include "amdgpu_mes.h"
#include "amdgpu.h"
#include "soc15_common.h"
#include "amdgpu_mes_ctx.h"
#define AMDGPU_MES_MAX_NUM_OF_QUEUES_PER_PROCESS 1024
#define AMDGPU_ONE_DOORBELL_SIZE 8
int amdgpu_mes_doorbell_process_slice(struct amdgpu_device *adev)
{
return roundup(AMDGPU_ONE_DOORBELL_SIZE *
AMDGPU_MES_MAX_NUM_OF_QUEUES_PER_PROCESS,
PAGE_SIZE);
}
static int amdgpu_mes_doorbell_init(struct amdgpu_device *adev)
{
int i;
struct amdgpu_mes *mes = &adev->mes;
/* Bitmap for dynamic allocation of kernel doorbells */
mes->doorbell_bitmap = bitmap_zalloc(PAGE_SIZE / sizeof(u32), GFP_KERNEL);
if (!mes->doorbell_bitmap) {
dev_err(adev->dev, "Failed to allocate MES doorbell bitmap\n");
return -ENOMEM;
}
mes->num_mes_dbs = PAGE_SIZE / AMDGPU_ONE_DOORBELL_SIZE;
for (i = 0; i < AMDGPU_MES_PRIORITY_NUM_LEVELS; i++) {
adev->mes.aggregated_doorbells[i] = mes->db_start_dw_offset + i * 2;
set_bit(i, mes->doorbell_bitmap);
}
return 0;
}
static int amdgpu_mes_event_log_init(struct amdgpu_device *adev)
{
int r;
if (!amdgpu_mes_log_enable)
return 0;
r = amdgpu_bo_create_kernel(adev, adev->mes.event_log_size, PAGE_SIZE,
AMDGPU_GEM_DOMAIN_VRAM,
&adev->mes.event_log_gpu_obj,
&adev->mes.event_log_gpu_addr,
&adev->mes.event_log_cpu_addr);
if (r) {
dev_warn(adev->dev, "failed to create MES event log buffer (%d)", r);
return r;
}
memset(adev->mes.event_log_cpu_addr, 0, adev->mes.event_log_size);
return 0;
}
static void amdgpu_mes_doorbell_free(struct amdgpu_device *adev)
{
bitmap_free(adev->mes.doorbell_bitmap);
}
static inline u32 amdgpu_mes_get_hqd_mask(u32 num_pipe,
u32 num_hqd_per_pipe,
u32 num_reserved_hqd)
{
if (num_pipe == 0)
return 0;
u32 total_hqd_mask = (u32)((1ULL << num_hqd_per_pipe) - 1);
u32 reserved_hqd_mask = (u32)((1ULL << DIV_ROUND_UP(num_reserved_hqd, num_pipe)) - 1);
return (total_hqd_mask & ~reserved_hqd_mask);
}
static void amdgpu_mes_userq_notify_unmap_work_handler(struct work_struct *work);
int amdgpu_mes_init(struct amdgpu_device *adev)
{
int i, r, num_pipes, num_queues = 0;
int num_xcc = adev->gfx.xcc_mask ? NUM_XCC(adev->gfx.xcc_mask) : 1;
u32 gfx_hqd_mask = amdgpu_mes_get_hqd_mask(adev->gfx.me.num_pipe_per_me,
adev->gfx.me.num_queue_per_pipe,
adev->gfx.disable_kq ? 0 : adev->gfx.num_gfx_rings);
u32 compute_hqd_mask = amdgpu_mes_get_hqd_mask(adev->gfx.mec.num_pipe_per_mec,
adev->gfx.mec.num_queue_per_pipe,
adev->gfx.disable_kq ? 0 : adev->gfx.num_compute_rings);
adev->mes.adev = adev;
ida_init(&adev->mes.doorbell_ida);
spin_lock_init(&adev->mes.queue_id_lock);
mutex_init(&adev->mes.mutex_hidden);
mutex_init(&adev->mes.dbgext_lock);
for (i = 0; i < AMDGPU_MAX_MES_PIPES * num_xcc; i++)
spin_lock_init(&adev->mes.ring_lock[i]);
adev->mes.total_max_queue = AMDGPU_FENCE_MES_QUEUE_ID_MASK;
atomic_set(&adev->mes.userq_hw_queue_count, 0);
INIT_DELAYED_WORK(&adev->mes.userq_notify_unmap_work,
amdgpu_mes_userq_notify_unmap_work_handler);
num_pipes = adev->gfx.me.num_pipe_per_me * adev->gfx.me.num_me;
if (num_pipes > AMDGPU_MES_MAX_GFX_PIPES)
dev_warn(adev->dev, "more gfx pipes than supported by MES! (%d vs %d)\n",
num_pipes, AMDGPU_MES_MAX_GFX_PIPES);
for (i = 0; i < AMDGPU_MES_MAX_GFX_PIPES; i++) {
if (i >= num_pipes)
break;
adev->mes.gfx_hqd_mask[i] = gfx_hqd_mask;
}
num_pipes = adev->gfx.mec.num_pipe_per_mec * adev->gfx.mec.num_mec;
if (num_pipes > AMDGPU_MES_MAX_COMPUTE_PIPES)
dev_warn(adev->dev, "more compute pipes than supported by MES! (%d vs %d)\n",
num_pipes, AMDGPU_MES_MAX_COMPUTE_PIPES);
for (i = 0; i < AMDGPU_MES_MAX_COMPUTE_PIPES; i++) {
/*
* Currently, only MEC1 is used for both kernel and user compute queue.
* To enable other MEC, we need to redistribute queues per pipe and
* adjust queue resource shared with kfd that needs a separate patch.
* Skip other MEC for now to avoid potential issues.
*/
if (i >= adev->gfx.mec.num_pipe_per_mec)
break;
adev->mes.compute_hqd_mask[i] = compute_hqd_mask;
}
num_pipes = adev->sdma.num_inst_per_xcc ?
adev->sdma.num_inst_per_xcc : adev->sdma.num_instances;
if (num_pipes > AMDGPU_MES_MAX_SDMA_PIPES)
dev_warn(adev->dev, "more SDMA pipes than supported by MES! (%d vs %d)\n",
num_pipes, AMDGPU_MES_MAX_SDMA_PIPES);
for (i = 0; i < AMDGPU_MES_MAX_SDMA_PIPES; i++) {
if (i >= num_pipes)
break;
adev->mes.sdma_hqd_mask[i] = 0xfc;
}
dev_info(adev->dev,
"MES: vmid_uq_mask_mmhub 0x%08x, vmid_uq_mask_gfxhub 0x%08x\n",
adev->vm_manager.vmid_uq_mask_mmhub,
adev->vm_manager.vmid_uq_mask_gfxhub);
dev_info(adev->dev,
"MES: gfx_hqd_mask 0x%08x, compute_hqd_mask 0x%08x, sdma_hqd_mask 0x%08x\n",
adev->mes.gfx_hqd_mask[0],
adev->mes.compute_hqd_mask[0],
adev->mes.sdma_hqd_mask[0]);
for (i = 0; i < AMDGPU_MAX_MES_PIPES * num_xcc; i++) {
r = amdgpu_wb_get(adev, &adev->mes.sch_ctx_offs[i]);
if (r) {
dev_err(adev->dev,
"(%d) ring trail_fence_offs wb alloc failed\n",
r);
goto error;
}
adev->mes.sch_ctx_gpu_addr[i] =
adev->wb.gpu_addr + (adev->mes.sch_ctx_offs[i] * 4);
adev->mes.sch_ctx_ptr[i] =
(uint64_t *)&adev->wb.wb[adev->mes.sch_ctx_offs[i]];
r = amdgpu_wb_get(adev,
&adev->mes.query_status_fence_offs[i]);
if (r) {
dev_err(adev->dev,
"(%d) query_status_fence_offs wb alloc failed\n",
r);
goto error;
}
adev->mes.query_status_fence_gpu_addr[i] = adev->wb.gpu_addr +
(adev->mes.query_status_fence_offs[i] * 4);
adev->mes.query_status_fence_ptr[i] =
(uint64_t *)&adev->wb.wb[adev->mes.query_status_fence_offs[i]];
}
r = amdgpu_mes_doorbell_init(adev);
if (r)
goto error;
r = amdgpu_mes_event_log_init(adev);
if (r)
goto error_doorbell;
if (amdgpu_ip_version(adev, GC_HWIP, 0) >= IP_VERSION(11, 0, 0)) {
/* When queue/pipe reset is done in MES instead of in the
* driver, MES passes hung queues information to the driver in
* hung_queue_hqd_info. Calculate required space to store this
* information.
*/
for (i = 0; i < AMDGPU_MES_MAX_GFX_PIPES; i++)
num_queues += hweight32(adev->mes.gfx_hqd_mask[i]);
for (i = 0; i < AMDGPU_MES_MAX_COMPUTE_PIPES; i++)
num_queues += hweight32(adev->mes.compute_hqd_mask[i]);
for (i = 0; i < AMDGPU_MES_MAX_SDMA_PIPES; i++)
num_queues += hweight32(adev->mes.sdma_hqd_mask[i]) * num_xcc;
adev->mes.hung_queue_hqd_info_offset = num_queues;
adev->mes.hung_queue_db_array_size = num_queues * 2;
}
if (adev->mes.hung_queue_db_array_size) {
for (i = 0; i < AMDGPU_MAX_MES_PIPES * num_xcc; i++) {
r = amdgpu_bo_create_kernel(adev,
adev->mes.hung_queue_db_array_size * sizeof(u32),
PAGE_SIZE,
AMDGPU_GEM_DOMAIN_GTT,
&adev->mes.hung_queue_db_array_gpu_obj[i],
&adev->mes.hung_queue_db_array_gpu_addr[i],
&adev->mes.hung_queue_db_array_cpu_addr[i]);
if (r) {
dev_warn(adev->dev, "failed to create MES hung db array buffer (%d)", r);
goto error_doorbell;
}
}
adev->gfx.mec.mes_hung_db_array =
kzalloc_objs(*adev->gfx.mec.mes_hung_db_array,
amdgpu_mes_get_hung_queue_db_array_size(adev));
if (!adev->gfx.mec.mes_hung_db_array) {
r = -ENOMEM;
goto error_doorbell;
}
}
return 0;
error_doorbell:
amdgpu_mes_doorbell_free(adev);
error:
for (i = 0; i < AMDGPU_MAX_MES_PIPES * num_xcc; i++) {
if (adev->mes.sch_ctx_ptr[i])
amdgpu_wb_free(adev, adev->mes.sch_ctx_offs[i]);
if (adev->mes.query_status_fence_ptr[i])
amdgpu_wb_free(adev,
adev->mes.query_status_fence_offs[i]);
if (adev->mes.hung_queue_db_array_gpu_obj[i])
amdgpu_bo_free_kernel(&adev->mes.hung_queue_db_array_gpu_obj[i],
&adev->mes.hung_queue_db_array_gpu_addr[i],
&adev->mes.hung_queue_db_array_cpu_addr[i]);
}
ida_destroy(&adev->mes.doorbell_ida);
mutex_destroy(&adev->mes.mutex_hidden);
return r;
}
void amdgpu_mes_fini(struct amdgpu_device *adev)
{
int i;
int num_xcc = adev->gfx.xcc_mask ? NUM_XCC(adev->gfx.xcc_mask) : 1;
cancel_delayed_work_sync(&adev->mes.userq_notify_unmap_work);
kfree(adev->gfx.mec.mes_hung_db_array);
amdgpu_bo_free_kernel(&adev->mes.event_log_gpu_obj,
&adev->mes.event_log_gpu_addr,
&adev->mes.event_log_cpu_addr);
for (i = 0; i < AMDGPU_MAX_MES_PIPES * num_xcc; i++) {
if (adev->mes.hung_queue_db_array_gpu_obj[i])
amdgpu_bo_free_kernel(&adev->mes.hung_queue_db_array_gpu_obj[i],
&adev->mes.hung_queue_db_array_gpu_addr[i],
&adev->mes.hung_queue_db_array_cpu_addr[i]);
if (adev->mes.sch_ctx_ptr[i])
amdgpu_wb_free(adev, adev->mes.sch_ctx_offs[i]);
if (adev->mes.query_status_fence_ptr[i])
amdgpu_wb_free(adev,
adev->mes.query_status_fence_offs[i]);
}
amdgpu_mes_doorbell_free(adev);
if (adev->mes.use_rs64mem)
amdgpu_mes_rs64mem_fini(&adev->mes);
ida_destroy(&adev->mes.doorbell_ida);
mutex_destroy(&adev->mes.mutex_hidden);
mutex_destroy(&adev->mes.dbgext_lock);
}
int amdgpu_mes_suspend(struct amdgpu_device *adev, u32 xcc_id)
{
struct mes_suspend_gang_input input;
int r;
if (!amdgpu_mes_suspend_resume_all_supported(adev))
return 0;
memset(&input, 0x0, sizeof(struct mes_suspend_gang_input));
input.suspend_all_gangs = 1;
input.xcc_id = xcc_id;
if ((amdgpu_ip_version(adev, GC_HWIP, 0) == IP_VERSION(12, 1, 0)) &&
((adev->mes.sched_version & AMDGPU_MES_VERSION_MASK) >= 0x71))
input.suspend_all_sdma_gangs = 1;
/*
* Avoid taking any other locks under MES lock to avoid circular
* lock dependencies.
*/
amdgpu_mes_lock(&adev->mes);
r = adev->mes.funcs->suspend_gang(&adev->mes, &input);
amdgpu_mes_unlock(&adev->mes);
if (r)
dev_err(adev->dev, "failed to suspend all gangs");
return r;
}
int amdgpu_mes_resume(struct amdgpu_device *adev, u32 xcc_id)
{
struct mes_resume_gang_input input;
int r;
if (!amdgpu_mes_suspend_resume_all_supported(adev))
return 0;
memset(&input, 0x0, sizeof(struct mes_resume_gang_input));
input.resume_all_gangs = 1;
input.xcc_id = xcc_id;
/*
* Avoid taking any other locks under MES lock to avoid circular
* lock dependencies.
*/
amdgpu_mes_lock(&adev->mes);
r = adev->mes.funcs->resume_gang(&adev->mes, &input);
amdgpu_mes_unlock(&adev->mes);
if (r)
dev_err(adev->dev, "failed to resume all gangs");
return r;
}
int amdgpu_mes_map_legacy_queue(struct amdgpu_device *adev,
struct amdgpu_ring *ring, uint32_t xcc_id)
{
struct mes_map_legacy_queue_input queue_input;
int r;
memset(&queue_input, 0, sizeof(queue_input));
queue_input.xcc_id = xcc_id;
queue_input.queue_type = ring->funcs->type;
queue_input.doorbell_offset = ring->doorbell_index;
queue_input.pipe_id = ring->pipe;
queue_input.queue_id = ring->queue;
queue_input.mqd_addr = amdgpu_bo_gpu_offset(ring->mqd_obj);
queue_input.wptr_addr = ring->wptr_gpu_addr;
amdgpu_mes_lock(&adev->mes);
r = adev->mes.funcs->map_legacy_queue(&adev->mes, &queue_input);
amdgpu_mes_unlock(&adev->mes);
if (r)
dev_err(adev->dev, "failed to map legacy queue\n");
return r;
}
int amdgpu_mes_unmap_legacy_queue(struct amdgpu_device *adev,
struct amdgpu_ring *ring,
enum amdgpu_unmap_queues_action action,
u64 gpu_addr, u64 seq, uint32_t xcc_id)
{
struct mes_unmap_legacy_queue_input queue_input;
int r;
queue_input.xcc_id = xcc_id;
queue_input.action = action;
queue_input.queue_type = ring->funcs->type;
queue_input.doorbell_offset = ring->doorbell_index;
queue_input.pipe_id = ring->pipe;
queue_input.queue_id = ring->queue;
queue_input.trail_fence_addr = gpu_addr;
queue_input.trail_fence_data = seq;
amdgpu_mes_lock(&adev->mes);
r = adev->mes.funcs->unmap_legacy_queue(&adev->mes, &queue_input);
amdgpu_mes_unlock(&adev->mes);
if (r)
dev_err(adev->dev, "failed to unmap legacy queue\n");
return r;
}
int amdgpu_mes_reset_legacy_queue(struct amdgpu_device *adev,
struct amdgpu_ring *ring,
unsigned int vmid,
bool use_mmio,
uint32_t xcc_id)
{
struct mes_reset_queue_input queue_input;
int r;
memset(&queue_input, 0, sizeof(queue_input));
queue_input.xcc_id = xcc_id;
queue_input.queue_type = ring->funcs->type;
queue_input.doorbell_offset = ring->doorbell_index;
queue_input.me_id = ring->me;
queue_input.pipe_id = ring->pipe;
queue_input.queue_id = ring->queue;
queue_input.mqd_addr = ring->mqd_obj ? amdgpu_bo_gpu_offset(ring->mqd_obj) : 0;
queue_input.wptr_addr = ring->wptr_gpu_addr;
queue_input.vmid = vmid;
queue_input.use_mmio = use_mmio;
queue_input.is_kq = true;
if (ring->funcs->type == AMDGPU_RING_TYPE_GFX)
queue_input.legacy_gfx = true;
amdgpu_mes_lock(&adev->mes);
r = adev->mes.funcs->reset_hw_queue(&adev->mes, &queue_input);
amdgpu_mes_unlock(&adev->mes);
if (r)
dev_err(adev->dev, "failed to reset legacy queue\n");
return r;
}
int amdgpu_mes_reset_queue_mmio(struct amdgpu_device *adev,
int queue_type,
unsigned int vmid,
unsigned int me,
unsigned int pipe,
unsigned int queue,
uint32_t xcc_id)
{
struct mes_reset_queue_input queue_input;
int r;
memset(&queue_input, 0, sizeof(queue_input));
queue_input.xcc_id = xcc_id;
queue_input.me_id = me;
queue_input.pipe_id = pipe;
queue_input.queue_id = queue;
queue_input.vmid = vmid;
queue_input.queue_type = queue_type;
queue_input.use_mmio = true;
amdgpu_mes_lock(&adev->mes);
r = adev->mes.funcs->reset_hw_queue(&adev->mes, &queue_input);
amdgpu_mes_unlock(&adev->mes);
if (r)
dev_err(adev->dev, "failed to reset legacy queue\n");
return r;
}
int amdgpu_mes_reset_user_queue(struct amdgpu_device *adev,
int queue_type,
unsigned int doorbell_index,
unsigned int xcc_id)
{
struct mes_reset_queue_input queue_input;
int r;
memset(&queue_input, 0, sizeof(queue_input));
queue_input.xcc_id = xcc_id;
queue_input.queue_type = queue_type;
queue_input.doorbell_offset = doorbell_index;
amdgpu_mes_lock(&adev->mes);
r = adev->mes.funcs->reset_hw_queue(&adev->mes, &queue_input);
amdgpu_mes_unlock(&adev->mes);
if (r)
dev_err(adev->dev, "failed to reset user queue\n");
return r;
}
int amdgpu_mes_get_hung_queue_db_array_size(struct amdgpu_device *adev)
{
return adev->mes.hung_queue_db_array_size;
}
int amdgpu_mes_detect_and_reset_hung_queues(struct amdgpu_device *adev,
int queue_type,
bool detect_only,
unsigned int *hung_db_num,
u32 *hung_db_array,
uint32_t xcc_id)
{
struct mes_detect_and_reset_queue_input input;
u32 *db_array = adev->mes.hung_queue_db_array_cpu_addr[xcc_id];
int hqd_info_offset = adev->mes.hung_queue_hqd_info_offset, r, i;
if (!hung_db_num || !hung_db_array)
return -EINVAL;
if ((queue_type != AMDGPU_RING_TYPE_GFX) &&
(queue_type != AMDGPU_RING_TYPE_COMPUTE) &&
(queue_type != AMDGPU_RING_TYPE_SDMA))
return -EINVAL;
/* Clear the doorbell array before detection */
memset(adev->mes.hung_queue_db_array_cpu_addr[xcc_id], AMDGPU_MES_INVALID_DB_OFFSET,
adev->mes.hung_queue_db_array_size * sizeof(u32));
input.queue_type = queue_type;
input.detect_only = detect_only;
input.xcc_id = xcc_id;
r = adev->mes.funcs->detect_and_reset_hung_queues(&adev->mes,
&input);
if (r && detect_only) {
dev_err(adev->dev, "Failed to detect hung queues\n");
return r;
}
*hung_db_num = 0;
/* MES passes hung queues' doorbell to driver */
for (i = 0; i < adev->mes.hung_queue_hqd_info_offset; i++) {
/* Finding hung queues where db_array[i] is a valid doorbell */
if (db_array[i] != AMDGPU_MES_INVALID_DB_OFFSET) {
hung_db_array[i] = db_array[i];
*hung_db_num += 1;
}
}
if (r && !(*hung_db_num)) {
dev_err(adev->dev, "Failed to detect and reset hung queues\n");
return r;
}
for (i = hqd_info_offset; i < hqd_info_offset + *hung_db_num; i++)
hung_db_array[i] = db_array[i];
return r;
}
uint32_t amdgpu_mes_rreg(struct amdgpu_device *adev, uint32_t reg,
uint32_t xcc_id)
{
struct mes_misc_op_input op_input;
int r, val = 0;
uint32_t addr_offset = 0;
uint64_t read_val_gpu_addr;
uint32_t *read_val_ptr;
if (amdgpu_wb_get(adev, &addr_offset)) {
dev_err(adev->dev, "critical bug! too many mes readers\n");
goto error;
}
read_val_gpu_addr = adev->wb.gpu_addr + (addr_offset * 4);
read_val_ptr = (uint32_t *)&adev->wb.wb[addr_offset];
op_input.xcc_id = xcc_id;
op_input.op = MES_MISC_OP_READ_REG;
op_input.read_reg.reg_offset = reg;
op_input.read_reg.buffer_addr = read_val_gpu_addr;
if (!adev->mes.funcs->misc_op) {
dev_err(adev->dev, "mes rreg is not supported!\n");
goto error;
}
amdgpu_mes_lock(&adev->mes);
r = adev->mes.funcs->misc_op(&adev->mes, &op_input);
amdgpu_mes_unlock(&adev->mes);
if (r)
dev_err(adev->dev, "failed to read reg (0x%x)\n", reg);
else
val = *(read_val_ptr);
error:
if (addr_offset)
amdgpu_wb_free(adev, addr_offset);
return val;
}
int amdgpu_mes_wreg(struct amdgpu_device *adev, uint32_t reg,
uint32_t val, uint32_t xcc_id)
{
struct mes_misc_op_input op_input;
int r;
op_input.xcc_id = xcc_id;
op_input.op = MES_MISC_OP_WRITE_REG;
op_input.write_reg.reg_offset = reg;
op_input.write_reg.reg_value = val;
if (!adev->mes.funcs->misc_op) {
dev_err(adev->dev, "mes wreg is not supported!\n");
r = -EINVAL;
goto error;
}
amdgpu_mes_lock(&adev->mes);
r = adev->mes.funcs->misc_op(&adev->mes, &op_input);
amdgpu_mes_unlock(&adev->mes);
if (r)
dev_err(adev->dev, "failed to write reg (0x%x)\n", reg);
error:
return r;
}
int amdgpu_mes_reg_write_reg_wait(struct amdgpu_device *adev,
uint32_t reg0, uint32_t reg1,
uint32_t ref, uint32_t mask,
uint32_t xcc_id)
{
struct mes_misc_op_input op_input;
int r;
op_input.xcc_id = xcc_id;
op_input.op = MES_MISC_OP_WRM_REG_WR_WAIT;
op_input.wrm_reg.reg0 = reg0;
op_input.wrm_reg.reg1 = reg1;
op_input.wrm_reg.ref = ref;
op_input.wrm_reg.mask = mask;
if (!adev->mes.funcs->misc_op) {
dev_err(adev->dev, "mes reg_write_reg_wait is not supported!\n");
r = -EINVAL;
goto error;
}
amdgpu_mes_lock(&adev->mes);
r = adev->mes.funcs->misc_op(&adev->mes, &op_input);
amdgpu_mes_unlock(&adev->mes);
if (r)
dev_err(adev->dev, "failed to reg_write_reg_wait\n");
error:
return r;
}
int amdgpu_mes_hdp_flush(struct amdgpu_device *adev)
{
uint32_t hdp_flush_req_offset, hdp_flush_done_offset;
struct amdgpu_ring *mes_ring;
uint32_t ref_and_mask = 0, reg_mem_engine = 0;
if (!adev->gfx.funcs->get_hdp_flush_mask) {
dev_err(adev->dev, "mes hdp flush is not supported.\n");
return -EINVAL;
}
mes_ring = &adev->mes.ring[0];
hdp_flush_req_offset = adev->nbio.funcs->get_hdp_flush_req_offset(adev);
hdp_flush_done_offset = adev->nbio.funcs->get_hdp_flush_done_offset(adev);
adev->gfx.funcs->get_hdp_flush_mask(mes_ring, &ref_and_mask, &reg_mem_engine);
return amdgpu_mes_reg_write_reg_wait(adev, hdp_flush_req_offset, hdp_flush_done_offset,
ref_and_mask, ref_and_mask, 0);
}
int amdgpu_mes_set_shader_debugger(struct amdgpu_device *adev,
uint64_t process_context_addr,
uint32_t spi_gdbg_per_vmid_cntl,
const uint32_t *tcp_watch_cntl,
uint32_t flags,
bool trap_en,
uint32_t xcc_id)
{
struct mes_misc_op_input op_input = {0};
int r;
if (!adev->mes.funcs->misc_op) {
dev_err(adev->dev,
"mes set shader debugger is not supported!\n");
return -EINVAL;
}
op_input.xcc_id = xcc_id;
op_input.op = MES_MISC_OP_SET_SHADER_DEBUGGER;
op_input.set_shader_debugger.process_context_addr = process_context_addr;
op_input.set_shader_debugger.flags.u32all = flags;
/* use amdgpu mes_flush_shader_debugger instead */
if (op_input.set_shader_debugger.flags.process_ctx_flush)
return -EINVAL;
op_input.set_shader_debugger.spi_gdbg_per_vmid_cntl = spi_gdbg_per_vmid_cntl;
memcpy(op_input.set_shader_debugger.tcp_watch_cntl, tcp_watch_cntl,
sizeof(op_input.set_shader_debugger.tcp_watch_cntl));
if (((adev->mes.sched_version & AMDGPU_MES_API_VERSION_MASK) >>
AMDGPU_MES_API_VERSION_SHIFT) >= 14)
op_input.set_shader_debugger.trap_en = trap_en;
amdgpu_mes_lock(&adev->mes);
r = adev->mes.funcs->misc_op(&adev->mes, &op_input);
if (r)
dev_err(adev->dev, "failed to set_shader_debugger\n");
amdgpu_mes_unlock(&adev->mes);
return r;
}
int amdgpu_mes_flush_shader_debugger(struct amdgpu_device *adev,
uint64_t process_context_addr,
uint32_t xcc_id)
{
struct mes_misc_op_input op_input = {0};
int r;
if (!adev->mes.funcs->misc_op) {
dev_err(adev->dev,
"mes flush shader debugger is not supported!\n");
return -EINVAL;
}
op_input.xcc_id = xcc_id;
op_input.op = MES_MISC_OP_SET_SHADER_DEBUGGER;
op_input.set_shader_debugger.process_context_addr = process_context_addr;
op_input.set_shader_debugger.flags.process_ctx_flush = true;
amdgpu_mes_lock(&adev->mes);
r = adev->mes.funcs->misc_op(&adev->mes, &op_input);
if (r)
dev_err(adev->dev, "failed to set_shader_debugger\n");
amdgpu_mes_unlock(&adev->mes);
return r;
}
uint32_t amdgpu_mes_get_aggregated_doorbell_index(struct amdgpu_device *adev,
enum amdgpu_mes_priority_level prio)
{
return adev->mes.aggregated_doorbells[prio];
}
int amdgpu_mes_init_microcode(struct amdgpu_device *adev, int pipe)
{
const struct mes_firmware_header_v1_0 *mes_hdr;
struct amdgpu_firmware_info *info;
char ucode_prefix[30];
char fw_name[50];
bool need_retry = false;
u32 *ucode_ptr;
int r;
amdgpu_ucode_ip_version_decode(adev, GC_HWIP, ucode_prefix,
sizeof(ucode_prefix));
if (adev->enable_uni_mes) {
snprintf(fw_name, sizeof(fw_name),
"amdgpu/%s_uni_mes.bin", ucode_prefix);
} else if (amdgpu_ip_version(adev, GC_HWIP, 0) >= IP_VERSION(11, 0, 0) &&
amdgpu_ip_version(adev, GC_HWIP, 0) < IP_VERSION(12, 0, 0)) {
snprintf(fw_name, sizeof(fw_name), "amdgpu/%s_mes%s.bin",
ucode_prefix,
pipe == AMDGPU_MES_SCHED_PIPE ? "_2" : "1");
need_retry = true;
} else {
snprintf(fw_name, sizeof(fw_name), "amdgpu/%s_mes%s.bin",
ucode_prefix,
pipe == AMDGPU_MES_SCHED_PIPE ? "" : "1");
}
r = amdgpu_ucode_request(adev, &adev->mes.fw[pipe], AMDGPU_UCODE_REQUIRED,
"%s", fw_name);
if (r && need_retry && pipe == AMDGPU_MES_SCHED_PIPE) {
dev_info(adev->dev, "try to fall back to %s_mes.bin\n", ucode_prefix);
r = amdgpu_ucode_request(adev, &adev->mes.fw[pipe],
AMDGPU_UCODE_REQUIRED,
"amdgpu/%s_mes.bin", ucode_prefix);
}
if (r)
goto out;
mes_hdr = (const struct mes_firmware_header_v1_0 *)
adev->mes.fw[pipe]->data;
adev->mes.uc_start_addr[pipe] =
le32_to_cpu(mes_hdr->mes_uc_start_addr_lo) |
((uint64_t)(le32_to_cpu(mes_hdr->mes_uc_start_addr_hi)) << 32);
adev->mes.data_start_addr[pipe] =
le32_to_cpu(mes_hdr->mes_data_start_addr_lo) |
((uint64_t)(le32_to_cpu(mes_hdr->mes_data_start_addr_hi)) << 32);
ucode_ptr = (u32 *)(adev->mes.fw[pipe]->data +
sizeof(union amdgpu_firmware_header));
adev->mes.fw_version[pipe] =
le32_to_cpu(ucode_ptr[24]) & AMDGPU_MES_VERSION_MASK;
if (adev->firmware.load_type == AMDGPU_FW_LOAD_PSP) {
int ucode, ucode_data;
if (pipe == AMDGPU_MES_SCHED_PIPE) {
ucode = AMDGPU_UCODE_ID_CP_MES;
ucode_data = AMDGPU_UCODE_ID_CP_MES_DATA;
} else {
ucode = AMDGPU_UCODE_ID_CP_MES1;
ucode_data = AMDGPU_UCODE_ID_CP_MES1_DATA;
}
info = &adev->firmware.ucode[ucode];
info->ucode_id = ucode;
info->fw = adev->mes.fw[pipe];
adev->firmware.fw_size +=
ALIGN(le32_to_cpu(mes_hdr->mes_ucode_size_bytes),
PAGE_SIZE);
info = &adev->firmware.ucode[ucode_data];
info->ucode_id = ucode_data;
info->fw = adev->mes.fw[pipe];
adev->firmware.fw_size +=
ALIGN(le32_to_cpu(mes_hdr->mes_ucode_data_size_bytes),
PAGE_SIZE);
}
return 0;
out:
amdgpu_ucode_release(&adev->mes.fw[pipe]);
return r;
}
void amdgpu_mes_validate_fw_version(struct amdgpu_device *adev)
{
u32 fw_from_ucode = adev->mes.fw_version[AMDGPU_MES_SCHED_PIPE];
u32 fw_from_reg = adev->mes.sched_version & AMDGPU_MES_VERSION_MASK;
if (fw_from_ucode != fw_from_reg)
dev_info(adev->dev,
"MES firmware reports incorrect version in ucode binary (0x%x vs 0x%x)\n",
fw_from_ucode, fw_from_reg);
}
bool amdgpu_mes_suspend_resume_all_supported(struct amdgpu_device *adev)
{
uint32_t mes_rev = adev->mes.sched_version & AMDGPU_MES_VERSION_MASK;
return ((amdgpu_ip_version(adev, GC_HWIP, 0) >= IP_VERSION(11, 0, 0) &&
amdgpu_ip_version(adev, GC_HWIP, 0) < IP_VERSION(12, 0, 0) &&
mes_rev >= 0x63) ||
amdgpu_ip_version(adev, GC_HWIP, 0) >= IP_VERSION(12, 0, 0));
}
bool amdgpu_mes_queue_reset_by_mes_supported(struct amdgpu_device *adev)
{
u32 ip_maj = IP_VERSION_MAJ(amdgpu_ip_version(adev, GC_HWIP, 0));
u32 ip_min = IP_VERSION_MIN(amdgpu_ip_version(adev, GC_HWIP, 0));
u32 mes_sched = adev->mes.sched_version & AMDGPU_MES_VERSION_MASK;
return (ip_maj == 11 && mes_sched >= 0x8c) ||
((ip_maj == 12 && ip_min == 0) && mes_sched >= 0x8d) ||
((ip_maj == 12 && ip_min == 1) && mes_sched >= 0x7b);
}
/* Fix me -- node_id is used to identify the correct MES instances in the future */
static int amdgpu_mes_set_enforce_isolation(struct amdgpu_device *adev,
uint32_t node_id, bool enable)
{
struct mes_misc_op_input op_input = {0};
int r;
op_input.op = MES_MISC_OP_CHANGE_CONFIG;
op_input.change_config.option.limit_single_process = enable ? 1 : 0;
if (!adev->mes.funcs->misc_op) {
dev_err(adev->dev, "mes change config is not supported!\n");
r = -EINVAL;
goto error;
}
amdgpu_mes_lock(&adev->mes);
r = adev->mes.funcs->misc_op(&adev->mes, &op_input);
amdgpu_mes_unlock(&adev->mes);
if (r)
dev_err(adev->dev, "failed to change_config.\n");
error:
return r;
}
int amdgpu_mes_update_enforce_isolation(struct amdgpu_device *adev)
{
int i, r = 0;
if (adev->enable_mes && adev->gfx.enable_cleaner_shader) {
mutex_lock(&adev->enforce_isolation_mutex);
for (i = 0; i < (adev->xcp_mgr ? adev->xcp_mgr->num_xcps : 1); i++) {
if (adev->enforce_isolation[i] == AMDGPU_ENFORCE_ISOLATION_ENABLE)
r |= amdgpu_mes_set_enforce_isolation(adev, i, true);
else
r |= amdgpu_mes_set_enforce_isolation(adev, i, false);
}
mutex_unlock(&adev->enforce_isolation_mutex);
}
return r;
}
/**
* amdgpu_mes_rs64mem_init - initialize RS64 local memory context arrays
*
* @mes: MES instance
*
* Returns 0 on success, negative errno on failure.
*/
int amdgpu_mes_rs64mem_init(struct amdgpu_mes *mes)
{
struct amdgpu_device *adev = container_of(mes, struct amdgpu_device, mes);
int r;
if (!mes->use_rs64mem)
return 0;
r = amdgpu_bo_create_kernel(adev, PAGE_SIZE, PAGE_SIZE,
AMDGPU_GEM_DOMAIN_GTT,
&mes->ctx_array_size_bo,
&mes->ctx_array_size_gpu_addr,
(void **)&mes->ctx_array_size_cpu_ptr);
if (r) {
dev_err(adev->dev,
"Failed to allocate ctx array size BO, r=%d\n", r);
return r;
}
memset(mes->ctx_array_size_cpu_ptr, 0, PAGE_SIZE);
return 0;
}
/**
* amdgpu_mes_rs64mem_fini - tear down RS64 local memory management
*
* @mes: MES instance
*/
void amdgpu_mes_rs64mem_fini(struct amdgpu_mes *mes)
{
if (mes->ctx_array_size_bo) {
amdgpu_bo_free_kernel(&mes->ctx_array_size_bo,
&mes->ctx_array_size_gpu_addr,
(void **)&mes->ctx_array_size_cpu_ptr);
}
bitmap_free(mes->proc_ctx_bitmap);
bitmap_free(mes->gang_ctx_bitmap);
mes->use_rs64mem = false;
}
/**
* amdgpu_mes_rs64mem_setup_bitmaps - allocate bitmaps after querying MES
*
* Called after QUERY_SCHEDULER_STATUS returns and MES has written
* the array sizes to the GPU buffer. Reads the sizes and allocates
* the tracking bitmaps.
*
* @mes: MES instance
*
* Returns 0 on success, negative errno on failure.
*/
int amdgpu_mes_rs64mem_setup_bitmaps(struct amdgpu_mes *mes)
{
struct amdgpu_device *adev = container_of(mes, struct amdgpu_device, mes);
if (!mes->use_rs64mem || !mes->ctx_array_size_cpu_ptr)
return 0;
/*
* MES FW wrote the sizes to the GPU buffer:
* ctx_array_size_cpu_ptr[0] = proc_ctx_array_size (N)
* ctx_array_size_cpu_ptr[1] = gang_ctx_array_size (M)
*/
mes->proc_ctx_array_size = mes->ctx_array_size_cpu_ptr[0];
mes->gang_ctx_array_size = mes->ctx_array_size_cpu_ptr[1];
/* Sanity check - MES FW typically returns N=50, M=300 */
if (mes->proc_ctx_array_size == 0 || mes->gang_ctx_array_size == 0) {
dev_warn(adev->dev,
"MES returned zero ctx array sizes (proc=%u, gang=%u), "
"disabling RS64 local memory optimization\n",
mes->proc_ctx_array_size, mes->gang_ctx_array_size);
mes->use_rs64mem = false;
return 0;
}
/* Cap to safety limits */
if (mes->proc_ctx_array_size > AMDGPU_MES_PROC_CTX_ARRAY_MAX)
mes->proc_ctx_array_size = AMDGPU_MES_PROC_CTX_ARRAY_MAX;
if (mes->gang_ctx_array_size > AMDGPU_MES_GANG_CTX_ARRAY_MAX)
mes->gang_ctx_array_size = AMDGPU_MES_GANG_CTX_ARRAY_MAX;
dev_info(adev->dev,
"MES RS64 local memory: proc_ctx_array_size:%u, "
"gang_ctx_array_size:%u\n",
mes->proc_ctx_array_size, mes->gang_ctx_array_size);
/* Allocate bitmaps */
mes->proc_ctx_bitmap = bitmap_zalloc(mes->proc_ctx_array_size,
GFP_KERNEL);
if (!mes->proc_ctx_bitmap) {
mes->use_rs64mem = false;
return -ENOMEM;
}
mes->gang_ctx_bitmap = bitmap_zalloc(mes->gang_ctx_array_size,
GFP_KERNEL);
if (!mes->gang_ctx_bitmap) {
bitmap_free(mes->proc_ctx_bitmap);
mes->proc_ctx_bitmap = NULL;
mes->use_rs64mem = false;
return -ENOMEM;
}
return 0;
}
/**
* amdgpu_mes_alloc_proc_ctx_index - allocate a process context slot
*
* @mes: MES instance
* @index: the allocated process context index
*
* Returns 0 on success, -ENOSPC if all slots are used, or
* -EOPNOTSUPP if RS64 local memory is unavailable.
*/
int amdgpu_mes_alloc_proc_ctx_index(struct amdgpu_mes *mes,
uint32_t *index)
{
unsigned long bit;
if (!mes->use_rs64mem || !mes->proc_ctx_bitmap)
return -EOPNOTSUPP;
amdgpu_mes_lock(mes);
bit = find_first_zero_bit(mes->proc_ctx_bitmap,
mes->proc_ctx_array_size);
if (bit >= mes->proc_ctx_array_size) {
amdgpu_mes_unlock(mes);
return -ENOSPC;
}
set_bit(bit, mes->proc_ctx_bitmap);
*index = (uint32_t)bit;
amdgpu_mes_unlock(mes);
return 0;
}
/**
* amdgpu_mes_free_proc_ctx_index - free a process context slot
*
* @mes: MES instance
* @index: process context index is released
*/
void amdgpu_mes_free_proc_ctx_index(struct amdgpu_mes *mes,
uint32_t index)
{
if (!mes->use_rs64mem || !mes->proc_ctx_bitmap)
return;
if (index >= mes->proc_ctx_array_size)
return;
amdgpu_mes_lock(mes);
clear_bit(index, mes->proc_ctx_bitmap);
amdgpu_mes_unlock(mes);
}
/**
* amdgpu_mes_alloc_gang_ctx_index - allocate a gang context slot
*
* @mes: MES instance
* @index: the allocated gang context index
*
* Returns 0 on success, -ENOSPC if all slots are used, or
* -EOPNOTSUPP if RS64 local memory is unavailable.
*/
int amdgpu_mes_alloc_gang_ctx_index(struct amdgpu_mes *mes,
uint32_t *index)
{
unsigned long bit;
if (!mes->use_rs64mem || !mes->gang_ctx_bitmap)
return -EOPNOTSUPP;
amdgpu_mes_lock(mes);
bit = find_first_zero_bit(mes->gang_ctx_bitmap,
mes->gang_ctx_array_size);
if (bit >= mes->gang_ctx_array_size) {
amdgpu_mes_unlock(mes);
return -ENOSPC;
}
set_bit(bit, mes->gang_ctx_bitmap);
*index = bit;
amdgpu_mes_unlock(mes);
return 0;
}
/**
* amdgpu_mes_free_gang_ctx_index - free a gang context slot
*
* @mes: MES instance
* @index: gang context index is released
*/
void amdgpu_mes_free_gang_ctx_index(struct amdgpu_mes *mes,
uint32_t index)
{
if (!mes->use_rs64mem || !mes->gang_ctx_bitmap)
return;
if (index >= mes->gang_ctx_array_size)
return;
amdgpu_mes_lock(mes);
clear_bit(index, mes->gang_ctx_bitmap);
amdgpu_mes_unlock(mes);
}
int amdgpu_mes_notify_unmap_queue(struct amdgpu_device *adev)
{
struct mes_misc_op_input op_input = {0};
int r;
op_input.op = MES_MISC_OP_NOTIFY_WORK_ON_UNMAPPED_QUEUE;
if (!adev->mes.funcs->misc_op) {
dev_err(adev->dev, "mes notify unmap queue is not supported!\n");
r = -EINVAL;
goto error;
}
amdgpu_mes_lock(&adev->mes);
r = adev->mes.funcs->misc_op(&adev->mes, &op_input);
amdgpu_mes_unlock(&adev->mes);
if (r)
dev_err(adev->dev, "failed to notify unmap queue.\n");
error:
return r;
}
/* Interval for notifying MES of work on unmapped queues during oversubscription */
#define AMDGPU_USERQ_UNMAP_NOTIFY_DELAY_US 50
static unsigned int amdgpu_mes_userq_hw_queue_num(struct amdgpu_device *adev)
{
int num_xcc = adev->gfx.xcc_mask ? NUM_XCC(adev->gfx.xcc_mask) : 1;
unsigned int n = bitmap_weight(adev->gfx.me.queue_bitmap, AMDGPU_MAX_GFX_QUEUES);
int i;
for (i = 0; i < num_xcc; i++)
n += bitmap_weight(adev->gfx.mec_bitmap[i].queue_bitmap,
AMDGPU_MAX_COMPUTE_QUEUES);
return n;
}
static void amdgpu_mes_userq_notify_unmap_work_handler(struct work_struct *work)
{
struct amdgpu_mes *mes = container_of(work, struct amdgpu_mes,
userq_notify_unmap_work.work);
struct amdgpu_device *adev = mes->adev;
amdgpu_mes_notify_unmap_queue(adev);
/* Re-arm if still oversubscribed */
if (atomic_read(&mes->userq_hw_queue_count) >
amdgpu_mes_userq_hw_queue_num(adev))
queue_delayed_work(system_wq, &mes->userq_notify_unmap_work,
usecs_to_jiffies(AMDGPU_USERQ_UNMAP_NOTIFY_DELAY_US));
}
/*
* Called after a GFX11 usermode queue is successfully mapped to MES.
* Starts the periodic unmap-notify timer if this pushed the device into
* HW queue oversubscription.
*/
void amdgpu_mes_userq_queue_mapped(struct amdgpu_device *adev)
{
if (amdgpu_sriov_vf(adev))
return;
if (!(amdgpu_ip_version(adev, GC_HWIP, 0) >= IP_VERSION(11, 0, 0) &&
amdgpu_ip_version(adev, GC_HWIP, 0) < IP_VERSION(12, 0, 0)))
return;
if (atomic_inc_return(&adev->mes.userq_hw_queue_count) >
amdgpu_mes_userq_hw_queue_num(adev))
queue_delayed_work(system_wq, &adev->mes.userq_notify_unmap_work,
usecs_to_jiffies(AMDGPU_USERQ_UNMAP_NOTIFY_DELAY_US));
}
/*
* Called after a GFX11 usermode queue is unmapped from MES. Stops the
* periodic unmap-notify timer once oversubscription clears.
*/
void amdgpu_mes_userq_queue_unmapped(struct amdgpu_device *adev)
{
if (amdgpu_sriov_vf(adev))
return;
if (!(amdgpu_ip_version(adev, GC_HWIP, 0) >= IP_VERSION(11, 0, 0) &&
amdgpu_ip_version(adev, GC_HWIP, 0) < IP_VERSION(12, 0, 0)))
return;
if (atomic_dec_return(&adev->mes.userq_hw_queue_count) <=
amdgpu_mes_userq_hw_queue_num(adev))
cancel_delayed_work(&adev->mes.userq_notify_unmap_work);
}
/*
* MES firmware debug extension ("mes_dbgext")
*
* The driver hands the MES a log buffer; the MES firmware writes text log items
* into it and the driver drains and prints them. The buffer is a single
* circular byte stream with a 16-byte header at offset 0:
*
* dword0 : rptr - driver reads/advances (with wrap)
* dword1 : wptr - firmware writes/advances (with wrap)
* dword2 : buffer_size - total buffer size in bytes (set by the driver)
* dword3 : header_size - size of this header / wrap-back offset (16)
*
* Item data starts at header_size and wraps from buffer_size back to
* header_size. Each log item begins with a 4-byte header (type, xor-signature,
* 16-bit length including the header) followed by NUL-free text.
*
* The driver owns rptr; the firmware owns wptr. The collection method is
* selectable via the mes_dbgext_options bit0: interrupt-driven (default - the
* FW raises its host interrupt per message, handled via the CP EOP path) or a
* polling kthread. Polling is also used automatically as a fallback when the
* ASIC has no IRQ-enable hook.
*
* NOTE: this requires an MES firmware image built with debug-extension support.
*/
#define MES_DBGEXT_MAX_ITEM_SIZE 2048
#define MES_DBGEXT_POLL_INTERVAL_MS 200
/* Default log-buffer size (KB) used when enabled at runtime with no size set. */
#define MES_DBGEXT_DEFAULT_KB 8
/* Log item text record types (must match mes_aux LOG__* in mes_dbgext.cpp). */
#define MES_DBGEXT_MSG 0x80
#define MES_DBGEXT_MSG_ASSERT 0x81
#define MES_DBGEXT_MSG_HALT 0x82
/*
* Log buffer option bits, must match the MES firmware's MES_DBGEXT_INIT_DATA.
* bit0 = trigger_interrupt_per_new_msg: when SET, the firmware raises the
* debug-message host interrupt after each message
* (mes_dbgext.cpp: "if (trigger_interrupt_per_new_msg) SendIntToHost()").
* When CLEAR, the firmware only writes the buffer and the driver must poll.
*/
#define MES_DBGEXT_OPT_TRIGGER_INT_PER_MSG (1ULL << 0)
/*
* MES firmware routes mes_dbgext through the shared "mes_aux" component, which
* uses a zone-partitioned buffer layout:
*
* offset 0: struct { u32 zone_count; struct {u32 offset, length}[zone_count]; }
*
* Each zone starts (at its byte offset from the buffer base) with a 16-byte
* LOG_ZONE_HEADER {rptr, wptr, buffer_size, header_size} whose rptr/wptr are
* relative to the zone start and wrap from buffer_size back to header_size.
* Every log item begins with an 8-byte header: type, xor-signature, a 24-bit
* big-endian length (total item size including the header, in bytes[2..4]),
* then level, seq and a reserved byte. Zone 0 carries human-readable text
* (printed to dmesg); other zones carry binary event/interrupt/api records
* (types 0x93..0x95) that are not text and are skipped.
*
* The driver seeds only the total buffer size in the first dword; the firmware
* (mes_aux InitializeLogBuffer) reads it and writes the zone header in place.
*/
#define MES_DBGEXT_ITEM_HDR_SIZE 8
#define MES_DBGEXT_MAX_ZONES 8
/*
* mes_aux option word (struct MesExtConfig) layout, which differs from the
* gfx11 MES_DBGEXT_INIT_DATA: bit0 is host_poll_msg (INVERTED sense - when set,
* the firmware does not raise the per-message interrupt), bit1 enables logging,
* and bit3 enables an internal write-back cache (left off for prompt delivery).
*/
#define MES_DBGEXT_AUX_OPT_HOST_POLL (1ULL << 0)
#define MES_DBGEXT_AUX_OPT_ENABLE_MES_LOG (1ULL << 1)
#define MES_DBGEXT_AUX_OPT_ENABLE_LOG_CACHE (1ULL << 3)
struct mes_dbgext_zone_info {
u32 offset;
u32 length;
};
struct mes_dbgext_zone_header {
u32 rptr;
u32 wptr;
u32 buffer_size;
u32 header_size;
};
/*
* Copy @n bytes out of the circular data region starting at byte offset @off,
* wrapping back to @hdr_size when @buffer_size is reached.
*/
static void mes_dbgext_buf_read(const u8 *buf, u32 buffer_size, u32 hdr_size,
u32 off, u8 *dst, u32 n)
{
while (n--) {
*dst++ = buf[off++];
if (off >= buffer_size)
off = hdr_size;
}
}
static void mes_dbgext_print_item(struct amdgpu_device *adev, int xcc,
u32 type, char *text)
{
size_t n = strlen(text);
char pfx[12] = "";
/* Normalize to exactly one trailing newline: the gfx11 firmware appends
* one to the text, the gfx12/mes_aux firmware does not.
*/
while (n && (text[n - 1] == '\n' || text[n - 1] == '\r'))
text[--n] = '\0';
/* Tag with the source XCC only when more than one is being logged, so
* single-XCC (gfx11/gfx12) output is unchanged.
*/
if (adev->mes.dbgext_num_xcc > 1)
snprintf(pfx, sizeof(pfx), " xcc%d", xcc);
switch (type) {
case MES_DBGEXT_MSG_ASSERT:
dev_err(adev->dev, "[mes_dbgext%s] ASSERT %s\n", pfx, text);
break;
case MES_DBGEXT_MSG:
dev_info(adev->dev, "[mes_dbgext%s] %s\n", pfx, text);
break;
default:
dev_warn(adev->dev, "[mes_dbgext%s] %s\n", pfx, text);
break;
}
}
/*
* Drain a single zone of the mes_aux buffer. @zbase points at the
* zone start (its LOG_ZONE_HEADER); rptr/wptr are relative to @zbase. @item is
* caller-provided scratch of at least MES_DBGEXT_MAX_ITEM_SIZE + 1 bytes.
*/
static void mes_dbgext_process_zone(struct amdgpu_device *adev, u8 *zbase,
u8 *item, int xcc)
{
struct mes_dbgext_zone_header *zh =
(struct mes_dbgext_zone_header *)zbase;
u32 rptr, wptr, buffer_size, hdr_size;
buffer_size = READ_ONCE(zh->buffer_size);
hdr_size = READ_ONCE(zh->header_size);
rptr = READ_ONCE(zh->rptr);
wptr = READ_ONCE(zh->wptr);
/* Nothing to do until the firmware has written a new message. */
if (rptr == wptr)
return;
/*
* Order the wptr load ahead of the item-body loads below. The firmware
* publishes an item by writing its body first and advancing wptr last;
* this barrier ensures we observe the body that wptr claims is present.
*/
dma_rmb();
if (hdr_size < sizeof(*zh) || buffer_size <= hdr_size ||
rptr < hdr_size || rptr >= buffer_size ||
wptr < hdr_size || wptr >= buffer_size)
return;
while (rptr != wptr) {
u8 hb[MES_DBGEXT_ITEM_HDR_SIZE];
u32 type, len;
mes_dbgext_buf_read(zbase, buffer_size, hdr_size, rptr,
hb, sizeof(hb));
type = hb[0];
len = ((u32)hb[2] << 16) | ((u32)hb[3] << 8) | hb[4];
/* sign = xor of all header bytes except the sign byte itself. */
if ((u8)(hb[0] ^ hb[2] ^ hb[3] ^ hb[4] ^ hb[5] ^ hb[6]) != hb[1] ||
len <= MES_DBGEXT_ITEM_HDR_SIZE ||
len > MES_DBGEXT_MAX_ITEM_SIZE ||
len > buffer_size - hdr_size) {
dev_dbg(adev->dev,
"mes_dbgext: bad item @%u (type 0x%x len %u), skipping to %u\n",
rptr, type, len, wptr);
rptr = wptr;
break;
}
/*
* Zones also carry binary event/interrupt/api records (types
* 0x93..0x95) whose payload is a NUL-terminated file name
* followed by raw struct bytes - not human-readable text. Only
* print the text record types (MSG/ASSERT/HALT); consume and
* skip everything else so the binary records' file-name prefix
* is not dumped to dmesg.
*/
if (type == MES_DBGEXT_MSG ||
type == MES_DBGEXT_MSG_ASSERT ||
type == MES_DBGEXT_MSG_HALT) {
mes_dbgext_buf_read(zbase, buffer_size, hdr_size, rptr,
item, len);
item[len] = '\0';
mes_dbgext_print_item(adev, xcc, type,
(char *)item + MES_DBGEXT_ITEM_HDR_SIZE);
}
rptr += len;
if (rptr >= buffer_size)
rptr -= (buffer_size - hdr_size);
if (rptr < hdr_size || rptr >= buffer_size) {
rptr = wptr;
break;
}
}
/*
* Ensure all item-body reads complete before we publish the new rptr;
* otherwise the firmware may observe the advanced rptr and reuse buffer
* space we have not finished reading.
*/
dma_wmb();
WRITE_ONCE(zh->rptr, rptr);
}
/*
* Drain one per-XCC region: parse its zone table (a zone_count dword followed
* by per-zone {offset,length} descriptors) and drain each zone. @base points
* at the region start; @xcc tags the output.
*/
static void mes_dbgext_process_region(struct amdgpu_device *adev, u8 *base, int xcc)
{
struct amdgpu_mes *mes = &adev->mes;
u32 zone_count, hdr_len, z;
u8 *item;
if (!base)
return;
zone_count = READ_ONCE(*(u32 *)base);
if (zone_count == 0 || zone_count > MES_DBGEXT_MAX_ZONES)
return;
hdr_len = sizeof(u32) + zone_count * sizeof(struct mes_dbgext_zone_info);
if (hdr_len >= mes->dbgext_log_size)
return;
/* Scratch to linearize a (possibly wrapped) item; +1 for NUL. */
item = kmalloc(MES_DBGEXT_MAX_ITEM_SIZE + 1, GFP_KERNEL);
if (!item)
return;
for (z = 0; z < zone_count; z++) {
struct mes_dbgext_zone_info *zi =
(struct mes_dbgext_zone_info *)(base + sizeof(u32)) + z;
u32 zoff = READ_ONCE(zi->offset);
u32 zlen = READ_ONCE(zi->length);
/* Skip a bogus zone descriptor rather than the whole buffer. */
if (zoff < hdr_len || zoff > mes->dbgext_log_size ||
zlen < sizeof(struct mes_dbgext_zone_header) ||
zlen > mes->dbgext_log_size - zoff)
continue;
mes_dbgext_process_zone(adev, base + zoff, item, xcc);
}
kfree(item);
}
static void mes_dbgext_process_all(struct amdgpu_device *adev)
{
struct amdgpu_mes *mes = &adev->mes;
u32 num_xcc = mes->dbgext_num_xcc ? mes->dbgext_num_xcc : 1;
u8 *buf = mes->dbgext_log_cpu_addr;
u32 xcc;
if (!buf)
return;
/*
* The buffer holds num_xcc back-to-back per-XCC regions, each
* dbgext_log_size bytes. Drain each region and tag its output with the
* source XCC (single-XCC ASICs have exactly one region).
*/
for (xcc = 0; xcc < num_xcc; xcc++) {
u8 *base = buf + xcc * mes->dbgext_log_size;
mes_dbgext_process_region(adev, base, xcc);
}
}
static int amdgpu_mes_dbgext_reader(void *param)
{
struct amdgpu_device *adev = param;
while (!kthread_should_stop()) {
mes_dbgext_process_all(adev);
msleep_interruptible(MES_DBGEXT_POLL_INTERVAL_MS);
}
return 0;
}
/* Runs in process context; does the (sleepable) buffer drain. */
static void amdgpu_mes_dbgext_work_fn(struct work_struct *work)
{
struct amdgpu_mes *mes = container_of(work, struct amdgpu_mes,
dbgext_work);
struct amdgpu_device *adev =
container_of(mes, struct amdgpu_device, mes);
mes_dbgext_process_all(adev);
}
/*
* Called from the MES interrupt handler (hard/soft IRQ context). Decodes the
* MES->host interrupt type and, for a debug-message notification, schedules the
* drain on a workqueue (the parser sleeps / allocates, so it cannot run here).
*/
void amdgpu_mes_dbgext_notify(struct amdgpu_device *adev, u32 context_data)
{
struct amdgpu_mes *mes = &adev->mes;
/*
* Caller has already matched MES_DBGMSG (type 7 in bits 31:26 of the
* IH context dword). Kick the drain; it is a no-op when the log has
* no new data.
*/
/*
* Only the interrupt path may schedule the drain work. In polling mode
* the kthread owns rptr; honoring a stale/spurious interrupt here would
* let the work item and the kthread drain concurrently and race on rptr.
*/
if (!READ_ONCE(mes->dbgext_use_irq))
return;
if (READ_ONCE(mes->dbgext_log_cpu_addr))
schedule_work(&mes->dbgext_work);
}
static int amdgpu_mes_dbgext_setup_fw(struct amdgpu_device *adev, u32 xcc_id,
u64 log_buffer_mc_addr, u64 log_options)
{
struct amdgpu_mes *mes = &adev->mes;
struct mes_misc_op_input op_input = {0};
int r;
if (!mes->funcs || !mes->funcs->misc_op)
return -EINVAL;
op_input.xcc_id = xcc_id;
op_input.op = MES_MISC_OP_SETUP_MES_DBGEXT;
op_input.setup_mes_dbgext.log_buffer_mc_addr = log_buffer_mc_addr;
op_input.setup_mes_dbgext.log_options = log_options;
amdgpu_mes_lock(mes);
r = mes->funcs->misc_op(mes, &op_input);
amdgpu_mes_unlock(mes);
return r;
}
static int amdgpu_mes_dbgext_start_locked(struct amdgpu_device *adev)
{
struct amdgpu_mes *mes = &adev->mes;
struct task_struct *reader;
bool use_irq;
u64 fw_options;
u32 size;
u32 req_kb;
u32 num_xcc, xcc;
int r;
/*
* Effective buffer size (KB): the module parameter wins (so a boot-time
* request is honored), otherwise use the runtime-requested size set by
* the debugfs on/off switch. Zero means the feature is off.
*/
req_kb = amdgpu_mes_dbgext_buffer_size ?
amdgpu_mes_dbgext_buffer_size : mes->dbgext_runtime_kb;
if (!req_kb)
return 0;
/*
* Already armed for this hw bring-up. hw_init() can run start() more
* than once (e.g. kiq_hw_init() -> hw_init(), then the MES IP block's
* own hw_init()); only the first should arm. A post-suspend resume
* comes back here with the buffer kept but dbgext_active cleared by
* stop(), so re-arm runs below.
*/
if (mes->dbgext_active)
return 0;
if (!mes->funcs || !mes->funcs->misc_op) {
dev_warn(adev->dev, "mes_dbgext not supported by this MES\n");
return 0;
}
/*
* Log every XCC's MES firmware. Each XCC runs its own MES and gets its
* own per-XCC region within one buffer (see dbgext_num_xcc); single-XCC
* ASICs (gfx11/gfx12) collapse to a single region.
*/
num_xcc = adev->gfx.xcc_mask ? NUM_XCC(adev->gfx.xcc_mask) : 1;
mes->dbgext_num_xcc = num_xcc;
if (!mes->dbgext_log_gpu_obj) {
/* Clamp to a sane range (4 KB .. 1 MB), per XCC. */
size = clamp(req_kb, 4U, 1024U);
size = ALIGN((u32)size * SZ_1K, PAGE_SIZE);
/*
* The mes_aux firmware splits the buffer into per-thread zones
* and rejects buffers that are not larger than its 4 KB minimum,
* so give it at least 8 KB.
*/
if (size < SZ_8K)
size = SZ_8K;
/* One buffer holds num_xcc back-to-back per-XCC regions. */
r = amdgpu_bo_create_kernel(adev, size * num_xcc, PAGE_SIZE,
AMDGPU_GEM_DOMAIN_GTT,
&mes->dbgext_log_gpu_obj,
&mes->dbgext_log_gpu_addr,
&mes->dbgext_log_cpu_addr);
if (r) {
dev_warn(adev->dev,
"failed to create mes_dbgext log buffer (%d)\n", r);
return r;
}
mes->dbgext_log_size = size;
INIT_WORK(&mes->dbgext_work, amdgpu_mes_dbgext_work_fn);
} else {
/*
* Resume: the log buffer is kept allocated across a suspend/
* resume cycle (freeing a kernel BO while suspended is not
* allowed), so reuse it and just re-arm the firmware below.
*/
size = mes->dbgext_log_size;
}
/*
* (Re)initialize each per-XCC region's header. On resume the MES was
* reset and re-lays its header, and the driver's rptr/wptr must restart
* clean. Clear the whole buffer once, then seed every region.
*/
memset(mes->dbgext_log_cpu_addr, 0, size * num_xcc);
for (xcc = 0; xcc < num_xcc; xcc++) {
u8 *base = (u8 *)mes->dbgext_log_cpu_addr + xcc * size;
/*
* mes_aux: seed only the total region size in the first dword.
* The firmware (InitializeLogBuffer) reads it during setup and
* lays out its own zone-partitioned header in place.
*/
*(u32 *)base = size;
}
/*
* The options word is passed verbatim to the firmware. bit0
* (trigger_interrupt_per_new_msg) selects the collection method:
* set -> firmware raises an interrupt per message; the driver
* collects them via the CP EOP path (gfx_v11_0_eop_irq).
* clear -> firmware only writes the buffer; the driver polls with a
* kthread.
* If interrupts are requested but the ASIC has no CP enable hook, fall
* back to polling and clear the bit so the firmware does not raise an
* interrupt nobody will service.
*/
mes->dbgext_log_options = (u32)amdgpu_mes_dbgext_options;
use_irq = (mes->dbgext_log_options & MES_DBGEXT_OPT_TRIGGER_INT_PER_MSG) &&
mes->funcs->enable_dbgext_irq;
if (!use_irq)
mes->dbgext_log_options &= ~MES_DBGEXT_OPT_TRIGGER_INT_PER_MSG;
mes->dbgext_use_irq = use_irq;
/*
* Translate the options to the mes_aux firmware layout: enable logging
* (bit1), select interrupt vs polling via host_poll_msg (bit0, inverted
* sense), and leave the write-back cache off for prompt delivery.
*/
fw_options = MES_DBGEXT_AUX_OPT_ENABLE_MES_LOG;
if (!use_irq)
fw_options |= MES_DBGEXT_AUX_OPT_HOST_POLL;
/*
* Mark the feature active *before* arming the firmware. setup_fw()
* below makes the firmware emit its first ("enabled") message and, in
* interrupt mode, immediately raise the host interrupt for it. The EOP
* handler drops the drain unless dbgext_active is already set, so if it
* were set only after setup_fw() the enable interrupt would race the
* handler and be lost - and on emulation no further message/interrupt
* follows to recover it. Cleared again on the error paths below.
*/
mes->dbgext_active = true;
/* Enable delivery of the MES host interrupt at the CP (process ctx). */
if (use_irq)
mes->funcs->enable_dbgext_irq(mes, true);
/* Point each XCC's MES firmware at its own per-XCC region. */
for (xcc = 0; xcc < num_xcc; xcc++) {
r = amdgpu_mes_dbgext_setup_fw(adev, xcc,
mes->dbgext_log_gpu_addr + xcc * size,
fw_options);
if (r) {
dev_err(adev->dev,
"failed to setup mes_dbgext in FW on xcc%u (%d)\n",
xcc, r);
/* Detach the XCCs already armed before bailing. */
while (xcc--)
amdgpu_mes_dbgext_setup_fw(adev, xcc, 0, 0);
goto err_disable;
}
}
if (!use_irq) {
/*
* Never leak a reader: if one is somehow still around (a missed
* stop()), reap it before creating a new one so it cannot outlive
* the module.
*/
if (WARN_ON(mes->dbgext_reader)) {
kthread_stop(mes->dbgext_reader);
mes->dbgext_reader = NULL;
}
reader = kthread_run(amdgpu_mes_dbgext_reader, adev,
"amdgpu_mes_dbgext");
if (IS_ERR(reader)) {
r = PTR_ERR(reader);
dev_err(adev->dev,
"failed to start mes_dbgext reader (%d)\n", r);
goto err_fw_detach;
}
mes->dbgext_reader = reader;
}
dev_info(adev->dev,
"mes_dbgext enabled: %u KB x %u xcc @ 0x%llx (%s, fw options 0x%llx)\n",
size / SZ_1K, num_xcc, mes->dbgext_log_gpu_addr,
use_irq ? "interrupt" : "polling", fw_options);
/*
* In interrupt mode the firmware has already written its "enabled"
* message (and raised the one-shot enable interrupt) during setup_fw()
* above. Kick an explicit drain now so that message is collected even
* if that interrupt was missed, and so the log is not left waiting for
* the next firmware message - which, on emulation, may never come.
*/
if (use_irq)
amdgpu_mes_dbgext_notify(adev, 0);
return 0;
err_fw_detach:
for (xcc = 0; xcc < num_xcc; xcc++)
amdgpu_mes_dbgext_setup_fw(adev, xcc, 0, 0);
err_disable:
/* Undo the early arming done before setup_fw(). */
mes->dbgext_active = false;
if (use_irq)
mes->funcs->enable_dbgext_irq(mes, false);
mes->dbgext_use_irq = false;
/* Never free a kernel BO while suspended; keep it for the next start. */
if (!adev->in_suspend) {
amdgpu_bo_free_kernel(&mes->dbgext_log_gpu_obj,
&mes->dbgext_log_gpu_addr,
&mes->dbgext_log_cpu_addr);
mes->dbgext_log_size = 0;
}
return r;
}
int amdgpu_mes_dbgext_start(struct amdgpu_device *adev)
{
int r;
mutex_lock(&adev->mes.dbgext_lock);
r = amdgpu_mes_dbgext_start_locked(adev);
mutex_unlock(&adev->mes.dbgext_lock);
return r;
}
static void amdgpu_mes_dbgext_stop_locked(struct amdgpu_device *adev)
{
struct amdgpu_mes *mes = &adev->mes;
mes->dbgext_active = false;
/*
* Tear down the reader kthread and the host interrupt first, and do so
* independently of the buffer state. The kthread runs code that lives
* in this module, so it must never be left alive past module unload -
* otherwise it faults on an instruction fetch once the module text is
* freed. A second stop() in a teardown/reset sequence, or one reached
* after the buffer was already freed on an error path, must still be
* able to reap a stale reader; hence this runs before the buffer guard.
*/
if (mes->dbgext_reader) {
kthread_stop(mes->dbgext_reader);
mes->dbgext_reader = NULL;
}
if (mes->dbgext_use_irq) {
mes->funcs->enable_dbgext_irq(mes, false);
mes->dbgext_use_irq = false;
}
if (!mes->dbgext_log_gpu_obj)
return;
/*
* Detach the firmware from the buffer before we free it, on every XCC we
* armed. Skip this across a suspend: the MES is being torn down anyway,
* the buffer is kept for resume, and submitting a packet on the suspend
* path is unnecessary.
*/
if (!adev->in_suspend) {
u32 num_xcc = mes->dbgext_num_xcc ? mes->dbgext_num_xcc : 1;
u32 xcc;
for (xcc = 0; xcc < num_xcc; xcc++)
amdgpu_mes_dbgext_setup_fw(adev, xcc, 0, 0);
}
/* Make sure no drain work is still touching the buffer. */
cancel_work_sync(&mes->dbgext_work);
/* Drain anything the firmware wrote before we stopped. */
mes_dbgext_process_all(adev);
/*
* Keep the buffer allocated across a suspend/resume cycle (freeing a
* kernel BO while suspended is not allowed - it trips a WARN in
* amdgpu_bo_free_kernel()); only free it on real teardown.
*/
if (adev->in_suspend)
return;
amdgpu_bo_free_kernel(&mes->dbgext_log_gpu_obj,
&mes->dbgext_log_gpu_addr,
&mes->dbgext_log_cpu_addr);
mes->dbgext_log_size = 0;
}
void amdgpu_mes_dbgext_stop(struct amdgpu_device *adev)
{
mutex_lock(&adev->mes.dbgext_lock);
amdgpu_mes_dbgext_stop_locked(adev);
mutex_unlock(&adev->mes.dbgext_lock);
}
#if defined(CONFIG_DEBUG_FS)
static int amdgpu_debugfs_mes_event_log_show(struct seq_file *m, void *unused)
{
struct amdgpu_device *adev = m->private;
uint32_t *mem = (uint32_t *)(adev->mes.event_log_cpu_addr);
seq_hex_dump(m, "", DUMP_PREFIX_OFFSET, 32, 4,
mem, adev->mes.event_log_size, false);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(amdgpu_debugfs_mes_event_log);
/*
* Runtime on/off switch for the MES firmware debug extension.
*
* cat <debugfs>/amdgpu_mes_dbgext -> current state / size / mode
* echo 1 > <debugfs>/amdgpu_mes_dbgext -> enable
* echo 0 > <debugfs>/amdgpu_mes_dbgext -> disable
*
* When enabled with no boot-time size (mes_dbgext_buffer_size=0) a default
* buffer size is used; the collection method still follows mes_dbgext_options.
*/
static int amdgpu_debugfs_mes_dbgext_show(struct seq_file *m, void *unused)
{
struct amdgpu_device *adev = m->private;
struct amdgpu_mes *mes = &adev->mes;
mutex_lock(&mes->dbgext_lock);
seq_printf(m, "state: %s\n", mes->dbgext_active ? "on" : "off");
seq_printf(m, "size: %u KB x %u xcc\n", mes->dbgext_log_size / SZ_1K,
mes->dbgext_num_xcc ? mes->dbgext_num_xcc : 1);
seq_printf(m, "mode: %s\n",
mes->dbgext_use_irq ? "interrupt" : "polling");
seq_printf(m, "options: 0x%llx\n", mes->dbgext_log_options);
mutex_unlock(&mes->dbgext_lock);
return 0;
}
static int amdgpu_debugfs_mes_dbgext_open(struct inode *inode, struct file *file)
{
return single_open(file, amdgpu_debugfs_mes_dbgext_show,
inode->i_private);
}
static ssize_t amdgpu_debugfs_mes_dbgext_write(struct file *file,
const char __user *buf,
size_t count, loff_t *ppos)
{
struct amdgpu_device *adev =
((struct seq_file *)file->private_data)->private;
struct amdgpu_mes *mes = &adev->mes;
bool enable;
int r;
r = kstrtobool_from_user(buf, count, &enable);
if (r)
return r;
if (!mes->funcs || !mes->funcs->misc_op)
return -EOPNOTSUPP;
mutex_lock(&mes->dbgext_lock);
if (enable) {
mes->dbgext_runtime_kb = amdgpu_mes_dbgext_buffer_size ?
amdgpu_mes_dbgext_buffer_size : MES_DBGEXT_DEFAULT_KB;
r = amdgpu_mes_dbgext_start_locked(adev);
} else {
amdgpu_mes_dbgext_stop_locked(adev);
mes->dbgext_runtime_kb = 0;
r = 0;
}
mutex_unlock(&mes->dbgext_lock);
return r ? r : count;
}
static const struct file_operations amdgpu_debugfs_mes_dbgext_fops = {
.owner = THIS_MODULE,
.open = amdgpu_debugfs_mes_dbgext_open,
.read = seq_read,
.write = amdgpu_debugfs_mes_dbgext_write,
.llseek = seq_lseek,
.release = single_release,
};
#endif
void amdgpu_debugfs_mes_init(struct amdgpu_device *adev)
{
#if defined(CONFIG_DEBUG_FS)
struct drm_minor *minor = adev_to_drm(adev)->primary;
struct dentry *root = minor->debugfs_root;
if (!adev->enable_mes)
return;
if (amdgpu_mes_log_enable)
debugfs_create_file("amdgpu_mes_event_log", 0444, root,
adev, &amdgpu_debugfs_mes_event_log_fops);
debugfs_create_file("amdgpu_mes_dbgext", 0644, root,
adev, &amdgpu_debugfs_mes_dbgext_fops);
#endif
}