Select Git revision
Forked from
PSP Fanclub / AVRSimV2
2 commits behind, 1 commit ahead of the upstream repository.
-
Jonas Broeckmann authoredJonas Broeckmann authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
avrsimv2.c 15.78 KiB
#include "avrsimv2.h"
#include <stdio.h>
#include <string.h>
#if IS_WIN
#include <windows.h>
#include <crtdbg.h>
#endif
#include "utils/whereami.h"
#include "utils/opts.h"
enum {
AVRSIMV2_ARG_FW = 0,
AVRSIMV2_ARG_M ,
AVRSIMV2_ARG_F ,
AVRSIMV2_ARG_B ,
AVRSIMV2_ARG_BA ,
AVRSIMV2_ARG_XML ,
AVRSIMV2_ARG_G ,
AVRSIMV2_ARG_COUNT
};
enum {
AVRSIMV2_OPT_HELP = 0,
AVRSIMV2_OPT_V ,
AVRSIMV2_OPT_M ,
AVRSIMV2_OPT_F ,
AVRSIMV2_OPT_B ,
AVRSIMV2_OPT_BA ,
AVRSIMV2_OPT_XML ,
AVRSIMV2_OPT_X ,
AVRSIMV2_OPT_FLOAT ,
AVRSIMV2_OPT_WSLX ,
AVRSIMV2_OPT_NCTC ,
AVRSIMV2_OPT_G ,
AVRSIMV2_OPT_COUNT
};
static opt_arg_t avrsimv2_args_arr[] = {
[AVRSIMV2_ARG_FW ] = { "firmware", OPT_ARG_STRING, "Path to the .elf file"},
[AVRSIMV2_ARG_M ] = { "name", OPT_ARG_STRING, "Example: \"atmega644\""},
[AVRSIMV2_ARG_F ] = {"frequency", OPT_ARG_ULONG, "Hz. Example: \"2000000\""},
[AVRSIMV2_ARG_B ] = { "board", OPT_ARG_STRING, "The .so or .dll file to load"},
[AVRSIMV2_ARG_BA ] = { "argument", OPT_ARG_STRING, "The argument to pass to the board"},
[AVRSIMV2_ARG_XML] = { "file", OPT_ARG_STRING, "The .xml file to use as the board"},
[AVRSIMV2_ARG_G ] = { "port", OPT_ARG_ULONG, "The port to use for debugging"}
};
static opt_t avrsimv2_opts_arr[] = {
[AVRSIMV2_OPT_HELP] = { '?', "help", 0, 0, NULL,
"Get help"},
[AVRSIMV2_OPT_V ] = { 'v', "verbose", 0, 0, NULL,
"Increase the verbosity level by one. Can be used multiple times"},
[AVRSIMV2_OPT_M ] = { 'm', "mmcu", 1, 1, &avrsimv2_args_arr[AVRSIMV2_ARG_M ],
"Required"},
[AVRSIMV2_OPT_F ] = { 'f', "freq", 1, 1, &avrsimv2_args_arr[AVRSIMV2_ARG_F ],
"The Frequency to use"},
[AVRSIMV2_OPT_B ] = { 'b', "board", 1, 1, &avrsimv2_args_arr[AVRSIMV2_ARG_B ],
"Load a board to simulate the peripherals with"},
[AVRSIMV2_OPT_BA ] = { 'a', "board-arg", 1, 1, &avrsimv2_args_arr[AVRSIMV2_ARG_BA ],
"Specify an argument for the board. Can be used multiple times"},
[AVRSIMV2_OPT_XML ] = { 0 , "xml", 1, 1, &avrsimv2_args_arr[AVRSIMV2_ARG_XML],
"Shorthand for loading XML based boards"},
[AVRSIMV2_OPT_X ] = { 'x', NULL, 0, 0, NULL,
"Enable visualization using GLFW"},
[AVRSIMV2_OPT_FLOAT] = { 0 , "floating", 0, 0, NULL,
"Hint for the window to be floating (a.k.a. always-on-top). May not work with X11"},
[AVRSIMV2_OPT_WSLX] = { 0 , "wsl-X11", 0, 0, NULL,
"Helps to set up X11 forwarding on WSL"},
[AVRSIMV2_OPT_NCTC] = { 0 , "no-ct-correct", 0, 0, NULL,
"Do not try to correct the cycle time of simavr"},
[AVRSIMV2_OPT_G ] = { 'g', NULL, 1, 1, &avrsimv2_args_arr[AVRSIMV2_ARG_G ],
"Enable debugging (GDB)"}
};
static opts_t avrsimv2_opts = {
AVRSIMV2_OPT_COUNT, avrsimv2_opts_arr, 1, 1, &avrsimv2_args_arr[AVRSIMV2_ARG_FW]
};
avrsimv2_args_t avrsimv2_args_default(void) {
return (avrsimv2_args_t) {
.help = false,
.verbosity = L_LEVEL_INFO,
.firmwareFile = NULL,
.mmcu = NULL,
.frequency = 0,
.gdbPort = 0,
.boardFile = NULL,
.boardArgc = 0,
.boardArgv = NULL,
.useGUI = false,
.floatingWindow = false,
.wslX11Forwarding = false,
.noCTCorrection = false
};
}
static int boardArgCallback(int *argc, char ***argv, opt_arg_t arg) {
avrsimv2_args_appendBoardArg((avrsimv2_args_t *) arg.customData, (*argv)[0]);
(*argc)--;
(*argv)++;
return OPT_SUCCESS;
}
void avrsimv2_args_appendBoardArg(avrsimv2_args_t *args, char *arg) {
args->boardArgc++;
args->boardArgv = realloc(args->boardArgv, sizeof(char *) * args->boardArgc);
args->boardArgv[args->boardArgc - 1] = malloc(strlen(arg) + 1);
strcpy(args->boardArgv[args->boardArgc - 1], arg);
}
bool avrsimv2_args_parse(avrsimv2_args_t *args, int *argc, char ***argv) {
avrsimv2_args_arr[AVRSIMV2_ARG_FW].target = &args->firmwareFile;
avrsimv2_args_arr[AVRSIMV2_ARG_M].target = &args->mmcu;
avrsimv2_args_arr[AVRSIMV2_ARG_F].target = &args->frequency;
avrsimv2_args_arr[AVRSIMV2_ARG_B].target = &args->boardFile;
avrsimv2_args_arr[AVRSIMV2_ARG_BA].callback = boardArgCallback;
avrsimv2_args_arr[AVRSIMV2_ARG_BA].customData = args;
avrsimv2_args_arr[AVRSIMV2_ARG_XML].target = &args->xml;
avrsimv2_args_arr[AVRSIMV2_ARG_G].target = &args->gdbPort;
avrsimv2_opts_arr[AVRSIMV2_OPT_HELP].flag = &args->help;
avrsimv2_opts_arr[AVRSIMV2_OPT_HELP].flagVal = true;
avrsimv2_opts_arr[AVRSIMV2_OPT_V].counter = &args->verbosity;
avrsimv2_opts_arr[AVRSIMV2_OPT_X].flag = &args->useGUI;
avrsimv2_opts_arr[AVRSIMV2_OPT_X].flagVal = true;
avrsimv2_opts_arr[AVRSIMV2_OPT_FLOAT].flag = &args->floatingWindow;
avrsimv2_opts_arr[AVRSIMV2_OPT_FLOAT].flagVal = true;
avrsimv2_opts_arr[AVRSIMV2_OPT_WSLX].flag = &args->wslX11Forwarding;
avrsimv2_opts_arr[AVRSIMV2_OPT_WSLX].flagVal = true;
avrsimv2_opts_arr[AVRSIMV2_OPT_NCTC].flag = &args->noCTCorrection;
avrsimv2_opts_arr[AVRSIMV2_OPT_NCTC].flagVal = true;
opts_debugOut = logger.level >= L_LEVEL_DEBUG;
(*argc)--;
(*argv)++;
return opts_parse(argc, argv, avrsimv2_opts) != OPT_ERROR;
}
void avrsimv2_args_free(avrsimv2_args_t *args) {
for (size_t i = 0; i < args->boardArgc; i++) free(args->boardArgv[i]);
free(args->boardArgv);
}
static char *findExecutableDir(void) {
int dirIndex = 0;
int length = wai_getExecutablePath(NULL, 0, NULL);
char *buffer = malloc(length + 1);
wai_getExecutablePath(buffer, length, &dirIndex);
buffer[dirIndex] = '\0';
char *dir = malloc(dirIndex + 1);
strcpy(dir, buffer);
free(buffer);
return dir;
}
void avrsimv2_global_logger(avr_t* avr, const int level, const char *format, va_list args) {
size_t l;
char *str = vstrallocf(&l, format, args);
switch (level) {
default:
case LOG_NONE:
case LOG_OUTPUT:
if (logger.level > L_LEVEL_NONE) printf("%s", str);
break;
case LOG_TRACE:
if (str[l - 1] == '\n') str[l - 1] = '\0';
L_TRACE("AVR: %s", str);
break;
case LOG_DEBUG:
if (str[l - 1] == '\n') str[l - 1] = '\0';
L_DEBUG("AVR: %s", str);
break;
case LOG_WARNING:
if (str[l - 1] == '\n') str[l - 1] = '\0';
L_WARNING("AVR: %s", str); break;
case LOG_ERROR:
if (str[l - 1] == '\n') str[l - 1] = '\0';
L_ERROR("AVR: %s", str); break;
}
free(str);
}
#if IS_WIN
int avrsimv2_crtReportHook(int reportType, char *message, int *returnValue) {
switch (reportType) {
default: L_INFO("CRT: %s", message); break;
case _CRT_WARN: L_WARNING("CRT: %s", message); break;
case _CRT_ERROR: L_ERROR("CRT: %s", message); break;
case _CRT_ASSERT: L_ERROR("CRT_ASSERT: %s", message); break;
}
return TRUE;
}
#endif
#if IS_WIN
/**
* Enable color codes also on cmd.exe
* See: https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
*/
static void avrsimv2_enableVirtualTerminal(avrsimv2_t *s) {
HANDLE handle;
// OUTPUT
if ((handle = GetStdHandle(STD_OUTPUT_HANDLE)) == INVALID_HANDLE_VALUE)
L_WARNING("Failed to get STD_OUTPUT_HANDLE (0x%lx)", GetLastError());
else if (!GetConsoleMode(handle, &s->origConOutputMode))
L_WARNING("Failed to get console output mode (0x%lx)", GetLastError());
else if (!SetConsoleMode(handle, s->origConOutputMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN)
&& !SetConsoleMode(handle, s->origConOutputMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING))
L_WARNING("Failed to set ENABLE_VIRTUAL_TERMINAL_PROCESSING (0x%lx)", GetLastError());
else {
logger.color = true;
L_INFO("Virtual terminal output enabled");
}
// INPUT
if ((handle = GetStdHandle(STD_INPUT_HANDLE)) == INVALID_HANDLE_VALUE)
L_WARNING("Failed to get STD_INPUT_HANDLE (0x%lx)", GetLastError());
else if (!GetConsoleMode(handle, &s->origConInputMode))
L_WARNING("Failed to get console input mode (0x%lx)", GetLastError());
else if (!SetConsoleMode(handle, s->origConInputMode | ENABLE_VIRTUAL_TERMINAL_INPUT))
L_WARNING("Failed to set ENABLE_VIRTUAL_TERMINAL_INPUT (0x%lx)", GetLastError());
else
L_INFO("Virtual terminal input enabled");
}
static void avrsimv2_restoreVirtualTerminal(avrsimv2_t *s) {
HANDLE handle;
// INPUT
if (!s->origConInputMode) /* noop */;
else if ((handle = GetStdHandle(STD_INPUT_HANDLE)) == INVALID_HANDLE_VALUE)
L_WARNING("Failed to get STD_INPUT_HANDLE (0x%lx)", GetLastError());
else if (!SetConsoleMode(handle, s->origConInputMode))
L_WARNING("Failed to restore console input mode (0x%lx)", GetLastError());
// OUTPUT
if (!s->origConOutputMode) logger.color = false; // NOLINT(bugprone-branch-clone)
else if ((handle = GetStdHandle(STD_OUTPUT_HANDLE)) == INVALID_HANDLE_VALUE)
L_WARNING("Failed to get STD_OUTPUT_HANDLE (0x%lx)", GetLastError());
else if (!SetConsoleMode(handle, s->origConOutputMode))
L_WARNING("Failed to restore console output mode (0x%lx)", GetLastError());
else logger.color = false;
}
#endif
void avrsimv2_setupCLI(avrsimv2_t *s, int *argc, char **(*argv)) {
#if IS_WIN
_CrtSetReportHook(avrsimv2_crtReportHook);
avrsimv2_enableVirtualTerminal(s);
#else
logger.color = true;
#endif
avrsimv2_args_t args = avrsimv2_args_default();
if (!avrsimv2_args_parse(&args, argc, argv)) {
L_ERROR_EXIT(s, "Could not parse arguments! Use -%c or --%s to print usage",
avrsimv2_opts_arr[AVRSIMV2_OPT_HELP].sname,
avrsimv2_opts_arr[AVRSIMV2_OPT_HELP].lname);
} else L_DEBUG("Arguments parsed successfully");
// setup logging
L_DEBUG("Setting up avr logger");
logger.level = args.verbosity;
avr_global_logger_set(avrsimv2_global_logger);
switch (logger.level) {
case L_LEVEL_TRACE : L_TRACE("<- current log level (%i)", logger.level); break;
case L_LEVEL_DEBUG : L_DEBUG("<- current log level (%i)", logger.level); break;
case L_LEVEL_INFO : L_INFO("<- current log level (%i)", logger.level); break;
default: /* noop */ break;
}
// help
if (args.help) {
L_DEBUG("Printing help");
opts_printUsage(avrsimv2_opts);
avrsimv2_exit(s, EXIT_FAILURE);
}
s->directory = findExecutableDir();
L_DEBUG("Executable directory: %s", s->directory);
// setup sim
L_DEBUG("Setting up sim");
s->sim = malloc(sizeof(sim_t));
memset(s->sim, 0, sizeof(sim_t));
s->sim->s = s;
{
// load elf
if (!args.firmwareFile) L_ERROR_EXIT(s, "Please specify a firmware");
L_INFO("Loading firmware \"%s\"...", args.firmwareFile);
if (elf_read_firmware(args.firmwareFile, &s->sim->firmware) < 0)
L_ERROR_EXIT(s, "Firmware \"%s\" could not be read", args.firmwareFile);
L_INFO("Firmware info: frequency=%d, mmcu=%s", s->sim->firmware.frequency, s->sim->firmware.mmcu);
}
s->sim->mmcu = args.mmcu ? args.mmcu : s->sim->firmware.mmcu;
s->sim->frequency = args.frequency ? args.frequency : s->sim->firmware.frequency;
s->sim->gdbPort = (uint16_t) args.gdbPort;
s->sim->tryCTCorrection = !args.noCTCorrection;
// load board
L_DEBUG("Loading board");
bool doFreeBoardFile = false;
if (args.xml) {
#if IS_WIN
#define BOARD_XML_PATH "boards\\board_xml.dll"
#else
#define BOARD_XML_PATH "boards/board_xml.so"
#endif
if ((doFreeBoardFile = !args.boardFile))
args.boardFile = strallocf(NULL, "%s"PSEP""BOARD_XML_PATH, s->directory);
if (args.boardArgv)
L_ERROR_EXIT(s, "Can not use '--xml' and '-a' together");
avrsimv2_args_appendBoardArg(&args, args.xml);
}
if (args.boardFile) {
L_INFO("Loading board \"%s\"...", args.boardFile);
for (size_t i = 0; i < args.boardArgc; i++) L_INFO(" - '%s'", args.boardArgv[i]);
s->board = board_load(s, args.boardFile, args.boardArgc, args.boardArgv);
if (!s->board) L_ERROR_EXIT(s, "Could not load board \"%s\"", args.boardFile);
} else L_INFO("No board loaded");
if (doFreeBoardFile) free(args.boardFile);
// setup gui
if (args.useGUI) {
L_INFO("Using GUI");
s->gui = malloc(sizeof(gui_t));
memset(s->gui, 0, sizeof(gui_t));
s->gui->s = s;
s->gui->floating = args.floatingWindow;
if (args.wslX11Forwarding) {
#if IS_WIN
L_WARNING("\"--wsl-x11\" was passed, but we are on Windows");
#endif
// setup x11 forwarding for wsl and clion
FILE *fp = popen("awk '/nameserver / {print $2; exit}' /etc/resolv.conf 2>/dev/null", "r");
if (fp) {
const size_t prefixSize = sizeof("DISPLAY=") - 1;
const size_t maxValueLength = 1000;
const size_t postfixSize = sizeof(":0") - 1;
size_t totalSize = prefixSize + maxValueLength - 1 + postfixSize + 1;
char *display = malloc(totalSize);
memset(display, 0, totalSize);
strcpy(display, "DISPLAY=");
if (fgets(display + prefixSize, maxValueLength, fp) == NULL) {
int err;
if ((err = feof(fp))) L_ERROR("EOF (%i)", err);
if ((err = ferror(fp))) L_ERROR("%i", err);
pclose(fp);
avrsimv2_exit(s, EXIT_FAILURE + err);
}
pclose(fp);
char *pos;
if ((pos = strchr(display, '\n')) != NULL) strcpy(pos, ":0\0");
else L_ERROR_EXIT(s, "\"/etc/resolv.conf\"'s nameserver value is too long (> %zu). Got: \"%s\"", maxValueLength, display);
putenv(display);
}
putenv("LIBGL_ALWAYS_INDIRECT=0");
}
L_DEBUG("DISPLAY=%s", getenv("DISPLAY"));
L_DEBUG("LIBGL_ALWAYS_INDIRECT=%s", getenv("LIBGL_ALWAYS_INDIRECT"));
} else
L_DEBUG("No GUI");
avrsimv2_args_free(&args);
}
void avrsimv2_init(avrsimv2_t *s) {
L_INFO("Initializing...");
sim_init(s->sim);
if (s->board) s->board->avr.init(s->board, s->sim->avr);
if (s->gui) gui_init(s->gui);
}
void avrsimv2_main(avrsimv2_t *s) {
L_INFO("Starting...");
sim_start(s->sim);
if (s->gui) gui_loop(s->gui);
sim_waitTillDone(s->sim);
}
void avrsimv2_exit(avrsimv2_t *s, int status) {
if (s->gui) {
gui_close(s->gui);
free(s->gui);
s->gui = NULL;
L_DEBUG("GUI closed");
}
if (s->board) {
board_unload(&s->board);
L_DEBUG("Board unloaded");
}
free(s->sim);
s->sim = NULL;
#if IS_WIN
avrsimv2_restoreVirtualTerminal(s);
#endif
exit(status);
}
char *avrsimv2_createResourcePath(avrsimv2_t *s, char *resource) {
char *path = malloc(strlen(s->directory) + sizeof("/res/") - 1 + strlen(resource) + 1);
return strcat(strcat(strcpy(path, s->directory), "/res/"), resource);
}