| /******************************************************************************* |
| * Copyright (c) 2020 Dortmund University of Applied Sciences and Arts and others. |
| * |
| * This program and the accompanying materials are made |
| * available under the terms of the Eclipse Public License 2.0 |
| * which is available at https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Dortmund University of Applied Sciences and Arts - initial API and implementation |
| *******************************************************************************/ |
| |
| #include <getopt.h> |
| #include <time.h> |
| #include "trace_utils_BTF.h" |
| #include <limits.h> |
| #include <unistd.h> |
| |
| /*------------------------------DEFINES-------------------------*/ |
| #define BTF_TRACE_ENTITY_TABLE_SIZE 64 |
| #define CORE_STACK_SIZE 16 |
| #define TRACE_PATH_SIZE 512 |
| |
| /*-------------------------GOLBAL VARIABLES--------------------*/ |
| const char *btf_trace_version = "#version 1.0"; |
| |
| /*-------------------------STATIC GOLBAL VARIABLES--------------------*/ |
| static btf_trace_header_config_t btf_header; |
| static uint8_t output_trace_path[TRACE_PATH_SIZE]; |
| static btf_trace_entity_table entity_table[BTF_TRACE_ENTITY_TABLE_SIZE]; |
| static uint8_t isEntityTypeHeaderWritten = BTF_TRACE_FALSE; |
| static uint8_t isEntityTableHeaderWritten = BTF_TRACE_FALSE; |
| static uint8_t isEntityTypTableHeaderWritten = BTF_TRACE_FALSE; |
| static btf_trace_data core0_trace_data[CORE_STACK_SIZE]; |
| static btf_trace_data core1_trace_data[CORE_STACK_SIZE]; |
| static int8_t core0_stack_top = -1; |
| static int8_t core1_stack_top = -1; |
| static int scale_factor = 1; |
| |
| |
| /*-------------------------CONST VARIABLES---------------------------*/ |
| const uint8_t event_type[][8] = { |
| "T", |
| "ISR", |
| "R", |
| "IB", |
| "STI", |
| "ECU", |
| "P", |
| "C", |
| "SCHED", |
| "SIG", |
| "SEM", |
| "SIM" |
| }; |
| |
| const uint8_t event_name[][16] = { |
| "start", |
| "terminate", |
| "preempt", |
| "suspend", |
| "resume", |
| "read", |
| "write" |
| }; |
| |
| |
| /*-------------------------STATIC FUNCTIONS----------------------------*/ |
| static void print_usage(void); |
| static void get_trace_timestamp(uint8_t *buffer); |
| static int16_t find_first_free_index(void); |
| static void get_trace_timestamp(uint8_t *buffer); |
| static uint8_t update_entity_entry(unsigned int id, unsigned int instance, unsigned int event_state); |
| static void process_btf_trace_data(FILE *stream, btf_trace_data *data, int8_t *top_of_stack, unsigned int *data_buffer); |
| static void push_on_stack(btf_trace_data *data, int8_t *top_of_stack, unsigned int *data_buffer); |
| static btf_trace_data pop_from_stack(btf_trace_data *data, int8_t *top_of_stack); |
| static int find_task_in_execution(btf_trace_data *data, int8_t stack_top, btf_trace_event_type type); |
| static void dump_btf_trace_data(FILE *stream, unsigned int ticks, unsigned int srcId, unsigned int srcInstance, |
| btf_trace_event_type type, unsigned int target, unsigned int targetInstance, |
| btf_trace_event_name event, unsigned int data); |
| |
| /* Function to get the first free available index */ |
| static int16_t find_first_free_index(void) |
| { |
| int index = 0; |
| for(index = 0; index < BTF_TRACE_ENTITY_TABLE_SIZE; index++) |
| { |
| if (entity_table[index].is_occupied == 0x00) |
| { |
| return index; |
| } |
| } |
| return -1; |
| } |
| |
| /* Function to get the entity name based on the id passed */ |
| static unsigned char * get_entity_name(unsigned int id) |
| { |
| int index = 0; |
| for(index = 0; index < BTF_TRACE_ENTITY_TABLE_SIZE; index++) |
| { |
| if (entity_table[index].is_occupied == 0x01) |
| { |
| if (id == entity_table[index].entity_data.entity_id) |
| { |
| return entity_table[index].entity_data.entity_name; |
| } |
| } |
| } |
| return NULL; |
| } |
| |
| /* Function to get the current time of creation of btf trace file */ |
| static void get_trace_timestamp(uint8_t *buffer) |
| { |
| time_t timer; |
| char date[16] = {0}; |
| char time_t[16] = {0}; |
| struct tm* tm_info; |
| time(&timer); |
| tm_info = localtime(&timer); |
| /* The total number of characters to display time is 26 */ |
| strftime((char *)date, 16, "%Y-%m-%d", tm_info); |
| strftime((char *)time_t, 16, "%H:%M:%S", tm_info); |
| /* Set the time in ISO 8601 extended specification format */ |
| snprintf((char *)buffer, 26, "%s%c%s", date, 'T', time_t); |
| } |
| |
| /* Function to display to usage of the command line parameters */ |
| static void print_usage(void) |
| { |
| fprintf(stdout, "Usage:\n"); |
| fprintf(stdout,"\t[-t|--trace-btf]=<Output trace file name.>\n"); |
| fprintf(stdout,"\t[-m|--model-file]=<Model file name used to generate the trace file.>\n"); |
| fprintf(stdout,"\t[-s|--scale]=<Timing scale used to generate the trace file in microseconds. Accepted values are 100 and 1000.>\n"); |
| fprintf(stdout,"\t[-d|--device]=<Device target on which the trace file is generated.>\n"); |
| fprintf(stdout,"\t[-h|--help]=<Print the help message.>\n"); |
| fprintf(stdout,"Example:\n"); |
| fprintf(stdout,"\t./host_main_example1 -t trace.btf -s 100 -m model.xml -d parallella\n"); |
| fflush(stdout); |
| } |
| |
| /* Function to update the entity entry table */ |
| static uint8_t update_entity_entry(unsigned int id, unsigned int instance, unsigned int event_state) |
| { |
| int index; |
| //Parse the entity table to check for any new event or instance. |
| for(index = 0; index < BTF_TRACE_ENTITY_TABLE_SIZE; index++) |
| { |
| if ((entity_table[index].is_occupied == 0x01) && (id == entity_table[index].entity_data.entity_id)) |
| { |
| if ((entity_table[index].entity_data.instance != instance) || |
| (entity_table[index].entity_data.state != event_state)) |
| { |
| entity_table[index].entity_data.instance = instance; |
| entity_table[index].entity_data.state = event_state; |
| return BTF_TRACE_TRUE; |
| } |
| } |
| } |
| return BTF_TRACE_FALSE; |
| } |
| |
| /* Function to push any entity event on core stack */ |
| static void push_on_stack(btf_trace_data *data, int8_t *top_of_stack, unsigned int *data_buffer) |
| { |
| int8_t stack_top = *top_of_stack; |
| if(stack_top >= CORE_STACK_SIZE - 1) |
| { |
| fprintf(stdout, "\n\tSTACK is over flow"); |
| return; |
| } |
| stack_top++; |
| data[stack_top].eventTypeId = data_buffer[EVENT_TYPE_FLAG]; |
| data[stack_top].srcId = data_buffer[SOURCE_FLAG]; |
| data[stack_top].srcInstance = data_buffer[SOURCE_INSTANCE_FLAG]; |
| data[stack_top].taskId = data_buffer[TARGET_FLAG]; |
| data[stack_top].taskInstance = data_buffer[TARGET_INSTANCE_FLAG]; |
| data[stack_top].eventState = data_buffer[EVENT_FLAG]; |
| data[stack_top].data = data_buffer[DATA_FLAG]; |
| *top_of_stack = stack_top; |
| } |
| |
| /* Function to pop any entity event on core stack */ |
| static btf_trace_data pop_from_stack(btf_trace_data *data, int8_t *top_of_stack) |
| { |
| btf_trace_data task = {0}; |
| int8_t stack_top = *top_of_stack; |
| if(stack_top <= -1) |
| { |
| fprintf(stdout, "\n\t Stack is under flow"); |
| return task; |
| } |
| else |
| { |
| task = data[stack_top]; |
| stack_top--; |
| *top_of_stack = stack_top; |
| return task; |
| } |
| } |
| |
| /* Function to find the current active task */ |
| static int find_task_in_execution(btf_trace_data *data, int8_t stack_top, btf_trace_event_type type) |
| { |
| int index = -1; |
| int found = -1; |
| for(index = 0; index <= stack_top; index++) |
| { |
| if (data[index].eventTypeId == type && ((data[index].eventState == PROCESS_START) |
| || (data[index].eventState == PROCESS_RESUME))) |
| { |
| found = index; |
| } |
| } |
| return found; |
| } |
| |
| |
| /* Function to dump the BTF trace data on output trace file */ |
| static void dump_btf_trace_data(FILE *stream, unsigned int ticks, |
| unsigned int srcId, unsigned int srcInstance, |
| btf_trace_event_type type, |
| unsigned int target, unsigned int targetInstance, |
| btf_trace_event_name event, unsigned int data) |
| { |
| unsigned char * source_name = get_entity_name(srcId); |
| unsigned char * target_name = get_entity_name(target); |
| const unsigned char *event_type_string = event_type[type]; |
| const unsigned char *event_name_string = event_name[event]; |
| if ((source_name != NULL) && (target_name != NULL)) |
| { |
| fprintf(stream,"%d,%s,%d,%s,%s,%d,%s,%d\n", (ticks * scale_factor * 1000) , source_name, srcInstance, |
| event_type_string, target_name, targetInstance, event_name_string, data); |
| fflush(stream); |
| } |
| } |
| |
| /* Function to process event on each core */ |
| static void process_btf_trace_data(FILE *stream, btf_trace_data *data, int8_t *top_of_stack, unsigned int *data_buffer) |
| { |
| if (*top_of_stack == -1) |
| { |
| // Stack is empty.Push on stack |
| push_on_stack(data, top_of_stack, data_buffer); |
| dump_btf_trace_data(stream, data_buffer[TIME_FLAG], data_buffer[SOURCE_FLAG], |
| data_buffer[SOURCE_INSTANCE_FLAG], data_buffer[EVENT_TYPE_FLAG], |
| data_buffer[TARGET_FLAG], data_buffer[TARGET_INSTANCE_FLAG], |
| data_buffer[EVENT_FLAG], data_buffer[DATA_FLAG]); |
| } |
| else |
| { |
| btf_trace_data previousTask; |
| if (data_buffer[EVENT_FLAG] == PROCESS_START) |
| { |
| //Preempt the current running task and suspend the associated runnable |
| int task = find_task_in_execution(data, *top_of_stack, TASK_EVENT); |
| int runnable = find_task_in_execution(data, *top_of_stack, RUNNABLE_EVENT); |
| if((task != -1) && (data[task].taskId != data_buffer[SOURCE_FLAG])) |
| { |
| // print task to preemption |
| data[task].eventState = PROCESS_PREEMPT; |
| dump_btf_trace_data(stream, data_buffer[TIME_FLAG], data[task].srcId, |
| data[task].srcInstance, data[task].eventTypeId, |
| data[task].taskId, data[task].taskInstance, |
| data[task].eventState, data[task].data); |
| } |
| if((runnable != -1) && (data[runnable].srcId != data_buffer[TARGET_FLAG])) |
| { |
| // print runnable to suspend |
| data[runnable].eventState = PROCESS_SUSPEND; |
| dump_btf_trace_data(stream, data_buffer[TIME_FLAG], data[runnable].srcId, |
| data[runnable].srcInstance, data[runnable].eventTypeId, |
| data[runnable].taskId, data[runnable].taskInstance, |
| data[runnable].eventState, data[runnable].data); |
| } |
| //print the task which started and push it on the stack |
| push_on_stack(data, top_of_stack, data_buffer); |
| dump_btf_trace_data(stream, data_buffer[TIME_FLAG], data_buffer[SOURCE_FLAG], |
| data_buffer[SOURCE_INSTANCE_FLAG], data_buffer[EVENT_TYPE_FLAG], |
| data_buffer[TARGET_FLAG], data_buffer[TARGET_INSTANCE_FLAG], |
| data_buffer[EVENT_FLAG], data_buffer[DATA_FLAG]); |
| } |
| else if (data_buffer[EVENT_FLAG] == PROCESS_TERMINATE) |
| { |
| //Get the previous task and dump on the trace file if the task ID and instance match |
| previousTask = data[*top_of_stack]; |
| if((data_buffer[TARGET_FLAG] == previousTask.taskId) |
| && (data_buffer[TARGET_INSTANCE_FLAG] == previousTask.taskInstance)) |
| { |
| dump_btf_trace_data(stream, data_buffer[TIME_FLAG], data_buffer[SOURCE_FLAG], |
| data_buffer[SOURCE_INSTANCE_FLAG], data_buffer[EVENT_TYPE_FLAG], |
| data_buffer[TARGET_FLAG], data_buffer[TARGET_INSTANCE_FLAG], |
| data_buffer[EVENT_FLAG], data_buffer[DATA_FLAG]); |
| pop_from_stack(data, top_of_stack); |
| } |
| |
| if ((*top_of_stack >= 1) && (data_buffer[EVENT_TYPE_FLAG] == TASK_EVENT)) |
| { |
| // Check if there is any task in preempt or suspend state. Resume them. |
| previousTask = data[*top_of_stack - 1]; |
| int8_t prevRunnable = *top_of_stack; |
| btf_trace_data runnableTask = data[prevRunnable]; |
| dump_btf_trace_data(stream, data_buffer[TIME_FLAG], previousTask.srcId, previousTask.srcInstance, |
| previousTask.eventTypeId, previousTask.taskId, previousTask.taskInstance, |
| PROCESS_RESUME, previousTask.data); |
| dump_btf_trace_data(stream, data_buffer[TIME_FLAG], runnableTask.srcId, runnableTask.srcInstance, |
| runnableTask.eventTypeId, runnableTask.taskId, runnableTask.taskInstance, |
| PROCESS_RESUME, runnableTask.data); |
| data[*top_of_stack].eventState = PROCESS_RESUME; |
| data[prevRunnable].eventState = PROCESS_RESUME; |
| } |
| } |
| else |
| { |
| //Do nothing |
| } |
| } |
| } |
| |
| /** |
| * Function to get the file name of the trace file along with the |
| * absolute path. |
| */ |
| void get_btf_trace_file_path(char *trace_file_path) |
| { |
| if (trace_file_path == NULL) |
| { |
| return; |
| } |
| char lcwd[PATH_MAX-1]; |
| |
| if (getcwd(lcwd, sizeof(lcwd)) != NULL) { |
| fprintf(stderr,"Current working dir: %s\n", lcwd); |
| } else { |
| perror("getcwd() error"); |
| return; |
| } |
| if(0 != access(lcwd, W_OK)) |
| { |
| fprintf(stderr,"You don't have write access to the directory in which you are trying to create the btf file\n"); |
| } |
| sprintf(trace_file_path,"%s" "%c" "%s",lcwd,'/', output_trace_path); |
| fflush(stderr); |
| } |
| |
| /** |
| * Parse the command line arguments for generating the BTF trace file |
| */ |
| int parse_btf_trace_arguments(int argc, char **argv) |
| { |
| int opt= 0; |
| int is_time_unit_provided = BTF_TRACE_FALSE; |
| int is_time_scale_provided = BTF_TRACE_FALSE; |
| |
| /* Reset the posix variable associated to getopt_long */ |
| opterr = 0; |
| static const struct option long_options[] = { |
| {"trace-btf", required_argument, NULL, 't' }, |
| {"model-file", required_argument, NULL, 'm' }, |
| {"scale", optional_argument, NULL, 's'}, |
| {"unit", optional_argument, NULL, 'u'}, |
| {"device", required_argument, NULL, 'd'}, |
| {"help", no_argument, NULL, 'h'}, |
| {NULL, 0, NULL, 0} |
| }; |
| |
| int long_index =0; |
| while ((opt = getopt_long(argc, argv,"t:m:s:d:", |
| long_options, &long_index )) != -1) { |
| switch (opt) { |
| case 't' : |
| strncpy((char *)output_trace_path, (const char *)optarg, sizeof(output_trace_path)); |
| break; |
| case 'm' : |
| strncpy((char *)btf_header.modelfile, (const char *)optarg, sizeof(btf_header.modelfile)); |
| break; |
| case 'd' : |
| strncpy((char *)btf_header.creator, (const char *)optarg, sizeof(btf_header.creator)); |
| break; |
| case 'u' : |
| strncpy((char *)btf_header.timeunit, (const char *)optarg, sizeof(btf_header.timeunit)); |
| is_time_unit_provided = BTF_TRACE_TRUE; |
| break; |
| case 's' : |
| btf_header.timescale = atoi(optarg); |
| scale_factor = btf_header.timescale; |
| /* Accepted scale factor is 100us and 1000us */ |
| if ((scale_factor == 100) || (scale_factor == 1000)) |
| { |
| // No action needed. |
| } |
| else |
| { |
| print_usage(); |
| exit(EXIT_FAILURE); |
| } |
| is_time_scale_provided = BTF_TRACE_TRUE; |
| break; |
| case 'h' : |
| print_usage(); |
| exit(EXIT_SUCCESS); |
| case '?' : |
| default : |
| print_usage(); |
| exit(EXIT_FAILURE); |
| } |
| } |
| /* Set the default time scale as us */ |
| if (is_time_unit_provided == BTF_TRACE_FALSE) |
| { |
| strncpy((char *)btf_header.timeunit, (const char *)"ns", sizeof(btf_header.timeunit)); |
| } |
| |
| /* Set the default time scale as 1000 which corresponds to 1 ms. */ |
| if (is_time_scale_provided == BTF_TRACE_FALSE) |
| { |
| btf_header.timescale = 1000; |
| scale_factor = btf_header.timescale; |
| } |
| |
| return btf_header.timescale; |
| |
| } |
| |
| |
| |
| /** |
| * This function is used to store the entity information of all the |
| * tasks, runnables and labels. |
| */ |
| void store_entity_entry(entity_id typeId, btf_trace_event_type type, const char *name) |
| { |
| int16_t index = 0; |
| index = find_first_free_index(); |
| if (index >= 0) |
| { |
| entity_table[index].entity_data.entity_id = typeId; |
| entity_table[index].entity_data.instance = -1; |
| entity_table[index].entity_data.state = INIT; |
| entity_table[index].entity_data.entity_type = type; |
| strcpy((char *)entity_table[index].entity_data.entity_name, (const char *)name); |
| entity_table[index].is_occupied = 0x01; |
| } |
| } |
| |
| |
| /** |
| * This function is responsible for writing the BTF trace header information. |
| */ |
| void write_btf_trace_header_config(FILE *stream) |
| { |
| if (stream == NULL) |
| { |
| return; |
| } |
| /* 32 bytes is enough to store the timescale format */ |
| uint8_t time_string[32] = {0}; |
| fprintf(stream, "%s\n", btf_trace_version); |
| fprintf(stream, "#creator %s\n", btf_header.creator); |
| get_trace_timestamp(time_string); |
| fprintf(stream, "#creationdate %s\n", time_string); |
| fprintf(stream, "#inputFile %s\n", btf_header.modelfile); |
| fprintf(stream, "#timescale %s\n", btf_header.timeunit); |
| fflush(stream); |
| } |
| |
| /** |
| * This function to write entity type in BTF header data. |
| */ |
| void write_btf_trace_header_entity_type(FILE *stream, btf_trace_event_type type) |
| { |
| if (stream == NULL) |
| { |
| return; |
| } |
| if (isEntityTypeHeaderWritten == BTF_TRACE_FALSE) |
| { |
| fprintf(stream, "#entityType\n"); |
| isEntityTypeHeaderWritten = BTF_TRACE_TRUE; |
| } |
| fprintf(stream, "#-%d %s\n", type, event_type[type]); |
| fflush(stream); |
| } |
| |
| |
| /** |
| * This function writes the entity type table in the BTF header. |
| */ |
| void write_btf_trace_header_entity_type_table(FILE *stream) |
| { |
| if (stream == NULL) |
| { |
| return; |
| } |
| if (isEntityTypTableHeaderWritten == BTF_TRACE_FALSE) |
| { |
| fprintf(stream, "#entityTypeTable\n"); |
| isEntityTypTableHeaderWritten = BTF_TRACE_TRUE; |
| } |
| int index = 0; |
| for(index = 0; index < BTF_TRACE_ENTITY_TABLE_SIZE; index++) |
| { |
| if (entity_table[index].is_occupied == 0x01) |
| { |
| int type_index = entity_table[index].entity_data.entity_type; |
| fprintf(stream, "#-%s %s\n", event_type[type_index], |
| entity_table[index].entity_data.entity_name); |
| fflush(stream); |
| } |
| } |
| } |
| |
| |
| /** |
| * Function to write entity type in BTF header data |
| */ |
| void write_btf_trace_header_entity_table(FILE *stream) |
| { |
| if (stream == NULL) |
| { |
| return; |
| } |
| if (isEntityTableHeaderWritten == BTF_TRACE_FALSE) |
| { |
| fprintf(stream, "#entityTable\n"); |
| isEntityTableHeaderWritten = BTF_TRACE_TRUE; |
| } |
| int index = 0; |
| for(index = 0; index < BTF_TRACE_ENTITY_TABLE_SIZE; index++) |
| { |
| if (entity_table[index].is_occupied == 0x01) |
| { |
| fprintf(stream, "#-%d %s\n", entity_table[index].entity_data.entity_id, |
| entity_table[index].entity_data.entity_name); |
| fflush(stream); |
| } |
| } |
| } |
| |
| /** |
| * Function to write the data section of the BTF |
| */ |
| void write_btf_trace_data(FILE *stream, uint8_t core_id, unsigned int * data_buffer) |
| { |
| if (stream == NULL || (data_buffer == NULL)) |
| { |
| return; |
| } |
| if (core_id == 1) |
| { |
| process_btf_trace_data(stream, core1_trace_data, &core1_stack_top, data_buffer); |
| } |
| else if (core_id == 0) |
| { |
| #if 0 |
| fprintf(stream,"Input ---%d,%d,%d,%d,%d,%d,%d\n", data_buffer[TIME_FLAG], data_buffer[SOURCE_FLAG], |
| data_buffer[SOURCE_INSTANCE_FLAG], data_buffer[EVENT_TYPE_FLAG], data_buffer[TARGET_FLAG], |
| data_buffer[TARGET_INSTANCE_FLAG], data_buffer[EVENT_FLAG]); |
| fflush(stream); |
| #endif |
| process_btf_trace_data(stream, core0_trace_data, &core0_stack_top, data_buffer); |
| #if 0 |
| int index = 0; |
| if (core0_stack_top >= 0) |
| { |
| fprintf(stream, "stack size=%d\n", core0_stack_top); |
| for(index = core0_stack_top; index >= 0; index--) |
| { |
| fprintf(stream,"Src=%d Target=%d Event=%d\n",core0_trace_data[index].srcId, |
| core0_trace_data[index].taskId, core0_trace_data[index].eventState); |
| fflush(stream); |
| } |
| } |
| #endif |
| } |
| else |
| { |
| // Do nothing |
| } |
| |
| } |