root/drivers/accel/habanalabs/common/decoder.c
// SPDX-License-Identifier: GPL-2.0

/*
 * Copyright 2022 HabanaLabs, Ltd.
 * All Rights Reserved.
 */

#include "habanalabs.h"

#define VCMD_CONTROL_OFFSET                     0x40    /* SWREG16 */
#define VCMD_IRQ_STATUS_OFFSET                  0x44    /* SWREG17 */

#define VCMD_IRQ_STATUS_ENDCMD_MASK             0x1
#define VCMD_IRQ_STATUS_BUSERR_MASK             0x2
#define VCMD_IRQ_STATUS_TIMEOUT_MASK            0x4
#define VCMD_IRQ_STATUS_CMDERR_MASK             0x8
#define VCMD_IRQ_STATUS_ABORT_MASK              0x10
#define VCMD_IRQ_STATUS_RESET_MASK              0x20

static void dec_print_abnrm_intr_source(struct hl_device *hdev, u32 irq_status)
{
        const char *format = "abnormal interrupt source:%s%s%s%s%s%s\n";
        char *intr_source[6] = {"Unknown", "", "", "", "", ""};
        int i = 0;

        if (!irq_status)
                return;

        if (irq_status & VCMD_IRQ_STATUS_ENDCMD_MASK)
                intr_source[i++] = " ENDCMD";
        if (irq_status & VCMD_IRQ_STATUS_BUSERR_MASK)
                intr_source[i++] = " BUSERR";
        if (irq_status & VCMD_IRQ_STATUS_TIMEOUT_MASK)
                intr_source[i++] = " TIMEOUT";
        if (irq_status & VCMD_IRQ_STATUS_CMDERR_MASK)
                intr_source[i++] = " CMDERR";
        if (irq_status & VCMD_IRQ_STATUS_ABORT_MASK)
                intr_source[i++] = " ABORT";
        if (irq_status & VCMD_IRQ_STATUS_RESET_MASK)
                intr_source[i++] = " RESET";

        dev_err(hdev->dev, format, intr_source[0], intr_source[1],
                intr_source[2], intr_source[3], intr_source[4], intr_source[5]);
}

static void dec_abnrm_intr_work(struct work_struct *work)
{
        struct hl_dec *dec = container_of(work, struct hl_dec, abnrm_intr_work);
        struct hl_device *hdev = dec->hdev;
        u32 irq_status, event_mask = 0;
        bool reset_required = false;

        irq_status = RREG32(dec->base_addr + VCMD_IRQ_STATUS_OFFSET);

        dev_err(hdev->dev, "Decoder abnormal interrupt %#x, core %d\n", irq_status, dec->core_id);

        dec_print_abnrm_intr_source(hdev, irq_status);

        /* Clear the interrupt */
        WREG32(dec->base_addr + VCMD_IRQ_STATUS_OFFSET, irq_status);

        /* Flush the interrupt clear */
        RREG32(dec->base_addr + VCMD_IRQ_STATUS_OFFSET);

        if (irq_status & VCMD_IRQ_STATUS_TIMEOUT_MASK) {
                reset_required = true;
                event_mask |= HL_NOTIFIER_EVENT_GENERAL_HW_ERR;
        }

        if (irq_status & VCMD_IRQ_STATUS_CMDERR_MASK)
                event_mask |= HL_NOTIFIER_EVENT_UNDEFINED_OPCODE;

        if (irq_status & (VCMD_IRQ_STATUS_ENDCMD_MASK |
                                VCMD_IRQ_STATUS_BUSERR_MASK |
                                VCMD_IRQ_STATUS_ABORT_MASK))
                event_mask |= HL_NOTIFIER_EVENT_USER_ENGINE_ERR;

        if (reset_required) {
                event_mask |= HL_NOTIFIER_EVENT_DEVICE_RESET;
                hl_device_cond_reset(hdev, 0, event_mask);
        } else if (event_mask) {
                hl_notifier_event_send_all(hdev, event_mask);
        }
}

void hl_dec_fini(struct hl_device *hdev)
{
        kfree(hdev->dec);
}

int hl_dec_init(struct hl_device *hdev)
{
        struct asic_fixed_properties *prop = &hdev->asic_prop;
        struct hl_dec *dec;
        int rc, j;

        /* if max core is 0, nothing to do*/
        if (!prop->max_dec)
                return 0;

        hdev->dec = kzalloc_objs(struct hl_dec, prop->max_dec);
        if (!hdev->dec)
                return -ENOMEM;

        for (j = 0 ; j < prop->max_dec ; j++) {
                dec = hdev->dec + j;

                dec->hdev = hdev;
                INIT_WORK(&dec->abnrm_intr_work, dec_abnrm_intr_work);
                dec->core_id = j;
                dec->base_addr = hdev->asic_funcs->get_dec_base_addr(hdev, j);
                if (!dec->base_addr) {
                        dev_err(hdev->dev, "Invalid base address of decoder %d\n", j);
                        rc = -EINVAL;
                        goto err_dec_fini;
                }
        }

        return 0;

err_dec_fini:
        hl_dec_fini(hdev);

        return rc;
}

void hl_dec_ctx_fini(struct hl_ctx *ctx)
{
        struct hl_device *hdev = ctx->hdev;
        struct asic_fixed_properties *prop = &hdev->asic_prop;
        struct hl_dec *dec;
        int j;

        for (j = 0 ; j < prop->max_dec ; j++) {
                if (!!(prop->decoder_enabled_mask & BIT(j))) {
                        dec = hdev->dec + j;
                        /* Stop the decoder */
                        WREG32(dec->base_addr + VCMD_CONTROL_OFFSET, 0);
                }
        }
}