Skip to content
Snippets Groups Projects
Select Git revision
  • develop
  • master
  • v2.3.4
3 results

avrsimv2.c

Blame
  • Forked from PSP Fanclub / AVRSimV2
    2 commits behind, 1 commit ahead of the upstream repository.
    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);
    }