| // 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(sizeof(*map), GFP_KERNEL); |
| 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; |
| } |