/* * Copyright (C) 2022-2025 Ian Scott * * 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. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "pgusinit.h" static card_mode_t gMode; static card_mode_t newMode = 0; static board_type_t board_type; static bool wifichg = false; static bool permanent = false; static bool resources_from_env = false; static bool is_console; static uint8_t page_lines; #include "../common/picogus.h" const uint8_t get_screen_lines(void) { uint16_t bios_screensize = *(uint16_t far *)MK_FP(0x40, 0x4c); uint8_t bios_screencols = *(uint8_t far *)MK_FP(0x40, 0x4a); if (bios_screensize && bios_screencols) { return bios_screensize / bios_screencols / 2; } return 25; } static void pageprintf(char *format, ...) { static uint8_t printed_lines = 0; va_list arglist; va_start(arglist, format); vprintf(format, arglist); va_end(arglist); printed_lines++; if (is_console && (printed_lines >= page_lines)) { fprintf(stderr, "Press any key to continue..."); getch(); putchar('\r'); printed_lines = 0; } } static void banner(void) { printf("PicoGUSinit v3.9.0 (c) 2025 Ian Scott - licensed under the GNU GPL v2\n"); } static void usage(card_mode_t mode, bool print_all) { // Max line length @ 80 chars: // "...............................................................................\n" pageprintf("Usage:\n"); pageprintf(" /? - show this message (/?? to show options for all modes)\n"); pageprintf(" /flash fw.uf2 - program the PicoGUS with the firmware file fw.uf2\n"); pageprintf(" /mode x - change card mode to x (gus, sb, mpu, psg, adlib, usb)\n"); pageprintf(" /save - save settings to the card to persist on system boot\n"); pageprintf(" /defaults - set all settings for all modes to defaults\n"); pageprintf(" /wtvol x - set volume of WT header. 0-100, Default 100 (2.0 cards only)\n"); pageprintf(" /joy 1|0 - enable/disable USB joystick support, Default: 0\n"); pageprintf(" /mainvol x - set the main audio volume: 0 - 100\n"); // "...............................................................................\n" pageprintf("MPU-401 settings:\n"); pageprintf(" /mpuport x - set the base port of the MPU-401. Default: 330, 0 to disable\n"); pageprintf(" /mpudelay 1|0 - delay SYSEX (for rev.0 Roland MT-32)\n"); pageprintf(" /mpufake 1|0 - fake all notes off (for Roland RA-50)\n"); if (mode == GUS_MODE || print_all) { // "...............................................................................\n" pageprintf("GUS settings:\n"); pageprintf(" /gusenv - set the base port of the GUS from ULTRASND variable\n"); pageprintf(" /gusport x - set the base port of the GUS. Default: 240\n"); pageprintf(" /gusbuf n - set audio buffer to n samples. Default: 4, Min: 1, Max: 256\n"); pageprintf(" (tweaking can help programs that hang or have audio glitches)\n"); pageprintf(" /gusdma n - force DMA interval to n us. Default: 0, Min: 0, Max: 255\n"); pageprintf(" Specifying 0 restores the GUS default behavior.\n"); pageprintf(" (increase to fix games with streaming audio like Doom)\n"); pageprintf(" /gus44k 1|0 - Fixed 44.1kHz output for all active voice #s [EXPERIMENTAL]\n"); pageprintf(" /gusvol x - set the GUS audio volume: 0 - 100\n"); } if (mode == SB_MODE || print_all) { // "...............................................................................\n" pageprintf("Sound Blaster settings:\n"); pageprintf(" /sbenv - set the SB base port, IRQ, DMA and type from SET BLASTER=\n"); pageprintf(" /sbport x - set the SB base port. Default: 220\n"); pageprintf(" /sbirq x - set the SB IRQ (must match jumper settings!). Default: 5\n"); pageprintf(" /sbdma x - set the SB DMA (must match jumper settings!). Default: 1\n"); pageprintf(" /sbtype x - set the Sound Blaster type. Default: 6 (SB 16)\n"); pageprintf(" 1 - SB 1.x, 2 - SB Pro 1 (dual OPL2), 3 - SB 2.0,\n"); pageprintf(" 4 - SB Pro 2 (OPL3), 6 - SB 16\n"); pageprintf(" /sbvol x - set the Sound Blaster audio volume: 0 - 100%\n"); pageprintf(" /sbfixtc 1|0 - fix SB time constant to match common rates. Default: 0\n"); pageprintf(" /sblockmixer n - lock SB mixer settings. Default: 0 (no lock)\n"); pageprintf(" 0 - no lock, 1 - lock all but Voice Volume, 2 - lock all\n"); } if (mode == SB_MODE || mode == ADLIB_MODE || print_all) { pageprintf("AdLib settings:\n"); pageprintf(" /oplport x - set the base port of the OPL2. Default: 388, 0 to disable\n"); pageprintf(" /oplwait 1|0 - wait on OPL2 write. Can fix speed-sensitive early AdLib games\n"); pageprintf(" /oplvol x - set the OPL2 audio volume: 0 - 100\n"); } if (mode == SB_MODE || mode == USB_MODE || print_all) { pageprintf("CD-ROM settings:\n"); pageprintf(" /cdport x - set base port of CD interface. Default: 250, 0 to disable\n"); pageprintf(" /cdlist - list CD images on the inserted USB drive\n"); pageprintf(" /cdload n - load image n in the list given by /cdlist. 0 to unload image\n"); pageprintf(" /cdloadname x - load CD image by name. Names with spaces can be quoted\n"); pageprintf(" /cdvol n - set the CD audio volume: 0 - 100\n"); pageprintf(" /cdauto 1|0 - auto-advance loaded image when same USB drive is reinserted\n"); } if (mode == PSG_MODE || print_all) { // "...............................................................................\n" pageprintf("PSG settings:\n"); pageprintf(" /tandyport x - set the base port of the Tandy 3-voice. Default: 2C0\n"); pageprintf(" /cmsport x - set the base port of the CMS. Default: 220\n"); pageprintf(" /psgvol x - set the PSG audio volume: 0 - 100\n"); } if (mode == USB_MODE || mode == PSG_MODE || mode == ADLIB_MODE || print_all) { // "...............................................................................\n" pageprintf("Serial Mouse settings:\n"); pageprintf(" /mousecom n - mouse COM port. Default: 0, Choices: 0 (disable), 1, 2, 3, 4\n"); pageprintf(" /mouseproto n - set mouse protocol. Default: 0 (Microsoft)\n"); pageprintf(" 0 - Microsoft Mouse 2-button, 1 - Logitech 3-button\n"); pageprintf(" 2 - IntelliMouse 3-button + wheel, 3 - Mouse Systems 3-button\n"); pageprintf(" /mouserate n - set report rate in Hz. Default: 60, Min: 20, Max: 200\n"); pageprintf(" (increase for smoother cursor movement, decrease for lower CPU load)\n"); pageprintf(" /mousesen n - set mouse sensitivity (256 - 100%, 128 - 50%, 512 - 200%)\n"); } if (mode == NE2000_MODE) { // NE2000 mode is special enough it should only be shown if the card is running the dedicated pg-ne2k firmware // "...............................................................................\n" pageprintf("NE2000/WiFi settings:\n"); pageprintf(" /ne2kport x - set the base port of the NE2000. Default: 300\n"); pageprintf(" /wifissid abc - set the WiFi SSID to abc\n"); pageprintf(" /wifipass xyz - set the WiFi WPA/WPA2 password/key to xyz\n"); pageprintf(" /wifinopass - unset the WiFi password to connect to an open access point\n"); pageprintf(" /wifistatus - print current WiFi status\n"); } } static const char* mouse_protocol_str[] = { "Microsoft", "Logitech", "IntelliMouse", "Mouse Systems" }; static void err_ultrasnd(void) { // "................................................................................\n" fprintf(stderr, "ERROR: In GUS mode but no ULTRASND variable set or is malformed!\n"); fprintf(stderr, "The ULTRASND environment variable must be set in the following format:\n"); fprintf(stderr, "\tset ULTRASND=xxx,y,n,z,n\n"); fprintf(stderr, "Where xxx = port, y = DMA, z = IRQ. n is ignored.\n"); fprintf(stderr, "Port is set via /gusport xxx option; DMA and IRQ configued via jumper.\n"); } typedef enum { BLASTER_UNSET, BLASTER_MALFORMED, BLASTER_DMA16_MISMATCH, BLASTER_MISMATCH, } blaster_err_t; static void err_blaster_format_help(void) { // "................................................................................\n" fprintf(stderr, "The BLASTER environment variable must be set in the following format:\n"); fprintf(stderr, "\tset BLASTER=Axxx Iy Dz Hz Tw\n"); fprintf(stderr, "Where xxx = port, y = IRQ, z = DMA. w = SB type:\n"); fprintf(stderr, " 1 - SB 1.x, 2 - SB Pro 1 (dual OPL2), 3 - SB 2.0,\n"); fprintf(stderr, " 4 - SB Pro 2 (OPL3), 6 - SB 16\n"); } static void err_blaster(blaster_err_t reason) { // "................................................................................\n" switch (reason) { case BLASTER_UNSET: fprintf(stderr, "ERROR: In SB mode but BLASTER environment variable is not set!\n"); err_blaster_format_help(); fprintf(stderr, "Then run pgusinit /sbenv to apply the BLASTER settings to the card.\n"); break; case BLASTER_MALFORMED: fprintf(stderr, "ERROR: BLASTER environment variable is malformed!\n"); fprintf(stderr, "Current value: BLASTER=%s\n", getenv("BLASTER")); err_blaster_format_help(); break; case BLASTER_DMA16_MISMATCH: fprintf(stderr, "ERROR: In SB 16 mode, low (Dx) and high (Hx) DMA in BLASTER variable must match!\n"); fprintf(stderr, "Current value: BLASTER=%s\n", getenv("BLASTER")); break; case BLASTER_MISMATCH: fprintf(stderr, "ERROR: SB settings on card don't match the BLASTER environment variable!\n"); fprintf(stderr, "Double-check that PicoGUS is jumpered correctly for the desired IRQ and DMA,\n"); fprintf(stderr, "then run pgusinit /sbenv to set card resources from the BLASTER variable.\n"); fprintf(stderr, "Alternatively, use /sbport, /sbirq, /sbdma, /sbtype to set values directly.\n"); break; } } static void err_pigus(void) { fprintf(stderr, "ERROR: no PicoGUS detected!\n"); } static void err_protocol(uint8_t expected, uint8_t got) { fprintf(stderr, "ERROR: PicoGUS card using protocol %u, needs %u\n", got, expected); fprintf(stderr, "Please run the latest PicoGUS firmware and pgusinit.exe versions together!\n"); fprintf(stderr, "To flash new firmware, run pgusinit /flash picogus.uf2\n"); } static int init_gus(void) { char* ultrasnd = getenv("ULTRASND"); if (ultrasnd == NULL) { err_ultrasnd(); return 1; } // Parse ULTRASND uint16_t port; int e; e = sscanf(ultrasnd, "%hx,%*hhu,%*hhu,%*hhu,%*hhu", &port); if (e != 1) { err_ultrasnd(); return 2; } if (resources_from_env) { // init GUS base port from ULTRASND environment variable outp(CONTROL_PORT, CMD_GUSPORT); // Select port register outpw(DATA_PORT_LOW, port); } else { // verify ULTRASND against GUS mode settings outp(CONTROL_PORT, CMD_GUSPORT); // Select port register uint16_t tmp_port = inpw(DATA_PORT_LOW); if (port != tmp_port) { err_ultrasnd(); return 2; } } // Detect if there's something GUS-like... // Set memory address to 0 outp(port + 0x103, 0x43); outpw(port + 0x104, 0x0); outp(port + 0x103, 0x44); outpw(port + 0x104, 0x0); // Write something outp(port + 0x107, 0xDD); // Read it and see if it's the same if (inp(port + 0x107) != 0xDD) { fprintf(stderr, "ERROR: Card not responding to GUS commands on port %x\n", port); return 98; } printf("GUS-like card detected on port %x...\n", port); // Enable IRQ latches outp(port, 0x8); // Select reset register outp(port + 0x103, 0x4C); // Master reset to run. DAC enable and IRQ enable will be done by the application. outp(port + 0x105, 0x1); return 0; } static int init_sb(void) { // SB DSP version table const uint16_t dspver[] = {0, 0x105, 0x300, 0x201, 0x301, 0, 0x405}; char* blaster = getenv("BLASTER"); if (blaster == NULL) { err_blaster(BLASTER_UNSET); return 1; } char blasterTemp[256]; strncpy(blasterTemp, blaster, sizeof(blasterTemp)); // Parse BLASTER char* p = strtok(blasterTemp, " "); uint16_t port = -1, irq = -1, dma8 = -1, dma16 = -1, sbtype = 0; while (p != NULL) { switch (*p) { case 'a': case 'A': port = strtol(p + 1, NULL, 16); break; case 'i': case 'I': irq = strtol(p + 1, NULL, 10); break; case 'd': case 'D': dma8 = strtol(p + 1, NULL, 10); break; case 'h': case 'H': dma16 = strtol(p + 1, NULL, 10); break; case 't': case 'T': sbtype = strtol(p + 1, NULL, 10); break; default: break; } p = strtok(NULL, " "); } if ((port == -1) || (irq == -1) || (dma8 == -1) || (sbtype > 6) || (dspver[sbtype] == 0)) { err_blaster(BLASTER_MALFORMED); return 2; } if ((sbtype == 6) && (dma8 != dma16)) { err_blaster(BLASTER_DMA16_MISMATCH); return 2; } if (resources_from_env) { // update resources from BLASTER environment variable outp(CONTROL_PORT, CMD_SBPORT); // Select port register outpw(DATA_PORT_LOW, port); outp(CONTROL_PORT, CMD_SBIRQ); // Select IRQ register outp(DATA_PORT_HIGH, irq); outp(CONTROL_PORT, CMD_SBDMA); // Select DMA register outp(DATA_PORT_HIGH, dma8); outp(CONTROL_PORT, CMD_SBTYPE); // Select SB type register outp(DATA_PORT_HIGH, sbtype); } else { // verify BLASTER variable against current SB mode settings outp(CONTROL_PORT, CMD_SBPORT); // Select port register uint16_t tmp_port = inpw(DATA_PORT_LOW); if (port != tmp_port) { err_blaster(BLASTER_MISMATCH); return 2; } outp(CONTROL_PORT, CMD_SBIRQ); // Select IRQ register uint8_t tmp_irq = inp(DATA_PORT_HIGH); if (irq != tmp_irq) { err_blaster(BLASTER_MISMATCH); return 2; } outp(CONTROL_PORT, CMD_SBDMA); // Select DMA register uint8_t tmp_dma = inp(DATA_PORT_HIGH); if (dma8 != tmp_dma) { err_blaster(BLASTER_MISMATCH); return 2; } outp(CONTROL_PORT, CMD_SBTYPE); // Select SB type register uint8_t tmp_type = inp(DATA_PORT_HIGH); if (sbtype != tmp_type) { err_blaster(BLASTER_MISMATCH); return 2; } } return 0; } static void print_string(uint8_t cmd) { outp(CONTROL_PORT, 0xCC); // Knock on the door... outp(CONTROL_PORT, cmd); // Select command register char str[256] = {0}; for (uint8_t i = 0; i < 255; ++i) { str[i] = inp(DATA_PORT_HIGH); if (!str[i]) { break; } } puts(str); } static bool wait_for_read(const uint8_t value) { for (uint32_t i = 0; i < 6000000; ++i) { // Up to 6000000, for bigger fws like pg-multi.uf2, waiting for flash erase. If not, timeout and error. if (inp(DATA_PORT_HIGH) == value) { return true; } } return false; } static cdrom_image_status_t wait_for_cd_status(void) { outp(CONTROL_PORT, 0xCC); // Knock on the door... outp(CONTROL_PORT, CMD_CDSTATUS); // Select CD image status register cdrom_image_status_t cd_status; for (uint16_t i = 0; i < 256; ++i) { delay(100); cd_status = (cdrom_image_status_t)inp(DATA_PORT_HIGH); if (cd_status != CD_STATUS_BUSY) { break; } } return cd_status; } static int print_cdimage_list(void) { outp(CONTROL_PORT, 0xCC); // Knock on the door... outp(CONTROL_PORT, CMD_CDLOAD); // Get currently loaded index uint8_t current_index = inp(DATA_PORT_HIGH); pageprintf("Listing CD images on USB drive:\n"); outp(CONTROL_PORT, CMD_CDLIST); // Select CD image list register cdrom_image_status_t cd_status = wait_for_cd_status(); if (cd_status == CD_STATUS_BUSY) { printf("Timeout getting CD image list\n"); return 99; } else if (cd_status == CD_STATUS_ERROR) { printf("Error getting CD image list: "); print_string(CMD_CDERROR); return 99; } outp(CONTROL_PORT, CMD_CDLIST); // Select CD image list register char b[256], c, *p = b; uint8_t line = 1; while ((c = inp(DATA_PORT_HIGH)) != 4 /* ASCII EOT */) { *p++ = c; if (!c) { putchar(current_index == line ? '*' : ' '); pageprintf(" %2d: %s\n", line++, b); p = b; } } if (current_index) { printf("Currently loaded image marked with \"*\".\n"); } else { printf("No image currently loaded.\n"); } printf("Run \"pgusinit /cdload n\" to load the nth image in the above list, 0 to unload.\n"); return 0; } static int print_cdimage_current(void) { outp(CONTROL_PORT, CMD_CDLOAD); // Get currently loaded index uint8_t current_index = inp(DATA_PORT_HIGH); if (!current_index) { printf("No CD image loaded.\n"); return 97; } printf("CD image loaded: "); print_string(CMD_CDNAME); return 0; } static int wait_for_cd_load(void) { cdrom_image_status_t cd_status = wait_for_cd_status(); if (cd_status == CD_STATUS_BUSY) { printf("Timeout loading CD image.\n"); return 99; } else if ((int8_t)cd_status == CD_STATUS_ERROR) { printf("Error loading CD image: "); print_string(CMD_CDERROR); return 98; } return print_cdimage_current(); } static void print_cdemu_status(void) { outp(CONTROL_PORT, CMD_CDAUTOADV); // Select joystick enable register uint8_t tmp_uint8 = inp(DATA_PORT_HIGH); outp(CONTROL_PORT, CMD_CDPORT); // Select port register uint16_t tmp_uint16 = inpw(DATA_PORT_LOW); // Get port printf("CD-ROM emulation on port %x, image auto-advance %s\n", tmp_uint16, tmp_uint8 ? "enabled" : "disabled"); print_cdimage_current(); } static void write_settings(void) { outp(CONTROL_PORT, CMD_SAVE); // Select save settings register outp(DATA_PORT_HIGH, 0xff); printf("Settings saved to flash.\n"); delay(100); } static int reboot_to_firmware(const uint8_t value, const bool permanent) { outp(CONTROL_PORT, 0xCC); // Knock on the door... outp(CONTROL_PORT, CMD_BOOTMODE); // Select firmware selection register outp(DATA_PORT_HIGH, value); // Send firmware number and permanent flag delay(100); printf("Mode change requested.\n"); if (permanent) { write_settings(); } printf("Rebooting to fw: %s...\n", modenames[value]); outp(CONTROL_PORT, CMD_REBOOT); // Select reboot register outp(DATA_PORT_HIGH, 0xff); delay(100); // Wait for card to reboot to new firmware if (!wait_for_read(0xDD)) { fprintf(stderr, "ERROR: card is not alive after rebooting to new firmware\n"); return 99; } printf("PicoGUS detected: Firmware version: "); print_string(CMD_FWSTRING); return 0; } void print_progress_bar(uint16_t current, uint16_t total) { #define BAR_WIDTH 50 static uint16_t bar_step = 0; // init flag static uint16_t pct_step; static uint16_t next_bar, next_pct; static uint8_t filled = 0, percent = 0; static uint8_t last_filled = 255, last_percent = 255; if (bar_step == 0) { bar_step = (total + BAR_WIDTH - 1) / BAR_WIDTH; // ceiling division pct_step = (total + 99) / 100; next_bar = bar_step; next_pct = pct_step; } // Special case: if we're at 100% completion, ensure we show it if (current == total) { filled = BAR_WIDTH; percent = 100; } else { while (current >= next_bar && filled < BAR_WIDTH) { filled++; next_bar += bar_step; } while (current >= next_pct && percent < 100) { percent++; next_pct += pct_step; } } if (filled != last_filled) { // If the bar changed, redraw percentage and bar last_filled = filled; last_percent = percent; fprintf(stderr, "\r%3u%% [", percent); for (uint8_t i = 0; i < BAR_WIDTH; i++) fputc((i < filled) ? '=' : ' ', stderr); fputc(']', stderr); fflush(stderr); } else if (percent != last_percent) { // If only the percent changed, redraw just the percent last_percent = percent; fprintf(stderr, "\r%3u%%", percent); fflush(stderr); } } static int write_firmware(const char* fw_filename) { union { uint8_t buf[512]; struct UF2_Block { // 32 byte header uint32_t magicStart0; uint32_t magicStart1; uint32_t flags; uint32_t targetAddr; uint32_t payloadSize; uint32_t blockNo; uint32_t numBlocks; uint32_t fileSize; // or familyID; uint8_t data[476]; uint32_t magicEnd; } uf2; } uf2_buf; FILE* fp = fopen(fw_filename, "rb"); if (!fp) { fprintf(stderr, "ERROR: unable to open firmware file %s\n", fw_filename); return 10; } outp(CONTROL_PORT, 0x01); // Select protocol version register uint8_t protocol = inp(DATA_PORT_HIGH); if (protocol < PICOGUS_PROTOCOL_VER) { printf("Older fw protocol version %d detected, upgrading firmware in compatibility mode\n", protocol); } uint16_t numBlocks = 1; for (uint16_t i = 0; i < numBlocks; ++i) { if (fread(uf2_buf.buf, 1, 512, fp) != 512) { fprintf(stderr, "ERROR: file %s is not a valid UF2 file - too short\n", fw_filename); return 11; } if (uf2_buf.uf2.magicStart0 != 0x0A324655 || uf2_buf.uf2.magicStart1 != 0x9E5D5157 || uf2_buf.uf2.magicEnd != 0x0AB16F30) { fprintf(stderr, "ERROR: file %s is not a valid UF2 file - bad magic\n", fw_filename); return 12; } if (i == 0) { numBlocks = uf2_buf.uf2.numBlocks; // Put card into programming mode outp(CONTROL_PORT, 0xCC); // Knock on the door... outp(CONTROL_PORT, CMD_FLASH); // Select firmware programming mode // Wait a bit for 2nd core on Pico to restart delay(100); if (!wait_for_read(PICO_FIRMWARE_IDLE)) { fprintf(stderr, "ERROR: Card is not in programming mode?\n"); return 13; } fflush(stdout); fprintf(stderr, "Preparing to program %d blocks...", numBlocks); } if (i != uf2_buf.uf2.blockNo) { fprintf(stderr, "\nERROR: file %s is not a valid UF2 file - block mismatch\n", fw_filename); return 14; } for (uint16_t b = 0; b < 512; ++b) { // Write firmware byte outp(DATA_PORT_HIGH, uf2_buf.buf[b]); if (b == 511 && protocol == 1) { // Protocol 1 abuses IOCHRDY to pause during flash erase/write. Some chipsets give // up waiting on IOCHRDY and release the ISA bus after a certain amount of time before // the flash operation is finished. This is an extra delay to work around this issue. if (i == 0) { // first block takes longer due to flash erase delay(5000); } else { delay(25); } } if (i < (numBlocks - 1) || b < 511) { // If it's not the very last byte if (!wait_for_read(PICO_FIRMWARE_WRITING)) { fprintf(stderr, "\nERROR: Card is not in firmware writing mode?\n"); return 15; } } else if (protocol > 1) { if (!wait_for_read(PICO_FIRMWARE_DONE)) { fprintf(stderr, "\nERROR: Card has written last firmware byte but is not done\n"); return 15; } outp(CONTROL_PORT, 0xCC); // Knock on the door... outp(CONTROL_PORT, CMD_FLASH); // Select firmware programming mode, which will reboot the card in DONE } } print_progress_bar(i + 1, numBlocks); //fprintf(stderr, "%u ", i); } fclose(fp); // Wait for card to reboot printf("\nProgramming complete. Waiting for the card to reboot...\n"); if (!wait_for_read(0xDD)) { fprintf(stderr, "ERROR: card is not alive after programming firmware\n"); return 99; } printf("PicoGUS detected: Firmware version: "); print_string(CMD_FWSTRING); return 0; } static void wifi_printStatus(void) { outp(CONTROL_PORT, 0xCC); // Knock on the door... outp(CONTROL_PORT, CMD_WIFISTAT); // Select WiFi status command outp(DATA_PORT_HIGH, 0); // Write to start getting the status printf("WiFi status: "); uint16_t try = 0; char c; while (c = inp(DATA_PORT_HIGH)) { if ((unsigned char)c == 255) { if (++try == 10000) { printf("Error getting WiFI status\n"); break; } else { continue; } } putchar(c); } putchar('\n'); } static void send_string(const uint8_t cmd, const char* str, const int16_t max_len) { outp(CONTROL_PORT, 0xCC); // Knock on the door... outp(CONTROL_PORT, cmd); char chr; for (int16_t i = 0; i < max_len; ++i) { if (str[i] == 0) { // End of string break; } outp(DATA_PORT_HIGH, str[i]); } outp(DATA_PORT_HIGH, 0); } static bool cmdSetResourcesFromEnv(const char* arg, const int cmd, const int cmd2, const int cmd3) { resources_from_env = true; return true; } static bool cmdDisplayUsage(const char* arg, const int print_all, const int cmd2, const int cmd3) { usage(gMode, print_all); return true; } static bool cmdSendBool(const char* arg, const int cmd, const int cmd2, const int cmd3) { uint8_t value; if (!strcmp(arg, "1") || !stricmp(arg, "true") || !stricmp(arg, "on")) { value = 1; } else if (!strcmp(arg, "0") || !stricmp(arg, "false") || !stricmp(arg, "off")) { value = 0; } else { usage(gMode, false); return false; } outp(CONTROL_PORT, cmd); outp(DATA_PORT_HIGH, value); return true; } static bool cmdSendBoolMasked(const char* arg, const int cmd, const int mask, const int shift) { uint8_t value, oldvalue; if (!strcmp(arg, "1") || !stricmp(arg, "true") || !stricmp(arg, "on")) { value = 1; } else if (!strcmp(arg, "0") || !stricmp(arg, "false") || !stricmp(arg, "off")) { value = 0; } else { usage(gMode, false); return false; } outp(CONTROL_PORT, cmd); oldvalue = inp(DATA_PORT_HIGH); outp(DATA_PORT_HIGH, (uint8_t)(oldvalue & ~mask) | ((value << shift) & mask)); return true; } static bool cmdSetMode(const char* arg, const int cmd, const int cmd2, const int cmd3) { if (!stricmp(arg, "TANDY") || !stricmp(arg, "CMS")) { // Backwards compatibility for old tandy and cms modes newMode = PSG_MODE; return true; } int j; for (j = 1; j < 7; ++j) { if (stricmp(modenames[j], arg) == 0) { newMode = j; return true; } } fprintf(stderr, "Invalid mode %s. Valid modes: gus, sb, mpu, psg, adlib, usb\n", arg); return false; } static bool ctrlSendUint8(const char *arg, int cmd, int min, int max) { char* endptr; uint8_t val = (uint8_t)strtoul(arg, &endptr, 10); if (*endptr != '\0' || val < min || val > max) { usage(gMode, false); return false; } outp(CONTROL_PORT, cmd); outp(DATA_PORT_HIGH, (uint8_t)val); return true; } static bool ctrlSendUint8Masked(const char *arg, int cmd, int min, int max, int mask, int shift) { char* endptr; uint8_t val = (uint8_t)strtoul(arg, &endptr, 10); uint8_t oldval = 0; if (*endptr != '\0' || val < min || val > max) { usage(gMode, false); return false; } outp(CONTROL_PORT, cmd); oldval = inp(DATA_PORT_HIGH); outp(DATA_PORT_HIGH, (uint8_t)(oldval & ~mask) | ((val << shift) & mask)); return true; } static bool cmdSendUint8(const char* arg, const int cmd, const int cmd2, const int cmd3) { return ctrlSendUint8(arg, cmd, 0, 255); } static bool cmdSendSBType(const char* arg, const int cmd, const int cmd2, const int cmd3) { char* endptr; uint8_t val = (uint8_t)strtoul(arg, &endptr, 10); if (*endptr != '\0' || val < 1 || val > 6 || val == 5) { usage(gMode, false); return false; } outp(CONTROL_PORT, cmd); outp(DATA_PORT_HIGH, (uint8_t)val); return true; } static bool cmdSendUint8Masked(const char* arg, const int cmd, const int cmd2, const int cmd3) { // cmd2 - mask, cmd3 - shift return ctrlSendUint8Masked(arg, cmd, 0, 255, cmd2, cmd3); } static bool ctrlSendUint16(const char *arg, int cmd, long min, long max, int base) { char *endptr; uint16_t val = (uint16_t)strtoul(arg, &endptr, base); if (*endptr != '\0' || val < min || val > max) { usage(gMode, false); return false; } outp(CONTROL_PORT, cmd); outpw(DATA_PORT_LOW, (uint16_t)val); return true; } static bool cmdSendUint16(const char* arg, const int cmd, const int cmd2, const int cmd3) { return ctrlSendUint16(arg, cmd, 0, 65535, 10); } static bool cmdSendPort(const char* arg, const int cmd, const int cmd2, const int cmd3) { return ctrlSendUint16(arg, cmd, 0, 0x3FF, 16); } static bool cmdDefaults(const char* arg, const int cmd, const int cmd2, const int cmd3) { outp(CONTROL_PORT, CMD_DEFAULTS); outp(DATA_PORT_HIGH, 0xff); return true; } static bool cmdSetVol(const char* arg, const int cmd, const int cmd2, const int cmd3) { return ctrlSendUint8(arg, cmd, 0, 100); } static bool cmdSendMousePort(const char *arg, const int cmd, const int cmd2, const int cmd3) { char* endptr; uint8_t val = (uint8_t)strtoul(arg, &endptr, 10); if (*endptr != '\0' || val > 4) { usage(gMode, false); return false; } uint16_t port; switch (val) { case 0: port = 0x000; break; case 1: port = 0x3F8; break; case 2: port = 0x2F8; break; case 3: port = 0x3E8; break; case 4: port = 0x2E8; break; default: usage(gMode, false); return false; } outp(CONTROL_PORT, cmd); outpw(DATA_PORT_LOW, port); return true; } static bool cmdSendMouseSen(const char* arg, const int cmd, const int cmd2, const int cmd3) { return ctrlSendUint16(arg, cmd, 0, 1024, 10); } static bool cmdSendMouseProto(const char* arg, const int cmd, const int cmd2, const int cmd3) { return ctrlSendUint8(arg, cmd, 0, 3); } static bool cmdSendMouseRate(const char* arg, const int cmd, const int cmd2, const int cmd3) { return ctrlSendUint8(arg, cmd, 20, 200); } static bool cmdSendSBLockMixer(const char* arg, const int cmd, const int cmd2, const int cmd3) { return ctrlSendUint8Masked(arg, cmd, 0, 2, (2 << 1), 1); } static bool cmdWifiStatus(const char* arg, const int cmd, const int cmd2, const int cmd3) { wifi_printStatus(); exit(0); } static bool cmdWifiSSID(const char* arg, const int cmd, const int cmd2, const int cmd3) { wifichg = true; send_string(cmd, arg, 32); return true; } static bool cmdWifiPass(const char* arg, const int cmd, const int cmd2, const int cmd3) { wifichg = true; send_string(cmd, arg, 63); return true; } static bool cmdWifiNoPass(const char* arg, const int cmd, const int cmd2, const int cmd3) { wifichg = true; send_string(cmd, "", 1); return true; } static bool cmdCDLoadName(const char* arg, const int cmd, const int cmd2, const int cmd3) { send_string(cmd, arg, 127); exit(wait_for_cd_load()); } static bool cmdCDList(const char* arg, const int cmd, const int cmd2, const int cmd3) { exit(print_cdimage_list()); } static bool cmdCDLoad(const char* arg, const int cmd, const int cmd2, const int cmd3) { ctrlSendUint8(arg, cmd, 0, 255); exit(wait_for_cd_load()); } static bool cmdGUSBuffer(const char* arg, const int cmd, const int cmd2, const int cmd3) { uint8_t tmp_uint8; uint8_t e = sscanf(arg, "%hhu", &tmp_uint8); if (e != 1 || tmp_uint8 < 1) { usage(gMode, false); return false; } outp(CONTROL_PORT, cmd); outp(DATA_PORT_HIGH, (unsigned char)(tmp_uint8 - 1)); return true; } static bool cmdFlashPico(const char* arg, const int cmd, const int cmd2, const int cmd3) { if (strlen(arg) > 255) { usage(INVALID_MODE, false); return false; } write_firmware(arg); return true; } static bool cmdSave(const char* arg, const int cmd, const int cmd2, const int cmd3) { permanent = true; return true; } ParseCommand parseCommandsMinimal[] = { {"/?", cmdDisplayUsage, 0, ARG_NONE}, {"/??", cmdDisplayUsage, 1, ARG_NONE}, {0} }; ParseCommand parseCommandsFlash[] = { {"/?", cmdDisplayUsage, 0, ARG_NONE}, {"/??", cmdDisplayUsage, 1, ARG_NONE}, {"/flash", cmdFlashPico, 0, ARG_REQUIRE, "picogus.uf2"}, {0} }; ParseCommand parseCommands[] = { {"/flash", cmdFlashPico, 0, ARG_REQUIRE, "picogus.uf2"}, {"/save", cmdSave, 0, ARG_NONE}, {"/defaults", cmdDefaults, 0, ARG_NONE}, {"/?", cmdDisplayUsage, 0, ARG_NONE}, {"/??", cmdDisplayUsage, 1, ARG_NONE}, {"/joy", cmdSendBool, CMD_JOYEN, ARG_REQUIRE}, {"/mode", cmdSetMode, 0, ARG_REQUIRE}, {"/wtvol", cmdSetVol, CMD_WTVOL, ARG_REQUIRE}, {"/gusenv", cmdSetResourcesFromEnv, 1, ARG_NONE}, {"/gus44k", cmdSendBool, CMD_GUS44K, ARG_REQUIRE, "false"}, {"/gusbuf", cmdGUSBuffer, CMD_GUSBUF, ARG_REQUIRE, "4"}, {"/gusdma", cmdSendUint8, CMD_GUSDMA, ARG_REQUIRE, "0"}, {"/gusport", cmdSendPort, CMD_GUSPORT, ARG_REQUIRE, "240"}, {"/sbenv", cmdSetResourcesFromEnv, 1, ARG_NONE}, {"/sbport", cmdSendPort, CMD_SBPORT, ARG_REQUIRE, "220"}, {"/sbirq", cmdSendUint8, CMD_SBIRQ, ARG_REQUIRE, "5"}, {"/sbdma", cmdSendUint8, CMD_SBDMA, ARG_REQUIRE, "1"}, {"/sbtype", cmdSendSBType, CMD_SBTYPE, ARG_REQUIRE, "6"}, {"/sbfixtc", cmdSendBoolMasked, CMD_SBOPTS, ARG_REQUIRE, "0", (1 << 0), 0}, {"/sblockmixer", cmdSendSBLockMixer, CMD_SBOPTS, ARG_REQUIRE, "0"}, {"/oplport", cmdSendPort, CMD_OPLPORT, ARG_REQUIRE, "388"}, {"/oplwait", cmdSendBool, CMD_OPLWAIT, ARG_REQUIRE, "false"}, {"/mpuport", cmdSendPort, CMD_MPUPORT, ARG_REQUIRE, "330"}, {"/mpudelay", cmdSendBool, CMD_MPUDELAY, ARG_REQUIRE, "false"}, {"/mpufake", cmdSendBool, CMD_MPUFAKE, ARG_REQUIRE, "false"}, {"/tandyport", cmdSendPort, CMD_TANDYPORT, ARG_REQUIRE, "2c0"}, {"/cmsport", cmdSendPort, CMD_CMSPORT, ARG_REQUIRE, "220"}, {"/mousecom", cmdSendMousePort, CMD_MOUSEPORT, ARG_REQUIRE, "0"}, {"/mousesen", cmdSendMouseSen, CMD_MOUSESEN, ARG_REQUIRE, "256"}, {"/mouseproto", cmdSendMouseProto, CMD_MOUSEPROTO, ARG_REQUIRE, "0"}, {"/mouserate", cmdSendMouseRate, CMD_MOUSERATE, ARG_REQUIRE, "60"}, {"/ne2kport", cmdSendPort, CMD_NE2KPORT, ARG_REQUIRE, "300"}, {"/wifistatus", cmdWifiStatus, 0, ARG_NONE}, {"/wifissid", cmdWifiSSID, CMD_WIFISSID, ARG_REQUIRE}, {"/wifipass", cmdWifiPass, CMD_WIFIPASS, ARG_REQUIRE}, {"/wifinopass", cmdWifiNoPass, CMD_WIFIPASS, ARG_NONE}, {"/cdport", cmdSendPort, CMD_CDPORT, ARG_REQUIRE, "250"}, {"/cdlist", cmdCDList, 0, ARG_NONE}, {"/cdload", cmdCDLoad, CMD_CDLOAD, ARG_REQUIRE}, {"/cdauto", cmdSendBool, CMD_CDAUTOADV, ARG_REQUIRE, "true"}, {"/cdloadname", cmdCDLoadName, CMD_CDNAME, ARG_REQUIRE}, {"/mainvol", cmdSetVol, CMD_MAINVOL, ARG_REQUIRE, "100"}, {"/oplvol", cmdSetVol, CMD_OPLVOL, ARG_REQUIRE, "100"}, {"/sbvol", cmdSetVol, CMD_SBVOL, ARG_REQUIRE, "100"}, {"/cdvol", cmdSetVol, CMD_CDVOL, ARG_REQUIRE, "100"}, {"/gusvol", cmdSetVol, CMD_GUSVOL, ARG_REQUIRE, "100"}, {"/psgvol", cmdSetVol, CMD_PSGVOL, ARG_REQUIRE, "100"}, {0} }; ParseCommand *matchCommand(char *str, ParseCommand commands[]) { for (int i = 0; commands[i].name != NULL; i++) { if (!stricmp(commands[i].name, str)) { return &commands[i]; } } return NULL; } int parseCommand(int argc, char* argv[], int* i, ParseCommand commands[]) { bool retVal = false; int idx = *i; if (!argv[idx]) { return retVal; } ParseCommand *command = matchCommand(argv[idx], commands); if (command) { char* arg = NULL; if (command->type == ARG_REQUIRE) { if (idx + 1 >= argc || argv[idx + 1][0] == '/') { printf("Error: Command %s requires input argument. ", argv[idx]); if (command->def) { printf("Example: %s %s\n", argv[idx], command->def); } else { printf("\n"); } return retVal; } arg = argv[++(*i)]; if (!stricmp(arg, "default")) { if (command->def) { arg = command->def; } else { return retVal; } } } return command->routine(arg, command->cmd, command->cmd2, command->cmd3); } else { printf("Invalid command %s. Run pgusinit /? for usage help", argv[idx]); } return retVal; } static uint8_t ctrlGetUint8(int cmd) { outp(CONTROL_PORT, cmd); return inp(DATA_PORT_HIGH); } static uint16_t ctrlGetUint16(int cmd) { outp(CONTROL_PORT, cmd); return inpw(DATA_PORT_LOW); } static void printPicoGus() { printf("USB joystick support %s\n", ctrlGetUint8(CMD_JOYEN) ? "enabled" : "disabled"); if (board_type == PICOGUS_2) { printf("Wavetable volume set to %u\n", ctrlGetUint8(CMD_WTVOL)); } uint16_t mpuPort = ctrlGetUint16(CMD_MPUPORT); if (mpuPort) { printf("MPU-401: port %x; sysex delay: %s, ", mpuPort, ctrlGetUint8(CMD_MPUDELAY) ? "on" : "off"); printf("fake all notes off: %s\n", ctrlGetUint8(CMD_MPUFAKE) ? "on" : "off"); } else { printf("MPU-401 disabled\n"); } } static void printGUSMode() { if (init_gus()) { return; } printf("GUS mode: "); printf("Audio buffer: %u samples; ", ctrlGetUint8(CMD_GUSBUF) + 1); uint8_t tmp_uint8 = ctrlGetUint8(CMD_GUSDMA); if (tmp_uint8 == 0) { printf("DMA interval: default; "); } else { printf("DMA interval: %u us; ", tmp_uint8); } tmp_uint8 = ctrlGetUint8(CMD_GUS44K); if (tmp_uint8) { printf("Sample rate: fixed 44.1k\n"); } else { printf("Sample rate: variable\n"); } printf("Running in GUS mode on port %x\n", ctrlGetUint16(CMD_GUSPORT)); } static void printAdlibMode() { printf("Running in AdLib/OPL2 mode on port %x", ctrlGetUint16(CMD_OPLPORT)); printf("%s\n", ctrlGetUint8(CMD_OPLWAIT) & 1 ? ", wait on" : ""); } static void printUSBMode() { printf("Running in USB mode\n"); print_cdemu_status(); } static void printPSGMode() { printf("Running in PSG mode (Tandy 3-voice on port %x, ", ctrlGetUint16(CMD_TANDYPORT)); printf("CMS/Game Blaster on port %x)\n", ctrlGetUint16(CMD_CMSPORT)); } static void printSBMode() { static char *strMode[] = {"(invalid)", "1.x", "Pro 1", "2.0", "Pro 2", "(invalid)", "16"}; static char *strLockMixerMode[] = {"unlocked", "locked except Voice Volume", "locked", "(invalid)"}; if (init_sb()) { return; } printf("Running in Sound Blaster %s mode on port %x, IRQ %d, DMA %d\n", ctrlGetUint8(CMD_SBTYPE) > 6 ? "(invalid)" : strMode[ctrlGetUint8(CMD_SBTYPE)], ctrlGetUint16(CMD_SBPORT), ctrlGetUint8(CMD_SBIRQ), ctrlGetUint8(CMD_SBDMA) ); uint8_t tmp_uint8 = ctrlGetUint8(CMD_SBOPTS); printf("SB Time Constant fix: %s, SB mixer: %s\n", (tmp_uint8 >> 0) & 1 ? "enabled" : "disabled", strLockMixerMode[(tmp_uint8 >> 1) & 3] ); uint16_t tmp_uint16 = ctrlGetUint16(CMD_OPLPORT); if (tmp_uint16) { printf("AdLib port %x", tmp_uint16); printf("%s\n", ctrlGetUint8(CMD_OPLWAIT) & 1 ? ", wait on" : ""); } else { printf("AdLib port disabled\n"); } print_cdemu_status(); } static void printNE2000Mode() { printf("Running in NE2000 mode on port %x\n", ctrlGetUint16(CMD_NE2KPORT)); wifi_printStatus(); } static void printMultiMode() { if (gMode == USB_MODE || gMode == PSG_MODE || gMode == ADLIB_MODE) { uint16_t tmp_uint16 = ctrlGetUint16(CMD_MOUSEPORT); printf("Serial Mouse "); switch (tmp_uint16) { case 0: printf("disabled (enable with /mousecom n option)\n"); break; case 0x3f8: printf("enabled on COM1\n"); break; case 0x2f8: printf("enabled on COM2\n"); break; case 0x3e8: printf("enabled on COM3\n"); break; case 0x2e8: printf("enabled on COM4\n"); break; default: printf("enabled on IO port %x\n", tmp_uint16); } if (tmp_uint16 != 0) { printf("Mouse report rate: %d Hz, ", ctrlGetUint8(CMD_MOUSERATE)); printf("protocol: %s\n", mouse_protocol_str[ctrlGetUint8(CMD_MOUSEPROTO)]); tmp_uint16 = ctrlGetUint16(CMD_MOUSESEN); printf("Mouse sensitivity: %d (%d.%02d)\n", tmp_uint16, (tmp_uint16 >> 8), ((tmp_uint16 & 0xFF) * 100) >> 8); } } } static void printVolume() { printf("Volume: "); printf("Main: %u ", ctrlGetUint8(CMD_MAINVOL)); if (gMode == GUS_MODE) { printf("GUS: %u ", ctrlGetUint8(CMD_GUSVOL)); } if (gMode == SB_MODE) { printf("SB: %u ", ctrlGetUint8(CMD_SBVOL)); } if (gMode == SB_MODE || gMode == ADLIB_MODE) { printf("OPL: %u ", ctrlGetUint8(CMD_OPLVOL)); } if (gMode == SB_MODE || gMode == USB_MODE) { printf("CD: %u ", ctrlGetUint8(CMD_CDVOL)); } if (gMode == PSG_MODE) { printf("PSG: %u ", ctrlGetUint8(CMD_PSGVOL)); } printf("\n"); } typedef enum { INIT_OK, INIT_FW_MISMATCH, INIT_NOT_DETECTED } init_status; static init_status initPicoGUS() { // Get magic value from port on PicoGUS that is not on real GUS outp(CONTROL_PORT, 0xCC); // Knock on the door... outp(CONTROL_PORT, CMD_MAGIC); // Select magic string register if (inp(DATA_PORT_HIGH) != 0xDD) { err_pigus(); return INIT_NOT_DETECTED; }; printf("PicoGUS detected: Firmware version: "); print_string(CMD_FWSTRING); outp(CONTROL_PORT, 0x01); // Select protocol version register uint8_t protocol_got = inp(DATA_PORT_HIGH); if (PICOGUS_PROTOCOL_VER != protocol_got) { err_protocol(PICOGUS_PROTOCOL_VER, protocol_got); return INIT_FW_MISMATCH; } outp(CONTROL_PORT, CMD_BOOTMODE); // Select mode register gMode = inp(DATA_PORT_HIGH); outp(CONTROL_PORT, CMD_HWTYPE); // Select hardware type register board_type = inp(DATA_PORT_HIGH); if (board_type == PICO_BASED) { printf("Hardware: PicoGUS v1.x or PicoGUS Femto\n"); } else if (board_type == PICOGUS_2) { printf("Hardware: PicoGUS v2.0\n"); } else { printf("Hardware: Unknown\n"); } printf("\n"); return INIT_OK; } int main(int argc, char* argv[]) { is_console = isatty(fileno(stdout)); if (is_console) { page_lines = get_screen_lines() - 1; } banner(); init_status status = initPicoGUS(); if (status == INIT_FW_MISMATCH) { // Still allow firmware upgrade if firmware version mismatches for(int i = 1; i < argc; ++i) { if (!parseCommand(argc, argv, &i, parseCommandsFlash)) { return 1; } else { return 0; } } return 1; } else if (status == INIT_NOT_DETECTED) { // Still allow /? and /?? commands if not detected for(int i = 1; i < argc; ++i) { if (!parseCommand(argc, argv, &i, parseCommandsMinimal)) { return 1; } } return 1; } int commands = 0; for(int i = 1; i < argc; ++i) { if (!parseCommand(argc, argv, &i, parseCommands)) { return 1; } } // If mode was set in commands, apply it if (newMode) { return reboot_to_firmware(newMode, permanent); } if (wifichg) { ctrlSendUint8("0", CMD_WIFIAPPLY, 0, 255); } printPicoGus(); printVolume(); switch(gMode) { case GUS_MODE: printGUSMode(); break; case ADLIB_MODE: printAdlibMode(); break; case MPU_MODE: printf("Running in MPU-401 only mode (with IRQ)\n"); break; case USB_MODE: printUSBMode(); break; case PSG_MODE: printPSGMode(); break; case SB_MODE: printSBMode(); break; case NE2000_MODE: printNE2000Mode(); break; default: printf("Running in unknown mode (maybe upgrade pgusinit?)\n"); break; } printMultiMode(); printf("PicoGUS initialized!\n"); if (permanent) { write_settings(); } return 0; }