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

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

#include <linux/vmalloc.h>
#include <uapi/drm/habanalabs_accel.h>
#include "habanalabs.h"

/**
 * hl_format_as_binary - helper function, format an integer as binary
 *                       using supplied scratch buffer
 * @buf: the buffer to use
 * @buf_len: buffer capacity
 * @n: number to format
 *
 * Returns pointer to buffer
 */
char *hl_format_as_binary(char *buf, size_t buf_len, u32 n)
{
        int i;
        u32 bit;
        bool leading0 = true;
        char *wrptr = buf;

        if (buf_len > 0 && buf_len < 3) {
                *wrptr = '\0';
                return buf;
        }

        wrptr[0] = '0';
        wrptr[1] = 'b';
        wrptr += 2;
        /* Remove 3 characters from length for '0b' and '\0' termination */
        buf_len -= 3;

        for (i = 0; i < sizeof(n) * BITS_PER_BYTE && buf_len; ++i, n <<= 1) {
                /* Writing bit calculation in one line would cause a false
                 * positive static code analysis error, so splitting.
                 */
                bit = n & (1 << (sizeof(n) * BITS_PER_BYTE - 1));
                bit = !!bit;
                leading0 &= !bit;
                if (!leading0) {
                        *wrptr = '0' + bit;
                        ++wrptr;
                }
        }

        *wrptr = '\0';

        return buf;
}

/**
 * resize_to_fit - helper function, resize buffer to fit given amount of data
 * @buf: destination buffer double pointer
 * @size: pointer to the size container
 * @desired_size: size the buffer must contain
 *
 * Returns 0 on success or error code on failure.
 * On success, the size of buffer is at least desired_size. Buffer is allocated
 * via vmalloc and must be freed with vfree.
 */
static int resize_to_fit(char **buf, size_t *size, size_t desired_size)
{
        char *resized_buf;
        size_t new_size;

        if (*size >= desired_size)
                return 0;

        /* Not enough space to print all, have to resize */
        new_size = max_t(size_t, PAGE_SIZE, round_up(desired_size, PAGE_SIZE));
        resized_buf = vmalloc(new_size);
        if (!resized_buf)
                return -ENOMEM;
        memcpy(resized_buf, *buf, *size);
        vfree(*buf);
        *buf = resized_buf;
        *size = new_size;

        return 1;
}

/**
 * hl_snprintf_resize() - print formatted data to buffer, resize as needed
 * @buf: buffer double pointer, to be written to and resized, must be either
 *       NULL or allocated with vmalloc.
 * @size: current size of the buffer
 * @offset: current offset to write to
 * @format: format of the data
 *
 * This function will write formatted data into the buffer. If buffer is not
 * large enough, it will be resized using vmalloc. Size may be modified if the
 * buffer was resized, offset will be advanced by the number of bytes written
 * not including the terminating character
 *
 * Returns 0 on success or error code on failure
 *
 * Note that the buffer has to be manually released using vfree.
 */
int hl_snprintf_resize(char **buf, size_t *size, size_t *offset,
                           const char *format, ...)
{
        va_list args;
        size_t length;
        int rc;

        if (*buf == NULL && (*size != 0 || *offset != 0))
                return -EINVAL;

        va_start(args, format);
        length = vsnprintf(*buf + *offset, *size - *offset, format, args);
        va_end(args);

        rc = resize_to_fit(buf, size, *offset + length + 1);
        if (rc < 0)
                return rc;
        else if (rc > 0) {
                /* Resize was needed, write again */
                va_start(args, format);
                length = vsnprintf(*buf + *offset, *size - *offset, format,
                                   args);
                va_end(args);
        }

        *offset += length;

        return 0;
}

/**
 * hl_sync_engine_to_string - convert engine type enum to string literal
 * @engine_type: engine type (TPC/MME/DMA)
 *
 * Return the resolved string literal
 */
const char *hl_sync_engine_to_string(enum hl_sync_engine_type engine_type)
{
        switch (engine_type) {
        case ENGINE_DMA:
                return "DMA";
        case ENGINE_MME:
                return "MME";
        case ENGINE_TPC:
                return "TPC";
        }
        return "Invalid Engine Type";
}

/**
 * hl_print_resize_sync_engine - helper function, format engine name and ID
 * using hl_snprintf_resize
 * @buf: destination buffer double pointer to be used with hl_snprintf_resize
 * @size: pointer to the size container
 * @offset: pointer to the offset container
 * @engine_type: engine type (TPC/MME/DMA)
 * @engine_id: engine numerical id
 *
 * Returns 0 on success or error code on failure
 */
static int hl_print_resize_sync_engine(char **buf, size_t *size, size_t *offset,
                                enum hl_sync_engine_type engine_type,
                                u32 engine_id)
{
        return hl_snprintf_resize(buf, size, offset, "%s%u",
                        hl_sync_engine_to_string(engine_type), engine_id);
}

/**
 * hl_state_dump_get_sync_name - transform sync object id to name if available
 * @hdev: pointer to the device
 * @sync_id: sync object id
 *
 * Returns a name literal or NULL if not resolved.
 * Note: returning NULL shall not be considered as a failure, as not all
 * sync objects are named.
 */
const char *hl_state_dump_get_sync_name(struct hl_device *hdev, u32 sync_id)
{
        struct hl_state_dump_specs *sds = &hdev->state_dump_specs;
        struct hl_hw_obj_name_entry *entry;

        hash_for_each_possible(sds->so_id_to_str_tb, entry,
                                node, sync_id)
                if (sync_id == entry->id)
                        return entry->name;

        return NULL;
}

/**
 * hl_state_dump_get_monitor_name - transform monitor object dump to monitor
 * name if available
 * @hdev: pointer to the device
 * @mon: monitor state dump
 *
 * Returns a name literal or NULL if not resolved.
 * Note: returning NULL shall not be considered as a failure, as not all
 * monitors are named.
 */
const char *hl_state_dump_get_monitor_name(struct hl_device *hdev,
                                        struct hl_mon_state_dump *mon)
{
        struct hl_state_dump_specs *sds = &hdev->state_dump_specs;
        struct hl_hw_obj_name_entry *entry;

        hash_for_each_possible(sds->monitor_id_to_str_tb,
                                entry, node, mon->id)
                if (mon->id == entry->id)
                        return entry->name;

        return NULL;
}

/**
 * hl_state_dump_free_sync_to_engine_map - free sync object to engine map
 * @map: sync object to engine map
 *
 * Note: generic free implementation, the allocation is implemented per ASIC.
 */
void hl_state_dump_free_sync_to_engine_map(struct hl_sync_to_engine_map *map)
{
        struct hl_sync_to_engine_map_entry *entry;
        struct hlist_node *tmp_node;
        int i;

        hash_for_each_safe(map->tb, i, tmp_node, entry, node) {
                hash_del(&entry->node);
                kfree(entry);
        }
}

/**
 * hl_state_dump_get_sync_to_engine - transform sync_id to
 * hl_sync_to_engine_map_entry if available for current id
 * @map: sync object to engine map
 * @sync_id: sync object id
 *
 * Returns the translation entry if found or NULL if not.
 * Note, returned NULL shall not be considered as a failure as the map
 * does not cover all possible, it is a best effort sync ids.
 */
static struct hl_sync_to_engine_map_entry *
hl_state_dump_get_sync_to_engine(struct hl_sync_to_engine_map *map, u32 sync_id)
{
        struct hl_sync_to_engine_map_entry *entry;

        hash_for_each_possible(map->tb, entry, node, sync_id)
                if (entry->sync_id == sync_id)
                        return entry;
        return NULL;
}

/**
 * hl_state_dump_read_sync_objects - read sync objects array
 * @hdev: pointer to the device
 * @index: sync manager block index starting with E_N
 *
 * Returns array of size SP_SYNC_OBJ_AMOUNT on success or NULL on failure
 */
static u32 *hl_state_dump_read_sync_objects(struct hl_device *hdev, u32 index)
{
        struct hl_state_dump_specs *sds = &hdev->state_dump_specs;
        u32 *sync_objects;
        s64 base_addr; /* Base addr can be negative */
        int i;

        base_addr = sds->props[SP_SYNC_OBJ_BASE_ADDR] +
                        sds->props[SP_NEXT_SYNC_OBJ_ADDR] * index;

        sync_objects = vmalloc(sds->props[SP_SYNC_OBJ_AMOUNT] * sizeof(u32));
        if (!sync_objects)
                return NULL;

        for (i = 0; i < sds->props[SP_SYNC_OBJ_AMOUNT]; ++i)
                sync_objects[i] = RREG32(base_addr + i * sizeof(u32));

        return sync_objects;
}

/**
 * hl_state_dump_free_sync_objects - free sync objects array allocated by
 * hl_state_dump_read_sync_objects
 * @sync_objects: sync objects array
 */
static void hl_state_dump_free_sync_objects(u32 *sync_objects)
{
        vfree(sync_objects);
}


/**
 * hl_state_dump_print_syncs_single_block - print active sync objects on a
 * single block
 * @hdev: pointer to the device
 * @index: sync manager block index starting with E_N
 * @buf: destination buffer double pointer to be used with hl_snprintf_resize
 * @size: pointer to the size container
 * @offset: pointer to the offset container
 * @map: sync engines names map
 *
 * Returns 0 on success or error code on failure
 */
static int
hl_state_dump_print_syncs_single_block(struct hl_device *hdev, u32 index,
                                char **buf, size_t *size, size_t *offset,
                                struct hl_sync_to_engine_map *map)
{
        struct hl_state_dump_specs *sds = &hdev->state_dump_specs;
        const char *sync_name;
        u32 *sync_objects = NULL;
        int rc = 0, i;

        if (sds->sync_namager_names) {
                rc = hl_snprintf_resize(
                        buf, size, offset, "%s\n",
                        sds->sync_namager_names[index]);
                if (rc)
                        goto out;
        }

        sync_objects = hl_state_dump_read_sync_objects(hdev, index);
        if (!sync_objects) {
                rc = -ENOMEM;
                goto out;
        }

        for (i = 0; i < sds->props[SP_SYNC_OBJ_AMOUNT]; ++i) {
                struct hl_sync_to_engine_map_entry *entry;
                u64 sync_object_addr;

                if (!sync_objects[i])
                        continue;

                sync_object_addr = sds->props[SP_SYNC_OBJ_BASE_ADDR] +
                                sds->props[SP_NEXT_SYNC_OBJ_ADDR] * index +
                                i * sizeof(u32);

                rc = hl_snprintf_resize(buf, size, offset, "sync id: %u", i);
                if (rc)
                        goto free_sync_objects;
                sync_name = hl_state_dump_get_sync_name(hdev, i);
                if (sync_name) {
                        rc = hl_snprintf_resize(buf, size, offset, " %s",
                                                sync_name);
                        if (rc)
                                goto free_sync_objects;
                }
                rc = hl_snprintf_resize(buf, size, offset, ", value: %u",
                                        sync_objects[i]);
                if (rc)
                        goto free_sync_objects;

                /* Append engine string */
                entry = hl_state_dump_get_sync_to_engine(map,
                        (u32)sync_object_addr);
                if (entry) {
                        rc = hl_snprintf_resize(buf, size, offset,
                                                ", Engine: ");
                        if (rc)
                                goto free_sync_objects;
                        rc = hl_print_resize_sync_engine(buf, size, offset,
                                                entry->engine_type,
                                                entry->engine_id);
                        if (rc)
                                goto free_sync_objects;
                }

                rc = hl_snprintf_resize(buf, size, offset, "\n");
                if (rc)
                        goto free_sync_objects;
        }

free_sync_objects:
        hl_state_dump_free_sync_objects(sync_objects);
out:
        return rc;
}

/**
 * hl_state_dump_print_syncs - print active sync objects
 * @hdev: pointer to the device
 * @buf: destination buffer double pointer to be used with hl_snprintf_resize
 * @size: pointer to the size container
 * @offset: pointer to the offset container
 *
 * Returns 0 on success or error code on failure
 */
static int hl_state_dump_print_syncs(struct hl_device *hdev,
                                        char **buf, size_t *size,
                                        size_t *offset)

{
        struct hl_state_dump_specs *sds = &hdev->state_dump_specs;
        struct hl_sync_to_engine_map *map;
        u32 index;
        int rc = 0;

        map = kzalloc_obj(*map);
        if (!map)
                return -ENOMEM;

        rc = sds->funcs.gen_sync_to_engine_map(hdev, map);
        if (rc)
                goto free_map_mem;

        rc = hl_snprintf_resize(buf, size, offset, "Non zero sync objects:\n");
        if (rc)
                goto out;

        if (sds->sync_namager_names) {
                for (index = 0; sds->sync_namager_names[index]; ++index) {
                        rc = hl_state_dump_print_syncs_single_block(
                                hdev, index, buf, size, offset, map);
                        if (rc)
                                goto out;
                }
        } else {
                for (index = 0; index < sds->props[SP_NUM_CORES]; ++index) {
                        rc = hl_state_dump_print_syncs_single_block(
                                hdev, index, buf, size, offset, map);
                        if (rc)
                                goto out;
                }
        }

out:
        hl_state_dump_free_sync_to_engine_map(map);
free_map_mem:
        kfree(map);

        return rc;
}

/**
 * hl_state_dump_alloc_read_sm_block_monitors - read monitors for a specific
 * block
 * @hdev: pointer to the device
 * @index: sync manager block index starting with E_N
 *
 * Returns an array of monitor data of size SP_MONITORS_AMOUNT or NULL
 * on error
 */
static struct hl_mon_state_dump *
hl_state_dump_alloc_read_sm_block_monitors(struct hl_device *hdev, u32 index)
{
        struct hl_state_dump_specs *sds = &hdev->state_dump_specs;
        struct hl_mon_state_dump *monitors;
        s64 base_addr; /* Base addr can be negative */
        int i;

        monitors = vmalloc(sds->props[SP_MONITORS_AMOUNT] *
                           sizeof(struct hl_mon_state_dump));
        if (!monitors)
                return NULL;

        base_addr = sds->props[SP_NEXT_SYNC_OBJ_ADDR] * index;

        for (i = 0; i < sds->props[SP_MONITORS_AMOUNT]; ++i) {
                monitors[i].id = i;
                monitors[i].wr_addr_low =
                        RREG32(base_addr + sds->props[SP_MON_OBJ_WR_ADDR_LOW] +
                                i * sizeof(u32));

                monitors[i].wr_addr_high =
                        RREG32(base_addr + sds->props[SP_MON_OBJ_WR_ADDR_HIGH] +
                                i * sizeof(u32));

                monitors[i].wr_data =
                        RREG32(base_addr + sds->props[SP_MON_OBJ_WR_DATA] +
                                i * sizeof(u32));

                monitors[i].arm_data =
                        RREG32(base_addr + sds->props[SP_MON_OBJ_ARM_DATA] +
                                i * sizeof(u32));

                monitors[i].status =
                        RREG32(base_addr + sds->props[SP_MON_OBJ_STATUS] +
                                i * sizeof(u32));
        }

        return monitors;
}

/**
 * hl_state_dump_free_monitors - free the monitors structure
 * @monitors: monitors array created with
 *            hl_state_dump_alloc_read_sm_block_monitors
 */
static void hl_state_dump_free_monitors(struct hl_mon_state_dump *monitors)
{
        vfree(monitors);
}

/**
 * hl_state_dump_print_monitors_single_block - print active monitors on a
 * single block
 * @hdev: pointer to the device
 * @index: sync manager block index starting with E_N
 * @buf: destination buffer double pointer to be used with hl_snprintf_resize
 * @size: pointer to the size container
 * @offset: pointer to the offset container
 *
 * Returns 0 on success or error code on failure
 */
static int hl_state_dump_print_monitors_single_block(struct hl_device *hdev,
                                                u32 index,
                                                char **buf, size_t *size,
                                                size_t *offset)
{
        struct hl_state_dump_specs *sds = &hdev->state_dump_specs;
        struct hl_mon_state_dump *monitors = NULL;
        int rc = 0, i;

        if (sds->sync_namager_names) {
                rc = hl_snprintf_resize(
                        buf, size, offset, "%s\n",
                        sds->sync_namager_names[index]);
                if (rc)
                        goto out;
        }

        monitors = hl_state_dump_alloc_read_sm_block_monitors(hdev, index);
        if (!monitors) {
                rc = -ENOMEM;
                goto out;
        }

        for (i = 0; i < sds->props[SP_MONITORS_AMOUNT]; ++i) {
                if (!(sds->funcs.monitor_valid(&monitors[i])))
                        continue;

                /* Monitor is valid, dump it */
                rc = sds->funcs.print_single_monitor(buf, size, offset, hdev,
                                                        &monitors[i]);
                if (rc)
                        goto free_monitors;

                hl_snprintf_resize(buf, size, offset, "\n");
        }

free_monitors:
        hl_state_dump_free_monitors(monitors);
out:
        return rc;
}

/**
 * hl_state_dump_print_monitors - print active monitors
 * @hdev: pointer to the device
 * @buf: destination buffer double pointer to be used with hl_snprintf_resize
 * @size: pointer to the size container
 * @offset: pointer to the offset container
 *
 * Returns 0 on success or error code on failure
 */
static int hl_state_dump_print_monitors(struct hl_device *hdev,
                                        char **buf, size_t *size,
                                        size_t *offset)
{
        struct hl_state_dump_specs *sds = &hdev->state_dump_specs;
        u32 index;
        int rc = 0;

        rc = hl_snprintf_resize(buf, size, offset,
                "Valid (armed) monitor objects:\n");
        if (rc)
                goto out;

        if (sds->sync_namager_names) {
                for (index = 0; sds->sync_namager_names[index]; ++index) {
                        rc = hl_state_dump_print_monitors_single_block(
                                hdev, index, buf, size, offset);
                        if (rc)
                                goto out;
                }
        } else {
                for (index = 0; index < sds->props[SP_NUM_CORES]; ++index) {
                        rc = hl_state_dump_print_monitors_single_block(
                                hdev, index, buf, size, offset);
                        if (rc)
                                goto out;
                }
        }

out:
        return rc;
}

/**
 * hl_state_dump_print_engine_fences - print active fences for a specific
 * engine
 * @hdev: pointer to the device
 * @engine_type: engine type to use
 * @buf: destination buffer double pointer to be used with hl_snprintf_resize
 * @size: pointer to the size container
 * @offset: pointer to the offset container
 */
static int
hl_state_dump_print_engine_fences(struct hl_device *hdev,
                                  enum hl_sync_engine_type engine_type,
                                  char **buf, size_t *size, size_t *offset)
{
        struct hl_state_dump_specs *sds = &hdev->state_dump_specs;
        int rc = 0, i, n_fences;
        u64 base_addr, next_fence;

        switch (engine_type) {
        case ENGINE_TPC:
                n_fences = sds->props[SP_NUM_OF_TPC_ENGINES];
                base_addr = sds->props[SP_TPC0_CMDQ];
                next_fence = sds->props[SP_NEXT_TPC];
                break;
        case ENGINE_MME:
                n_fences = sds->props[SP_NUM_OF_MME_ENGINES];
                base_addr = sds->props[SP_MME_CMDQ];
                next_fence = sds->props[SP_NEXT_MME];
                break;
        case ENGINE_DMA:
                n_fences = sds->props[SP_NUM_OF_DMA_ENGINES];
                base_addr = sds->props[SP_DMA_CMDQ];
                next_fence = sds->props[SP_DMA_QUEUES_OFFSET];
                break;
        default:
                return -EINVAL;
        }
        for (i = 0; i < n_fences; ++i) {
                rc = sds->funcs.print_fences_single_engine(
                        hdev,
                        base_addr + next_fence * i +
                                sds->props[SP_FENCE0_CNT_OFFSET],
                        base_addr + next_fence * i +
                                sds->props[SP_CP_STS_OFFSET],
                        engine_type, i, buf, size, offset);
                if (rc)
                        goto out;
        }
out:
        return rc;
}

/**
 * hl_state_dump_print_fences - print active fences
 * @hdev: pointer to the device
 * @buf: destination buffer double pointer to be used with hl_snprintf_resize
 * @size: pointer to the size container
 * @offset: pointer to the offset container
 */
static int hl_state_dump_print_fences(struct hl_device *hdev, char **buf,
                                      size_t *size, size_t *offset)
{
        int rc = 0;

        rc = hl_snprintf_resize(buf, size, offset, "Valid (armed) fences:\n");
        if (rc)
                goto out;

        rc = hl_state_dump_print_engine_fences(hdev, ENGINE_TPC, buf, size, offset);
        if (rc)
                goto out;

        rc = hl_state_dump_print_engine_fences(hdev, ENGINE_MME, buf, size, offset);
        if (rc)
                goto out;

        rc = hl_state_dump_print_engine_fences(hdev, ENGINE_DMA, buf, size, offset);
        if (rc)
                goto out;

out:
        return rc;
}

/**
 * hl_state_dump() - dump system state
 * @hdev: pointer to device structure
 */
int hl_state_dump(struct hl_device *hdev)
{
        char *buf = NULL;
        size_t offset = 0, size = 0;
        int rc;

        rc = hl_snprintf_resize(&buf, &size, &offset,
                                "Timestamp taken on: %llu\n\n",
                                ktime_to_ns(ktime_get()));
        if (rc)
                goto err;

        rc = hl_state_dump_print_syncs(hdev, &buf, &size, &offset);
        if (rc)
                goto err;

        hl_snprintf_resize(&buf, &size, &offset, "\n");

        rc = hl_state_dump_print_monitors(hdev, &buf, &size, &offset);
        if (rc)
                goto err;

        hl_snprintf_resize(&buf, &size, &offset, "\n");

        rc = hl_state_dump_print_fences(hdev, &buf, &size, &offset);
        if (rc)
                goto err;

        hl_snprintf_resize(&buf, &size, &offset, "\n");

        hl_debugfs_set_state_dump(hdev, buf, size);

        return 0;
err:
        vfree(buf);
        return rc;
}