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