├── .vscode ├── settings.json └── tasks.json ├── LICENSE ├── Makefile ├── README.md ├── assets └── update_process.png └── src ├── agontimer.c ├── agontimer.h ├── crc32.asm ├── crc32.h ├── ez80f92.h ├── filesize.asm ├── filesize.h ├── flash.asm ├── flash.h └── main.c /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "*.s": "ez80-asm", 4 | "agontimer.h": "c" 5 | } 6 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "ZDS Rebuild all", 8 | "type": "shell", 9 | "command": "make rebuildall -f ${workspaceFolder}/src/${workspaceFolderBasename}.mak", 10 | "options": { 11 | "cwd": "${workspaceFolder}/src/Debug" 12 | }, 13 | "problemMatcher": [], 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | }, 19 | { 20 | "label": "Upload Intel HEX file", 21 | "type": "shell", 22 | "command": "py.exe ./send.py ./src/Debug/flash.hex", 23 | "group": "test", 24 | "presentation": { 25 | "reveal": "always", 26 | "panel": "new", 27 | "close": true 28 | } 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Jeroen Venema 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NAME=flash 2 | RAM_START = 0x0b0000 3 | RAM_SIZE = 0x008000 4 | include $(shell agondev-config --makefile) 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Agon firmware update utility 2 | The Agon firmware update utility is able to flash MOS / VDP firmware from files on the SD card on any Agon, without using any cables. 3 | 4 | ## Requirements 5 | This utility needs at least MOS version 1.03. If you are still running an older MOS version, please see this legacy [utility version](https://github.com/envenomator/agon-flashlegacy). 6 | 7 | Flashing VDP firmware from a file on the SD card, requires a programmed VDP that has OTA (Over-the-air) functionality built in. This is available in Console8 VDP 2.0.0+ and Quark VDP 1.4RC3+ firmware versions. Once a compatible VDP is present in the system, new VDP versions can be flashed using the flash utility. 8 | 9 | ## Installation 10 | 1. Make sure to create a **mos** directory on the microSD card, if it's not already present. 11 | 2. Place the [flash.bin](https://github.com/envenomator/agon-flash/releases/latest/download/flash.bin) in the **mos** directory 12 | 3. Place the firmware files in the **root** directory of the microSD card: 13 | - MOS firmware - default filename 'MOS.bin' (download [latest](https://github.com/AgonConsole8/agon-mos/releases/latest/download/MOS.bin) version) 14 | - VDP firmware - default filename 'firmware.bin' (download [latest](https://github.com/AgonConsole8/agon-vdp/releases/latest/download/firmware.bin) version) 15 | 16 | ## Usage 17 | 18 | ```console 19 | Usage: FLASH [all | [mos ] [vdp ] | batch] <-f> 20 | ``` 21 | 22 | | **Option** | **Function** | 23 | |:----------:|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 24 | | all | flash all firmware, using the default filenames 'MOS.bin' for MOS firmware, 'firmware.bin' for VDP firmware | 25 | | mos | flash mos firmware, with optional filename | 26 | | vdp | flash vdp firmware, with optional filename | 27 | | batch | used to batch-flash an Agon system using the command in autoexec.txt. In order to facilitate headless flashing, the utility beeps during the flashing sequence (1 for startup, 2 for completing VDP firmware, 3 for completing the MOS firmware) and waits at completion forever | 28 | | -f | skips asking the user to verify firmware CRC codes and is set by default using the batch command | 29 | ## Upgrade process workflow 30 | This workflow outlines the update process, depending on your specific current MOS/VDP version: 31 | ![process](assets/update_process.png) 32 | 33 | ## Target firmware versions 34 | Any MOS / VDP version can be flashed. 35 | 36 | Because literally ANY version can be flashed, please be mindful going back to older versions. The Pleistocene may not have a wall-outlet to charge your time travel device. Take an extra battery pack with you and study the [upgrade workflow](#upgrade-process-workflow) to get back. 37 | 38 | ## Disclaimer 39 | Having subjected my own gear to this utility many hundreds of times, I feel it is safe for general use in upgrading the MOS firmware to a new version. 40 | The responsibility for any issues during and/or after the firmware upgrade, lies with the user of this utility. 41 | 42 | ## If all else fails 43 | Did you also update your VDP firmware, after updating the MOS? Weird things may happen if both are far apart. 44 | 45 | And even then; stuff breaks, things happen. Yeah, wonderful, but what if after whatever happened, you have bricked your AgonLight? Take a deep breath, and have a look at [this](https://github.com/envenomator/agon-recovery) utility to perform a *baremetal* recovery. 46 | -------------------------------------------------------------------------------- /assets/update_process.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgonPlatform/agon-flash/e670b5bd910dfe372c29aa9e896e6c24cc8530ec/assets/update_process.png -------------------------------------------------------------------------------- /src/agontimer.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Title: AGON timer interface 3 | * Author: Jeroen Venema 4 | * Created: 06/11/2022 5 | * Last Updated: 12/04/2025 6 | * 7 | * Modinfo: 8 | * 06/11/2022: Initial version 9 | * 12/04/2025: Updated for agondev 10 | */ 11 | 12 | #include "ez80f92.h" 13 | #include 14 | 15 | #define TMR0_COUNTER_1ms (unsigned short)(((18432000 / 1000) * 1) / 16) 16 | 17 | void delayms(int ms) 18 | { 19 | unsigned short rr; 20 | uint16_t timer0; 21 | 22 | rr = TMR0_COUNTER_1ms - 19; // 1,7% correction for cycles during while(ms) loop 23 | 24 | IO(TMR0_CTL) = 0x00; // disable timer0 25 | 26 | while(ms) 27 | { 28 | IO(TMR0_RR_H) = (unsigned char)(rr >> 8); 29 | IO(TMR0_RR_L) = (unsigned char)(rr); 30 | 31 | IO(TMR0_CTL) = 0x87; // enable, single pass, stop at 0, start countdown immediately 32 | do 33 | { 34 | timer0 = IO(TMR0_DR_L); 35 | timer0 |= (IO(TMR0_DR_H) << 8); 36 | } 37 | while(timer0); 38 | ms--; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/agontimer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Title: AGON timer interface 3 | * Author: Jeroen Venema 4 | * Created: 06/11/2022 5 | * Last Updated: 06/11/2022 6 | * 7 | * Modinfo: 8 | * 06/11/2022: Initial version 9 | */ 10 | 11 | #ifndef AGONTIMER_H 12 | #define AGONTIMER_H 13 | 14 | void delayms(int ms); 15 | 16 | #endif //AGONTIMER_H 17 | -------------------------------------------------------------------------------- /src/crc32.asm: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: crc32 3 | ; Author: Leigh Brown 4 | ; Created: 26/05/2023 5 | ; Last Updated: 13/04/2025 - Jeroen Venema 6 | 7 | ; Modinfo 8 | ; crc32 can han.d32e different blocks now 9 | ; crc32 is now assembled using gnu-as 10 | 11 | .global _crc32 12 | .global _crc32_initialize 13 | .global _crc32_finalize 14 | 15 | .assume adl = 1 16 | .text 17 | ; UINT32 crc32(const char *s, UINT24 len); 18 | ; IX+6 IX+9 19 | ; NB: We use local stack storage without allocating it by decrementing SP. 20 | ; That's okay, /as.d32 as/ we don't call any other functions. 21 | 22 | _crc32_initialize: 23 | ; Initialise output to 0xFFFFFFFF 24 | EXX 25 | LD A, $FF 26 | LD (crc32result+3), A 27 | LD (crc32result+2), A 28 | LD (crc32result+1), A 29 | LD (crc32result), A 30 | EXX 31 | 32 | ; replacement for LD HL, crc32_lookup_table >> 2 33 | ; which doesn't work in gnu-as with linked code segments, where the symbol address is unknown at assembly 34 | PUSH HL 35 | LD HL, crc32_lookup_table ; load address of the crc32_lookup_table 36 | LD (shiftedtable), HL ; save the address, so we can reach the upper byte individually 37 | LD A, (shiftedtable+2) ; get HLU 38 | SRL A ; start shifting 1st bit 39 | RR H 40 | RR L 41 | SRL A ; start shifting 2nd bit 42 | RR H 43 | RR L 44 | LD (shiftedtable), HL 45 | LD (shiftedtable+2), A ; crc32_lookup_table >> 2 is now stored in (shiftedtable) 46 | POP HL 47 | RET 48 | 49 | _crc32_finalize: 50 | PUSH IX 51 | LD IX,0 52 | ADD IX,SP 53 | 54 | LD A, (crc32result+3) 55 | CPL 56 | LD (crc32result+3), A 57 | LD A, (crc32result+2) 58 | CPL 59 | LD (crc32result+2), A 60 | LD A, (crc32result+1) 61 | CPL 62 | LD (crc32result+1), A 63 | LD A, (crc32result) 64 | CPL 65 | LD (crc32result), A 66 | 67 | LD A, (crc32result+3) 68 | LD DE, 0 69 | LD E, A 70 | LD HL, (crc32result) 71 | POP IX 72 | RET 73 | 74 | _crc32: 75 | ; Function prologue 76 | PUSH IX 77 | LD IX,0 78 | ADD IX,SP 79 | 80 | LD DE, (IX+6) 81 | LD HL, (IX+9) 82 | ; expects DE to point to buffer to check 83 | ; expects HL to contain the bytecount 84 | LD BC,1 85 | 86 | EXX 87 | LD A, (crc32result+3) 88 | LD D, A 89 | LD A, (crc32result+2) 90 | LD E, A 91 | LD A, (crc32result+1) 92 | LD B, A 93 | LD A, (crc32result) 94 | LD C, A 95 | EXX 96 | 97 | 1: 98 | LD A,(DE) 99 | INC DE 100 | 101 | ; Calculate offset into lookup table 102 | EXX 103 | XOR C 104 | LD HL, (shiftedtable) ;LD HL,crc32_lookup_table >> 2 105 | LD L,A 106 | ADD HL,HL 107 | ADD HL,HL 108 | 109 | LD A,B 110 | XOR (HL) 111 | INC HL 112 | LD C,A 113 | 114 | LD A,E 115 | XOR (HL) 116 | INC HL 117 | LD B,A 118 | 119 | LD A,D 120 | XOR (HL) 121 | INC HL 122 | LD E,A 123 | 124 | LD D,(HL) 125 | EXX 126 | 127 | ; Decrement count and loop if not zero 128 | OR A,A 129 | SBC HL,BC 130 | JR NZ,1b 131 | 132 | EXX 133 | LD A, D 134 | LD (crc32result+3), A 135 | LD A, E 136 | LD (crc32result+2), A 137 | LD A, B 138 | LD (crc32result+1), A 139 | LD A, C 140 | LD (crc32result), A 141 | EXX 142 | 143 | ; Function epilogue 144 | POP IX 145 | RET 146 | 147 | .section .rodata 148 | ; The crc32 routine is optimised in such a way as to require 149 | ; the following table to be aligned on a 1024 byte boundary. 150 | 151 | .ALIGN 10 152 | crc32_lookup_table: 153 | .d32 $00000000, $77073096, $ee0e612c, $990951ba 154 | .d32 $076dc419, $706af48f, $e963a535, $9e6495a3 155 | .d32 $0edb8832, $79dcb8a4, $e0d5e91e, $97d2d988 156 | .d32 $09b64c2b, $7eb17cbd, $e7b82d07, $90bf1d91 157 | .d32 $1db71064, $6ab020f2, $f3b97148, $84be41de 158 | .d32 $1adad47d, $6ddde4eb, $f4d4b551, $83d385c7 159 | .d32 $136c9856, $646ba8c0, $fd62f97a, $8a65c9ec 160 | .d32 $14015c4f, $63066cd9, $fa0f3d63, $8d080df5 161 | .d32 $3b6e20c8, $4c69105e, $d56041e4, $a2677172 162 | .d32 $3c03e4d1, $4b04d447, $d20d85fd, $a50ab56b 163 | .d32 $35b5a8fa, $42b2986c, $dbbbc9d6, $acbcf940 164 | .d32 $32d86ce3, $45df5c75, $dcd60dcf, $abd13d59 165 | .d32 $26d930ac, $51de003a, $c8d75180, $bfd06116 166 | .d32 $21b4f4b5, $56b3c423, $cfba9599, $b8bda50f 167 | .d32 $2802b89e, $5f058808, $c60cd9b2, $b10be924 168 | .d32 $2f6f7c87, $58684c11, $c1611dab, $b6662d3d 169 | .d32 $76dc4190, $01db7106, $98d220bc, $efd5102a 170 | .d32 $71b18589, $06b6b51f, $9fbfe4a5, $e8b8d433 171 | .d32 $7807c9a2, $0f00f934, $9609a88e, $e10e9818 172 | .d32 $7f6a0dbb, $086d3d2d, $91646c97, $e6635c01 173 | .d32 $6b6b51f4, $1c6c6162, $856530d8, $f262004e 174 | .d32 $6c0695ed, $1b01a57b, $8208f4c1, $f50fc457 175 | .d32 $65b0d9c6, $12b7e950, $8bbeb8ea, $fcb9887c 176 | .d32 $62dd1ddf, $15da2d49, $8cd37cf3, $fbd44c65 177 | .d32 $4db26158, $3ab551ce, $a3bc0074, $d4bb30e2 178 | .d32 $4adfa541, $3dd895d7, $a4d1c46d, $d3d6f4fb 179 | .d32 $4369e96a, $346ed9fc, $ad678846, $da60b8d0 180 | .d32 $44042d73, $33031de5, $aa0a4c5f, $dd0d7cc9 181 | .d32 $5005713c, $270241aa, $be0b1010, $c90c2086 182 | .d32 $5768b525, $206f85b3, $b966d409, $ce61e49f 183 | .d32 $5edef90e, $29d9c998, $b0d09822, $c7d7a8b4 184 | .d32 $59b33d17, $2eb40d81, $b7bd5c3b, $c0ba6cad 185 | .d32 $edb88320, $9abfb3b6, $03b6e20c, $74b1d29a 186 | .d32 $ead54739, $9dd277af, $04db2615, $73dc1683 187 | .d32 $e3630b12, $94643b84, $0d6d6a3e, $7a6a5aa8 188 | .d32 $e40ecf0b, $9309ff9d, $0a00ae27, $7d079eb1 189 | .d32 $f00f9344, $8708a3d2, $1e01f268, $6906c2fe 190 | .d32 $f762575d, $806567cb, $196c3671, $6e6b06e7 191 | .d32 $fed41b76, $89d32be0, $10da7a5a, $67dd4acc 192 | .d32 $f9b9df6f, $8ebeeff9, $17b7be43, $60b08ed5 193 | .d32 $d6d6a3e8, $a1d1937e, $38d8c2c4, $4fdff252 194 | .d32 $d1bb67f1, $a6bc5767, $3fb506dd, $48b2364b 195 | .d32 $d80d2bda, $af0a1b4c, $36034af6, $41047a60 196 | .d32 $df60efc3, $a867df55, $316e8eef, $4669be79 197 | .d32 $cb61b38c, $bc66831a, $256fd2a0, $5268e236 198 | .d32 $cc0c7795, $bb0b4703, $220216b9, $5505262f 199 | .d32 $c5ba3bbe, $b2bd0b28, $2bb45a92, $5cb36a04 200 | .d32 $c2d7ffa7, $b5d0cf31, $2cd99e8b, $5bdeae1d 201 | .d32 $9b64c2b0, $ec63f226, $756aa39c, $026d930a 202 | .d32 $9c0906a9, $eb0e363f, $72076785, $05005713 203 | .d32 $95bf4a82, $e2b87a14, $7bb12bae, $0cb61b38 204 | .d32 $92d28e9b, $e5d5be0d, $7cdcefb7, $0bdbdf21 205 | .d32 $86d3d2d4, $f1d4e242, $68ddb3f8, $1fda836e 206 | .d32 $81be16cd, $f6b9265b, $6fb077e1, $18b74777 207 | .d32 $88085ae6, $ff0f6a70, $66063bca, $11010b5c 208 | .d32 $8f659eff, $f862ae69, $616bffd3, $166ccf45 209 | .d32 $a00ae278, $d70dd2ee, $4e048354, $3903b3c2 210 | .d32 $a7672661, $d06016f7, $4969474d, $3e6e77db 211 | .d32 $aed16a4a, $d9d65adc, $40df0b66, $37d83bf0 212 | .d32 $a9bcae53, $debb9ec5, $47b2cf7f, $30b5ffe9 213 | .d32 $bdbdf21c, $cabac28a, $53b39330, $24b4a3a6 214 | .d32 $bad03605, $cdd70693, $54de5729, $23d967bf 215 | .d32 $b3667a2e, $c4614ab8, $5d681b02, $2a6f2b94 216 | .d32 $b40bbe37, $c30c8ea1, $5a05df1b, $2d02ef8d 217 | 218 | .data 219 | crc32result: 220 | .d32 0 221 | shiftedtable: 222 | .d24 0 223 | 224 | END 225 | -------------------------------------------------------------------------------- /src/crc32.h: -------------------------------------------------------------------------------- 1 | #ifndef CRC32_H 2 | #define CRC32_H 3 | 4 | #include 5 | 6 | void crc32(const char *s, uint24_t length); 7 | void crc32_initialize(void); 8 | uint32_t crc32_finalize(void); 9 | #endif //CRC32_H 10 | -------------------------------------------------------------------------------- /src/ez80f92.h: -------------------------------------------------------------------------------- 1 | #ifndef EZ80F92_H 2 | #define EZ80F92_H 3 | 4 | #define IO(addr) (*((volatile uint8_t __attribute__((address_space(3)))*)(addr))) 5 | 6 | #define TMR0_CTL 0x80 7 | #define TMR0_DR_L 0x81 8 | #define TMR0_RR_L 0x81 9 | #define TMR0_DR_H 0x82 10 | #define TMR0_RR_H 0x82 11 | #define TMR1_CTL 0x83 12 | #define TMR1_DR_L 0x84 13 | #define TMR1_RR_L 0x84 14 | #define TMR1_DR_H 0x85 15 | #define TMR1_RR_H 0x85 16 | #define TMR2_CTL 0x86 17 | #define TMR2_DR_L 0x87 18 | #define TMR2_RR_L 0x87 19 | #define TMR2_DR_H 0x88 20 | #define TMR2_RR_H 0x88 21 | #define TMR3_CTL 0x89 22 | #define TMR3_DR_L 0x8A 23 | #define TMR3_RR_L 0x8A 24 | #define TMR3_DR_H 0x8B 25 | #define TMR3_RR_H 0x8B 26 | #define TMR4_CTL 0x8C 27 | #define TMR4_DR_L 0x8D 28 | #define TMR4_RR_L 0x8D 29 | #define TMR4_DR_H 0x8E 30 | #define TMR4_RR_H 0x8E 31 | #define TMR5_CTL 0x8F 32 | #define TMR5_DR_L 0x90 33 | #define TMR5_RR_L 0x90 34 | #define TMR5_DR_H 0x91 35 | #define TMR5_RR_H 0x91 36 | #define TMR_ISS 0x92 37 | #define WDT_CTL 0x93 38 | #define WDT_RR 0x94 39 | #define PB_DR 0x9A 40 | #define PB_DDR 0x9B 41 | #define PB_ALT1 0x9C 42 | #define PB_ALT2 0x9D 43 | #define PC_DR 0x9E 44 | #define PC_DDR 0x9F 45 | #define PC_ALT1 0xA0 46 | #define PC_ALT2 0xA1 47 | #define PD_DR 0xA2 48 | #define PD_DDR 0xA3 49 | #define PD_ALT1 0xA4 50 | #define PD_ALT2 0xA5 51 | #define CS0_LBR 0xA8 52 | #define CS0_UBR 0xA9 53 | #define CS0_CTL 0xAA 54 | #define CS1_LBR 0xAB 55 | #define CS1_UBR 0xAC 56 | #define CS1_CTL 0xAD 57 | #define CS2_LBR 0xAE 58 | #define CS2_UBR 0xAF 59 | #define CS2_CTL 0xB0 60 | #define CS3_LBR 0xB1 61 | #define CS3_UBR 0xB2 62 | #define CS3_CTL 0xB3 63 | #define RAM_CTL 0xB4 64 | #define RAM_ADDR_U 0xB5 65 | #define SPI_BRG_L 0xB8 66 | #define SPI_BRG_H 0xB9 67 | #define SPI_CTL 0xBA 68 | #define SPI_SR 0xBB 69 | #define SPI_TSR 0xBC 70 | #define SPI_RBR 0xBC 71 | #define IR_CTL 0xBF 72 | #define UART0_RBR 0xC0 73 | #define UART0_THR 0xC0 74 | #define UART0_BRG_L 0xC0 75 | #define UART0_IER 0xC1 76 | #define UART0_BRG_H 0xC1 77 | #define UART0_IIR 0xC2 78 | #define UART0_FCTL 0xC2 79 | #define UART0_LCTL 0xC3 80 | #define UART0_MCTL 0xC4 81 | #define UART0_LSR 0xC5 82 | #define UART0_MSR 0xC6 83 | #define UART0_SPR 0xC7 84 | #define I2C_SAR 0xC8 85 | #define I2C_XSAR 0xC9 86 | #define I2C_DR 0xCA 87 | #define I2C_CTL 0xCB 88 | #define I2C_SR 0xCC 89 | #define I2C_CCR 0xCC 90 | #define I2C_SRR 0xCD 91 | #define UART1_RBR 0xD0 92 | #define UART1_THR 0xD0 93 | #define UART1_BRG_L 0xD0 94 | #define UART1_IER 0xD1 95 | #define UART1_BRG_H 0xD1 96 | #define UART1_IIR 0xD2 97 | #define UART1_FCTL 0xD2 98 | #define UART1_LCTL 0xD3 99 | #define UART1_MCTL 0xD4 100 | #define UART1_LSR 0xD5 101 | #define UART1_MSR 0xD6 102 | #define UART1_SPR 0xD7 103 | #define CLK_PPD1 0xDB 104 | #define CLK_PPD2 0xDC 105 | #define RTC_SEC 0xE0 106 | #define RTC_MIN 0xE1 107 | #define RTC_HRS 0xE2 108 | #define RTC_DOW 0xE3 109 | #define RTC_DOM 0xE4 110 | #define RTC_MON 0xE5 111 | #define RTC_YR 0xE6 112 | #define RTC_CEN 0xE7 113 | #define RTC_ASEC 0xE8 114 | #define RTC_AMIN 0xE9 115 | #define RTC_AHRS 0xEA 116 | #define RTC_ADOW 0xEB 117 | #define RTC_ACTRL 0xEC 118 | #define RTC_CTRL 0xED 119 | #define CS0_BMC 0xF0 120 | #define CS1_BMC 0xF1 121 | #define CS2_BMC 0xF2 122 | #define CS3_BMC 0xF3 123 | #define FLASH_KEY 0xF5 124 | #define FLASH_DATA 0xF6 125 | #define FLASH_ADDR_U 0xF7 126 | #define FLASH_CTRL 0xF8 127 | #define FLASH_FDIV 0xF9 128 | #define FLASH_PROT 0xFA 129 | #define FLASH_IRQ 0xFB 130 | #define FLASH_PAGE 0xFC 131 | #define FLASH_ROW 0xFD 132 | #define FLASH_COL 0xFE 133 | #define FLASH_PGCTL 0xFF 134 | 135 | #define FLASH_IVECT 0x08 136 | #define PRT0_IVECT 0x0A 137 | #define PRT1_IVECT 0x0C 138 | #define PRT2_IVECT 0x0E 139 | #define PRT3_IVECT 0x10 140 | #define PRT4_IVECT 0x12 141 | #define PRT5_IVECT 0x14 142 | #define RTC_IVECT 0x16 143 | #define UART0_IVECT 0x18 144 | #define UART1_IVECT 0x1A 145 | #define I2C_IVECT 0x1C 146 | #define SPI_IVECT 0x1E 147 | #define PORTB0_IVECT 0x30 148 | #define PORTB1_IVECT 0x32 149 | #define PORTB2_IVECT 0x34 150 | #define PORTB3_IVECT 0x36 151 | #define PORTB4_IVECT 0x38 152 | #define PORTB5_IVECT 0x3A 153 | #define PORTB6_IVECT 0x3C 154 | #define PORTB7_IVECT 0x3E 155 | #define PORTC0_IVECT 0x40 156 | #define PORTC1_IVECT 0x42 157 | #define PORTC2_IVECT 0x44 158 | #define PORTC3_IVECT 0x46 159 | #define PORTC4_IVECT 0x48 160 | #define PORTC5_IVECT 0x4A 161 | #define PORTC6_IVECT 0x4C 162 | #define PORTC7_IVECT 0x4E 163 | #define PORTD0_IVECT 0x50 164 | #define PORTD1_IVECT 0x52 165 | #define PORTD2_IVECT 0x54 166 | #define PORTD3_IVECT 0x56 167 | #define PORTD4_IVECT 0x58 168 | #define PORTD5_IVECT 0x5A 169 | #define PORTD6_IVECT 0x5C 170 | #define PORTD7_IVECT 0x5E 171 | 172 | #define PORTB_DRVAL_DEF 0xFF 173 | #define PORTB_DDRVAL_DEF 0xFF 174 | #define PORTB_ALT0VAL_DEF 0xFF 175 | #define PORTB_ALT1VAL_DEF 0x00 176 | #define PORTB_ALT2VAL_DEF 0x00 177 | #define PORTC_DRVAL_DEF 0xFF 178 | #define PORTC_DDRVAL_DEF 0xFF 179 | #define PORTC_ALT0VAL_DEF 0xFF 180 | #define PORTC_ALT1VAL_DEF 0x00 181 | #define PORTC_ALT2VAL_DEF 0x00 182 | #define PORTD_DRVAL_DEF 0xFF 183 | #define PORTD_DDRVAL_DEF 0xFF 184 | #define PORTD_ALT0VAL_DEF 0xFF 185 | #define PORTD_ALT1VAL_DEF 0x00 186 | #define PORTD_ALT2VAL_DEF 0x00 187 | 188 | #define PORTPIN_ZERO 0x01 189 | #define PORTPIN_ONE 0x02 190 | #define PORTPIN_TWO 0x04 191 | #define PORTPIN_THREE 0x08 192 | #define PORTPIN_FOUR 0x10 193 | #define PORTPIN_FIVE 0x20 194 | #define PORTPIN_SIX 0x40 195 | #define PORTPIN_SEVEN 0x80 196 | #define PORTPIN_FOURBITS_L 0x0F 197 | #define PORTPIN_FOURBITS_U 0xF0 198 | #define PORTPIN_PATTERN_AA 0xAA 199 | #define PORTPIN_PATTERN_55 0x55 200 | #define PORTPIN_ALL 0xFF 201 | 202 | #endif /* EZ80F92_H */ 203 | -------------------------------------------------------------------------------- /src/filesize.asm: -------------------------------------------------------------------------------- 1 | ; Gets filesize from an open file 2 | ; requires MOS mos_getfil call 3 | ; Input: A - MOS filehandle 4 | ; Output: HL - 24bit filesize 5 | 6 | .global _getFileSize 7 | .assume adl = 1 8 | .text 9 | 10 | _getFileSize: 11 | PUSH IX 12 | LD IX, 0h 13 | ADD IX, SP 14 | 15 | ; get pointer to FIL structure in MOS memory 16 | LD A, (IX+6) 17 | LD C, A 18 | LD A, 19h ; MOS_GETFIL API call 19 | RST.LIL 08h 20 | 21 | LD DE, 11 ; offset to lower 3bytes in FSIZE_t, part of the FFOBJD struct that HL points to 22 | ADD HL, DE 23 | LD HL, (HL) ; load actual FSIZE_t value (lower 3 bytes) 24 | 25 | LD SP, IX 26 | POP IX 27 | RET 28 | end 29 | -------------------------------------------------------------------------------- /src/filesize.h: -------------------------------------------------------------------------------- 1 | #ifndef FILESIZE_H 2 | #define FILESIZE_H 3 | 4 | #include 5 | extern uint24_t getFileSize(uint8_t filehandle); 6 | 7 | #endif //FILESIZE_H 8 | -------------------------------------------------------------------------------- /src/flash.asm: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: Flash interface 3 | ; Author: Jeroen Venema 4 | ; Created: 16/12/2022 5 | ; Last Updated: 22/04/2025 6 | ; 7 | ; Modinfo: 8 | ; 14/10/2023: VDP update routine added 9 | ; 12/04/2025: Updated for agondev 10 | ; 22/04/2025: Saving BC/DE/HL registers required in _startVDPupdate 11 | 12 | .global _enableFlashKeyRegister 13 | .global _fastmemcpy 14 | .global _reset 15 | .global _startVDPupdate 16 | 17 | .assume adl = 1 18 | .text 19 | 20 | BUFFERSIZE EQU 1024 21 | buffer EQU $50000 ; memory location 22 | 23 | _enableFlashKeyRegister: 24 | PUSH IX 25 | LD IX, 0 26 | ADD IX, SP 27 | 28 | ; actual work here 29 | LD A, $b6 ; unlock 30 | OUT0 ($F5), A 31 | LD A, $49 32 | OUT0 ($F5), A 33 | 34 | LD SP, IX 35 | POP IX 36 | RET 37 | 38 | _reset: 39 | RST 0 40 | RET ; will never get here 41 | 42 | _fastmemcpy: 43 | PUSH IX 44 | LD IX, 0 45 | ADD IX, SP 46 | 47 | PUSH BC 48 | PUSH DE 49 | PUSH HL 50 | 51 | LD DE, (IX+6) ; destination address 52 | LD HL, (IX+9) ; source 53 | LD BC, (IX+12) ; number of bytes to write to flash 54 | LDIR 55 | 56 | POP HL 57 | POP DE 58 | POP BC 59 | 60 | LD SP, IX 61 | POP IX 62 | RET 63 | 64 | _startVDPupdate: 65 | PUSH IX 66 | LD IX, 0 67 | ADD IX, SP 68 | 69 | PUSH BC 70 | PUSH DE 71 | PUSH HL 72 | 73 | LD A, (IX+6) 74 | LD (filehandle), A 75 | LD HL, (IX+9) 76 | LD (filesize), HL 77 | 78 | sendstartsequence: 79 | LD A, 23 80 | RST.LIL $10 81 | LD A, 0 82 | RST.LIL $10 83 | LD A, $A1 84 | RST.LIL $10 85 | LD A, 1 86 | RST.LIL $10 87 | 88 | sendsize: 89 | LD HL, (filesize) 90 | LD A, L 91 | RST.LIL $10 ; send LSB 92 | LD A, H 93 | RST.LIL $10 ; send middle byte 94 | PUSH HL 95 | INC SP 96 | POP AF 97 | DEC SP 98 | RST.LIL $10 ; send MSB 99 | 100 | senddata_start: 101 | LD A, 0 102 | LD (checksum), A 103 | 104 | senddata: 105 | LD A, (filehandle) 106 | LD C, A 107 | LD HL, buffer 108 | LD DE, BUFFERSIZE 109 | LD A, $1A ; mos_fread 110 | RST.LIL $08 111 | LD A, D 112 | OR E ; 0 bytes read? 113 | JR Z, sendchecksum 114 | 115 | LD HL, buffer 116 | PUSH DE 117 | POP BC ; length of the bufferstream to bc 118 | LD A, 0 ; don't care as bc is set 119 | PUSH DE 120 | RST.LIL $18 121 | POP DE 122 | ; calculate checksum 123 | LD HL, buffer 124 | 1: 125 | LD A, (checksum) 126 | ADD A, (HL) 127 | LD (checksum), A 128 | DEC DE 129 | INC HL 130 | LD A, D ; check if de == 0 131 | OR E 132 | JR NZ, 1b ; more bytes to send 133 | 134 | JR senddata ; next buffer read from disk 135 | 136 | sendchecksum: 137 | LD A, (checksum) 138 | NEG ; calculate two's complement 139 | RST.LIL $10 140 | 141 | PUSH HL 142 | PUSH DE 143 | PUSH BC 144 | 145 | LD A, 0 146 | LD SP, IX 147 | POP IX 148 | RET 149 | 150 | .data 151 | checksum: 152 | .db 0 153 | filehandle: 154 | .db 0 155 | filesize: 156 | .d24 0 157 | end 158 | -------------------------------------------------------------------------------- /src/flash.h: -------------------------------------------------------------------------------- 1 | #ifndef FLASH_H 2 | #define FLASH_H 3 | 4 | #define BUFFER1 0x50000 5 | #define BUFFER2 0x70000 6 | #define FLASHSIZE 0x20000 // 128KB 7 | 8 | #define PAGESIZE 1024 9 | #define FLASHPAGES 128 10 | #define FLASHSTART 0x0 11 | #define BLOCKSIZE 16384 12 | 13 | #include 14 | 15 | extern void enableFlashKeyRegister(void); 16 | extern void lockFlashKeyRegister(void); 17 | extern void fastmemcpy(uint24_t destination, uint24_t source, uint24_t size); 18 | extern void reset(void); 19 | extern void startVDPupdate(uint8_t filehandle, uint24_t filesize); 20 | 21 | #endif //FLASH_H 22 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Title: Agon firmware upgrade utility 3 | * Author: Jeroen Venema 4 | * Created: 17/12/2022 5 | * Last Updated: 13/04/2025 6 | * 7 | * Modinfo: 8 | * 17/12/2022: Initial version 9 | * 05/04/2022: Changed timer to 5sec at reset. 10 | * Sends cls just before reset 11 | * 07/06/2023: Included faster crc32, by Leigh Brown 12 | * 14/10/2023: VDP update code, MOS update rewritten for simplicity 13 | * 02/11/2023: Batched mode, rewrite of UI 14 | * 13/04/2025: Ported to agondev 15 | * 22/04/2025: echoVDP now asks for screen dimensions specifically 16 | * Added DEBUG options to debug updating VDP 17 | */ 18 | 19 | // DEBUG if set to 1: 20 | // - PortC bit position 0 upon entry to vdp_update 21 | // - PortC bit position 1 will flash during echoVDP, to show activity while the VDP isn't responding 22 | #define DEBUG 0 23 | 24 | #include "ez80f92.h" 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include "flash.h" 30 | #include "agontimer.h" 31 | #include "crc32.h" 32 | #include 33 | #include 34 | #include 35 | #include "filesize.h" 36 | 37 | #define UNLOCKMATCHLENGTH 9 38 | #define EXIT_FILENOTFOUND 4 39 | #define EXIT_INVALIDPARAMETER 19 40 | #define DEFAULT_MOSFIRMWARE "MOS.bin" 41 | #define DEFAULT_VDPFIRMWARE "firmware.bin" 42 | 43 | #define CMDUNKNOWN 0 44 | #define CMDALL 1 45 | #define CMDMOS 2 46 | #define CMDVDP 3 47 | #define CMDFORCE 4 48 | #define CMDBATCH 5 49 | 50 | int errno; // needed by standard library 51 | 52 | bool flashmos = false; 53 | char mosfilename[256]; 54 | FILE* mosfilehandle; 55 | uint32_t moscrc; 56 | bool flashvdp = false; 57 | char vdpfilename[256]; 58 | FILE* vdpfilehandle; 59 | uint32_t vdpcrc; 60 | bool optbatch = false; 61 | bool optforce = false; // No y/n user input required 62 | 63 | char message[256]; 64 | 65 | // separate putch function that doesn't rely on a running MOS firmware 66 | // UART0 initialization done by MOS firmware previously 67 | // This utility doesn't run without MOS to load it anyway 68 | int putch(int c) { 69 | while((IO(UART0_LSR) & 0x40) == 0); 70 | IO(UART0_THR) = c; 71 | return c; 72 | } 73 | 74 | void outstring(const char *str) { 75 | while(*str) { 76 | putch(*str); 77 | str++; 78 | } 79 | } 80 | 81 | void beep(unsigned int number) { 82 | while(number--) { 83 | putch(7); 84 | delayms(250); 85 | } 86 | } 87 | 88 | uint8_t getCharAt(uint16_t x, uint16_t y) { 89 | delayms(20); 90 | putch(23); 91 | putch(0); 92 | putch(131); 93 | putch(x & 0xFF); 94 | putch((x >> 8) & 0xFF); 95 | putch(y & 0xFF); 96 | putch((y >> 8) & 0xFF); 97 | delayms(100); 98 | return getsysvar_scrchar(); 99 | } 100 | 101 | bool vdp_ota_present(void) { 102 | char test[UNLOCKMATCHLENGTH]; 103 | uint16_t n; 104 | 105 | putch(23); 106 | putch(0); 107 | putch(0xA1); 108 | putch(0); 109 | outstring("unlock"); 110 | 111 | for(n = 0; n < UNLOCKMATCHLENGTH+1; n++) test[n] = getCharAt(n+8, 3); 112 | // 3 - line on-screen 113 | if(memcmp(test, "unlocked!",UNLOCKMATCHLENGTH) == 0) return true; 114 | else return false; 115 | } 116 | 117 | uint8_t mos_magicnumbers[] = {0xF3, 0xED, 0x7D, 0x5B, 0xC3}; 118 | #define MOS_MAGICLENGTH 5 119 | bool containsMosHeader(uint8_t *filestart) { 120 | uint8_t n; 121 | bool match = true; 122 | 123 | for(n = 0; n < MOS_MAGICLENGTH; n++) if(mos_magicnumbers[n] != filestart[n]) match = false; 124 | return match; 125 | } 126 | 127 | uint8_t esp32_magicnumbers[] = {0x32, 0x54, 0xCD, 0xAB}; 128 | #define ESP32_MAGICLENGTH 4 129 | #define ESP32_MAGICSTART 0x20 130 | bool containsESP32Header(uint8_t *filestart) { 131 | uint8_t n; 132 | bool match = true; 133 | 134 | filestart += ESP32_MAGICSTART; // start of ESP32 magic header 135 | for(n = 0; n < ESP32_MAGICLENGTH; n++) { 136 | if(esp32_magicnumbers[n] != filestart[n]) match = false; 137 | } 138 | return match; 139 | } 140 | 141 | void print_version(void) { 142 | outstring("Agon firmware update utility v1.9\n\r\n\r"); 143 | } 144 | 145 | void usage(void) { 146 | print_version(); 147 | outstring("Usage: FLASH [all | [mos ] [vdp ] | batch] <-f>\n\r"); 148 | } 149 | 150 | bool getResponse(void) { 151 | uint8_t response = 0; 152 | 153 | outstring("Flash firmware (y/n)?"); 154 | while((response != 'y') && (response != 'n')) response = tolower(getch()); 155 | if(response == 'n') outstring("\r\nUser abort\n\r\n\r"); 156 | else outstring("\r\n\r\n"); 157 | return response == 'y'; 158 | } 159 | 160 | void askEscapeToContinue(void) { 161 | uint8_t response = 0; 162 | 163 | outstring("Press ESC to continue"); 164 | while(response != 0x1B) response = tolower(getch()); 165 | outstring("\r\n"); 166 | } 167 | 168 | bool update_vdp(void) { 169 | uint24_t filesize; 170 | 171 | putch(12); // cls 172 | print_version(); 173 | outstring("Unlocking VDP updater...\r\n"); 174 | 175 | if(!vdp_ota_present()) { 176 | outstring(" failed - OTA not present in current VDP\r\n\r\n"); 177 | outstring("Program the VDP using Arduino / PlatformIO / esptool\r\n\r\n"); 178 | return false; 179 | } 180 | // Do actual work here 181 | outstring("Updating VDP firmware\r\n"); 182 | filesize = getFileSize(vdpfilehandle->fhandle); 183 | startVDPupdate(vdpfilehandle->fhandle, filesize); 184 | return true; 185 | } 186 | 187 | bool update_mos(char *filename) { 188 | uint32_t crcresult; 189 | uint24_t bytesread; 190 | char* ptr = (char*)BUFFER1; 191 | uint24_t counter, pagemax, lastpagebytes; 192 | uint24_t addressto,addressfrom; 193 | uint24_t filesize; 194 | int attempt; 195 | bool success = false; 196 | 197 | putch(12); // cls 198 | print_version(); 199 | 200 | outstring("Programming MOS firmware to ez80 flash...\r\n\r\n"); 201 | outstring("Reading MOS firmware"); 202 | filesize = getFileSize(mosfilehandle->fhandle); 203 | // Read file to memory 204 | crc32_initialize(); 205 | while((bytesread = fread(ptr, 1, BLOCKSIZE, mosfilehandle)) > 0) { 206 | crc32(ptr, bytesread); 207 | ptr += bytesread; 208 | putch('.'); 209 | } 210 | crcresult = crc32_finalize(); 211 | outstring("\r\n"); 212 | // Final memory check to given crc32 213 | if(crcresult != moscrc) { 214 | outstring("Error reading file to memory\r\n"); 215 | return false; 216 | } 217 | outstring("\r\n"); 218 | // Actual work here 219 | asm volatile("di"); // prohibit any access to the old MOS firmware 220 | attempt = 0; 221 | while((!success) && (attempt < 3)) { 222 | // start address in flash 223 | addressto = FLASHSTART; 224 | addressfrom = BUFFER1; 225 | // Write attempt# 226 | if(attempt > 0) { 227 | sprintf(message,"Retry attempt #%d\r\n", attempt); 228 | outstring(message); 229 | } 230 | // Unprotect and erase flash 231 | outstring("Erasing flash... "); 232 | 233 | enableFlashKeyRegister(); // unlock Flash Key Register, so we can write to the Flash Write/Erase protection registers 234 | IO(FLASH_PROT) = 0; // disable protection on all 8x16KB blocks in the flash 235 | enableFlashKeyRegister(); // will need to unlock again after previous write to the flash protection register 236 | IO(FLASH_FDIV) = 0x5F; // Ceiling(18Mhz * 5,1us) = 95, or 0x5F 237 | 238 | for(counter = 0; counter < FLASHPAGES; counter++) { 239 | IO(FLASH_PAGE) = counter; 240 | IO(FLASH_PGCTL) = 0x02; // Page erase bit enable, start erase 241 | while(IO(FLASH_PGCTL) & 0x02); // wait for completion of erase 242 | } 243 | outstring("\r\n"); 244 | 245 | // determine number of pages to write 246 | pagemax = filesize/PAGESIZE; 247 | if(filesize%PAGESIZE) {// last page has less than PAGESIZE bytes 248 | pagemax += 1; 249 | lastpagebytes = filesize%PAGESIZE; 250 | } 251 | else lastpagebytes = PAGESIZE; // normal last page 252 | 253 | // write out each page to flash 254 | for(counter = 0; counter < pagemax; counter++) { 255 | sprintf(message,"\rWriting flash page %03d/%03d", counter+1, pagemax); 256 | outstring(message); 257 | 258 | if(counter == (pagemax - 1)) // last page to write - might need to write less than PAGESIZE 259 | fastmemcpy(addressto,addressfrom,lastpagebytes); 260 | else 261 | fastmemcpy(addressto,addressfrom,PAGESIZE); 262 | 263 | addressto += PAGESIZE; 264 | addressfrom += PAGESIZE; 265 | } 266 | // lock the flash before WARM reset 267 | enableFlashKeyRegister(); // unlock Flash Key Register, so we can write to the Flash Write/Erase protection registers 268 | IO(FLASH_PROT) = 0xff; // enable protection on all 8x16KB blocks in the flash 269 | 270 | outstring("\r\nChecking CRC... "); 271 | 272 | crc32_initialize(); 273 | crc32(FLASHSTART, filesize); 274 | crcresult = crc32_finalize(); 275 | if(crcresult == moscrc) { 276 | outstring("OK\r\n"); 277 | success = true; 278 | } 279 | else { 280 | outstring("ERROR\r\n"); 281 | } 282 | attempt++; 283 | } 284 | outstring("\r\n"); 285 | return success; 286 | } 287 | 288 | void echoVDP(uint8_t value) { 289 | // Disable flowcontrol 290 | putch(23); 291 | putch(0); 292 | putch(0xF9); 293 | putch(0x01); 294 | putch(0x01); 295 | // Request general Poll 296 | putch(23); 297 | putch(0); 298 | putch(0x80); 299 | putch(value); 300 | 301 | #if defined(DEBUG) && (DEBUG == 1) 302 | IO(PC_DR) = IO(PC_DR) | 0x02; // set bit position 1 303 | delayms(150); 304 | IO(PC_DR) = IO(PC_DR) & (0x01); // everything off, except bit 0 305 | #endif 306 | 307 | // Get screen dimensions 308 | putch(23); 309 | putch(0); 310 | putch(0x86); 311 | // Wait a while before sending the next echo 312 | delayms(150); 313 | } 314 | 315 | int getCommand(const char *command) { 316 | if(memcmp(command, "all\0", 4) == 0) return CMDALL; 317 | if(memcmp(command, "mos\0", 4) == 0) return CMDMOS; 318 | if(memcmp(command, "vdp\0", 4) == 0) return CMDVDP; 319 | if(memcmp(command, "batch\0", 6) == 0) return CMDBATCH; 320 | if(memcmp(command, "-f\0", 3) == 0) return CMDFORCE; 321 | if(memcmp(command, "force\0", 6) == 0) return CMDFORCE; 322 | if(memcmp(command, "-force\0", 7) == 0) return CMDFORCE; 323 | return CMDUNKNOWN; 324 | } 325 | 326 | bool parseCommands(int argc, char *argv[]) { 327 | int argcounter; 328 | int command; 329 | 330 | argcounter = 1; 331 | while(argcounter < argc) { 332 | command = getCommand(argv[argcounter]); 333 | switch(command) { 334 | case CMDUNKNOWN: 335 | return false; 336 | break; 337 | case CMDALL: 338 | if(flashmos || flashvdp) return false; 339 | strcpy(mosfilename, DEFAULT_MOSFIRMWARE); 340 | strcpy(vdpfilename, DEFAULT_VDPFIRMWARE); 341 | flashmos = true; 342 | flashvdp = true; 343 | break; 344 | case CMDMOS: 345 | if(flashmos) return false; 346 | if((argc > (argcounter+1)) && (getCommand(argv[argcounter + 1]) == CMDUNKNOWN)) { 347 | strcpy(mosfilename, argv[argcounter + 1]); 348 | argcounter++; 349 | } 350 | else { 351 | strcpy(mosfilename, DEFAULT_MOSFIRMWARE); 352 | } 353 | flashmos = true; 354 | break; 355 | case CMDVDP: 356 | if(flashvdp) return false; 357 | if((argc > (argcounter+1)) && (getCommand(argv[argcounter + 1]) == CMDUNKNOWN)) { 358 | strcpy(vdpfilename, argv[argcounter + 1]); 359 | argcounter++; 360 | } 361 | else { 362 | strcpy(vdpfilename, DEFAULT_VDPFIRMWARE); 363 | } 364 | flashvdp = true; 365 | break; 366 | case CMDBATCH: 367 | if(optbatch) return false; 368 | optbatch = true; 369 | optforce = true; 370 | strcpy(mosfilename, DEFAULT_MOSFIRMWARE); 371 | strcpy(vdpfilename, DEFAULT_VDPFIRMWARE); 372 | flashmos = true; 373 | flashvdp = true; 374 | break; 375 | case CMDFORCE: 376 | if(optforce && !optbatch) return false; 377 | optforce = true; 378 | break; 379 | } 380 | argcounter++; 381 | } 382 | return (flashvdp || flashmos); 383 | } 384 | 385 | bool openFiles(void) { 386 | bool filesexist = true; 387 | 388 | mosfilehandle = NULL; 389 | vdpfilehandle = NULL; 390 | 391 | if(flashmos) { 392 | mosfilehandle = fopen(mosfilename, "rb"); 393 | if(!mosfilehandle) { 394 | sprintf(message,"Error opening MOS firmware \"%s\"\n\r",mosfilename); 395 | outstring(message); 396 | filesexist = false; 397 | } 398 | } 399 | if(flashvdp) { 400 | vdpfilehandle = fopen(vdpfilename, "rb"); 401 | if(!vdpfilehandle) { 402 | sprintf(message,"Error opening VDP firmware \"%s\"\n\r",vdpfilename); 403 | outstring(message); 404 | filesexist = false; 405 | if(mosfilehandle) fclose(mosfilehandle); 406 | } 407 | } 408 | return filesexist; 409 | } 410 | 411 | bool validFirmwareFiles(void) { 412 | FILE* file; 413 | uint24_t filesize; 414 | uint8_t buffer[ESP32_MAGICLENGTH + ESP32_MAGICSTART]; 415 | bool validfirmware = true; 416 | 417 | if(flashmos) { 418 | fseek(mosfilehandle, 0, SEEK_SET); 419 | fread((char *)BUFFER1, 1, MOS_MAGICLENGTH, mosfilehandle); 420 | if(!containsMosHeader((uint8_t *)BUFFER1)) { 421 | sprintf(message,"\"%s\" does not contain valid MOS ez80 startup code\r\n", mosfilename); 422 | outstring(message); 423 | validfirmware = false; 424 | } 425 | filesize = getFileSize(mosfilehandle->fhandle); 426 | if(filesize > FLASHSIZE) { 427 | sprintf(message,"\"%s\" too large for 128KB embedded flash\r\n", mosfilename); 428 | outstring(message); 429 | validfirmware = false; 430 | } 431 | fseek(mosfilehandle, 0, SEEK_SET); 432 | } 433 | if(flashvdp) { 434 | fseek(vdpfilehandle, 0, SEEK_SET); 435 | fread((char *)buffer, 1, ESP32_MAGICLENGTH + ESP32_MAGICSTART, vdpfilehandle); 436 | if(!containsESP32Header(buffer)) { 437 | sprintf(message,"\"%s\" does not contain valid ESP32 code\r\n", vdpfilename); 438 | outstring(message); 439 | validfirmware = false; 440 | } 441 | fseek(vdpfilehandle, 0, SEEK_SET); 442 | } 443 | return validfirmware; 444 | } 445 | 446 | void showCRC32(void) { 447 | if(flashmos) {sprintf(message,"MOS CRC 0x%08lX\r\n", moscrc); outstring(message);} 448 | if(flashvdp) {sprintf(message,"VDP CRC 0x%08lX\r\n", vdpcrc); outstring(message);} 449 | outstring("\r\n"); 450 | } 451 | 452 | void calculateCRC32(void) { 453 | uint24_t bytesread; 454 | char* ptr; 455 | 456 | moscrc = 0; 457 | vdpcrc = 0; 458 | 459 | outstring("Calculating CRC"); 460 | 461 | if(flashmos) { 462 | fseek(mosfilehandle, 0, SEEK_SET); 463 | ptr = (char*)BUFFER1; 464 | crc32_initialize(); 465 | 466 | // Read file to memory 467 | while((bytesread = fread(ptr, 1, BLOCKSIZE, mosfilehandle)) > 0) { 468 | crc32(ptr, bytesread); 469 | ptr += bytesread; 470 | putch('.'); 471 | } 472 | moscrc = crc32_finalize(); 473 | fseek(mosfilehandle, 0, SEEK_SET); 474 | } 475 | if(flashvdp) { 476 | fseek(vdpfilehandle, 0, SEEK_SET); 477 | crc32_initialize(); 478 | while((bytesread = fread((char *)BUFFER1, 1, BLOCKSIZE, vdpfilehandle)) > 0) { 479 | crc32((char *)BUFFER1, bytesread); 480 | putch('.'); 481 | } 482 | vdpcrc = crc32_finalize(); 483 | fseek(vdpfilehandle, 0, SEEK_SET); 484 | } 485 | outstring("\r\n\r\n"); 486 | } 487 | 488 | int main(int argc, char * argv[]) { 489 | SYSVAR *sysvars = (SYSVAR *)mos_sysvars(); 490 | uint16_t tmp; 491 | 492 | // DEBUG PortC pin option 493 | #if defined(DEBUG) && (DEBUG == 1) // Set all PortC pins to output && to 0 494 | IO(PC_DDR) = 0; 495 | IO(PC_DR) = 0; 496 | #endif 497 | 498 | // All checks 499 | if(argc == 1) { 500 | usage(); 501 | return 0; 502 | } 503 | if(!parseCommands(argc, argv)) { 504 | usage(); 505 | return EXIT_INVALIDPARAMETER; 506 | } 507 | 508 | if(!openFiles()) return EXIT_FILENOTFOUND; 509 | if(!validFirmwareFiles()) { 510 | return EXIT_INVALIDPARAMETER; 511 | } 512 | 513 | putch(12); 514 | print_version(); 515 | calculateCRC32(); 516 | // Skip showing CRC32 and user input when 'silent' is requested 517 | if(!optforce) { 518 | putch(12); 519 | print_version(); 520 | showCRC32(); 521 | if(!getResponse()) return 0; 522 | } 523 | if(optbatch) beep(1); 524 | 525 | if(flashvdp) { 526 | while(sysvars->scrHeight == 0); // wait for 1st feedback from VDP 527 | tmp = sysvars->scrHeight; 528 | sysvars->scrHeight = 0; 529 | 530 | #if defined(DEBUG) && (DEBUG == 1) // Start update indicator, set PortC bit 0 to 1 531 | IO(PC_DR) = 1; 532 | #endif 533 | 534 | if(update_vdp()) { 535 | while(sysvars->scrHeight == 0) { 536 | echoVDP(1); 537 | }; 538 | if(optbatch) beep(2); 539 | } 540 | else { 541 | if(!optforce && flashmos) { 542 | askEscapeToContinue(); 543 | sysvars->scrHeight = tmp; 544 | } 545 | } 546 | fclose(vdpfilehandle); 547 | 548 | #if defined(DEBUG) && (DEBUG == 1) // Stop update indicator (VDP is responsive), set PortC bit 0 to 0 549 | IO(PC_DR) = 0; 550 | #endif 551 | } 552 | 553 | if(flashmos) { 554 | if(update_mos(mosfilename)) { 555 | outstring("\r\nDone\r\n\r\n"); 556 | if(optbatch) { 557 | outstring("Press reset button"); 558 | beep(3); 559 | while(1); // don't repeatedly run this command batched (autoexec.txt) 560 | } 561 | else { 562 | outstring("System reset in "); 563 | for(int n = 3; n > 0; n--) { 564 | sprintf(message,"%d...", n); 565 | outstring(message); 566 | delayms(1000); 567 | } 568 | reset(); 569 | } 570 | } 571 | else { 572 | outstring("\r\nMultiple errors occured during flash write.\r\n"); 573 | outstring("Bare-metal recovery required.\r\n"); 574 | while(1); // No live MOS to return to 575 | } 576 | } 577 | return 0; 578 | } 579 | 580 | --------------------------------------------------------------------------------