root/drivers/accel/amdxdna/aie2_error.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2023-2024, Advanced Micro Devices, Inc.
 */

#include <drm/drm_cache.h>
#include <drm/drm_device.h>
#include <drm/drm_print.h>
#include <drm/gpu_scheduler.h>
#include <linux/dma-mapping.h>
#include <linux/kthread.h>
#include <linux/kernel.h>

#include "aie.h"
#include "aie2_msg_priv.h"
#include "aie2_pci.h"
#include "amdxdna_error.h"
#include "amdxdna_mailbox.h"
#include "amdxdna_pci_drv.h"

struct async_event {
        struct amdxdna_dev_hdl          *ndev;
        struct async_event_msg_resp     resp;
        struct workqueue_struct         *wq;
        struct work_struct              work;
        u8                              *buf;
        dma_addr_t                      addr;
        u32                             size;
};

struct async_events {
        struct workqueue_struct         *wq;
        u8                              *buf;
        dma_addr_t                      addr;
        u32                             size;
        u32                             event_cnt;
        struct async_event              event[] __counted_by(event_cnt);
};

/*
 * Below enum, struct and lookup tables are porting from XAIE util header file.
 *
 * Below data is defined by AIE device and it is used for decode error message
 * from the device.
 */

enum aie_module_type {
        AIE_MEM_MOD = 0,
        AIE_CORE_MOD,
        AIE_PL_MOD,
        AIE_UNKNOWN_MOD,
};

enum aie_error_category {
        AIE_ERROR_SATURATION = 0,
        AIE_ERROR_FP,
        AIE_ERROR_STREAM,
        AIE_ERROR_ACCESS,
        AIE_ERROR_BUS,
        AIE_ERROR_INSTRUCTION,
        AIE_ERROR_ECC,
        AIE_ERROR_LOCK,
        AIE_ERROR_DMA,
        AIE_ERROR_MEM_PARITY,
        /* Unknown is not from XAIE, added for better category */
        AIE_ERROR_UNKNOWN,
};

/* Don't pack, unless XAIE side changed */
struct aie_error {
        __u8                    row;
        __u8                    col;
        __u32                   mod_type;
        __u8                    event_id;
};

struct aie_err_info {
        u32                     err_cnt;
        u32                     ret_code;
        u32                     rsvd;
        struct aie_error        payload[] __counted_by(err_cnt);
};

struct aie_event_category {
        u8                      event_id;
        enum aie_error_category category;
};

#define EVENT_CATEGORY(id, cat) { id, cat }
static const struct aie_event_category aie_ml_mem_event_cat[] = {
        EVENT_CATEGORY(88U,  AIE_ERROR_ECC),
        EVENT_CATEGORY(90U,  AIE_ERROR_ECC),
        EVENT_CATEGORY(91U,  AIE_ERROR_MEM_PARITY),
        EVENT_CATEGORY(92U,  AIE_ERROR_MEM_PARITY),
        EVENT_CATEGORY(93U,  AIE_ERROR_MEM_PARITY),
        EVENT_CATEGORY(94U,  AIE_ERROR_MEM_PARITY),
        EVENT_CATEGORY(95U,  AIE_ERROR_MEM_PARITY),
        EVENT_CATEGORY(96U,  AIE_ERROR_MEM_PARITY),
        EVENT_CATEGORY(97U,  AIE_ERROR_DMA),
        EVENT_CATEGORY(98U,  AIE_ERROR_DMA),
        EVENT_CATEGORY(99U,  AIE_ERROR_DMA),
        EVENT_CATEGORY(100U, AIE_ERROR_DMA),
        EVENT_CATEGORY(101U, AIE_ERROR_LOCK),
};

static const struct aie_event_category aie_ml_core_event_cat[] = {
        EVENT_CATEGORY(55U, AIE_ERROR_ACCESS),
        EVENT_CATEGORY(56U, AIE_ERROR_STREAM),
        EVENT_CATEGORY(57U, AIE_ERROR_STREAM),
        EVENT_CATEGORY(58U, AIE_ERROR_BUS),
        EVENT_CATEGORY(59U, AIE_ERROR_INSTRUCTION),
        EVENT_CATEGORY(60U, AIE_ERROR_ACCESS),
        EVENT_CATEGORY(62U, AIE_ERROR_ECC),
        EVENT_CATEGORY(64U, AIE_ERROR_ECC),
        EVENT_CATEGORY(65U, AIE_ERROR_ACCESS),
        EVENT_CATEGORY(66U, AIE_ERROR_ACCESS),
        EVENT_CATEGORY(67U, AIE_ERROR_LOCK),
        EVENT_CATEGORY(70U, AIE_ERROR_INSTRUCTION),
        EVENT_CATEGORY(71U, AIE_ERROR_STREAM),
        EVENT_CATEGORY(72U, AIE_ERROR_BUS),
};

static const struct aie_event_category aie_ml_mem_tile_event_cat[] = {
        EVENT_CATEGORY(130U, AIE_ERROR_ECC),
        EVENT_CATEGORY(132U, AIE_ERROR_ECC),
        EVENT_CATEGORY(133U, AIE_ERROR_DMA),
        EVENT_CATEGORY(134U, AIE_ERROR_DMA),
        EVENT_CATEGORY(135U, AIE_ERROR_STREAM),
        EVENT_CATEGORY(136U, AIE_ERROR_STREAM),
        EVENT_CATEGORY(137U, AIE_ERROR_STREAM),
        EVENT_CATEGORY(138U, AIE_ERROR_BUS),
        EVENT_CATEGORY(139U, AIE_ERROR_LOCK),
};

static const struct aie_event_category aie_ml_shim_tile_event_cat[] = {
        EVENT_CATEGORY(64U, AIE_ERROR_BUS),
        EVENT_CATEGORY(65U, AIE_ERROR_STREAM),
        EVENT_CATEGORY(66U, AIE_ERROR_STREAM),
        EVENT_CATEGORY(67U, AIE_ERROR_BUS),
        EVENT_CATEGORY(68U, AIE_ERROR_BUS),
        EVENT_CATEGORY(69U, AIE_ERROR_BUS),
        EVENT_CATEGORY(70U, AIE_ERROR_BUS),
        EVENT_CATEGORY(71U, AIE_ERROR_BUS),
        EVENT_CATEGORY(72U, AIE_ERROR_DMA),
        EVENT_CATEGORY(73U, AIE_ERROR_DMA),
        EVENT_CATEGORY(74U, AIE_ERROR_LOCK),
};

static const enum amdxdna_error_num aie_cat_err_num_map[] = {
        [AIE_ERROR_SATURATION] = AMDXDNA_ERROR_NUM_AIE_SATURATION,
        [AIE_ERROR_FP] = AMDXDNA_ERROR_NUM_AIE_FP,
        [AIE_ERROR_STREAM] = AMDXDNA_ERROR_NUM_AIE_STREAM,
        [AIE_ERROR_ACCESS] = AMDXDNA_ERROR_NUM_AIE_ACCESS,
        [AIE_ERROR_BUS] = AMDXDNA_ERROR_NUM_AIE_BUS,
        [AIE_ERROR_INSTRUCTION] = AMDXDNA_ERROR_NUM_AIE_INSTRUCTION,
        [AIE_ERROR_ECC] = AMDXDNA_ERROR_NUM_AIE_ECC,
        [AIE_ERROR_LOCK] = AMDXDNA_ERROR_NUM_AIE_LOCK,
        [AIE_ERROR_DMA] = AMDXDNA_ERROR_NUM_AIE_DMA,
        [AIE_ERROR_MEM_PARITY] = AMDXDNA_ERROR_NUM_AIE_MEM_PARITY,
        [AIE_ERROR_UNKNOWN] = AMDXDNA_ERROR_NUM_UNKNOWN,
};

static_assert(ARRAY_SIZE(aie_cat_err_num_map) == AIE_ERROR_UNKNOWN + 1);

static const enum amdxdna_error_module aie_err_mod_map[] = {
        [AIE_MEM_MOD] = AMDXDNA_ERROR_MODULE_AIE_MEMORY,
        [AIE_CORE_MOD] = AMDXDNA_ERROR_MODULE_AIE_CORE,
        [AIE_PL_MOD] = AMDXDNA_ERROR_MODULE_AIE_PL,
        [AIE_UNKNOWN_MOD] = AMDXDNA_ERROR_MODULE_UNKNOWN,
};

static_assert(ARRAY_SIZE(aie_err_mod_map) == AIE_UNKNOWN_MOD + 1);

static enum aie_error_category
aie_get_error_category(u8 row, u8 event_id, enum aie_module_type mod_type)
{
        const struct aie_event_category *lut;
        int num_entry;
        int i;

        switch (mod_type) {
        case AIE_PL_MOD:
                lut = aie_ml_shim_tile_event_cat;
                num_entry = ARRAY_SIZE(aie_ml_shim_tile_event_cat);
                break;
        case AIE_CORE_MOD:
                lut = aie_ml_core_event_cat;
                num_entry = ARRAY_SIZE(aie_ml_core_event_cat);
                break;
        case AIE_MEM_MOD:
                if (row == 1) {
                        lut = aie_ml_mem_tile_event_cat;
                        num_entry = ARRAY_SIZE(aie_ml_mem_tile_event_cat);
                } else {
                        lut = aie_ml_mem_event_cat;
                        num_entry = ARRAY_SIZE(aie_ml_mem_event_cat);
                }
                break;
        default:
                return AIE_ERROR_UNKNOWN;
        }

        for (i = 0; i < num_entry; i++) {
                if (event_id != lut[i].event_id)
                        continue;

                if (lut[i].category > AIE_ERROR_UNKNOWN)
                        return AIE_ERROR_UNKNOWN;

                return lut[i].category;
        }

        return AIE_ERROR_UNKNOWN;
}

static void aie2_update_last_async_error(struct amdxdna_dev_hdl *ndev, void *err_info, u32 num_err)
{
        struct aie_error *errs = err_info;
        enum amdxdna_error_module err_mod;
        enum aie_error_category aie_err;
        enum amdxdna_error_num err_num;
        struct aie_error *last_err;

        last_err = &errs[num_err - 1];
        if (last_err->mod_type >= AIE_UNKNOWN_MOD) {
                err_num = aie_cat_err_num_map[AIE_ERROR_UNKNOWN];
                err_mod = aie_err_mod_map[AIE_UNKNOWN_MOD];
        } else {
                aie_err = aie_get_error_category(last_err->row,
                                                 last_err->event_id,
                                                 last_err->mod_type);
                err_num = aie_cat_err_num_map[aie_err];
                err_mod = aie_err_mod_map[last_err->mod_type];
        }

        ndev->last_async_err.err_code = AMDXDNA_ERROR_ENCODE(err_num, err_mod);
        ndev->last_async_err.ts_us = ktime_to_us(ktime_get_real());
        ndev->last_async_err.ex_err_code = AMDXDNA_EXTRA_ERR_ENCODE(last_err->row, last_err->col);
}

static u32 aie2_error_backtrack(struct amdxdna_dev_hdl *ndev, void *err_info, u32 num_err)
{
        struct aie_error *errs = err_info;
        u32 err_col = 0; /* assume that AIE has less than 32 columns */
        int i;

        /* Get err column bitmap */
        for (i = 0; i < num_err; i++) {
                struct aie_error *err = &errs[i];
                enum aie_error_category cat;

                cat = aie_get_error_category(err->row, err->event_id, err->mod_type);
                XDNA_ERR(ndev->aie.xdna, "Row: %d, Col: %d, module %d, event ID %d, category %d",
                         err->row, err->col, err->mod_type,
                         err->event_id, cat);

                if (err->col >= 32) {
                        XDNA_WARN(ndev->aie.xdna, "Invalid column number");
                        break;
                }

                err_col |= (1 << err->col);
        }

        return err_col;
}

static int aie2_error_async_cb(void *handle, void __iomem *data, size_t size)
{
        struct async_event *e = handle;

        if (data) {
                e->resp.type = readl(data + offsetof(struct async_event_msg_resp, type));
                wmb(); /* Update status in the end, so that no lock for here */
                e->resp.status = readl(data + offsetof(struct async_event_msg_resp, status));
        }
        queue_work(e->wq, &e->work);
        return 0;
}

static int aie2_error_event_send(struct async_event *e)
{
        drm_clflush_virt_range(e->buf, e->size); /* device can access */
        return aie2_register_asyn_event_msg(e->ndev, e->addr, e->size, e,
                                            aie2_error_async_cb);
}

static void aie2_error_worker(struct work_struct *err_work)
{
        struct aie_err_info *info;
        struct amdxdna_dev *xdna;
        struct async_event *e;
        u32 max_err;
        u32 err_col;

        e = container_of(err_work, struct async_event, work);

        xdna = e->ndev->aie.xdna;

        if (e->resp.status == MAX_AIE2_STATUS_CODE)
                return;

        e->resp.status = MAX_AIE2_STATUS_CODE;

        print_hex_dump_debug("AIE error: ", DUMP_PREFIX_OFFSET, 16, 4,
                             e->buf, 0x100, false);

        info = (struct aie_err_info *)e->buf;
        XDNA_DBG(xdna, "Error count %d return code %d", info->err_cnt, info->ret_code);

        max_err = (e->size - sizeof(*info)) / sizeof(struct aie_error);
        if (unlikely(info->err_cnt > max_err)) {
                WARN_ONCE(1, "Error count too large %d\n", info->err_cnt);
                return;
        }
        err_col = aie2_error_backtrack(e->ndev, info->payload, info->err_cnt);
        if (!err_col) {
                XDNA_WARN(xdna, "Did not get error column");
                return;
        }

        mutex_lock(&xdna->dev_lock);
        aie2_update_last_async_error(e->ndev, info->payload, info->err_cnt);

        /* Re-sent this event to firmware */
        if (aie2_error_event_send(e))
                XDNA_WARN(xdna, "Unable to register async event");
        mutex_unlock(&xdna->dev_lock);
}

void aie2_error_async_events_free(struct amdxdna_dev_hdl *ndev)
{
        struct amdxdna_dev *xdna = ndev->aie.xdna;
        struct async_events *events;

        events = ndev->async_events;

        mutex_unlock(&xdna->dev_lock);
        destroy_workqueue(events->wq);
        mutex_lock(&xdna->dev_lock);

        amdxdna_free_msg_buffer(xdna, events->size, events->buf, events->addr);
        kfree(events);
}

int aie2_error_async_events_alloc(struct amdxdna_dev_hdl *ndev)
{
        struct amdxdna_dev *xdna = ndev->aie.xdna;
        u32 total_col = ndev->total_col;
        u32 total_size = ASYNC_BUF_SIZE * total_col;
        struct async_events *events;
        int i, ret;

        events = kzalloc_flex(*events, event, total_col);
        if (!events)
                return -ENOMEM;

        events->buf = amdxdna_alloc_msg_buffer(xdna, &total_size, &events->addr);
        if (IS_ERR(events->buf)) {
                ret = PTR_ERR(events->buf);
                goto free_events;
        }
        events->size = total_size;
        events->event_cnt = total_col;

        events->wq = alloc_ordered_workqueue("async_wq", 0);
        if (!events->wq) {
                ret = -ENOMEM;
                goto free_buf;
        }

        for (i = 0; i < events->event_cnt; i++) {
                struct async_event *e = &events->event[i];
                u32 offset = i * ASYNC_BUF_SIZE;

                e->ndev = ndev;
                e->wq = events->wq;
                e->buf = &events->buf[offset];
                e->addr = events->addr + offset;
                e->size = ASYNC_BUF_SIZE;
                e->resp.status = MAX_AIE2_STATUS_CODE;
                INIT_WORK(&e->work, aie2_error_worker);

                ret = aie2_error_event_send(e);
                if (ret)
                        goto free_wq;
        }

        ndev->async_events = events;

        XDNA_DBG(xdna, "Async event count %d, buf total size 0x%x",
                 events->event_cnt, events->size);
        return 0;

free_wq:
        destroy_workqueue(events->wq);
free_buf:
        amdxdna_free_msg_buffer(xdna, events->size, events->buf, events->addr);
free_events:
        kfree(events);
        return ret;
}

int aie2_get_array_async_error(struct amdxdna_dev_hdl *ndev, struct amdxdna_drm_get_array *args)
{
        struct amdxdna_dev *xdna = ndev->aie.xdna;

        drm_WARN_ON(&xdna->ddev, !mutex_is_locked(&xdna->dev_lock));

        if (!args->num_element)
                return -EINVAL;

        args->num_element = 1;
        args->element_size = min(args->element_size, sizeof(ndev->last_async_err));
        if (copy_to_user(u64_to_user_ptr(args->buffer),
                         &ndev->last_async_err, args->element_size))
                return -EFAULT;

        return 0;
}