| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright 2021 HabanaLabs, Ltd. | |
| * All Rights Reserved. | |
| */ | |
| #include <linux/vmalloc.h> | |
| #include <uapi/misc/habanalabs.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; | |
| } |