├── C256Mgr └── empty_folder ├── .gitignore ├── bulk.csv ├── tools ├── sh │ ├── runfnx │ ├── uploadsrec │ ├── runpgx │ ├── runpgz │ ├── bulk_flash │ ├── pcopy │ ├── revision │ ├── flash_sector │ ├── upload │ ├── flash │ ├── uploadhex │ ├── lookup │ ├── dump │ └── deref ├── cmd │ ├── runfnx.bat │ ├── pcopy.bat │ ├── runpgx.bat │ ├── runpgz.bat │ ├── uploadsrec.bat │ ├── revision.bat │ ├── bulk_flash.bat │ ├── flash_sector.bat │ ├── upload.bat │ ├── flash.bat │ ├── uploadhex.bat │ ├── dump.bat │ ├── lookup.bat │ └── deref.bat └── README.md ├── FoenixMgr.zip ├── test ├── samplepgx │ ├── sample.pgx │ └── sample_pgx.asm └── samplepgz │ ├── sample.pgz │ └── sample.asm ├── foenixmgr.ini ├── bundle.sh ├── requirements.txt ├── bundle.bat ├── fm.sh ├── fm.bat ├── FoenixMgr ├── constants.py ├── wdc.py ├── intelhex.py ├── srec.py ├── pgx.py ├── foenix_config.py ├── pgz.py ├── foenix.py └── fnxmgr.py ├── LICENSE ├── help.txt └── Readme.md /C256Mgr/empty_folder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.pyc 3 | kernel.bin 4 | bounce.s19 5 | -------------------------------------------------------------------------------- /bulk.csv: -------------------------------------------------------------------------------- 1 | 01,01.bin 2 | 02,02.bin 3 | 03,03.bin 4 | 3e,3e.bin 5 | 3f,3f.bin 6 | -------------------------------------------------------------------------------- /tools/sh/runfnx: -------------------------------------------------------------------------------- 1 | 2 | python $FOENIXMGR/FoenixMgr/fnxmgr.py --upload $1 3 | -------------------------------------------------------------------------------- /FoenixMgr.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pweingar/FoenixMgr/HEAD/FoenixMgr.zip -------------------------------------------------------------------------------- /tools/cmd/runfnx.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | python %FOENIXMGR%\FoenixMgr\fnxmgr.py --upload %1 3 | -------------------------------------------------------------------------------- /test/samplepgx/sample.pgx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pweingar/FoenixMgr/HEAD/test/samplepgx/sample.pgx -------------------------------------------------------------------------------- /test/samplepgz/sample.pgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pweingar/FoenixMgr/HEAD/test/samplepgz/sample.pgz -------------------------------------------------------------------------------- /foenixmgr.ini: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | port=COM3 3 | labels=sample.lbl 4 | flash_address=380000 5 | chunk_size=1024 6 | cpu=65816 7 | -------------------------------------------------------------------------------- /tools/sh/uploadsrec: -------------------------------------------------------------------------------- 1 | # Upload an SREC file to the Foenix 2 | 3 | python $FOENIXMGR/FoenixMgr/fnxmgr.py --upload-srec $1 4 | -------------------------------------------------------------------------------- /tools/sh/runpgx: -------------------------------------------------------------------------------- 1 | 2 | # Upload and run a PGX file on the Foenix 3 | 4 | python $FOENIXMGR/FoenixMgr/fnxmgr.py --run-pgx $1 5 | -------------------------------------------------------------------------------- /tools/sh/runpgz: -------------------------------------------------------------------------------- 1 | 2 | # Upload and run a PGZ file on the Foenix 3 | 4 | python $FOENIXMGR/FoenixMgr/fnxmgr.py --run-pgz $1 5 | -------------------------------------------------------------------------------- /tools/sh/bulk_flash: -------------------------------------------------------------------------------- 1 | # Reprogram flash sectors based on a CSV file 2 | 3 | python $FOENIXMGR/FoenixMgr/fnxmgr.py --flash-bulk $1 4 | -------------------------------------------------------------------------------- /tools/sh/pcopy: -------------------------------------------------------------------------------- 1 | 2 | # Copy a file from the PC to the SDCARD on Foenix 3 | 4 | python $FOENIXMGR/FoenixMgr/fnxmgr.py --copy $1 5 | -------------------------------------------------------------------------------- /tools/sh/revision: -------------------------------------------------------------------------------- 1 | # Get the revision code of the Foenix's debug interface 2 | python $FOENIXMGR/FoenixMgr/fnxmgr.py --revision 3 | -------------------------------------------------------------------------------- /tools/cmd/pcopy.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM Copy a file from PC to the SDCARD on Foenix 3 | 4 | python %FOENIXMGR%\FoenixMgr\fnxmgr.py --copy %1 5 | -------------------------------------------------------------------------------- /tools/cmd/runpgx.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM Upload and run a PGX file on the Foenix 3 | 4 | python %FOENIXMGR%\FoenixMgr\fnxmgr.py --run-pgx %1 5 | -------------------------------------------------------------------------------- /tools/cmd/runpgz.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM Upload and run a PGZ file on the Foenix 3 | 4 | python %FOENIXMGR%\FoenixMgr\fnxmgr.py --run-pgz %1 5 | -------------------------------------------------------------------------------- /tools/cmd/uploadsrec.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM Upload an SREC file to the Foenix 3 | 4 | python %FOENIXMGR%\FoenixMgr\fnxmgr.py --upload-srec %1 5 | -------------------------------------------------------------------------------- /tools/cmd/revision.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM Get the revision code of the Foenix's debug interface 3 | python %FOENIXMGR%\FoenixMgr\fnxmgr.py --revision 4 | -------------------------------------------------------------------------------- /tools/cmd/bulk_flash.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM Reprogram flash sectors based on a CSV file 3 | 4 | python %FOENIXMGR%\FoenixMgr\fnxmgr.py --flash-bulk %1 5 | -------------------------------------------------------------------------------- /tools/sh/flash_sector: -------------------------------------------------------------------------------- 1 | # Reprogram an 8KB sector of the flash memory on the Foenix 2 | 3 | python $FOENIXMGR/FoenixMgr/fnxmgr.py --target $1 --flash-sector $2 --flash $3 4 | -------------------------------------------------------------------------------- /tools/cmd/flash_sector.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM Reprogram a flash sector on the Foenix 3 | 4 | python %FOENIXMGR%\FoenixMgr\fnxmgr.py --target %1 --flash-sector %2 --flash %3 5 | -------------------------------------------------------------------------------- /tools/sh/upload: -------------------------------------------------------------------------------- 1 | # Upload a binary file to the Foenix 2 | 3 | if [ -z $2 ] 4 | then 5 | python $FOENIXMGR/FoenixMgr/fnxmgr.py --binary $1 6 | else 7 | python $FOENIXMGR/FoenixMgr/fnxmgr.py --binary $1 --address $2 8 | fi 9 | -------------------------------------------------------------------------------- /tools/README.md: -------------------------------------------------------------------------------- 1 | # Tools 2 | 3 | This directory contains script files to help simplify calling into the FoenixMGR. The `cmd` directory contains Windows/DOS style batch files. The `sh` directory contains Unix/Linux style shell scripts. 4 | -------------------------------------------------------------------------------- /tools/sh/flash: -------------------------------------------------------------------------------- 1 | # Reprogram the flash memory on the Foenix 2 | 3 | if [ -z $2 ] 4 | then 5 | python $FOENIXMGR/FoenixMgr/fnxmgr.py --flash $1 6 | else 7 | python $FOENIXMGR/FoenixMgr/fnxmgr.py --flash $1 --address $2 8 | fi 9 | -------------------------------------------------------------------------------- /tools/sh/uploadhex: -------------------------------------------------------------------------------- 1 | # Upload an Intel HEX file to the Foenix 2 | 3 | if [ -z $2 ] 4 | then 5 | python $FOENIXMGR/FoenixMgr/fnxmgr.py --upload $1 6 | else 7 | python $FOENIXMGR/FoenixMgr/fnxmgr.py --upload $1 --address $2 8 | fi 9 | -------------------------------------------------------------------------------- /tools/cmd/upload.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM Upload a binary file to the Foenix 3 | 4 | if [%2%]==[] ( 5 | python %FOENIXMGR%\FoenixMgr\fnxmgr.py --binary %1 6 | ) ELSE ( 7 | python %FOENIXMGR%\FoenixMgr\fnxmgr.py --binary %1 --address %2 8 | ) 9 | -------------------------------------------------------------------------------- /tools/cmd/flash.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM Reprogram the flash memory on the Foenix 3 | 4 | if [%2%]==[] ( 5 | python %FOENIXMGR%\FoenixMgr\fnxmgr.py --flash %1 6 | ) ELSE ( 7 | python %FOENIXMGR%\FoenixMgr\fnxmgr.py --flash %1 --address %2 8 | ) 9 | -------------------------------------------------------------------------------- /tools/cmd/uploadhex.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM Upload a binary file to the Foenix 3 | 4 | if [%2%]==[] ( 5 | python %FOENIXMGR%\FoenixMgr\fnxmgr.py --upload %1 6 | ) ELSE ( 7 | python %FOENIXMGR%\FoenixMgr\fnxmgr.py --upload %1 --address %2 8 | ) 9 | -------------------------------------------------------------------------------- /tools/sh/lookup: -------------------------------------------------------------------------------- 1 | # Print the contents of memory at the labeled address 2 | # usage: lookup {label} 3 | 4 | if [ -z $2 ] 5 | python $FOENIXMGR/FoenixMgr/fnxmgr.py --lookup $1 6 | else 7 | python $FOENIXMGR/FoenixMgr/fnxmgr.py --lookup $1 --count $2 8 | fi 9 | -------------------------------------------------------------------------------- /bundle.sh: -------------------------------------------------------------------------------- 1 | # Bundle up the python scripts into a ZIP file for easier use 2 | 3 | cd FoenixMgr 4 | cp fnxmgr.py __main__.py 5 | zip FoenixMgr.zip *.py 6 | cd .. 7 | cp FoenixMgr/FoenixMgr.zip FoenixMgr.zip 8 | rm FoenixMgr/FoenixMgr.zip 9 | rm FoenixMgr/__main__.py 10 | -------------------------------------------------------------------------------- /tools/sh/dump: -------------------------------------------------------------------------------- 1 | # Print the contents of memory 2 | # usage: dump {start address} [{byte count}] 3 | 4 | if [ -z $2 ] 5 | then 6 | python $FOENIXMGR/FoenixMgr/fnxmgr.py --dump $1 7 | else 8 | python $FOENIXMGR/FoenixMgr/fnxmgr.py --dump $1 --count $2 9 | fi 10 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | asn1crypto>=0.24.0 2 | bitstring>=3.1.9 3 | cffi>=1.11.5 4 | Click>=7.0 5 | cryptography>=2.4.2 6 | ecdsa>=0.17.0 7 | esptool>=3.1 8 | idna>=2.8 9 | Pillow>=6.2.1 10 | pycparser>=2.19 11 | pyOpenSSL>=18.0.0 12 | pyserial>=3.4 13 | reedsolo>=1.5.4 14 | six>=1.12.0 15 | -------------------------------------------------------------------------------- /tools/cmd/dump.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM Print the contents of memory 3 | REM usage: dump {start address} [{byte count}] 4 | 5 | if [%2%]==[] ( 6 | python %FOENIXMGR%\FoenixMgr\fnxmgr.py --dump %1 7 | ) ELSE ( 8 | python %FOENIXMGR%\FoenixMgr\fnxmgr.py --dump %1 --count %2 9 | ) 10 | -------------------------------------------------------------------------------- /tools/sh/deref: -------------------------------------------------------------------------------- 1 | # Print the contents of memory, given the label of a pointer to the start address 2 | # usage: deref {label} 3 | if [ -z $2 ] 4 | then 5 | python $FOENIXMGR/FoenixMgr/fnxmgr.py --deref $1 6 | else 7 | python $FOENIXMGR/FoenixMgr/fnxmgr.py --deref $1 --count $2 8 | fi 9 | -------------------------------------------------------------------------------- /bundle.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | rem Bundle up the python scripts into a ZIP file for easier use 3 | 4 | cd FoenixMgr 5 | copy /y fnxmgr.py __main__.py 6 | zip FoenixMgr.zip *.py 7 | cd .. 8 | copy /y FoenixMgr\FoenixMgr.zip FoenixMgr.zip 9 | del FoenixMgr\FoenixMgr.zip 10 | del FoenixMgr\__main__.py -------------------------------------------------------------------------------- /tools/cmd/lookup.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM Print the contents of memory at the labeled address 3 | REM usage: lookup {label} 4 | 5 | if [%2%]==[] ( 6 | python %FOENIXMGR%\FoenixMgr\fnxmgr.py --lookup %1 7 | ) ELSE ( 8 | python %FOENIXMGR%\FoenixMgr\fnxmgr.py --lookup %1 --count %2 9 | ) 10 | -------------------------------------------------------------------------------- /tools/cmd/deref.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM Print the contents of memory, given the label of a pointer to the start address 3 | REM usage: deref {label} 4 | if [%2%]==[] ( 5 | python %FOENIXMGR%\FoenixMgr\fnxmgr.py --deref %1 6 | ) ELSE ( 7 | python %FOENIXMGR%\FoenixMgr\fnxmgr.py --deref %1 --count %2 8 | ) 9 | -------------------------------------------------------------------------------- /fm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DIR=$(realpath $(dirname "$0")) 4 | 5 | # Create virtual environment and install requirments. 6 | # This is used to avoid breaking host python environment. 7 | if [ ! -d $DIR/.venv ]; then 8 | python -m venv $DIR/.venv 9 | source $DIR/.venv/bin/activate 10 | pip install -r $DIR/requirements.txt 11 | deactivate 12 | fi 13 | 14 | # Launch script in virtual environment. 15 | source $DIR/.venv/bin/activate 16 | python $DIR/FoenixMgr/fnxmgr.py $@ 17 | deactivate 18 | -------------------------------------------------------------------------------- /test/samplepgx/sample_pgx.asm: -------------------------------------------------------------------------------- 1 | .cpu "65816" 2 | 3 | * = $004000 4 | 5 | .text 'PGX', $01 6 | .dword code1 7 | 8 | ; 9 | ; Code section 10 | ; 11 | code1: clc 12 | xce 13 | sep #$30 14 | .as 15 | .xs 16 | lda #'B' 17 | sta $afa000 ; Write A to the first cell of the text matrix 18 | lda #$f1 19 | sta $afc000 ; Write "white on black" to the first cell of color memory 20 | 21 | dummy: nop 22 | bra dummy 23 | -------------------------------------------------------------------------------- /fm.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | REM Get the directory of the script. 4 | SET DIR=%~dp0 5 | 6 | REM Remove the trailing backslash if it exists (Windows-specific). 7 | IF "%DIR:~-1%" == "\" SET DIR=%DIR:~0,-1% 8 | 9 | REM Create virtual environment and install requirements. 10 | IF NOT EXIST "%DIR%\.venv" ( 11 | python -m venv "%DIR%\.venv" 12 | CALL "%DIR%\.venv\Scripts\activate.bat" 13 | pip install -r "%DIR%\requirements.txt" 14 | CALL deactivate 15 | ) 16 | 17 | REM Launch script in virtual environment. 18 | CALL "%DIR%\.venv\Scripts\activate.bat" 19 | python "%DIR%\PhoenixMgr\fnxmgr.py" %* 20 | CALL deactivate 21 | 22 | -------------------------------------------------------------------------------- /test/samplepgz/sample.asm: -------------------------------------------------------------------------------- 1 | .cpu "65816" 2 | 3 | * = $004000 4 | 5 | .byte 'Z' 6 | 7 | ; 8 | ; Code section 9 | ; 10 | .long code1 11 | .long code2-code1 12 | code1: clc 13 | xce 14 | sep #$30 15 | .as 16 | .xs 17 | lda #'a' 18 | sta $afa000 ; Write A to the first cell of the text matrix 19 | lda #$f1 20 | sta $afc000 ; Write "white on black" to the first cell of color memory 21 | 22 | dummy: nop 23 | bra dummy 24 | 25 | code2: 26 | 27 | ; 28 | ; Start Address section 29 | ; 30 | .long code1 ; Start address 31 | .long 0 ; 0 data is in this section 32 | -------------------------------------------------------------------------------- /FoenixMgr/constants.py: -------------------------------------------------------------------------------- 1 | CMD_READ_MEM = 0x00 2 | CMD_WRITE_MEM = 0x01 3 | CMD_PROGRAM_FLASH = 0x10 4 | CMD_ERASE_FLASH = 0x11 5 | CMD_ERASE_SECTOR = 0x12 6 | CMD_PROGRAM_SECTOR = 0x13 7 | CMD_STOP_CPU = 0x20 8 | CMD_START_CPU = 0x21 9 | CMD_ENTER_DEBUG = 0x80 10 | CMD_EXIT_DEBUG = 0x81 11 | CMD_BOOT_RAM = 0x90 12 | CMD_BOOT_FLASH = 0x91 13 | CMD_REVISION = 0xFE 14 | 15 | DELAY_ERASE_SECTOR = 1 # Number of seconds to wait after issueing an ERASE_SECTOR command 16 | DELAY_PROGRAM_SECTOR = 2 # Number of seconds to wait after issueing an PROGRAM_SECTOR command 17 | 18 | BOOT_SRC_RAM = 0x00 # For F256jr Rev A -- boot from RAM 19 | BOOT_SRC_FLASH = 0x01 # For F256jr Rev A -- boot from Flash 20 | 21 | REQUEST_SYNC_BYTE = 0x55 22 | RESPONSE_SYNC_BYTE = 0xAA 23 | 24 | PGX_CPU_65816 = 0x01 25 | PGX_CPU_680X0 = 0x02 26 | PGX_CPU_65C02 = 0x03 27 | 28 | PGX_OFF_SIG_START = 0 29 | PGX_OFF_SIG_END = 3 30 | PGX_OFF_VERSION = 3 31 | PGX_OFF_ADDR_START = 4 32 | PGX_OFF_ADDR_END = 8 33 | PGX_OFF_DATA = 8 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /FoenixMgr/wdc.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | ## 4 | # See: http://www.westerndesigncenter.com/wdc/datasheets/Assembler_Linker.pdf 5 | # page 37 6 | # 7 | # Initial byte 'Z' as signature. 8 | # 9 | # Then for each block: 10 | # 3 byte address 11 | # 3 byte length 12 | # length bytes of data 13 | # 14 | # The final block has an address and length of 0. 15 | # 16 | 17 | class WdcBinFile: 18 | """Reads information from WDCTools BIN formated file""" 19 | data = 0 20 | handler = 0 21 | 22 | def __init__(self): 23 | pass 24 | 25 | def open(self, filename): 26 | self.data = Path(filename).read_bytes() 27 | 28 | def close(self): 29 | self.data = [] 30 | 31 | def set_handler(self, proc): 32 | self.handler = proc 33 | 34 | def read_blocks(self): 35 | offset = 1 36 | while offset < len(self.data): 37 | (addr, block, offset) = self.__read_block(self.data, offset) 38 | if addr > 0: 39 | self.handler(addr, block) 40 | 41 | def __read_block(self, data, offset): 42 | addr = int.from_bytes(data[offset:offset+3], byteorder='little', signed=False) 43 | size = int.from_bytes(data[offset+3:offset+6], byteorder='little', signed=False) 44 | if addr == 0: 45 | return (0, [], offset+6) 46 | block = data[offset+6:offset+6+size] 47 | return (addr, block, offset+6+size) 48 | 49 | 50 | -------------------------------------------------------------------------------- /FoenixMgr/intelhex.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | class HexFile: 4 | """Read information from an Intel Hex file.""" 5 | 6 | file = 0 7 | base_address = 0 8 | handler = 0 9 | 10 | def __init__(self): 11 | self.file = 0 12 | self.base_address = 0 13 | self.handler = 0 14 | 15 | def open(self, filename): 16 | self.file = open(filename, 'r') 17 | 18 | def set_handler(self, proc): 19 | self.handler = proc 20 | 21 | def close(self): 22 | self.file.close() 23 | 24 | def read_lines(self): 25 | line = self.file.readline() 26 | while line: 27 | self.parse_line(line) 28 | line = self.file.readline() 29 | 30 | def parse_line(self, line): 31 | m = re.match("^:([0-9a-fA-F]{2})([0-9a-fA-F]{4})([0-9a-fA-F]{2})([0-9a-fA-F]*)([0-9a-fA-F]{2})", line) 32 | size = int(m.group(1), 16) 33 | address = int(m.group(2), 16) 34 | code = int(m.group(3), 16) 35 | data = m.group(4) 36 | crc = int(m.group(5), 16) 37 | if code == 0: 38 | if self.handler: 39 | # print('Sending record to {:X}'.format(self.base_address + address)) 40 | self.handler(self.base_address + address, data) 41 | 42 | elif code == 2: 43 | # Set the base address based on a segment 44 | self.base_address = int(data, 16) << 4 # shity 80x86 real mode addressing : take the address an do *16 to get the final address 45 | # print('Setting base address to {:X}'.format(self.base_address)) 46 | 47 | elif code == 4: 48 | # Set the base address given on address[31..16] 49 | self.base_address = int(data, 16) << 16 50 | # print('Setting base address to {:X}'.format(self.base_address)) 51 | 52 | 53 | -------------------------------------------------------------------------------- /FoenixMgr/srec.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | class SRECFile: 4 | """Read information from a Motorola SREC file.""" 5 | 6 | file = 0 7 | handler = 0 8 | 9 | def __init__(self): 10 | self.file = 0 11 | self.handler = 0 12 | 13 | def open(self, filename): 14 | self.file = open(filename, 'r') 15 | 16 | def set_handler(self, proc): 17 | self.handler = proc 18 | 19 | def close(self): 20 | self.file.close() 21 | 22 | def read_lines(self): 23 | line = self.file.readline() 24 | while line: 25 | self.parse_line(line) 26 | line = self.file.readline() 27 | 28 | def parse_line(self, line): 29 | # Format of line will vary based on the type, so let's get the type first 30 | m = re.match("^S([0-9a-fA-F])([0-9a-fA-F]+)", line) 31 | code = int(m.group(1), 16) 32 | hex_digits = m.group(2) 33 | 34 | # Codes... 35 | # 0 = Comment/header 36 | # 1 = Data with 16-bit address 37 | # 2 = Data with 24-bit address 38 | # 3 = Data with 32-bit address 39 | # 4 = Reserved 40 | # 5 = 16-bit record count 41 | # 6 = 24-bit record count 42 | # 7 = 32-bit start address 43 | # 8 = 24-bit start address 44 | # 9 = 16-bit start address 45 | # 46 | # This code will ignore all records by 1, 2, and 3 47 | 48 | if code == 1: 49 | # Unpack a record with a 16-bit address 50 | m2 = re.match("^([0-9a-fA-F]{2})([0-9a-fA-F]{4})([0-9a-fA-F]*)([0-9a-fA-F]{2})", hex_digits) 51 | count = int(m2.group(1), 16) 52 | address = int(m2.group(2), 16) 53 | data = m2.group(3) 54 | crc = int(m2.group(4), 16) 55 | self.handler(address, data) 56 | 57 | elif code == 2: 58 | # Unpack a record with a 24-bit address 59 | m2 = re.match("^([0-9a-fA-F]{2})([0-9a-fA-F]{6})([0-9a-fA-F]*)([0-9a-fA-F]{2})", hex_digits) 60 | count = int(m2.group(1), 16) 61 | address = int(m2.group(2), 16) 62 | data = m2.group(3) 63 | crc = int(m2.group(4), 16) 64 | self.handler(address, data) 65 | 66 | elif code == 3: 67 | # Unpack a record with a 32-bit address 68 | m2 = re.match("^([0-9a-fA-F]{2})([0-9a-fA-F]{8})([0-9a-fA-F]*)([0-9a-fA-F]{2})", hex_digits) 69 | count = int(m2.group(1), 16) 70 | address = int(m2.group(2), 16) 71 | data = m2.group(3) 72 | crc = int(m2.group(4), 16) 73 | self.handler(address, data) 74 | -------------------------------------------------------------------------------- /help.txt: -------------------------------------------------------------------------------- 1 | usage: foenixmgr.zip [-h] [--port PORT] [--list-ports] 2 | [--label-file LABEL_FILE] [--count COUNT] 3 | [--dump ADDRESS] [--deref LABEL] [--lookup LABEL] 4 | [--revision] [--flash BINARY FILE] 5 | [--flash-sector NUMBER] [--flash-bulk CSV FILE] [--erase] 6 | [--binary BINARY FILE] [--copy COPY FILE] 7 | [--address ADDRESS] [--upload HEX FILE] 8 | [--upload-wdc BINARY FILE] [--run-pgz PGZ FILE] 9 | [--run-pgx PGX FILE] [--upload-srec SREC FILE] 10 | [--boot STRING] [--target STRING] 11 | [--tcp-bridge HOST:PORT] [--stop] [--start] [--quiet] 12 | 13 | Manage the C256 Foenix through its debug port. 14 | 15 | optional arguments: 16 | -h, --help show this help message and exit 17 | --port PORT Specify the serial port to use to access the C256 18 | debug port. 19 | --list-ports List available serial ports. 20 | --label-file LABEL_FILE 21 | Specify the label file to use for dereference and 22 | lookup 23 | --count COUNT the number of bytes to read 24 | --dump ADDRESS Read memory from the C256's memory and display it. 25 | --deref LABEL Lookup the address stored at LABEL and display the 26 | memory there. 27 | --lookup LABEL Display the memory starting at the address indicated 28 | by the label. 29 | --revision Display the revision code of the debug interface. 30 | --flash BINARY FILE Attempt to reprogram the flash using the binary file 31 | provided. 32 | --flash-sector NUMBER 33 | Sector number of the 8KB sector of flash to program. 34 | --flash-bulk CSV FILE 35 | Program multiple flash sectors based on a CSV file 36 | --erase Erase all of flash memory. Can be used by itself or 37 | with flash-bulk 38 | --binary BINARY FILE Upload a binary file to the C256's RAM. 39 | --copy COPY FILE Copy a file to F256jr SDCARD. 40 | --address ADDRESS Provide the starting address of the memory block to 41 | use in flashing memory. 42 | --upload HEX FILE Upload an Intel HEX file. 43 | --upload-wdc BINARY FILE 44 | Upload a WDCTools binary hex file. (WDCLN.EXE -HZ) 45 | --run-pgz PGZ FILE Upload and run a PGZ binary file. 46 | --run-pgx PGX FILE Upload and run a PGX binary file. 47 | --upload-srec SREC FILE 48 | Upload a Motorola SREC hex file. 49 | --boot STRING For F256k: set boot source: RAM or FLASH 50 | --target STRING Set the target machine 51 | --tcp-bridge HOST:PORT 52 | Setup a TCP-serial bridge, listening on HOST:PORT and 53 | relaying messages to the Foenix via the configured 54 | serial port 55 | --stop Stop the CPU from processing instructions (F256 only). 56 | --start Restart the CPU after a STOP (F256 only). 57 | --quiet Suppress some printed messages. 58 | 59 | -------------------------------------------------------------------------------- /FoenixMgr/pgx.py: -------------------------------------------------------------------------------- 1 | # 2 | # Loader for PGX files 3 | # 4 | 5 | import sys 6 | import constants 7 | import foenix_config 8 | from pathlib import Path 9 | 10 | class PGXBinFile: 11 | """Reads information from PGX formated file""" 12 | address_size = 0 13 | data = 0 14 | handler = 0 15 | cpu = "" 16 | config = 0 17 | 18 | def __init__(self): 19 | self.config = foenix_config.FoenixConfig() 20 | self.cpu = self.config.cpu() 21 | pass 22 | 23 | def open(self, filename): 24 | self.data = Path(filename).read_bytes() 25 | 26 | def close(self): 27 | self.data = [] 28 | 29 | def set_handler(self, proc): 30 | self.handler = proc 31 | 32 | def read_blocks(self): 33 | # Check the signature 34 | signature = self.data[constants.PGX_OFF_SIG_START:constants.PGX_OFF_SIG_END] 35 | if signature != b'PGX': 36 | print("Bad PGX signature: {}".format(signature)) 37 | sys.exit(1) 38 | 39 | # Check version tag 40 | pgx_ver = (self.data[constants.PGX_OFF_VERSION] >> 4) & 0x0f 41 | if pgx_ver > 0: 42 | print("Unsupported PGX version.") 43 | sys.exit(1) 44 | 45 | # Check the CPU tag 46 | pgx_cpu = self.data[constants.PGX_OFF_VERSION] & 0x0f 47 | if pgx_cpu == constants.PGX_CPU_65816: 48 | if self.cpu != "65816": 49 | print("PGX is built for the wrong CPU.") 50 | sys.exit(1) 51 | elif pgx_cpu == constants.PGX_CPU_65C02: 52 | if self.cpu != "65C02" and self.cpu != "65c02": 53 | print("PGX is built for the wrong CPU.") 54 | sys.exit(1) 55 | elif pgx_cpu == constants.PGX_CPU_680X0: 56 | if not self.config.cpu_is_680X0(): 57 | print("PGX is built for the wrong CPU.") 58 | sys.exit(1) 59 | else: 60 | print("Unsupported PGX CPU.") 61 | sys.exit(1) 62 | 63 | # Get the target address of the PGX file 64 | addr = int.from_bytes(self.data[constants.PGX_OFF_ADDR_START:constants.PGX_OFF_ADDR_END], byteorder='little', signed=False) 65 | 66 | # Get the actual block of data 67 | block = self.data[constants.PGX_OFF_DATA:] 68 | 69 | # Send the data to the address 70 | self.handler(addr, block) 71 | 72 | if self.cpu == "65816": 73 | if addr & 0xff0000 != 0: 74 | # Startup code is not in bank 0, so we need a stub... 75 | # clc 76 | # xce 77 | # jml
78 | self.handler(0xff80, bytes([0x18, 0xfb, 0x5c, addr & 0xff, (addr >> 8) & 0xff, (addr >> 16) & 0xff])) 79 | # Point the reset vector to our reset routine 80 | self.handler(0xfffc, bytes([0x80, 0xff])) 81 | else: 82 | # Startup code is in bank 0, so we can just jump to it directly 83 | # Point the reset vector to the start address 84 | self.handler(0xfffc, bytes([addr & 0xff, (addr >> 8) & 0xff])) 85 | 86 | elif (self.cpu == "65c02") or (self.cpu == "65C02"): 87 | # Point the reset vector to our reset routine 88 | # if using microkernel, this won't do anything 89 | # but if we're not, it's nice 90 | self.handler(0xfffc, bytes([addr & 0xff, (addr >> 8) & 0xff])) 91 | # "CROSSDEV" springboard if we're using the microkernel 92 | # and you have the crossdev tools installed 93 | self.handler(0x0080, bytes([0x43,0x52,0x4f,0x53,0x53,0x44,0x45,0x56])) 94 | self.handler(0x0088, bytes([addr & 0xff, (addr >> 8) & 0xff])) 95 | # Pass 0 to the kernel args extlen, at least until someone implements argument passing 96 | self.handler(0x00FA, bytes([0x00, 0x00])) 97 | 98 | elif self.config.cpu_is_680X0(): 99 | # Point the reset vector to our reset routine 100 | self.handler(0x00000004, bytes([(addr>>24) & 0xff, (addr>>16) & 0xff, (addr>>8) & 0xff, addr & 0xff])) 101 | -------------------------------------------------------------------------------- /FoenixMgr/foenix_config.py: -------------------------------------------------------------------------------- 1 | import configparser 2 | import os 3 | import sys 4 | 5 | class FoenixConfig: 6 | """Configuration data for the FoenixMgr. Exposes the foenix.ini file.""" 7 | 8 | def __init__(self): 9 | """Attempt to read and process the config file.""" 10 | config = configparser.ConfigParser() 11 | config.read(['foenixmgr.ini', os.path.expandvars('$FOENIXMGR/foenixmgr.ini'), os.path.expanduser('~/foenixmgr.ini')]) 12 | 13 | if not config.items("DEFAULT"): 14 | print("No proper foenixmgr.ini file found.") 15 | sys.exit(1) 16 | 17 | self._flash_size = int(config['DEFAULT'].get('flash_size', '524288'), 10) 18 | self._port = config['DEFAULT'].get('port', 'COM3') 19 | self._chunk_size = int(config['DEFAULT'].get('chunk_size', '4096'), 10) 20 | self._data_rate = int(config['DEFAULT'].get('data_rate', '6000000'), 10) 21 | self._label_file = config['DEFAULT'].get('labels', 'basic8') 22 | self._address = config['DEFAULT'].get('address', '380000') 23 | self._timeout = int(config['DEFAULT'].get('timeout', '60'), 10) 24 | self._cpu = config['DEFAULT'].get('cpu', '65c02') 25 | 26 | def set_target(self, machine_name): 27 | """Set the name of the target machine.""" 28 | 29 | machine_name = machine_name.lower() 30 | 31 | self._flash_page_size = 0 32 | self._flash_sector_size = 0 33 | self._ram_size = 8 34 | 35 | if machine_name == "fnx1591": 36 | self._flash_page_size = 8 37 | self._ram_size = 8 38 | self._flash_sector_size = 32 39 | 40 | elif machine_name == "f256k" or machine_name == "f256jr": 41 | self._flash_page_size = 8 42 | self._ram_size = 8 43 | self._flash_sector_size = 8 44 | 45 | def flash_size(self): 46 | """Return the required size of the flash binary file in bytes.""" 47 | return self._flash_size 48 | 49 | def chunk_size(self): 50 | """Return the size of the data packet that gets sent over the debug port.""" 51 | return self._chunk_size 52 | 53 | def data_rate(self): 54 | """Return the data rate in bits per second that the serial port should use.""" 55 | return self._data_rate 56 | 57 | def port(self): 58 | """Return the name of the port to use to connect to the debug port.""" 59 | return self._port 60 | 61 | def label_file(self): 62 | """Return the name of the label file.""" 63 | return self._label_file 64 | 65 | def address(self): 66 | """Return the address (in hex) to use in loading the flash file.""" 67 | return self._address 68 | 69 | def timeout(self): 70 | """Return the timeout to allow for serial communications (in seconds).""" 71 | return self._timeout 72 | 73 | def cpu(self): 74 | """Return the CPU of the target machine.""" 75 | return self._cpu 76 | 77 | def cpu_is_680X0(self): 78 | """Return true if the CPU is a Motorola 680X0""" 79 | if self.cpu() == "m68k" or self.cpu() == "68000" or self.cpu() == "68040" or self.cpu() == "68060": 80 | return True 81 | else: 82 | return False 83 | 84 | def cpu_is_m68k_32(self): 85 | """Return true if the CPU is a 32-bit Motorola 680X0""" 86 | if self.cpu() == "68040" or self.cpu() == "68060": 87 | return True 88 | else: 89 | return False 90 | 91 | def flash_page_size(self): 92 | """ 93 | Return the size of the largest block of memory that can be copied to flash at one time (in KB). 94 | If zero, the machine does not support paged programming of the flash memory. 95 | """ 96 | return self._flash_page_size 97 | 98 | def flash_sector_size(self): 99 | """ 100 | Return the size of the flash sector (in KB). 101 | If zero, the machine does not support paged programming of the flash memory. 102 | """ 103 | return self._flash_sector_size 104 | 105 | def ram_size(self): 106 | """ 107 | Number of bytes in RAM that can be used to write to flash (in KB) 108 | """ 109 | return self._ram_size -------------------------------------------------------------------------------- /FoenixMgr/pgz.py: -------------------------------------------------------------------------------- 1 | # 2 | # Loader for PGZ files 3 | # 4 | 5 | import foenix_config 6 | from pathlib import Path 7 | 8 | class PGZBinFile: 9 | """Reads information from PGZ formated file""" 10 | address_size = 0 11 | data = 0 12 | handler = 0 13 | cpu = "" 14 | config = 0 15 | 16 | def __init__(self): 17 | self.config = foenix_config.FoenixConfig() 18 | self.cpu = self.config.cpu() 19 | pass 20 | 21 | def open(self, filename): 22 | self.data = Path(filename).read_bytes() 23 | 24 | def close(self): 25 | self.data = [] 26 | 27 | def set_handler(self, proc): 28 | self.handler = proc 29 | 30 | def read_blocks(self): 31 | # Header is lower case z: address and size fields are four bytes long 32 | print("Initial {:x}".format(self.data[0])) 33 | 34 | if self.data[0] == 0x7a: 35 | self.address_size = 4 36 | elif self.data[0] == 0x5a: 37 | # Header is upper case Z: address and size fields are three bytes long 38 | self.address_size = 3 39 | print("Header Size 3") 40 | else: 41 | print("Error: bad PGZ file: {}.".format(self.data.hex())) 42 | exit(1) 43 | 44 | offset = 1 45 | while offset < len(self.data): 46 | (addr, block, offset) = self.__read_block(self.data, offset) 47 | if (len(block) == 0) and (addr > 0): 48 | # We have a start address block... register it with the Foenix so it gets called at reset 49 | if self.cpu == "65816": 50 | print("CPU 65816") 51 | if addr & 0xff0000 != 0: 52 | # Startup code is not in bank 0, so we need a stub... 53 | # clc 54 | # xce 55 | # jml
56 | self.handler(0xff80, bytes([0x18, 0xfb, 0x5c, addr & 0xff, (addr >> 8) & 0xff, (addr >> 16) & 0xff])) 57 | # Point the reset vector to our reset routine 58 | self.handler(0xfffc, bytes([0x80, 0xff])) 59 | else: 60 | # Startup code is in bank 0, so we can just jump to it directly 61 | # Point the reset vector to the start address 62 | self.handler(0xfffc, bytes([addr & 0xff, (addr >> 8) & 0xff])) 63 | 64 | elif (self.cpu == "65c02") or (self.cpu == "65C02"): 65 | print("CPU 65c02") 66 | # Point the reset vector to our reset routine 67 | # if using microkernel, this won't do anything 68 | # but if we're not, it's nice 69 | self.handler(0xfffc, bytes([addr & 0xff, (addr >> 8) & 0xff])) 70 | # "CROSSDEV" springboard if we're using the microkernel 71 | # and you have the crossdev tools installed 72 | self.handler(0x0080, bytes([0x43,0x52,0x4f,0x53,0x53,0x44,0x45,0x56])) 73 | self.handler(0x0088, bytes([addr & 0xff, (addr >> 8) & 0xff])) 74 | # Pass 0 to the kernel args extlen, at least until someone implements argument passing 75 | self.handler(0x00FA, bytes([0x00, 0x00])) 76 | 77 | elif self.config.cpu_is_680X0(): 78 | print("CPU m68k") 79 | # Point the reset vector to our reset routine 80 | print("Starting address {:x}".format(addr)) 81 | self.handler(4, bytes([(addr>>24) & 0xff, (addr>>16) & 0xff, (addr>>8) & 0xff, addr & 0xff])) 82 | 83 | elif addr > 0: 84 | # JGA Support for large blocks 85 | block_size = 1024 86 | if len(block) > block_size: 87 | #print("do block sizes =",block_size) 88 | total_length = len(block) 89 | chunk_offset = 0 90 | while total_length > 0: 91 | #print(chunk_offset, block_size) 92 | self.handler(addr+chunk_offset, block[chunk_offset:chunk_offset+block_size]) 93 | total_length -= block_size 94 | chunk_offset += block_size 95 | if total_length < block_size: 96 | block_size = total_length 97 | else: 98 | self.handler(addr, block) 99 | 100 | else: 101 | return 102 | 103 | def __read_block(self, data, offset): 104 | # PGZ blocks have the format (each character is a byte): 105 | # A..AS..Sd...d, or 106 | # Where A..A is the destination address in little endian format 107 | # S..S is the size of the block in bytes in little endian format 108 | # d..d is the data block 109 | # The number of bytes in the address and size fields is determined by address_size 110 | addr = int.from_bytes(data[offset:offset+self.address_size], byteorder='little', signed=False) 111 | offset += self.address_size 112 | 113 | size = int.from_bytes(data[offset:offset+self.address_size], byteorder='little', signed=False) 114 | offset += self.address_size 115 | 116 | print(addr, size) 117 | 118 | if addr == 0: 119 | return (0, [], offset) 120 | 121 | block = data[offset:offset+size] 122 | return (addr, block, offset+size) 123 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # FoenixMgr: A command line tool for connecting to the Foenix debug port 2 | 3 | FoenixMgr can be used to send binary and Intel HEX files to the Foenix or to read memory from the board. It is a python script that uses the debug port protocol to control the Foenix remotely. Currently, the debug port supports seven actions: stop the processor, restart the processor, read memory, write memory, erase the flash memory, program the flash memory, and retrieve a version code. 4 | 5 | ## Installation 6 | 7 | NOTE: To run FoenixMgr, you will need Python3 installed on your system. There are a couple of required libraries listed in `requirements.txt`. You can use Python's `pip` utility to install the required packages automatically using the command line: 8 | 9 | `pip install -r requirements.txt` 10 | 11 | FoenixMgr has been recoded from the C256Mgr original to be more flexible. One of the new features is that only one copy of the scripts are necessary on the system, where the old C256Mgr really needed to be included with every project. To get this to work, once you have copied the FoenixMgr project to your system, you will need to define an environment variable `FOENIXMGR`, which is the directory that contains this repository. The batch file tools will look for the Python scripts using that variable, and the Python scripts will look for the configuration file in that folder as well (although it can look for the configuration file in other folders as well... see below). 12 | 13 | ## Configuration File 14 | 15 | The core of the tool is the FoenixMgr Python script. It takes an `foenixmgr.ini` file with three initialization tags: 16 | 17 | * `port`, which is the name of the serial port to use for connecting to the debug port 18 | * `labels`, which is the path to the 64TASS LBL file for this project. 19 | * `address`, which is the default address (in hex) for any binary transfers. 20 | * `flash_size`, which is the required size of a flash binary file 21 | * `chunk_size`, which is the size of the data packet to send over the debug port 22 | * `data_rate`, which is the bit rate to use in communicating over the debug port 23 | * `timeout`, which is the amount of time (in seconds) to allow before timing out the serial connection 24 | * `cpu`, which is the name of the CPU on the target Foenix (currently: 65c02, 65816, m68k). This setting is used by the `run-pgz` option to determine what kind of bootstrap loader needs to be inserted to actually start the executable. 25 | 26 | The setting `port`, `labels`, and `address` can be over-ridden by command line options. 27 | 28 | FoenixMgr will look for the configuration file in your current directory, in your user home directory, or in the directory indicated by the environment variable `FOENIXMGR`. 29 | 30 | ## Target Machine Names 31 | 32 | As the range of Foenix machines increases, this script is becoming more and more unwieldy. To try to help manage this, a new target option has been added. This option will be used to specify machine-specific configurations in such a way that a user can use the script with multiple machines without having to edit the configuration file every time. Currently, it is only being used for the flash sector option, and the only machine names supported are `f256jr`, `f256jr`, and `fnx1591`... the only machines that support this capability. As an example of its use, the following command will send the file `dos1581.bin` to the FNX1591 drive on COM6, and upload it to flash sector 1 (strictly speaking, that is not flash sector 6, but the 6th 32 KB slot in the FNX1591's flash memory). 33 | 34 | ``` 35 | python FoenixMgr\fnxmgr.py --port COM6 --target fnx1591 --flash-sector 1 --flash dos1581.bin 36 | ``` 37 | 38 | ## Command Line Arguments 39 | 40 | To list the available serial ports on your computer: 41 | `FoenixMgr/fnxmgr --list-ports` 42 | 43 | To get the revision code of the Foenix's debug port: 44 | `FoenixMgr/fnxmgr --port --revision` 45 | 46 | To send a hex file: 47 | `FoenixMgr/fnxmgr --port --upload ` 48 | 49 | To send a binary file to a location in Foenix RAM: 50 | `FoenixMgr/fnxmgr --port --binary --address
` 51 | 52 | To reflash the Foenix flash memory (NOTE: the binary file must be exactly `flash_size` long, and the address is used as a temporary location in Foenix RAM to store the data to be flashed): 53 | `FoenixMgr/fnxmgr --port --flash --address
` 54 | 55 | To reflash a particular 8KB sector of flash memory (currently F256jr, F256k, and FNX1591 only): 56 | `FoenixMgr/fnxmgr --target --port --flash-sector=01 --flash ` 57 | 58 | To set a boot source (F256jr RevA boards and F256k): 59 | `FoenixMgr/fnxmgr --port --boot=RAM|FLASH` 60 | 61 | To display memory: 62 | `FoenixMgr/fnxmgr --port --dump
--count ` 63 | 64 | For the F256jr and F256k only, if you would like to stop the CPU from processing instructions, you can use the `stop` command: 65 | `FoenixMgr/fnxmgr --port --stop` 66 | Once that is issued, the machine will halt, a special indicator file called `f256.stp` will be created in your current directory, and the other FoenixMgr commands can be executed without resetting the CPU. To restart the CPU, you can issue the `start` command, which will start the machine back up but without resetting the processor: `FoenixMgr/fnxmgr --port --start` The indicator file created by `stop` will be removed by `start`. 67 | 68 | If you have a 64TASS label file (`*.lbl`), you can provide that as an option and display the contents of a memory location by its label: 69 | `FoenixMgr/fnxmgr --port --label-file