/* * Copyright (c) 2020 Intel Corporation. * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include "coredump_internal.h" #include LOG_MODULE_REGISTER(coredump, CONFIG_KERNEL_LOG_LEVEL); /** * @file * @brief Simple coredump backend to store data in flash partition. * * This provides a simple backend to store coredump data in a flash * partition, labeled "coredump-partition" in devicetree. * * On the partition, a header is stored at the beginning with padding * at the end to align with flash write size. Then the actual * coredump data follows. The padding is to simplify the data read * function so that the first read of a data stream is always * aligned to flash write size. */ #define FLASH_PARTITION coredump_partition #define FLASH_PARTITION_ID FIXED_PARTITION_ID(FLASH_PARTITION) #if !FIXED_PARTITION_EXISTS(FLASH_PARTITION) #error "Need a fixed partition named 'coredump-partition'!" #else #define FLASH_CONTROLLER \ DT_PARENT(DT_PARENT(DT_NODELABEL(FLASH_PARTITION))) #define FLASH_WRITE_SIZE DT_PROP(FLASH_CONTROLLER, write_block_size) #define FLASH_BUF_SIZE FLASH_WRITE_SIZE #define FLASH_ERASE_SIZE DT_PROP(FLASH_CONTROLLER, erase_block_size) #define HDR_VER 1 #define FLASH_BACKEND_SEM_TIMEOUT (k_is_in_isr() ? K_NO_WAIT : K_FOREVER) typedef int (*data_read_cb_t)(void *arg, uint8_t *buf, size_t len); static struct { /* For use with flash map */ const struct flash_area *flash_area; /* For use with streaming flash */ struct stream_flash_ctx stream_ctx; /* Checksum of data so far */ uint16_t checksum; /* Error encountered */ int error; } backend_ctx; /* Buffer used in stream flash context */ static uint8_t stream_flash_buf[FLASH_BUF_SIZE]; /* Buffer used in data_read() */ static uint8_t data_read_buf[FLASH_BUF_SIZE]; /* Semaphore for exclusive flash access */ K_SEM_DEFINE(flash_sem, 1, 1); struct flash_hdr_t { /* 'C', 'D' */ char id[2]; /* Header version */ uint16_t hdr_version; /* Coredump size, excluding this header */ size_t size; /* Flags */ uint16_t flags; /* Checksum */ uint16_t checksum; /* Error */ int error; } __packed; /** * @brief Open the flash partition. * * @return Same as flash_area_open(). */ static int partition_open(void) { int ret; (void)k_sem_take(&flash_sem, FLASH_BACKEND_SEM_TIMEOUT); ret = flash_area_open(FLASH_PARTITION_ID, &backend_ctx.flash_area); if (ret != 0) { LOG_ERR("Error opening flash partition for coredump!"); backend_ctx.flash_area = NULL; k_sem_give(&flash_sem); } return ret; } /** * @brief Close the flash partition. */ static void partition_close(void) { if (backend_ctx.flash_area == NULL) { return; } flash_area_close(backend_ctx.flash_area); backend_ctx.flash_area = NULL; k_sem_give(&flash_sem); } /** * @brief Read data from flash partition. * * This reads @p len bytes in the flash partition starting * from @p off and put into buffer pointed by @p dst if * @p dst is not NULL. * * If @p cb is not NULL, data being read are passed to * the callback for processing. Note that the data being * passed to callback may only be part of the data requested. * The end of read is signaled to the callback with NULL * buffer pointer and zero length as arguments. * * @param off offset of partition to begin reading * @param dst buffer to read data into (can be NULL) * @param len number of bytes to read * @param cb callback to process read data (can be NULL) * @param cb_arg argument passed to callback * @return 0 if successful, error otherwise. */ static int data_read(off_t off, uint8_t *dst, size_t len, data_read_cb_t cb, void *cb_arg) { int ret = 0; off_t offset = off; size_t remaining = len; size_t copy_sz; uint8_t *ptr = dst; if (backend_ctx.flash_area == NULL) { return -ENODEV; } copy_sz = FLASH_BUF_SIZE; while (remaining > 0) { if (remaining < FLASH_BUF_SIZE) { copy_sz = remaining; } ret = flash_area_read(backend_ctx.flash_area, offset, data_read_buf, FLASH_BUF_SIZE); if (ret != 0) { break; } if (dst != NULL) { (void)memcpy(ptr, data_read_buf, copy_sz); } if (cb != NULL) { ret = (*cb)(cb_arg, data_read_buf, copy_sz); if (ret != 0) { break; } } ptr += copy_sz; offset += copy_sz; remaining -= copy_sz; } if (cb != NULL) { ret = (*cb)(cb_arg, NULL, 0); } return ret; } /** * @brief Callback to calculate checksum. * * @param arg callback argument (not being used) * @param buf data buffer * @param len number of bytes in buffer to process * @return 0 */ static int cb_calc_buf_checksum(void *arg, uint8_t *buf, size_t len) { int i; ARG_UNUSED(arg); for (i = 0; i < len; i++) { backend_ctx.checksum += buf[i]; } return 0; } /** * @brief Process the stored coredump in flash partition. * * This reads the stored coredump data and processes it via * the callback function. * * @param cb callback to process the stored coredump data * @param cb_arg argument passed to callback * @return 1 if successful; 0 if stored coredump is not found * or is not valid; error otherwise */ static int process_stored_dump(data_read_cb_t cb, void *cb_arg) { int ret; struct flash_hdr_t hdr; off_t offset; ret = partition_open(); if (ret != 0) { goto out; } /* Read header */ ret = data_read(0, (uint8_t *)&hdr, sizeof(hdr), NULL, NULL); /* Verify header signature */ if ((hdr.id[0] != 'C') && (hdr.id[1] != 'D')) { ret = 0; goto out; } /* Error encountered while dumping, so non-existent */ if (hdr.error != 0) { ret = 0; goto out; } backend_ctx.checksum = 0; offset = ROUND_UP(sizeof(struct flash_hdr_t), FLASH_WRITE_SIZE); ret = data_read(offset, NULL, hdr.size, cb, cb_arg); if (ret == 0) { ret = (backend_ctx.checksum == hdr.checksum) ? 1 : 0; } out: partition_close(); return ret; } /** * @brief Get the stored coredump in flash partition. * * This reads the stored coredump data and copies the raw data * to the destination buffer. * * If the destination buffer is NULL, the offset and length are * ignored and the entire dump size is returned. * * @param off offset of partition to begin reading * @param dst buffer to read data into (can be NULL) * @param len number of bytes to read * @return dump size if successful; 0 if stored coredump is not found * or is not valid; error otherwise */ static int get_stored_dump(off_t off, uint8_t *dst, size_t len) { int ret; struct flash_hdr_t hdr; ret = partition_open(); if (ret != 0) { goto out; } /* Read header */ ret = data_read(0, (uint8_t *)&hdr, sizeof(hdr), NULL, NULL); if (ret != 0) { goto out; } /* Verify header signature */ if ((hdr.id[0] != 'C') && (hdr.id[1] != 'D')) { ret = 0; goto out; } /* Error encountered while dumping, so non-existent */ if (hdr.error != 0) { ret = 0; goto out; } /* Return the dump size if no destination buffer available */ if (!dst) { ret = (int)hdr.size; goto out; } /* Offset larger than dump size */ if (off >= hdr.size) { ret = 0; goto out; } /* Start reading the data, skip write-aligned header */ off += ROUND_UP(sizeof(struct flash_hdr_t), FLASH_WRITE_SIZE); ret = data_read(off, dst, len, NULL, NULL); if (ret == 0) { ret = (int)len; } out: partition_close(); return ret; } /** * @brief Erase the stored coredump header from flash partition. * * This erases the stored coredump header from the flash partition, * invalidating the coredump data. * * @return 0 if successful; error otherwise */ static int erase_coredump_header(void) { int ret; ret = partition_open(); if (ret == 0) { /* Erase header block */ ret = flash_area_erase(backend_ctx.flash_area, 0, ROUND_UP(sizeof(struct flash_hdr_t), FLASH_ERASE_SIZE)); } partition_close(); return ret; } /** * @brief Erase the stored coredump in flash partition. * * This erases the stored coredump data from the flash partition. * * @return 0 if successful; error otherwise */ static int erase_flash_partition(void) { int ret; ret = partition_open(); if (ret == 0) { /* Erase whole flash partition */ ret = flash_area_erase(backend_ctx.flash_area, 0, backend_ctx.flash_area->fa_size); } partition_close(); return ret; } /** * @brief Start of coredump session. * * This opens the flash partition for processing. */ static void coredump_flash_backend_start(void) { const struct device *flash_dev; size_t offset, header_size; int ret; ret = partition_open(); if (ret == 0) { /* Erase whole flash partition */ ret = flash_area_erase(backend_ctx.flash_area, 0, backend_ctx.flash_area->fa_size); } if (ret == 0) { backend_ctx.checksum = 0; flash_dev = flash_area_get_device(backend_ctx.flash_area); /* * Reserve space for header from beginning of flash device. * The header size is rounded up so the beginning of coredump * is aligned to write size (for easier read and seek). */ header_size = ROUND_UP(sizeof(struct flash_hdr_t), FLASH_WRITE_SIZE); offset = backend_ctx.flash_area->fa_off + header_size; ret = stream_flash_init(&backend_ctx.stream_ctx, flash_dev, stream_flash_buf, sizeof(stream_flash_buf), offset, backend_ctx.flash_area->fa_size - header_size, NULL); } if (ret != 0) { LOG_ERR("Cannot start coredump!"); backend_ctx.error = ret; partition_close(); } } /** * @brief End of coredump session. * * This ends the coredump session by flushing coredump data * flash, and writes the header in the beginning of flash * related to the stored coredump data. */ static void coredump_flash_backend_end(void) { int ret; struct flash_hdr_t hdr = { .id = {'C', 'D'}, .hdr_version = HDR_VER, }; if (backend_ctx.flash_area == NULL) { return; } /* Flush buffer */ backend_ctx.error = stream_flash_buffered_write( &backend_ctx.stream_ctx, stream_flash_buf, 0, true); /* Write header */ hdr.size = stream_flash_bytes_written(&backend_ctx.stream_ctx); hdr.checksum = backend_ctx.checksum; hdr.error = backend_ctx.error; hdr.flags = 0; ret = flash_area_write(backend_ctx.flash_area, 0, (void *)&hdr, sizeof(hdr)); if (ret != 0) { LOG_ERR("Cannot write coredump header!"); backend_ctx.error = ret; } if (backend_ctx.error != 0) { LOG_ERR("Error in coredump backend (%d)!", backend_ctx.error); } partition_close(); } /** * @brief Write a buffer to flash partition. * * This writes @p buf into the flash partition. Note that this is * using the stream flash interface, so there is no need to keep * track of where on flash to write next. * * @param buf buffer of data to write to flash * @param buflen number of bytes to write */ static void coredump_flash_backend_buffer_output(uint8_t *buf, size_t buflen) { int i; size_t remaining = buflen; size_t copy_sz; uint8_t *ptr = buf; uint8_t tmp_buf[FLASH_BUF_SIZE]; if ((backend_ctx.error != 0) || (backend_ctx.flash_area == NULL)) { return; } /* * Since the system is still running, memory content is constantly * changing (e.g. stack of this thread). We need to make a copy of * part of the buffer, so that the checksum corresponds to what is * being written. */ copy_sz = FLASH_BUF_SIZE; while (remaining > 0) { if (remaining < FLASH_BUF_SIZE) { copy_sz = remaining; } (void)memcpy(tmp_buf, ptr, copy_sz); for (i = 0; i < copy_sz; i++) { backend_ctx.checksum += tmp_buf[i]; } backend_ctx.error = stream_flash_buffered_write( &backend_ctx.stream_ctx, tmp_buf, copy_sz, false); if (backend_ctx.error != 0) { LOG_ERR("Flash write error: %d", backend_ctx.error); break; } ptr += copy_sz; remaining -= copy_sz; } } /** * @brief Perform query on this backend. * * @param query_id ID of query * @param arg argument of query * @return depends on query */ static int coredump_flash_backend_query(enum coredump_query_id query_id, void *arg) { int ret; switch (query_id) { case COREDUMP_QUERY_GET_ERROR: ret = backend_ctx.error; break; case COREDUMP_QUERY_HAS_STORED_DUMP: ret = process_stored_dump(cb_calc_buf_checksum, NULL); break; case COREDUMP_QUERY_GET_STORED_DUMP_SIZE: ret = get_stored_dump(0, NULL, 0); break; default: ret = -ENOTSUP; break; } return ret; } /** * @brief Perform command on this backend. * * @param cmd_id command ID * @param arg argument of command * @return depends on query */ static int coredump_flash_backend_cmd(enum coredump_cmd_id cmd_id, void *arg) { int ret; switch (cmd_id) { case COREDUMP_CMD_CLEAR_ERROR: ret = 0; backend_ctx.error = 0; break; case COREDUMP_CMD_VERIFY_STORED_DUMP: ret = process_stored_dump(cb_calc_buf_checksum, NULL); break; case COREDUMP_CMD_ERASE_STORED_DUMP: ret = erase_flash_partition(); break; case COREDUMP_CMD_COPY_STORED_DUMP: if (arg) { struct coredump_cmd_copy_arg *copy_arg = (struct coredump_cmd_copy_arg *)arg; ret = get_stored_dump(copy_arg->offset, copy_arg->buffer, copy_arg->length); } else { ret = -EINVAL; } break; case COREDUMP_CMD_INVALIDATE_STORED_DUMP: ret = erase_coredump_header(); break; default: ret = -ENOTSUP; break; } return ret; } struct coredump_backend_api coredump_backend_flash_partition = { .start = coredump_flash_backend_start, .end = coredump_flash_backend_end, .buffer_output = coredump_flash_backend_buffer_output, .query = coredump_flash_backend_query, .cmd = coredump_flash_backend_cmd, }; #ifdef CONFIG_DEBUG_COREDUMP_SHELL #include /* Length of buffer of printable size */ #define PRINT_BUF_SZ 64 /* Length of buffer of printable size plus null character */ #define PRINT_BUF_SZ_RAW (PRINT_BUF_SZ + 1) /* Print buffer */ static char print_buf[PRINT_BUF_SZ_RAW]; static off_t print_buf_ptr; /** * @brief Shell command to get backend error. * * @param sh shell instance * @param argc (not used) * @param argv (not used) * @return 0 */ static int cmd_coredump_error_get(const struct shell *sh, size_t argc, char **argv) { ARG_UNUSED(argc); ARG_UNUSED(argv); if (backend_ctx.error == 0) { shell_print(sh, "No error."); } else { shell_print(sh, "Error: %d", backend_ctx.error); } return 0; } /** * @brief Shell command to clear backend error. * * @param sh shell instance * @param argc (not used) * @param argv (not used) * @return 0 */ static int cmd_coredump_error_clear(const struct shell *sh, size_t argc, char **argv) { backend_ctx.error = 0; shell_print(sh, "Error cleared."); return 0; } /** * @brief Shell command to see if there is a stored coredump in flash. * * @param sh shell instance * @param argc (not used) * @param argv (not used) * @return 0 */ static int cmd_coredump_has_stored_dump(const struct shell *sh, size_t argc, char **argv) { int ret; ARG_UNUSED(argc); ARG_UNUSED(argv); ret = coredump_flash_backend_query(COREDUMP_QUERY_HAS_STORED_DUMP, NULL); if (ret == 1) { shell_print(sh, "Stored coredump found."); } else if (ret == 0) { shell_print(sh, "Stored coredump NOT found."); } else { shell_print(sh, "Failed to perform query: %d", ret); } return 0; } /** * @brief Shell command to verify if the stored coredump is valid. * * @param sh shell instance * @param argc (not used) * @param argv (not used) * @return 0 */ static int cmd_coredump_verify_stored_dump(const struct shell *sh, size_t argc, char **argv) { int ret; ARG_UNUSED(argc); ARG_UNUSED(argv); ret = coredump_flash_backend_cmd(COREDUMP_CMD_VERIFY_STORED_DUMP, NULL); if (ret == 1) { shell_print(sh, "Stored coredump verified."); } else if (ret == 0) { shell_print(sh, "Stored coredump verification failed " "or there is no stored coredump."); } else { shell_print(sh, "Failed to perform verify command: %d", ret); } return 0; } /** * @brief Flush the print buffer to shell. * * This prints what is in the print buffer to the shell. * * @param sh shell instance. */ static void flush_print_buf(const struct shell *sh) { shell_print(sh, "%s%s", COREDUMP_PREFIX_STR, print_buf); print_buf_ptr = 0; (void)memset(print_buf, 0, sizeof(print_buf)); } /** * @brief Callback to print stored coredump to shell * * This converts the binary data in @p buf to hexadecimal digits * which can be printed to the shell. * * @param arg shell instance * @param buf binary data buffer * @param len number of bytes in buffer to be printed * @return 0 if no issues; -EINVAL if error converting data */ static int cb_print_stored_dump(void *arg, uint8_t *buf, size_t len) { int ret = 0; size_t i = 0; size_t remaining = len; const struct shell *sh = (const struct shell *)arg; if (len == 0) { /* Flush print buffer */ flush_print_buf(sh); goto out; } /* Do checksum for process_stored_dump() */ cb_calc_buf_checksum(arg, buf, len); while (remaining > 0) { if (hex2char(buf[i] >> 4, &print_buf[print_buf_ptr]) < 0) { ret = -EINVAL; break; } print_buf_ptr++; if (hex2char(buf[i] & 0xf, &print_buf[print_buf_ptr]) < 0) { ret = -EINVAL; break; } print_buf_ptr++; remaining--; i++; if (print_buf_ptr == PRINT_BUF_SZ) { flush_print_buf(sh); } } out: return ret; } /** * @brief Shell command to print stored coredump data to shell * * @param sh shell instance * @param argc (not used) * @param argv (not used) * @return 0 */ static int cmd_coredump_print_stored_dump(const struct shell *sh, size_t argc, char **argv) { int ret; ARG_UNUSED(argc); ARG_UNUSED(argv); /* Verify first to see if stored dump is valid */ ret = coredump_flash_backend_cmd(COREDUMP_CMD_VERIFY_STORED_DUMP, NULL); if (ret == 0) { shell_print(sh, "Stored coredump verification failed " "or there is no stored coredump."); goto out; } else if (ret != 1) { shell_print(sh, "Failed to perform verify command: %d", ret); goto out; } /* If valid, start printing to shell */ print_buf_ptr = 0; (void)memset(print_buf, 0, sizeof(print_buf)); shell_print(sh, "%s%s", COREDUMP_PREFIX_STR, COREDUMP_BEGIN_STR); ret = process_stored_dump(cb_print_stored_dump, (void *)sh); if (print_buf_ptr != 0) { shell_print(sh, "%s%s", COREDUMP_PREFIX_STR, print_buf); } if (backend_ctx.error != 0) { shell_print(sh, "%s%s", COREDUMP_PREFIX_STR, COREDUMP_ERROR_STR); } shell_print(sh, "%s%s", COREDUMP_PREFIX_STR, COREDUMP_END_STR); if (ret == 1) { shell_print(sh, "Stored coredump printed."); } else if (ret == 0) { shell_print(sh, "Stored coredump verification failed " "or there is no stored coredump."); } else { shell_print(sh, "Failed to print: %d", ret); } out: return 0; } /** * @brief Shell command to erase stored coredump. * * @param sh shell instance * @param argc (not used) * @param argv (not used) * @return 0 */ static int cmd_coredump_erase_stored_dump(const struct shell *sh, size_t argc, char **argv) { int ret; ARG_UNUSED(argc); ARG_UNUSED(argv); ret = coredump_flash_backend_cmd(COREDUMP_CMD_ERASE_STORED_DUMP, NULL); if (ret == 0) { shell_print(sh, "Stored coredump erased."); } else { shell_print(sh, "Failed to perform erase command: %d", ret); } return 0; } SHELL_STATIC_SUBCMD_SET_CREATE(sub_coredump_error, SHELL_CMD(clear, NULL, "Clear Coredump error", cmd_coredump_error_clear), SHELL_CMD(get, NULL, "Get Coredump error", cmd_coredump_error_get), SHELL_SUBCMD_SET_END /* Array terminated. */ ); SHELL_STATIC_SUBCMD_SET_CREATE(sub_coredump, SHELL_CMD(error, &sub_coredump_error, "Get/clear backend error.", NULL), SHELL_CMD(erase, NULL, "Erase stored coredump", cmd_coredump_erase_stored_dump), SHELL_CMD(find, NULL, "Query if there is a stored coredump", cmd_coredump_has_stored_dump), SHELL_CMD(print, NULL, "Print stored coredump to shell", cmd_coredump_print_stored_dump), SHELL_CMD(verify, NULL, "Verify stored coredump", cmd_coredump_verify_stored_dump), SHELL_SUBCMD_SET_END /* Array terminated. */ ); SHELL_CMD_REGISTER(coredump, &sub_coredump, "Coredump commands (flash partition backend)", NULL); #endif /* CONFIG_DEBUG_COREDUMP_SHELL */ #endif /* FIXED_PARTITION_EXISTS(coredump_partition) */