This patch is to add support Coresight trace dump with perf mode from vmcore file. After kernel panic we can get kernel dump file vmcore and after enabling kernel configuration CONFIG_CORESIGHT_KDUMP the ETM trace data will be stored into vmcore file; this patch is to extract CoreSight ETM metadata from ETM driver data structure and config structure and use OpenCSD lib to decode ETM trace data.
It checks perf ring buffer and if the ring buffer is wrap around yet, the code firstly dump the tail of aux pages and then continue to dump data from the head of aux page. So finally we can merge the complete trace data from ring buffer. If the ring buffer isn't wrap around, then it's straightforward to dump data from the head of pages to the last writing point.
This is the initial version to only support ETMv4 trace data, later can extend this program to support more tracers. To let this version for easily applying for users, the command is pretty simple:
crash> extend arm_cs_dump.so crash> arm_cs_dump -o output_dir
Cc: Mathieu Poirier mathieu.poirier@linaro.org Signed-off-by: Leo Yan leo.yan@linaro.org --- extensions/arm_cs_dump.c | 955 ++++++++++++++++++++++++++++++++ extensions/arm_cs_dump.mk | 39 ++ extensions/arm_cs_dump/arm_cs_decoder.c | 319 +++++++++++ extensions/arm_cs_dump/arm_cs_decoder.h | 40 ++ 4 files changed, 1353 insertions(+) create mode 100644 extensions/arm_cs_dump.c create mode 100644 extensions/arm_cs_dump.mk create mode 100644 extensions/arm_cs_dump/arm_cs_decoder.c create mode 100644 extensions/arm_cs_dump/arm_cs_decoder.h
diff --git a/extensions/arm_cs_dump.c b/extensions/arm_cs_dump.c new file mode 100644 index 0000000..a0f46b0 --- /dev/null +++ b/extensions/arm_cs_dump.c @@ -0,0 +1,955 @@ +/* + * Extension module to extract dump buffer of ARM Coresight Trace + * + * Copyright (C) 2017, 2018 Linaro Ltd + * Author: Leo Yan leo.yan@linaro.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define _GNU_SOURCE +#include "defs.h" +#include <sys/file.h> +#include <sys/types.h> +#include "arm_cs_dump/arm_cs_decoder.h" + +#ifdef DEBUG +#define dbgprintf(...) fprintf(__VA_ARGS__) +#else +#define dbgprintf(...) {} +#endif + +#define INPUT_BLOCK_SIZE 1024 + +#define CS_MODE_NONE 0 +#define CS_MODE_PERF 1 + +struct arm_cs_info { + int mode; + + ulong aux_pages; + int aux_nr_pages; + ulong etm_event_data; + + ulong *buffer_ptr; + int curr_buf_idx; + ulong curr_buf_off; + ulong curr_data_sz; +} *arm_cs_info_list; + + +#define koffset(struct, member) struct##_##member##_offset + +/* at = ((struct *)ptr)->member */ +#define read_value(at, ptr, struct, member) \ +do { \ + readmem(ptr + koffset(struct, member), KVADDR, \ + &at, sizeof(at), #struct "'s " #member, \ + RETURN_ON_ERROR); \ +} while (0) + +#define init_offset(struct, member) \ +do { \ + koffset(struct, member) = MEMBER_OFFSET(#struct, #member); \ + if (koffset(struct, member) < 0) { \ + fprintf(fp, "failed to init the offset, struct:" \ + #struct ", member:" #member); \ + fprintf(fp, "\n"); \ + return -1; \ + } \ +} while (0) + + +static int koffset(perf_output_handle, event); +static int koffset(perf_output_handle, rb); + +static int koffset(ring_buffer, aux_pages); +static int koffset(ring_buffer, aux_nr_pages); +static int koffset(ring_buffer, aux_priv); + +static int koffset(etm_event_data, snk_config); + +static int koffset(cs_buffers, cur); +static int koffset(cs_buffers, offset); +static int koffset(cs_buffers, data_size); + +static int koffset(coresight_device, dev); +static int koffset(coresight_device, type); +static int koffset(coresight_device, subtype); + +static int koffset(device, parent); +static int koffset(device, driver_data); + +static int koffset(etmv4_drvdata, config); +static int koffset(etmv4_drvdata, trcid); + +static int koffset(etmv4_config, cfg); +static int koffset(etmv4_config, idr0); +static int koffset(etmv4_config, idr1); +static int koffset(etmv4_config, idr2); +static int koffset(etmv4_config, idr8); + +/* + * Thanks for Intel PT dump code ptdump.c giving good example for perf + * ring buffer dump. In this file the most perf ring buffer dump code + * reuses Intel PT dump code with minor changes for checking the last + * writing point of ring buffer; it supports to check if wrap around or + * not and copy trace data from perf ring buffer and save into a file. + */ +static inline int is_zero_page(ulong page, int offset) +{ + ulong read_addr = page + offset; + ulong read_size = PAGESIZE() - offset; + char *buf = malloc(PAGESIZE()); + int i; + + if (buf == NULL) { + fprintf(fp, "malloc failed\n"); + return FALSE; + } + + memset(buf, 0, PAGESIZE()); + dbgprintf(fp, "zero page chk: 0x%016lx, %lu\n", read_addr, read_size); + readmem(read_addr, KVADDR, buf, read_size, "zero page check", + FAULT_ON_ERROR); + + for (i = 0; i < PAGESIZE() - offset; i++) { + if (buf[i]) { + free(buf); + return FALSE; + } + } + + free(buf); + return TRUE; +} + +int check_wrap_around(int cpu) +{ + struct arm_cs_info *cs_info_ptr = arm_cs_info_list + cpu; + int wrapped = 0, i, page_idx; + ulong offset, mask, page; + + mask = (((ulong)1)<<PAGESHIFT()) - 1; + offset = cs_info_ptr->curr_buf_off & mask; + page_idx = cs_info_ptr->curr_buf_idx + + (cs_info_ptr->curr_buf_off >> PAGESHIFT()); + + dbgprintf(fp, "[%d] buf: mask=0x%lx\n", cpu, mask); + dbgprintf(fp, "[%d] buf: offset=0x%lx\n", cpu, offset); + dbgprintf(fp, "[%d] buf: page_idx=%d\n", cpu, page_idx); + + for (i=page_idx; i<cs_info_ptr->aux_nr_pages; i++) { + page = cs_info_ptr->buffer_ptr[i]; + + if (!is_zero_page(page, offset)) { + wrapped = 1; + break; + } + + offset = 0; + } + + return wrapped; +} + +int write_buffer_wrapped(int cpu, FILE *out_fp) +{ + struct arm_cs_info *cs_info_ptr = arm_cs_info_list + cpu; + int start_idx, idx, len, ret; + ulong mask, offset, page; + char *buf = malloc(PAGESIZE()); + + if (buf == NULL) { + fprintf(fp, "malloc failed\n"); + return FALSE; + } + + mask = (((ulong)1)<<PAGESHIFT()) - 1; + offset = cs_info_ptr->curr_buf_off & mask; + + start_idx = cs_info_ptr->curr_buf_idx + + (cs_info_ptr->curr_buf_off >> PAGESHIFT()); + + for (idx = start_idx; idx<cs_info_ptr->aux_nr_pages; idx++) { + page = cs_info_ptr->buffer_ptr[idx]; + len = PAGESIZE() - offset; + + readmem(page + offset, KVADDR, buf, len, "read page for write", + FAULT_ON_ERROR); + + dbgprintf(fp, "[%d] R/W1 buff: p=0x%lx, i=%d, o=%lu, l=%d\n", + cpu, page + offset, idx, offset, len); + + ret = fwrite(buf, len, 1, out_fp); + if (!ret) { + fprintf(fp, "[%d] Cannot write file\n", cpu); + free(buf); + return FALSE; + } + + offset = 0; + } + + for (idx = 0; idx < start_idx; idx++) { + page = cs_info_ptr->buffer_ptr[idx]; + len = PAGESIZE() - offset; + + readmem(page + offset, KVADDR, buf, len, "read page for write", + FAULT_ON_ERROR); + + dbgprintf(fp, "[%d] R/W2 buff: p=0x%lx, i=%d, o=%lu, l=%d\n", + cpu, page + offset, idx, offset, len); + + ret = fwrite(buf, len, 1, out_fp); + if (!ret) { + fprintf(fp, "[%d] Cannot write file\n", cpu); + free(buf); + return FALSE; + } + } + + idx = start_idx; + page = cs_info_ptr->buffer_ptr[idx]; + offset = cs_info_ptr->curr_buf_off & mask; + len = offset; + + if (len) { + readmem(page, KVADDR, buf, len, "read page for write", + FAULT_ON_ERROR); + + dbgprintf(fp, "[%d] R/W3 buff: p=0x%lx, i=%d, o=%lu, l=%d\n", cpu, + page, idx, offset, len); + + ret = fwrite(buf, len, 1, out_fp); + if (!ret) { + fprintf(fp, "[%d] Cannot write file\n", cpu); + free(buf); + return FALSE; + } + } + + free(buf); + return TRUE; +} + +int write_buffer_nowrapped(int cpu, FILE *out_fp) +{ + struct arm_cs_info *cs_info_ptr = arm_cs_info_list + cpu; + int last_idx, idx, len, ret; + ulong mask, page; + char *buf = malloc(PAGESIZE()); + + if (buf == NULL) { + fprintf(fp, "malloc failed\n"); + return FALSE; + } + + mask = (((ulong)1)<<PAGESHIFT()) - 1; + last_idx = cs_info_ptr->curr_buf_idx + + (cs_info_ptr->curr_buf_off >> PAGESHIFT()); + + for (idx = 0; idx < last_idx; idx++) { + page = cs_info_ptr->buffer_ptr[idx]; + len = PAGESIZE(); + + readmem(page, KVADDR, buf, len, "read page for write", + FAULT_ON_ERROR); + + dbgprintf(fp, "[%d] R/W1 buff: p=0x%lx, i=%d, o=%lu, l=%d\n", + cpu, page, idx, (ulong)0, len); + + ret = fwrite(buf, len, 1, out_fp); + if (!ret) { + fprintf(fp, "[%d] Cannot write file\n", cpu); + free(buf); + return FALSE; + } + } + + idx = last_idx; + page = cs_info_ptr->buffer_ptr[idx]; + len = cs_info_ptr->curr_buf_off & mask; + + readmem(page, KVADDR, buf, len, "read page for write", + FAULT_ON_ERROR); + + dbgprintf(fp, "[%d] R/W2 buff: p=0x%lx, i=%d, o=%lu, l=%d\n", cpu, + page, idx, (ulong)0, len); + + ret = fwrite(buf, len, 1, out_fp); + if (!ret) { + fprintf(fp, "[%d] Cannot write file\n", cpu); + free(buf); + return FALSE; + } + + free(buf); + return TRUE; +} + +int write_pt_log_buffer_cpu(int cpu, char *fname) +{ + int wrapped, ret; + FILE *out_fp; + + wrapped = check_wrap_around(cpu); + + if ((out_fp = fopen(fname, "w")) == NULL) { + fprintf(fp, "[%d] Cannot open file: %s\n", cpu, fname); + return FALSE; + } + dbgprintf(fp, "[%d] Open file: %s\n", cpu, fname); + + /* + * Write buffer to file + * + * Case 1: Not wrapped around + * + * start end + * | | + * v v + * +------+ +------+ +------+ +------+ + * |buffer| |buffer| ... |buffer| |buffer| + * +------+ +------+ +------+ +------+ + * + * In this case, just write data between 'start' and 'end' + * + * Case 2: Wrapped around + * + * end start + * | | + * v v + * +------+ +------+ +------+ +------+ + * |buffer| |buffer| ... |buffer| |buffer| + * +------+ +------+ +------+ +------+ + * + * In this case, at first write data between 'start' and end of last + * buffer, and then write data between beginning of first buffer and + * 'end'. + */ + if (wrapped) { + dbgprintf(fp, "[%d] wrap around: true\n", cpu); + ret = write_buffer_wrapped(cpu, out_fp); + } else { + dbgprintf(fp, "[%d] wrap around: false\n", cpu); + ret = write_buffer_nowrapped(cpu, out_fp); + } + + fclose(out_fp); + return ret; +} + +static int arm_cs_save_perf_ring_buffer(int cpu) +{ + char trace_file[sizeof("cstrace.9999999.bin")]; + ulong struct_ctx_handle; + ulong struct_ring_buffer; + ulong aux_pages, aux_priv; + ulong struct_cs_buffers; + uint cur; + ulong offset, data_size; + int i, aux_nr_pages, buf_len; + struct arm_cs_info *cs_info_ptr = arm_cs_info_list + cpu; + + /* Get pointer to struct ctx_handle for etm perf */ + if (!symbol_exists("ctx_handle")) { + fprintf(fp, "[CPU%d] symbol not found: pt_ctx\n", cpu); + return FALSE; + } + + struct_ctx_handle = symbol_value("ctx_handle") + kt->__per_cpu_offset[cpu]; + dbgprintf(fp, "struct_ctx_handle 0x%lx\n", struct_ctx_handle); + + read_value(struct_ring_buffer, struct_ctx_handle, + perf_output_handle, rb); + dbgprintf(fp, "struct_ring_buffer 0x%lx\n", struct_ring_buffer); + + if (!struct_ring_buffer) { + fprintf(fp, "No ring buffer\n"); + return FALSE; + } + + /* symbol access check */ + if (STRUCT_EXISTS("ring_buffer") && + !MEMBER_EXISTS("ring_buffer", "aux_pages")) { + fprintf(fp, "[CPU%d] invalid ring_buffer\n", cpu); + return FALSE; + } + + /* array of struct pages for pt buffer */ + read_value(aux_pages, struct_ring_buffer, ring_buffer, aux_pages); + + /* number of pages */ + read_value(aux_nr_pages, struct_ring_buffer, ring_buffer, aux_nr_pages); + + /* private data (struct etm_event_data) */ + read_value(aux_priv, struct_ring_buffer, ring_buffer, aux_priv); + + if (!aux_nr_pages) { + fprintf(fp, "No aux pages\n"); + return FALSE; + } + + cs_info_ptr->aux_pages = aux_pages; + cs_info_ptr->aux_nr_pages = aux_nr_pages; + cs_info_ptr->etm_event_data = aux_priv; + + dbgprintf(fp, "[CPU%d] rb.aux_pages=0x%016lx\n", cpu, aux_pages); + dbgprintf(fp, "[CPU%d] rb.aux_nr_pages=0x%d\n", cpu, aux_nr_pages); + dbgprintf(fp, "[CPU%d] rb.aux_priv=0x%016lx\n", cpu, aux_priv); + + /* Get address of pt buffer */ + buf_len = sizeof(void*)*aux_nr_pages; + cs_info_ptr->buffer_ptr = (ulong *)malloc(buf_len); + if (cs_info_ptr->buffer_ptr == NULL) { + fprintf(fp, "malloc failed\n"); + return FALSE; + } + memset(cs_info_ptr->buffer_ptr, 0, buf_len); + + for (i = 0; i < aux_nr_pages; i++) { + ulong pgaddr = aux_pages + i*sizeof(void*); + ulong page; + + if (!readmem(pgaddr, KVADDR, &page, sizeof(ulong), + "struct page", FAULT_ON_ERROR)) + continue; + + cs_info_ptr->buffer_ptr[i] = page; + + if (!i) + dbgprintf(fp, "[CPU%d] Dump aux pages\n", cpu); + dbgprintf(fp, " %d: 0x%016lx\n", i, page); + } + + read_value(struct_cs_buffers, cs_info_ptr->etm_event_data, + etm_event_data, snk_config); + + dbgprintf(fp, "struct_cs_buffers:0x%016lx\n", struct_cs_buffers); + + read_value(cur, struct_cs_buffers, cs_buffers, cur); + read_value(offset, struct_cs_buffers, cs_buffers, offset); + read_value(data_size, struct_cs_buffers, cs_buffers, data_size); + + cs_info_ptr->curr_buf_idx = cur; + cs_info_ptr->curr_buf_off = offset; + cs_info_ptr->curr_data_sz = data_size; + dbgprintf(fp, "[CPU%d] current bufidx=%d\n", cpu, cur); + dbgprintf(fp, "[CPU%d] current buf offset=%ld\n", cpu, offset); + dbgprintf(fp, "[CPU%d] current data size=%ld\n", cpu, data_size); + + for (i = 0; i < cpu; i++) { + struct arm_cs_info *ptr = arm_cs_info_list + i; + + if (ptr->aux_pages == cs_info_ptr->aux_pages) { + fprintf(fp, "[CPU%d] skip duplicate buffer with [%d]\n", + cpu, i); + cs_info_ptr->mode = CS_MODE_PERF; + return TRUE; + } + } + + sprintf(trace_file, "cstrace.%d.bin", cpu); + + if (write_pt_log_buffer_cpu(cpu, trace_file)) + fprintf(fp, "[CPU%d] buffer dump: %s\n", cpu, trace_file); + + cs_info_ptr->mode = CS_MODE_PERF; + + return TRUE; +} + +static int arm_cs_save_tracedata(void) +{ + int online_cpus, cpu; + int ret; + + dbgprintf(fp, "Extract raw trace data...\n"); + + online_cpus = get_cpus_online(); + switch (protocol) { + case OCSD_PROTOCOL_ETMV4I: + for (cpu = 0; cpu < online_cpus; cpu++) { + /* Firstly try perf ring buffer */ + ret = arm_cs_save_perf_ring_buffer(cpu); + if (ret) + continue; + + /* Return for failure */ + return ret; + } + break; + default: + /* Protocol isn't supported yet */ + return FALSE; + } + + return TRUE; +} + +static ulong arm_cs_get_perf_source_handler(int cpu) +{ + ulong struct_csdev, struct_csdev_ptr; + + /* Get pointer to struct ctx_handle for etm perf */ + if (!symbol_exists("csdev_src")) { + fprintf(fp, "[CPU0] symbol not found: pt_ctx\n"); + return 0; + } + + struct_csdev_ptr = symbol_value("csdev_src") + kt->__per_cpu_offset[cpu]; + dbgprintf(fp, "struct_csdev_ptr=0x%lx\n", struct_csdev_ptr); + + readmem(struct_csdev_ptr, KVADDR, &struct_csdev, + sizeof(struct_csdev), "read csdev", RETURN_ON_ERROR); + dbgprintf(fp,"struct_csdev=0x%lx\n", struct_csdev); + + return struct_csdev; +} + +static ulong arm_cs_get_drvdata_handle(ulong struct_csdev) +{ + ulong struct_dev, struct_parent_dev; + ulong struct_drvdata; + + struct_dev = struct_csdev + koffset(coresight_device, dev); + dbgprintf(fp,"struct_dev=0x%lx\n", struct_dev); + + read_value(struct_parent_dev, struct_dev, device, parent); + dbgprintf(fp,"struct_parent_dev=0x%lx\n", struct_parent_dev); + + read_value(struct_drvdata, struct_parent_dev, device, driver_data); + dbgprintf(fp,"struct_drvdata=0x%lx\n", struct_drvdata); + + return struct_drvdata; +} + +static ocsd_err_t arm_cs_create_etmv4_decoder(dcd_tree_handle_t dcd_tree_h, + int cpu) +{ + ulong csdev, etmv4_driverdata, etmv4_config; + ocsd_etmv4_cfg trace_config; + char trcid; + struct arm_cs_info *cs_info_ptr = arm_cs_info_list + cpu; + + switch (cs_info_ptr->mode) { + case CS_MODE_PERF: + csdev = arm_cs_get_perf_source_handler(cpu); + break; + default: + csdev = 0; + break; + } + + if (!csdev) { + fprintf(fp, "Failed to read csdev device handle\n"); + return OCSD_ERR_FAIL; + } + + etmv4_driverdata = arm_cs_get_drvdata_handle(csdev); + if (!etmv4_driverdata) { + fprintf(fp, "Failed to read etmv4 drvdata\n"); + return OCSD_ERR_FAIL; + } + + etmv4_config = etmv4_driverdata + koffset(etmv4_drvdata, config); + dbgprintf(fp,"etmv4_config=0x%lx\n", etmv4_config); + + read_value(trcid, etmv4_driverdata, etmv4_drvdata, trcid); + + /* + * Populate the ETMv4 configuration structure with hard coded + * values from snapshot .ini files. + */ + trace_config.arch_ver = ARCH_V8; + trace_config.core_prof = profile_CortexA; + trace_config.reg_traceidr = trcid; + trace_config.reg_idr9 = 0x0; + trace_config.reg_idr10 = 0x0; + trace_config.reg_idr11 = 0x0; + trace_config.reg_idr12 = 0x0; + trace_config.reg_idr13 = 0x0; + + read_value(trace_config.reg_configr, etmv4_config, etmv4_config, cfg); + read_value(trace_config.reg_idr0, etmv4_config, etmv4_config, idr0); + read_value(trace_config.reg_idr1, etmv4_config, etmv4_config, idr1); + read_value(trace_config.reg_idr2, etmv4_config, etmv4_config, idr2); + read_value(trace_config.reg_idr8, etmv4_config, etmv4_config, idr8); + + dbgprintf(fp, "reg_configr=0x%x\n", trace_config.reg_configr); + dbgprintf(fp, "reg_traceidr=0x%x\n", trace_config.reg_traceidr); + dbgprintf(fp, "reg_idr0=0x%x\n", trace_config.reg_idr0); + dbgprintf(fp, "reg_idr1=0x%x\n", trace_config.reg_idr1); + dbgprintf(fp, "reg_idr2=0x%x\n", trace_config.reg_idr2); + dbgprintf(fp, "reg_idr8=0x%x\n", trace_config.reg_idr8); + dbgprintf(fp, "reg_idr9=0x%x\n", trace_config.reg_idr9); + dbgprintf(fp, "reg_idr10=0x%x\n", trace_config.reg_idr10); + dbgprintf(fp, "reg_idr11=0x%x\n", trace_config.reg_idr11); + dbgprintf(fp, "reg_idr12=0x%x\n", trace_config.reg_idr12); + dbgprintf(fp, "reg_idr13=0x%x\n", trace_config.reg_idr13); + + /* + * create an ETMV4 decoder - no context needed as we have a + * single stream to a single handler. + */ + return arm_cs_create_generic_decoder(dcd_tree_h, + OCSD_BUILTIN_DCD_ETMV4I, + (void *)&trace_config, 0); +} + +/* Create a decoder according to options */ +static ocsd_err_t arm_cs_create_decoder(dcd_tree_handle_t dcd_tree_h) +{ + ocsd_err_t err; + int online_cpus, i; + + online_cpus = get_cpus_online(); + + switch (protocol) { + case OCSD_PROTOCOL_ETMV4I: + for (i = 0; i < online_cpus; i++) { + err = arm_cs_create_etmv4_decoder(dcd_tree_h, i); + if (err != OCSD_OK) + return err; + } + break; + default: + err = OCSD_ERR_NO_PROTOCOL; + break; + } + + return err; +} + +/* Process buffer until done or error */ +static ocsd_err_t arm_cs_process_data_block(dcd_tree_handle_t dcd_tree_h, + int block_index, + uint8_t *p_block, + const uint32_t block_size) +{ + ocsd_err_t ret = OCSD_OK; + ocsd_datapath_resp_t dp_ret = OCSD_RESP_CONT; + uint32_t total_sz = 0; + uint32_t sz = 0; + + while (total_sz < block_size) { + + if (OCSD_DATA_RESP_IS_CONT(dp_ret)) { + + dp_ret = ocsd_dt_process_data(dcd_tree_h, + OCSD_OP_DATA, + block_index + total_sz, + block_size - total_sz, + p_block+ total_sz, + &sz); + total_sz += sz; + + } else if (OCSD_DATA_RESP_IS_WAIT(dp_ret)) { + + dp_ret = ocsd_dt_process_data(dcd_tree_h, + OCSD_OP_FLUSH, + 0, 0, NULL, NULL); + } else { + ret = OCSD_ERR_DATA_DECODE_FATAL; + break; + } + } + + return ret; +} + +static int arm_cs_process_trace_data(FILE *pf) +{ + ocsd_err_t ret = OCSD_OK; + dcd_tree_handle_t dcdtree_handle = C_API_INVALID_TREE_HANDLE; + uint8_t data_buffer[INPUT_BLOCK_SIZE]; + ocsd_trc_index_t index = 0; + size_t data_read; + + /* + * Create a decode tree for this source data. Source data is frame + * formatted, memory aligned from an ETR (no frame syncs) so create + * tree accordingly. + */ + dcdtree_handle = ocsd_create_dcd_tree(OCSD_TRC_SRC_FRAME_FORMATTED, + OCSD_DFRMTR_FRAME_MEM_ALIGN | + OCSD_DFRMTR_RESET_ON_4X_FSYNC); + if (dcdtree_handle == C_API_INVALID_TREE_HANDLE) { + fprintf(fp, "Failed to create dcd tree\n"); + return FALSE; + } + + ret = arm_cs_create_decoder(dcdtree_handle); + if (ret != OCSD_OK) { + fprintf(fp, "Failed to create decoder\n"); + return FALSE; + } + + ret = arm_cs_create_test_memory_acc(dcdtree_handle); + if (ret != OCSD_OK) { + fprintf(fp, "Failed to create memory accessing\n"); + return FALSE; + } + + ocsd_tl_log_mapped_mem_ranges(dcdtree_handle); + + ret = ocsd_dt_set_gen_elem_outfn(dcdtree_handle, + arm_cs_gen_trace_elem_print, 0); + if (ret != OCSD_OK) { + fprintf(fp, "Failed to set elem outfn\n"); + return FALSE; + } + + /* Now push the trace data through the packet processor */ + while (!feof(pf)) { + + /* Read from file */ + data_read = fread(data_buffer, 1, INPUT_BLOCK_SIZE, pf); + if (data_read <= 0) { + if (ferror(pf)) + ret = OCSD_ERR_FILE_ERROR; + break; + } + + /* + * Process a block of data - any packets from the trace stream + * we have configured will appear at the callback. + */ + ret = arm_cs_process_data_block(dcdtree_handle, index, + data_buffer, data_read); + if (ret != OCSD_OK) + break; + + index += data_read; + } + + if (ret != OCSD_OK) { + fprintf(fp, "Failed to process data block.\n"); + return FALSE; + } + + ocsd_dt_process_data(dcdtree_handle, OCSD_OP_EOT, 0, 0, NULL, NULL); + + /* Shut down the mem acc CB if in use. */ + arm_cs_destroy_mem_acc_cb(dcdtree_handle); + + /* + * Dispose of the decode tree - which will dispose of any packet + * processors we created. + */ + ocsd_destroy_dcd_tree(dcdtree_handle); + return TRUE; +} + +static int arm_cs_decode_etmv4_trace_data(int cpu) +{ + FILE *trace_data; + char trace_file[sizeof("cstrace.9999999.bin")]; + char log_file[sizeof("cstrace.9999999.log")]; + int ret; + char message[512]; + + /* Trace file cstrace.X.bin, X is CPU number */ + sprintf(trace_file, "cstrace.%d.bin", cpu); + + trace_data = fopen(trace_file, "rb"); + if (!trace_data) { + fprintf(fp, "Trace file %s doesn't exist\n", trace_file); + return TRUE; + } + + /* + * Set up the logging in the library - enable the error logger, + * with an output printer. + */ + ret = ocsd_def_errlog_init(OCSD_ERR_SEV_INFO, 1); + if (ret != 0) + return FALSE; + + /* Log file cstrace.X.bin, X is CPU number */ + sprintf(log_file, "cstrace.%d.log", cpu); + + /* Set up the output - to file and stdout, set custom logfile name */ + ret = ocsd_def_errlog_config_output(C_API_MSGLOGOUT_FLG_FILE | + C_API_MSGLOGOUT_FLG_STDOUT, + log_file); + + /* Print sign-on message in log */ + sprintf(message, "C-API packet print test\nLibrary Version %s\n\n", + ocsd_get_version_str()); + ocsd_def_errlog_msgout(message); + + /* Process the trace data */ + ret = arm_cs_process_trace_data(trace_data); + if (!ret) + fprintf(fp,"Failed to process trace data.\n"); + + /* Close the data file */ + fclose(trace_data); + return TRUE; +} + +static int arm_cs_decode_tracedata(void) +{ + int online_cpus, cpu; + int ret; + + dbgprintf(fp, "Decode raw trace data...\n"); + + online_cpus = get_cpus_online(); + switch (protocol) { + case OCSD_PROTOCOL_ETMV4I: + for (cpu = 0; cpu < online_cpus; cpu++) { + ret = arm_cs_decode_etmv4_trace_data(cpu); + if (!ret) + return ret; + } + break; + default: + /* Protocol isn't supported yet */ + return FALSE; + } + + return TRUE; +} + +static int arm_cs_dump_prepare(void) +{ + int list_len, online_cpus; + + init_offset(perf_output_handle, event); + init_offset(perf_output_handle, rb); + + init_offset(ring_buffer, aux_pages); + init_offset(ring_buffer, aux_nr_pages); + init_offset(ring_buffer, aux_priv); + + init_offset(etm_event_data, snk_config); + + init_offset(cs_buffers, cur); + init_offset(cs_buffers, offset); + init_offset(cs_buffers, data_size); + + init_offset(coresight_device, dev); + init_offset(coresight_device, type); + init_offset(coresight_device, subtype); + + init_offset(device, parent); + init_offset(device, driver_data); + + init_offset(etmv4_drvdata, config); + init_offset(etmv4_drvdata, trcid); + + init_offset(etmv4_config, cfg); + init_offset(etmv4_config, idr0); + init_offset(etmv4_config, idr1); + init_offset(etmv4_config, idr2); + init_offset(etmv4_config, idr8); + + online_cpus = get_cpus_online(); + list_len = sizeof(struct arm_cs_info) * online_cpus; + arm_cs_info_list = malloc(list_len); + if (arm_cs_info_list == NULL) { + fprintf(fp, "Cannot alloc arm_cs_info_list\n"); + return FALSE; + } + memset(arm_cs_info_list, 0, list_len); + + return TRUE; +} + +int arm_cs_main(void) +{ + int ret; + + ret = arm_cs_save_tracedata(); + if (!ret) + return ret; + + ret = arm_cs_decode_tracedata(); + if (!ret) + return ret; + + return TRUE; +} + +void arm_cs_dump_cmd(void) +{ + char* outdir = NULL; + mode_t mode = S_IRUSR | S_IWUSR | S_IXUSR | + S_IRGRP | S_IXGRP | + S_IROTH | S_IXOTH; /* 0755 */ + int c, ret; + + /* Parse command line option */ + while ((c = getopt(argcnt, args, "t:o:")) != EOF) { + switch(c) { + case 't': + if (!strcmp(optarg, "etmv4")) + protocol = OCSD_PROTOCOL_ETMV4I; + else + argerrs++; + break; + case 'o': + outdir = optarg; + break; + default: + argerrs++; + break; + } + } + + if (argerrs || !outdir) + cmd_usage(pc->curcmd, SYNOPSIS); + + ocsd_def_errlog_init(OCSD_ERR_SEV_INFO, 1); + + if (!arm_cs_dump_prepare()) + return; + + if ((ret = mkdir(outdir, mode))) { + fprintf(fp, "Cannot create directory %s: %d\n", outdir, ret); + return; + } + + if ((ret = chdir(outdir))) { + fprintf(fp, "Cannot chdir %s: %d\n", outdir, ret); + return; + } + + arm_cs_main(); + + chdir(".."); + return; +} + +static char *arm_cs_dump_help[] = { + "arm_cs_dump", + "Dump log buffer of Coresight Trace", + "-o <output-dir>", + " This command extracts coresight trace to the output directory", + NULL +}; + +static struct command_table_entry command_table[] = { + { "arm_cs_dump", arm_cs_dump_cmd, arm_cs_dump_help, 0}, + { NULL }, +}; + +void __attribute__((constructor)) +arm_cs_dump_init(void) +{ + register_extension(command_table); +} + +void __attribute__((destructor)) +arm_cs_dump_fini(void) { } diff --git a/extensions/arm_cs_dump.mk b/extensions/arm_cs_dump.mk new file mode 100644 index 0000000..afe96dc --- /dev/null +++ b/extensions/arm_cs_dump.mk @@ -0,0 +1,39 @@ +# +# Copyright (C) 2018 Linaro Ltd. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# + +ifeq ($(shell /bin/ls /usr/include/crash/defs.h 2>/dev/null), /usr/include/crash/defs.h) + INCDIR=/usr/include/crash +endif +ifeq ($(shell /bin/ls ./defs.h 2> /dev/null), ./defs.h) + INCDIR=. +endif +ifeq ($(shell /bin/ls ../defs.h 2> /dev/null), ../defs.h) + INCDIR=.. +endif + +SUBDIR=arm_cs_dump +TARGET_CFILES=arm_cs_dump.c $(SUBDIR)/arm_cs_decoder.c + +COMMON_CFLAGS=-Wall -I$(INCDIR) -fPIC -D$(TARGET) + +all: arm_cs_dump.so + +arm_cs_dump.so: $(TARGET_CFILES) $(INCDIR)/defs.h + gcc $(RPM_OPT_FLAGS) $(CFLAGS) $(TARGET_CFLAGS) $(COMMON_CFLAGS) -nostartfiles -shared -rdynamic -o $@ $(TARGET_CFILES) -lelf -lopencsd -lopencsd_c_api + +debug: COMMON_CFLAGS+=-DDEBUG +debug: all + +clean: + rm -f *.so *.o diff --git a/extensions/arm_cs_dump/arm_cs_decoder.c b/extensions/arm_cs_dump/arm_cs_decoder.c new file mode 100644 index 0000000..9281bc5 --- /dev/null +++ b/extensions/arm_cs_dump/arm_cs_decoder.c @@ -0,0 +1,319 @@ +/* + * Extension module to extract dump buffer of ARM Coresight Trace + * + * Copyright (C) 2017, 2018 Linaro Ltd + * Author: Leo Yan leo.yan@linaro.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define _GNU_SOURCE +#include "defs.h" +#include <sys/file.h> +#include <sys/types.h> + +#include "arm_cs_decoder.h" + +#define INPUT_BLOCK_SIZE 1024 + +/* buffer to handle a packet string */ +#define PACKET_STR_LEN 1024 +static char packet_str[PACKET_STR_LEN]; + +test_op_t op_type = TEST_PKT_DECODE; +ocsd_trace_protocol_t protocol = OCSD_PROTOCOL_ETMV4I; + +uint32_t arm_cs_mem_acc_cb(const void *p_context, + const ocsd_vaddr_t address, + const ocsd_mem_space_acc_t mem_space, + const uint32_t size, + uint8_t *buffer) +{ + return 0; +} + +/* + * Create the memory accessor using the callback function + * and attach to decode tree + */ +ocsd_err_t arm_cs_create_mem_acc_cb(dcd_tree_handle_t dcd_tree_h) +{ + ocsd_err_t err; + + err = ocsd_dt_add_callback_mem_acc(dcd_tree_h, + 0x0L, ((uint64_t) -1L), + OCSD_MEM_SPACE_ANY, + &arm_cs_mem_acc_cb, 0); + return err; +} + +/* Remove the callback memory accessor from decode tree */ +void arm_cs_destroy_mem_acc_cb(dcd_tree_handle_t dcd_tree_h) +{ + ocsd_dt_remove_mem_acc(dcd_tree_h, 0x0L, OCSD_MEM_SPACE_ANY); +} + +/* + * Create and attach the memory accessor according to + * required test parameters. + */ +ocsd_err_t arm_cs_create_test_memory_acc(dcd_tree_handle_t handle) +{ + ocsd_err_t ret; + + /* + * Decide how to handle the file - test the normal memory accessor + * (contiguous binary file), a callback accessor or a multi-region + * file (e.g. similar to using the code region in a .so). + * + * The same memory dump file is used in each case, we just present + * it differently to test the API functions. + */ + + ret = arm_cs_create_mem_acc_cb(handle); + return ret; +} + +/* + * Callback function to process the packets in the packet processor + * output stream - simply print them out in this case to the + * library message/error logger. + */ +ocsd_datapath_resp_t arm_cs_packet_handler(void *context, + const ocsd_datapath_op_t op, + const ocsd_trc_index_t index_sop, + const void *p_packet_in) +{ + ocsd_err_t ret; + int offset = 0; + + switch (op) { + case OCSD_OP_DATA: + sprintf(packet_str, "Idx:%" OCSD_TRC_IDX_STR "; ", index_sop); + offset = strlen(packet_str); + + /* + * Got a packet - convert to string and use the libraries' + * message output to print to file and stdoout since the + * test always prints a single ID, we know the protocol type. + */ + ret = ocsd_pkt_str(protocol, p_packet_in, packet_str + offset, + PACKET_STR_LEN - offset); + if (ret != OCSD_OK) + return OCSD_RESP_FATAL_INVALID_PARAM; + + /* Add in <CR> */ + if (strlen(packet_str) == PACKET_STR_LEN - 1) /* max length */ + packet_str[PACKET_STR_LEN-2] = '\n'; + else + strcat(packet_str,"\n"); + + /* Print it using the library output logger. */ + ocsd_def_errlog_msgout(packet_str); + break; + + case OCSD_OP_EOT: + sprintf(packet_str, "*** END OF TRACE ***\n"); + ocsd_def_errlog_msgout(packet_str); + break; + + default: + break; + } + + return OCSD_RESP_CONT; +} + +/* + * Print an array of hex data - used by the packet monitor + * to print hex data from packet. + */ +int arm_cs_print_data_array(const uint8_t *p_array, + const int array_size, + char *buffer, int size) +{ + int printed = 0; + int idx; + + buffer[0] = 0; + + if (size > 9) { + /* set up the header */ + strcat(buffer,"[ "); + printed += 2; + + for (idx = 0; idx < array_size; idx++) { + sprintf(buffer + printed,"0x%02X ", p_array[idx]); + printed += 5; + if ((printed + 5) > size) + break; + } + + strcat(buffer, "];"); + printed += 2; + + } else if (size >= 4) { + sprintf(buffer,"[];"); + printed += 3; + } + + return printed; +} + +/* + * Callback function to process packets and packet data from + * the monitor output of the packet processor. Again print + * them to the library error logger. + */ +void arm_cs_packet_monitor(void *context, const ocsd_datapath_op_t op, + const ocsd_trc_index_t index_sop, + const void *p_packet_in, const uint32_t size, + const uint8_t *p_data) +{ + int offset = 0; + ocsd_err_t ret; + + switch(op) { + case OCSD_OP_DATA: + sprintf(packet_str, "Idx:%" OCSD_TRC_IDX_STR ";", index_sop); + offset = strlen(packet_str); + offset += arm_cs_print_data_array(p_data, size, + packet_str + offset, + PACKET_STR_LEN - offset); + + /* + * Got a packet - convert to string and use the libraries + * message output to print to file and stdoout. + */ + ret = ocsd_pkt_str(protocol, p_packet_in, + packet_str + offset, + PACKET_STR_LEN - offset); + if (ret != OCSD_OK) { + fprintf(fp,"Failed to get packet string.\n"); + return; + } + + /* Add in <CR> */ + if (strlen(packet_str) == PACKET_STR_LEN - 1) /* max length */ + packet_str[PACKET_STR_LEN-2] = '\n'; + else + strcat(packet_str,"\n"); + + /* Print it using the library output logger. */ + ocsd_def_errlog_msgout(packet_str); + break; + + case OCSD_OP_EOT: + sprintf(packet_str, "*** END OF TRACE ***\n"); + ocsd_def_errlog_msgout(packet_str); + break; + + default: + break; + } + + return; +} + +/* Output generic trace elements */ +ocsd_datapath_resp_t arm_cs_gen_trace_elem_print(const void *p_context, + const ocsd_trc_index_t index_sop, + const uint8_t trc_chan_id, + const ocsd_generic_trace_elem *elem) +{ + int offset = 0; + ocsd_err_t ret; + + sprintf(packet_str, "Idx:%" OCSD_TRC_IDX_STR "; TrcID:0x%02X; ", + index_sop, trc_chan_id); + + offset = strlen(packet_str); + + ret = ocsd_gen_elem_str(elem, packet_str + offset, + PACKET_STR_LEN - offset); + if (ret != OCSD_OK) { + strcat(packet_str,"Unable to create element string\n"); + } else { + /* Add in <CR> */ + if (strlen(packet_str) == PACKET_STR_LEN - 1) /* max length */ + packet_str[PACKET_STR_LEN-2] = '\n'; + else + strcat(packet_str,"\n"); + } + + /* Print it using the library output logger. */ + ocsd_def_errlog_msgout(packet_str); + + return OCSD_RESP_CONT; +} + +ocsd_err_t arm_cs_create_generic_decoder(dcd_tree_handle_t handle, + const char *p_name, + const void *p_cfg, + const void *p_context) +{ + ocsd_err_t ret = OCSD_OK; + uint8_t csid = 0; + + if (op_type == TEST_PKT_PRINT) { + /* + * Create a packet processor on the decode tree for the + * configuration we have. We need to supply the configuration. + */ + ret = ocsd_dt_create_decoder(handle, p_name, + OCSD_CREATE_FLG_PACKET_PROC, + p_cfg, &csid); + if (ret != OCSD_OK) { + fprintf(fp,"Failed to create decoder for PKT_PRINT\n"); + return ret; + } + + ret = ocsd_dt_attach_packet_callback(handle, csid, + OCSD_C_API_CB_PKT_SINK, + &arm_cs_packet_handler, + p_context); + if (ret != OCSD_OK) { + fprintf(fp,"Failed to attach CB for PKT_PRINT\n"); + ocsd_dt_remove_decoder(handle, csid); + return ret; + } + } else { + /* + * Full decode - need decoder, and memory dump; + * create the packet decoder and packet processor pair + * from the supplied name. + */ + ret = ocsd_dt_create_decoder(handle, p_name, + OCSD_CREATE_FLG_FULL_DECODER, + p_cfg, &csid); + if (ret != OCSD_OK) { + fprintf(fp,"Failed to create decoder for PKT_DECODE{ONLY}\n"); + return ret; + } + + if (op_type == TEST_PKT_DECODE) { + /* + * Print the packets as well as the decode - use the + * packet processors monitor output this time, as the + * main output is attached to the packet decoder. + */ + ret = ocsd_dt_attach_packet_callback(handle, csid, + OCSD_C_API_CB_PKT_MON, + arm_cs_packet_monitor, p_context); + if (ret != OCSD_OK) { + fprintf(fp,"Failed to attach CB for printing packet\n"); + return ret; + } + } + } + + return OCSD_OK; +} diff --git a/extensions/arm_cs_dump/arm_cs_decoder.h b/extensions/arm_cs_dump/arm_cs_decoder.h new file mode 100644 index 0000000..d8202b4 --- /dev/null +++ b/extensions/arm_cs_dump/arm_cs_decoder.h @@ -0,0 +1,40 @@ +#include <opencsd/c_api/opencsd_c_api.h> + +typedef enum test_op { + TEST_PKT_PRINT, /* Process discrete packets and print */ + TEST_PKT_DECODE, /* Decode and generic output */ + TEST_PKT_DECODEONLY /* Decode and output packets only */ +} test_op_t; + +extern test_op_t op_type; +extern ocsd_trace_protocol_t protocol; + +extern uint32_t arm_cs_mem_acc_cb(const void *p_context, + const ocsd_vaddr_t address, + const ocsd_mem_space_acc_t mem_space, + const uint32_t size, + uint8_t *buffer); +extern ocsd_err_t arm_cs_create_test_memory_acc(dcd_tree_handle_t handle); +extern ocsd_err_t arm_cs_create_mem_acc_cb(dcd_tree_handle_t dcd_tree_h); +extern void arm_cs_destroy_mem_acc_cb(dcd_tree_handle_t dcd_tree_h); +extern ocsd_datapath_resp_t arm_cs_packet_handler(void *context, + const ocsd_datapath_op_t op, + const ocsd_trc_index_t index_sop, + const void *p_packet_in); +extern int arm_cs_print_data_array(const uint8_t *p_array, + const int array_size, + char *buffer, int size); +extern void arm_cs_packet_monitor(void *context, + const ocsd_datapath_op_t op, + const ocsd_trc_index_t index_sop, + const void *p_packet_in, + const uint32_t size, + const uint8_t *p_data); +extern ocsd_datapath_resp_t arm_cs_gen_trace_elem_print(const void *p_context, + const ocsd_trc_index_t index_sop, + const uint8_t trc_chan_id, + const ocsd_generic_trace_elem *elem); +extern ocsd_err_t arm_cs_create_generic_decoder(dcd_tree_handle_t handle, + const char *p_name, + const void *p_cfg, + const void *p_context);