├── .github └── workflows │ └── build.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── core ├── AboveBoard.cpp ├── AboveBoard.h ├── CGACard.cpp ├── CGACard.h ├── CGAFont.h ├── CMakeLists.txt ├── CPU.cpp ├── CPU.h ├── FIFO.h ├── FixedDiskAdapter.cpp ├── FixedDiskAdapter.h ├── FloppyController.cpp ├── FloppyController.h ├── Scancode.h ├── SerialMouse.cpp ├── SerialMouse.h ├── System.cpp └── System.h ├── font.py ├── minsdl ├── CMakeLists.txt ├── DiskIO.cpp ├── DiskIO.h └── Main.cpp ├── pico-shared ├── BIOS.h ├── CMakeLists.txt ├── DiskIO.cpp ├── DiskIO.h ├── Filesystem.cpp ├── Storage.cpp ├── Storage.h ├── USBHID.cpp ├── fatfs │ ├── diskio.h │ ├── ff.c │ ├── ff.h │ ├── ffsystem.c │ └── ffunicode.c ├── ffconf.h ├── spi.pio └── tusb_config.h ├── pico2 ├── CMakeLists.txt ├── Display.cpp ├── Display.h ├── Main.cpp ├── config.h ├── psram.c └── psram.h ├── pico_sdk_import.cmake ├── picovision ├── CMakeLists.txt ├── Display.cpp ├── Display.h ├── Main.cpp ├── config.h └── picovision_import.cmake └── test ├── CMakeLists.txt └── Main.cpp /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' # only run on branches 7 | pull_request: 8 | release: 9 | types: [created] 10 | 11 | env: 12 | BUILD_TYPE: Release 13 | EM_VERSION: 2.0.18 # Emscripten version 14 | EM_CACHE_FOLDER: 'emsdk-cache' # Cache for Emscripten libs 15 | 16 | jobs: 17 | 18 | build: 19 | 20 | name: ${{matrix.name}} 21 | strategy: 22 | matrix: 23 | include: 24 | - os: ubuntu-24.04 25 | name: Linux 26 | release-suffix: LIN64 27 | apt-packages: libsdl2-dev 28 | 29 | - os: windows-latest 30 | name: Windows 31 | release-suffix: WIN64 32 | 33 | - os: ubuntu-24.04 34 | name: PicoVision 35 | cache-key: picovision 36 | release-suffix: PicoVision 37 | cmake-args: -DPICO_SDK_PATH=$GITHUB_WORKSPACE/pico-sdk -DPICO_BOARD=pico_w 38 | apt-packages: gcc-arm-none-eabi libnewlib-arm-none-eabi libstdc++-arm-none-eabi-newlib 39 | is-pico: true 40 | 41 | - os: ubuntu-24.04 42 | name: RP2350 Stamp XL 43 | cache-key: stamp-xl 44 | release-suffix: RP2350StampXL 45 | cmake-args: -DPICO_SDK_PATH=$GITHUB_WORKSPACE/pico-sdk -DPICO_BOARD=solderparty_rp2350_stamp_xl 46 | apt-packages: gcc-arm-none-eabi libnewlib-arm-none-eabi libstdc++-arm-none-eabi-newlib 47 | is-pico: true 48 | 49 | runs-on: ${{matrix.os}} 50 | 51 | env: 52 | RELEASE_FILE: ${{github.event.repository.name}}-${{github.event.release.tag_name}}-${{matrix.release-suffix}} 53 | 54 | steps: 55 | # Check out the main repo 56 | - name: Checkout 57 | uses: actions/checkout@v4 58 | with: 59 | path: main 60 | 61 | # pico sdk 62 | - name: Checkout Pico SDK 63 | if: matrix.is-pico 64 | uses: actions/checkout@v4 65 | with: 66 | repository: raspberrypi/pico-sdk 67 | path: pico-sdk 68 | submodules: true 69 | 70 | # PicoVision needs the RAM driver and the firmware 71 | - name: Checkout PicoVision 72 | if: matrix.name == 'PicoVision' 73 | uses: actions/checkout@v4 74 | with: 75 | repository: pimoroni/picovision 76 | ref: 03df7694ed4fb396c1d12adf90d0150ada6baedc 77 | path: picovision 78 | 79 | # Linux dependencies 80 | - name: Install Linux deps 81 | if: runner.os == 'Linux' 82 | run: | 83 | sudo apt update && sudo apt install ${{matrix.apt-packages}} 84 | pip3 install 32blit 85 | 86 | # could use this for the linux build too, but it's currently a bit broken there 87 | - uses: libsdl-org/setup-sdl@main 88 | id: sdl 89 | if: runner.os == 'Windows' 90 | with: 91 | install-linux-dependencies: true 92 | version: 2-latest 93 | build-type: Release 94 | 95 | # grab a BIOS 96 | # some things don't quite work with this one, but at least we can compile 97 | - name: Download BIOS 98 | if: matrix.is-pico 99 | working-directory: main 100 | run: wget -O bios-xt.rom https://github.com/skiselev/8088_bios/blob/v1.0.0/binaries/bios-xt.bin 101 | 102 | # Set up the cmake build environment 103 | - name: Create Build Environment 104 | run: cmake -E make_directory ${{runner.workspace}}/main/build 105 | 106 | # Ask cmake to build the makefiles 107 | - name: Configure CMake 108 | shell: bash 109 | working-directory: ${{runner.workspace}}/main/build 110 | run: ${{matrix.cmake-prefix}} cmake $GITHUB_WORKSPACE/main -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCPACK_PACKAGE_FILE_NAME=${{env.RELEASE_FILE}} -DCMAKE_INSTALL_PREFIX=$RUNNER_WORKSPACE/main/build/install ${{matrix.cmake-args}} 111 | 112 | # And then run the build itself 113 | - name: Build 114 | working-directory: ${{runner.workspace}}/main/build 115 | shell: bash 116 | run: | 117 | cmake --build . --config $BUILD_TYPE -j 2 118 | 119 | - name: Prepare Artifact 120 | shell: bash 121 | run: cmake --build $RUNNER_WORKSPACE/main/build --config $BUILD_TYPE --target install 122 | 123 | - name: Upload Artifact 124 | uses: actions/upload-artifact@v4 125 | with: 126 | name: ${{github.event.repository.name}}-${{github.sha}}-${{matrix.name}} 127 | path: ${{runner.workspace}}/main/build/install -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | build.*/ 3 | .vscode 4 | roms 5 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13.0) 2 | 3 | set(CMAKE_CXX_STANDARD 17) 4 | set(CMAKE_CXX_EXTENSIONS OFF) 5 | 6 | if(PICO_SDK_PATH OR PICO_SDK_FETCH_FROM_GIT) 7 | include(pico_sdk_import.cmake) 8 | endif() 9 | 10 | project(ProbablyAverageComputorEmulator) 11 | 12 | if(PICO_SDK_PATH) # set by import file 13 | set(IS_PICO true) 14 | pico_sdk_init() 15 | 16 | if(PICO_PLATFORM STREQUAL "rp2350-arm-s") 17 | set(IS_PICO2 true) 18 | endif() 19 | endif() 20 | 21 | if(MSVC) 22 | add_compile_options("/W4" "/wd4244" "/wd4324" "/wd4458" "/wd4100") 23 | else() 24 | add_compile_options("-Wall" "-Wextra" "-Wdouble-promotion" "-Wno-unused-parameter") 25 | endif() 26 | 27 | include(CMakeDependentOption) 28 | cmake_dependent_option(BUILD_SDL "Build minimal SDL UI" ON "NOT IS_PICO" OFF) 29 | cmake_dependent_option(BUILD_PICOVISION "Build PicoVision UI" ON "IS_PICO AND NOT IS_PICO2" OFF) 30 | cmake_dependent_option(BUILD_PICO2 "Build Pico 2 UI" ON "IS_PICO2" OFF) 31 | 32 | add_subdirectory(core) 33 | 34 | if(IS_PICO) 35 | add_subdirectory(pico-shared) 36 | endif() 37 | 38 | if(BUILD_SDL) 39 | add_subdirectory(minsdl) 40 | endif() 41 | 42 | if(BUILD_PICOVISION) 43 | add_subdirectory(picovision) 44 | endif() 45 | 46 | if(BUILD_PICO2) 47 | add_subdirectory(pico2) 48 | endif() 49 | 50 | # setup release packages 51 | set(PROJECT_DISTRIBS LICENSE README.md) 52 | install (FILES ${PROJECT_DISTRIBS} DESTINATION .) 53 | set (CPACK_INCLUDE_TOPLEVEL_DIRECTORY OFF) 54 | set (CPACK_GENERATOR "ZIP" "TGZ") 55 | include (CPack) 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Charlie Birks 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Probably Average Computer Emulator 2 | 3 | ## Features 4 | 5 | - Mostly complete 8088 CPU core 6 | - Basic chipset support (PIC/PIT/PPI), DMA is mostly missing so disk support is a bit of a hack] 7 | - Keyboard input 8 | - Colour Graphics Adapter 9 | - Some floppy controller support 10 | - Some fixed disk adapter support 11 | - A basic serial mouse 12 | - An "Above Board" for up to 8MB of expanded memory (TESTAB says it's a "Matched Memory Classic") 13 | 14 | ## BIOS 15 | 16 | A few BIOS files are required: 17 | - `bios-xt.rom` - _Theoretically_ any XT-compatible BIOS. If using the original IBM BIOS, this is both ROMs merged together. 18 | - `fixed-disk-bios.rom` - The BIOS for the IBM Fixed Disk Adapter, required to emulate it. (Optional if you don't want a hard drive) 19 | 20 | The location of these files depends on the frontend being used. 21 | 22 | ## SDL2 23 | 24 | For running on... a PC, the only dependency is SDL2. 25 | 26 | Supports up to four floppy drives and two hard drives. The BIOS files should be placed next to the executable. 27 | 28 | ### Building 29 | 30 | ``` 31 | cmake -B build -DCMAKE_BUILD_TYPE=Release . 32 | cmake --build build 33 | ``` 34 | 35 | ### Command Line Options 36 | 37 | - `--turbo` - Run as fast as possible 38 | - `--bios name.rom` - Specify an alternate BIOS file 39 | - `--floppyN name.img` Specify an image file for floppy drive N (0-3) 40 | - `--floppy-next name.img` Specify an image file to be loaded in floppy drive 0 later, can be used multiple times (RCTRL+RSHIFT+f cycles through) 41 | - `--fixedN name.img` Specify an image file for fixed/hard disk N (0-1) 42 | 43 | For example: 44 | ``` 45 | PACE_SDL --fixed0 hd0.img --floppy-next disk1.img --floppy-next disk2.img 46 | ``` 47 | would boot from `hd0.img` and allow installing something from the two floppy images later. 48 | 49 | ## PicoVision 50 | 51 | The more interesting frontend, depends on the pico-sdk. 52 | 53 | Currently only supports a single hard disk, loaded from `hd0.img` on the SD card. Keyboard/mouse input is supported through USB HID. The BIOS files should be placed at the root of the repository before building. 54 | 55 | Supports the full 640k of memory and 6MB of expanded memory through paging 16k blocks in and out of the PicoVision's PSRAMs. 192k of memory is kept in the Pico's RAM at once, software that accesses a lot of RAM frequently may cause display glitches as I have to force a wait for vsync to write the other PSRAM. (If possible, I try to flush dirty memory at the end of a frame to avoid this). 56 | 57 | ### Building 58 | 59 | ``` 60 | cmake -B build.picovision -DCMAKE_BUILD_TYPE=Release -DPICO_SDK_PATH=path/to/pico-sdk . 61 | cmake --build build.picovision 62 | ``` 63 | 64 | ## "Pico 2" 65 | Theoretically any RP2350-based board with PSRAM and DVI output. Similar to the PicoVision build but without the need for paging memory. Also generally faster. 66 | 67 | ### Building (Stamp XL + Carrier) 68 | 69 | ``` 70 | cmake -B build.pico2 -DCMAKE_BUILD_TYPE=Release -DPICO_SDK_PATH=path/to/pico-sdk -DPICO_BOARD=solderparty_rp2350_stamp_xl . 71 | cmake --build build.pico2 72 | ``` 73 | -------------------------------------------------------------------------------- /core/AboveBoard.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "AboveBoard.h" 4 | 5 | // EEPROM data 6 | 7 | // word 0 is related to bus/timings?, serial port(i, s) and parallel port(p) 8 | // ?????iis sspp???? 9 | // 10 | // ii 0-3 map to IRQ2,5,4,3 11 | // 12 | // sss = 000 = none 13 | // sss = 001 = 2E8 14 | // sss = 011 = 2F8 15 | // sss = 101 = 3E8 16 | // sss = 111 = 3F8 17 | // 18 | // pp = 00 = none 19 | // pp = 01 = 378 20 | // pp = 10 = 278 21 | 22 | // word 1 seems to be base addr(B), backfill(b) and extended ram(e,E) 23 | // bb?BBBee eeeEEEEE 24 | // base addr 0-7 map to 208,218,248,258,???,2A8,2B8,2E8 25 | // bb = 00 = none 26 | // bb = 11 = 512-640 27 | 28 | // word 2 gets set to FFFF a lot 29 | 30 | // word 3 only gets set if 8-bit bus? 31 | 32 | // word 6 is usually FFFF 33 | 34 | // words 8-18 are the serial number with the high byte as the inverse of the low byte 35 | // word 19 is a checksum of the serial number 36 | // set to "DaftRAM1234" here 37 | 38 | // word 63 needs to be AAxx, xx is possibly related to bus settings 39 | 40 | AboveBoard::AboveBoard(System &sys) : sys(sys) 41 | { 42 | // "base address" can be one of 208, 218, 248, 258, 2A8, 2B8 or 2E8 43 | // the page mapping registers seem to be at xx0-xx7 though 44 | // this is configured in the EEPROM 45 | sys.addIODevice(0x3F0, 0x250, 0, this); 46 | } 47 | 48 | unsigned int AboveBoard::remapMemoryBlockFromWindow(unsigned int block) 49 | { 50 | // hopefully all the * 16k / 16k gets optimised out... 51 | auto addr = block * System::getMemoryBlockSize(); 52 | 53 | if(addr < 0xC0000 || addr >= 0x100000) 54 | return block; 55 | 56 | int index = (addr - 0xC0000) / 0x4000; 57 | 58 | if(!(pageMapping[index] & 0x80)) 59 | return block; 60 | 61 | auto abPage = (pageMapping[index] & 0x7F) | (pageMapping[index] & 0x300) >> 1; 62 | 63 | auto mappedAddr = System::getNumMemoryBlocks() * System::getMemoryBlockSize() + abPage * (16 * 1024); 64 | 65 | return mappedAddr / System::getMemoryBlockSize(); 66 | } 67 | 68 | unsigned int AboveBoard::remapMemoryBlockToWindow(unsigned int block) 69 | { 70 | auto addr = block * System::getMemoryBlockSize(); 71 | 72 | if(addr < System::getNumMemoryBlocks() * System::getMemoryBlockSize()) 73 | return block; 74 | 75 | addr -= System::getNumMemoryBlocks() * System::getMemoryBlockSize(); 76 | 77 | auto abPage = addr / (16 * 1024); 78 | 79 | // adjust to internal format 80 | abPage = (abPage & 0x7F) | 0x80 | (abPage & 0x180) << 1; 81 | 82 | uint32_t windowAddr = 0xC0000; 83 | 84 | for(auto &mapping : pageMapping) 85 | { 86 | if(mapping == abPage) 87 | break; 88 | 89 | windowAddr += 0x4000; 90 | } 91 | 92 | return windowAddr / System::getMemoryBlockSize(); 93 | } 94 | 95 | uint8_t AboveBoard::read(uint16_t addr) 96 | { 97 | if((addr & 0x300F) <= 8) 98 | { 99 | // mapping readback 100 | auto page = ((addr & 0xF) + 6) << 16 | (addr & 0xC000); 101 | int index = (page - 0xC0000) / 0x4000; 102 | 103 | if(index >= 0 && index < 16) 104 | return pageMapping[index]; 105 | } 106 | 107 | switch(addr) 108 | { 109 | case 0x259: 110 | // part of board detection 111 | // | 0x18 is "Matched Memory Classic" (if we return | 0x18 for 025F below) 112 | // | 0x20 seems to mean the board is configured for a 16-bit bus 113 | return 0x18; 114 | 115 | case 0x25F: 116 | // software expects bottom three bits to read back as +3 from what was written 117 | // some of the other bits determine board type 118 | // | 0x18 is "Above Board Plus 8" or "Matched Memory Classic" 119 | // | 0x10 is "Above Board Plus" 120 | // ! 0x08 is invalid? (EMM.SYS fails) 121 | // | 0x00 is also "Above Board Plus"? 122 | return (detectF & 0xE0) | ((detectF + 3) & 7) | 0x18; 123 | 124 | // EEPROM access 125 | case 0xC25C: 126 | return eepromData & 0xFF; 127 | case 0xC25D: 128 | return eepromData >> 8; 129 | 130 | default: 131 | printf("AB R %04X @~%04X\n", addr, sys.getCPU().reg(CPU::Reg16::IP)); 132 | } 133 | return 0xFF; 134 | } 135 | 136 | void AboveBoard::write(uint16_t addr, uint8_t data) 137 | { 138 | if((addr & 0xF) <= 8) 139 | { 140 | // page mapping registers 141 | auto page = ((addr & 0xF) + 6) << 16 | (addr & 0xC000); 142 | 143 | // don't map (or more importantly, UNMAP) pages we don't control 144 | if(page < 0xC0000) 145 | return; 146 | 147 | int bit = (page - 0xC0000) / 0x4000; 148 | 149 | // keep the upper bits as well 150 | pageMapping[bit] = data | (addr & 0x3000) >> 4; 151 | 152 | // register is too small for the last 4, assume enabled? 153 | if(bit < 8 && !(pageMask & (1 << bit))) 154 | return; 155 | 156 | if(data & 0x80) 157 | { 158 | // the upper bits are in the address 159 | auto index = (data & 0x7F) | (addr & 0x3000) >> 5; 160 | 161 | size_t offset = index * 16 * 1024; 162 | 163 | // remap to after CPU address space 164 | auto mapOffset = offset + System::getNumMemoryBlocks() * System::getMemoryBlockSize(); 165 | 166 | // try to map memory 167 | auto memReqCb = sys.getMemoryRequestCallback(); 168 | if(memReqCb) 169 | sys.addMemory(page, 16 * 1024, memReqCb(mapOffset / System::getMemoryBlockSize())); 170 | } 171 | else 172 | { 173 | sys.removeMemory(page / System::getMemoryBlockSize()); 174 | } 175 | 176 | return; 177 | } 178 | 179 | switch(addr) 180 | { 181 | case 0x25F: 182 | detectF = data; 183 | break; 184 | 185 | case 0x125F: 186 | // looks like an enable mask for pages C000-DC00? 187 | printf("AB page mask %02X\n", data); 188 | pageMask = data; 189 | break; 190 | 191 | // EEPROM access 192 | case 0xC25C: 193 | eepromData = (eepromData & 0xFF00) | data; 194 | break; 195 | case 0xC25D: 196 | eepromData = (eepromData & 0xFF) | data << 8; 197 | break; 198 | case 0xC25E: 199 | { 200 | int cmd = data >> 6; 201 | int addr = data & 0x3F; 202 | 203 | if(cmd == 2) // read 204 | eepromData = eeprom[addr]; 205 | else if(cmd == 3) // write 206 | eeprom[addr] = eepromData; 207 | else 208 | printf("AB EEPROM %i? %02X\n", cmd, addr); 209 | 210 | break; 211 | } 212 | 213 | default: 214 | printf("AB W %04X = %02X @~%04X:%04X\n", addr, data, sys.getCPU().reg(CPU::Reg16::CS), sys.getCPU().reg(CPU::Reg16::IP)); 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /core/AboveBoard.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "System.h" 3 | 4 | class AboveBoard final : public IODevice 5 | { 6 | public: 7 | AboveBoard(System &sys); 8 | 9 | unsigned int remapMemoryBlockFromWindow(unsigned int block); 10 | unsigned int remapMemoryBlockToWindow(unsigned int block); 11 | 12 | uint8_t read(uint16_t addr) override; 13 | void write(uint16_t addr, uint8_t data) override; 14 | 15 | void updateForInterrupts() override {} 16 | int getCyclesToNextInterrupt(uint32_t cycleCount) override {return 0;} 17 | 18 | private: 19 | System &sys; 20 | 21 | uint8_t detectF; 22 | 23 | uint8_t pageMask = 0; 24 | 25 | uint16_t eeprom[64] { 26 | 0x000D, 0x0C00, 0xFFFF, 0x07FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 27 | 0xBB44, 0x9E61, 0x9966, 0x8B74, 0xAD52, 0xBE41, 0xB24D, 0xCE31, 28 | 0xCD32, 0xCC33, 0xCB34, 0x5CBA, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 29 | 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 30 | 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 31 | 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 32 | 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 33 | 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xAA01, 34 | }; 35 | uint16_t eepromData; 36 | 37 | uint16_t pageMapping[16]; 38 | }; -------------------------------------------------------------------------------- /core/CGACard.cpp: -------------------------------------------------------------------------------- 1 | #include "CGACard.h" 2 | 3 | #include "CGAFont.h" 4 | 5 | CGACard::CGACard(System &sys) : sys(sys) 6 | { 7 | sys.addMemory(0xB8000, sizeof(ram), ram); 8 | sys.addMemory(0xBC000, sizeof(ram), ram); // mirror 9 | sys.addIODevice(0x3F0, 0x3D0, 0, this); 10 | } 11 | 12 | bool CGACard::isInVBlank() const 13 | { 14 | int charHeight = regs[9/*max char scan*/] + 1; 15 | int vBlankStart = regs[7/*v sync*/] * charHeight; 16 | return scanline >= vBlankStart; 17 | } 18 | 19 | void CGACard::setScanlineCallback(ScanlineCallback cb) 20 | { 21 | scanCb = cb; 22 | } 23 | 24 | void CGACard::update() 25 | { 26 | auto elapsed = sys.getCPU().getCycleCount() - lastUpdateCycle; 27 | 28 | elapsed *= 3; // system clock 29 | 30 | // 80-col mode uses full system clock, other modes use half 31 | if(!(mode & (1 << 0))) 32 | elapsed /= 2; 33 | 34 | // FIXME: this loses a cycle sometimes in 40-col mode 35 | lastUpdateCycle = sys.getCPU().getCycleCount(); 36 | 37 | int lineClocks = (regs[0/*h total*/] + 1) * 8; 38 | int hDisplayed = regs[1/* h disp*/] * 8; 39 | 40 | int charHeight = regs[9/*max char scan*/] + 1; 41 | int totalLines = (regs[4/* v total*/] + 1) * charHeight + regs[5 /*v adjust*/]; 42 | int vDisplayed = regs[6/*v displayed*/] * charHeight; 43 | int vBlankStart = regs[7/*v sync*/] * charHeight; 44 | 45 | while(elapsed) 46 | { 47 | auto startCycle = scanlineCycle; 48 | auto step = std::max(UINT32_C(1), std::min(elapsed, static_cast(lineClocks) - scanlineCycle)); 49 | 50 | scanlineCycle += step; 51 | elapsed -= step; 52 | 53 | if(startCycle < hDisplayed && scanline < vDisplayed) 54 | { 55 | // in visible area, draw 56 | auto endCycle = scanlineCycle < hDisplayed ? scanlineCycle : hDisplayed; 57 | draw(startCycle, endCycle); 58 | } 59 | 60 | if(scanlineCycle >= lineClocks) 61 | { 62 | // display line 63 | if(scanline < vDisplayed && scanCb) 64 | { 65 | // hi-res gfx needs 2x width? 66 | int w = (mode & (1 << 4)) ? hDisplayed * 2 : hDisplayed; 67 | 68 | scanCb(scanlineBuf, scanline, w); 69 | } 70 | 71 | scanlineCycle = 0; 72 | scanline++; 73 | 74 | if((scanline % charHeight) == 0) 75 | curAddr += regs[1/* h disp*/] * 2; 76 | 77 | // check new scanline 78 | if(scanline >= totalLines) 79 | { 80 | scanline = 0; 81 | frame++; 82 | curAddr = (regs[12] << 8 | regs[13]) * 2; 83 | status &= ~(1 << 3); // clear vblank 84 | } 85 | else if(scanline >= vBlankStart) 86 | status |= 1 << 3; // vblank 87 | } 88 | } 89 | } 90 | 91 | uint8_t CGACard::read(uint16_t addr) 92 | { 93 | switch(addr) 94 | { 95 | case 0x3D1: 96 | case 0x3D3: 97 | case 0x3D5: // reg 98 | case 0x3D7: 99 | { 100 | // write only 101 | if(regSelect < 12) // datasheet suggests this should be 14, but that breaks HWiNFO? 102 | return 0xFF; 103 | 104 | if(regSelect < 18) 105 | return regs[regSelect]; 106 | 107 | return 0xFF; 108 | } 109 | 110 | case 0x3DA: // status 111 | { 112 | update(); 113 | 114 | int hDisplayed = regs[1/* h disp*/] * 8; 115 | 116 | int charHeight = regs[9/*max char scan*/] + 1; 117 | int vDisplayed = regs[6/*v displayed*/] * charHeight; 118 | 119 | // the low "accessible" bit is the inverse of DE 120 | bool de = scanlineCycle < hDisplayed && scanline < vDisplayed; 121 | 122 | return 0xF0 | status | (de ? 0 : 1); 123 | } 124 | } 125 | return 0xFF; 126 | } 127 | 128 | void CGACard::write(uint16_t addr, uint8_t data) 129 | { 130 | switch(addr) 131 | { 132 | case 0x3D0: 133 | case 0x3D2: 134 | case 0x3D4: // reg select 135 | case 0x3D6: 136 | { 137 | regSelect = data & 0x1F; 138 | break; 139 | } 140 | case 0x3D1: 141 | case 0x3D3: 142 | case 0x3D5: // reg 143 | case 0x3D7: 144 | { 145 | update(); 146 | if(regSelect == 12 || regSelect == 14) 147 | data &= 0x3F; 148 | 149 | if(regSelect < 18) 150 | regs[regSelect] = data; 151 | break; 152 | } 153 | 154 | case 0x3D8: // mode 155 | { 156 | update(); 157 | mode = data; 158 | break; 159 | } 160 | case 0x3D9: // colour select 161 | { 162 | update(); 163 | colSelect = data; 164 | break; 165 | } 166 | } 167 | } 168 | 169 | void CGACard::draw(int start, int end) 170 | { 171 | if(!(mode & (1 << 3))) // check enabled 172 | { 173 | for(int cycle = start; cycle < end; cycle++) 174 | scanlineBuf[cycle / 2] = 0; // black 175 | } 176 | else if(mode & (1 << 1)) 177 | { 178 | // graphics mode 179 | if(mode & (1 << 4)) 180 | { 181 | // hi-res 182 | auto addr = curAddr + ((scanline & 1) ? 0x2000 : 0); 183 | 184 | auto fg = colSelect & 0xF; 185 | 186 | int cycle = start; 187 | 188 | auto out = scanlineBuf + cycle; 189 | auto in = ram + addr + (cycle / 4); 190 | 191 | // round up 192 | if(cycle & 3) 193 | { 194 | auto data = *in++; 195 | data <<= (cycle & 3) * 2; 196 | for(; cycle & 3 && cycle < end; cycle++, data <<= 2) 197 | *out++ = (data & 0x80 ? fg : 0) | (data & 0x40 ? fg << 4 : 0); 198 | 199 | if(cycle == end) 200 | return; 201 | } 202 | 203 | // full bytes 204 | auto count = (end - cycle) / 4; 205 | while(count--) 206 | { 207 | auto data = *in++; 208 | 209 | *out++ = (data & 0x80 ? fg : 0) | (data & 0x40 ? fg << 4 : 0); 210 | *out++ = (data & 0x20 ? fg : 0) | (data & 0x10 ? fg << 4 : 0); 211 | *out++ = (data & 0x08 ? fg : 0) | (data & 0x04 ? fg << 4 : 0); 212 | *out++ = (data & 0x02 ? fg : 0) | (data & 0x01 ? fg << 4 : 0); 213 | } 214 | 215 | // remainder 216 | if(end & 3) 217 | { 218 | auto data = *in; 219 | for(int i = 0; i < (end & 7); i++, data <<= 2) 220 | *out++ = (data & 0x80 ? fg : 0) | (data & 0x40 ? fg << 4 : 0); 221 | } 222 | } 223 | else 224 | { 225 | int palIndex = (colSelect >> 5) & 1; 226 | bool bright = (colSelect & (1 << 4)); 227 | auto bg = colSelect & 0xF; 228 | 229 | auto addr = curAddr + ((scanline & 1) ? 0x2000 : 0); 230 | 231 | for(int cycle = start; cycle < end; cycle++) 232 | { 233 | auto charAddr = addr + (cycle / 4); 234 | 235 | auto data = ram[charAddr]; 236 | auto col = (data << ((cycle & 3) * 2) >> 6) & 3; 237 | 238 | if(col == 0) 239 | col = bg; 240 | else 241 | { 242 | // palette mapping is just shifting up 1 bit, palette select is the low bit 243 | // TODO: mixed palette if b/w bit set 244 | col = (col << 1) | palIndex | (bright ? 8 : 0); 245 | } 246 | 247 | if(cycle & 1) 248 | scanlineBuf[cycle / 2] |= col << 4; 249 | else 250 | scanlineBuf[cycle / 2] = col; 251 | } 252 | } 253 | } 254 | else 255 | { 256 | // text mode 257 | // assuming 8x8 chars... 258 | 259 | int charLine = scanline & 7; 260 | 261 | // check if line in cursor 262 | // for more accuracy, should toggle when reaching those lines (resulting in wrap around sometimes) 263 | // also check for blinking (handled outside 6845, 8/8 frames) 264 | bool cursorLine = (frame & 8) && charLine >= (regs[10/*cursor start*/] & 0x1F) && charLine <= (regs[11/*cursor end*/] & 0x1F); 265 | uint8_t *cursorPtr = nullptr; 266 | 267 | if(cursorLine) 268 | { 269 | uint16_t cursorAddr = regs[14] << 8 | regs[15]; 270 | 271 | // +2 because we check after incrementing 272 | // set to null if not cursor line 273 | cursorPtr = ram + cursorAddr * 2 + 2; 274 | } 275 | 276 | int cycle = start; 277 | 278 | auto out = scanlineBuf + cycle / 2; 279 | auto in = ram + curAddr + (cycle / 8) * 2; 280 | 281 | auto doSingle = [this, &out](bool cursor, uint8_t attr, uint8_t fontData, int cx) 282 | { 283 | int col; 284 | 285 | if(cursor) 286 | col = attr & 0xF; 287 | // not cursor or cursor off 288 | // blink character 289 | else if((attr & 0x80) && !(frame & 16)) 290 | col = (attr >> 4) & 7; 291 | else 292 | col = (fontData & 1 << cx) ? attr & 0xF : (attr >> 4) & 7; 293 | 294 | if(cx & 1) 295 | *out++ |= col << 4; 296 | else 297 | *out = col; 298 | }; 299 | 300 | auto lineFont = cgaFont + charLine; 301 | 302 | // round to char size 303 | if(cycle & 7) 304 | { 305 | auto ch = *in++; 306 | auto attr = *in++; 307 | auto fontData = lineFont[ch * 8]; 308 | 309 | // check if char in cursor 310 | bool cursor = in == cursorPtr; 311 | 312 | for(; cycle & 7 && cycle < end; cycle++) 313 | doSingle(cursor, attr, fontData, cycle & 7); 314 | 315 | if(cycle == end) 316 | return; 317 | } 318 | 319 | // full chars 320 | auto charCount = (end - cycle) / 8; 321 | while(charCount--) 322 | { 323 | auto ch = *in++; 324 | auto attr = *in++; 325 | 326 | auto fontData = lineFont[ch * 8]; 327 | 328 | // check if char in cursor (fg fill) 329 | if(in == cursorPtr) 330 | { 331 | out[0] = out[1] = out[2] = out[3] = (attr & 0xF) | attr << 4; 332 | out += 4; 333 | } 334 | // blink character (bg fill) 335 | // also check if char is blank and do the same 336 | else if(!fontData || ((attr & 0x80) && !(frame & 16))) 337 | { 338 | out[0] = out[1] = out[2] = out[3] = ((attr >> 4) & 7) | (attr & 0x70); 339 | out += 4; 340 | } 341 | else 342 | { 343 | for(int i = 0; i < 4; i++, fontData >>= 2) 344 | { 345 | int col0 = (fontData & 1) ? attr & 0xF : (attr >> 4) & 7; 346 | int col1 = (fontData & 2) ? attr & 0xF : (attr >> 4) & 7; 347 | 348 | *out++ = col0 | col1 << 4; 349 | } 350 | } 351 | } 352 | 353 | // remainder 354 | if(end & 7) 355 | { 356 | auto ch = *in++; 357 | auto attr = *in; 358 | auto fontData = lineFont[ch * 8]; 359 | 360 | // check if char in cursor 361 | bool cursor = in == cursorPtr; 362 | 363 | for(int i = 0; i < (end & 7); i++) 364 | doSingle(cursor, attr, fontData, i); 365 | } 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /core/CGACard.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "System.h" 3 | 4 | class CGACard final : public IODevice 5 | { 6 | public: 7 | using ScanlineCallback = void(*)(const uint8_t *data, int line, int w); 8 | 9 | CGACard(System &sys); 10 | 11 | bool isInVBlank() const; 12 | 13 | void setScanlineCallback(ScanlineCallback cb); 14 | 15 | void update(); 16 | 17 | uint8_t read(uint16_t addr) override; 18 | void write(uint16_t addr, uint8_t data) override; 19 | 20 | void updateForInterrupts() override {}; 21 | int getCyclesToNextInterrupt(uint32_t cycleCount) override {return 0;} 22 | 23 | private: 24 | void draw(int start, int end); 25 | 26 | System &sys; 27 | 28 | // 6845 registers 29 | uint8_t regSelect; 30 | uint8_t regs[18]; 31 | 32 | uint8_t mode = 0; 33 | uint8_t colSelect; 34 | uint8_t status = 0; 35 | 36 | uint32_t lastUpdateCycle = 0; 37 | uint16_t scanline = 0; 38 | uint16_t scanlineCycle = 0; 39 | uint16_t curAddr = 0; 40 | uint16_t frame = 0; 41 | uint8_t scanlineBuf[320]; 42 | 43 | uint8_t ram[16 * 1024]; // at B8000 44 | 45 | ScanlineCallback scanCb; 46 | }; -------------------------------------------------------------------------------- /core/CGAFont.h: -------------------------------------------------------------------------------- 1 | // actually a placeholder 2 | const uint8_t cgaFont[] 3 | { 4 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 5 | 0x3C, 0x42, 0xA5, 0x81, 0xA5, 0x99, 0x42, 0x3C, 6 | 0x3C, 0x7E, 0xDB, 0xFF, 0xDB, 0xE7, 0x7E, 0x3C, 7 | 0x36, 0x7F, 0x7F, 0x7F, 0x3E, 0x1C, 0x08, 0x00, 8 | 0x08, 0x1C, 0x1C, 0x3E, 0x1C, 0x1C, 0x08, 0x00, 9 | 0x1C, 0x1C, 0x08, 0x6B, 0x7F, 0x6B, 0x08, 0x00, 10 | 0x08, 0x1C, 0x3E, 0x7F, 0x7F, 0x3E, 0x08, 0x00, 11 | 0x00, 0x00, 0x18, 0x3C, 0x3C, 0x18, 0x00, 0x00, 12 | 0xFF, 0xFF, 0xE7, 0xC3, 0xC3, 0xE7, 0xFF, 0xFF, 13 | 0x00, 0x18, 0x24, 0x42, 0x42, 0x24, 0x18, 0x00, 14 | 0xFF, 0xE7, 0xDB, 0xBD, 0xBD, 0xDB, 0xE7, 0xFF, 15 | 0xF0, 0xC0, 0xA0, 0x9E, 0x11, 0x11, 0x11, 0x0E, 16 | 0x1C, 0x22, 0x22, 0x22, 0x1C, 0x08, 0x1C, 0x08, 17 | 0x3C, 0x3C, 0x04, 0x04, 0x04, 0x07, 0x03, 0x00, 18 | 0xFC, 0x84, 0xFC, 0x84, 0x84, 0xE7, 0x63, 0x00, 19 | 0x18, 0x42, 0x18, 0xA5, 0xA5, 0x18, 0x42, 0x18, 20 | 0x03, 0x0F, 0x3F, 0xFF, 0x3F, 0x0F, 0x03, 0x00, 21 | 0xC0, 0xF0, 0xFC, 0xFF, 0xFC, 0xF0, 0xC0, 0x00, 22 | 0x08, 0x1C, 0x2A, 0x08, 0x2A, 0x1C, 0x08, 0x00, 23 | 0x24, 0x24, 0x24, 0x24, 0x24, 0x00, 0x24, 0x00, 24 | 0x7C, 0x5E, 0x5E, 0x5C, 0x50, 0x50, 0x50, 0x00, 25 | 0x1C, 0x02, 0x1C, 0x24, 0x38, 0x40, 0x38, 0x00, 26 | 0x00, 0x00, 0x00, 0x00, 0x7E, 0x7E, 0x7E, 0x00, 27 | 0x08, 0x1C, 0x2A, 0x08, 0x2A, 0x1C, 0x08, 0x3E, 28 | 0x08, 0x1C, 0x2A, 0x08, 0x08, 0x08, 0x08, 0x00, 29 | 0x08, 0x08, 0x08, 0x08, 0x2A, 0x1C, 0x08, 0x00, 30 | 0x00, 0x20, 0x40, 0xFE, 0x40, 0x20, 0x00, 0x00, 31 | 0x00, 0x04, 0x02, 0x7F, 0x02, 0x04, 0x00, 0x00, 32 | 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x3E, 0x00, 33 | 0x00, 0x24, 0x42, 0xFF, 0x42, 0x24, 0x00, 0x00, 34 | 0x08, 0x08, 0x1C, 0x1C, 0x3E, 0x3E, 0x7F, 0x00, 35 | 0x7F, 0x3E, 0x3E, 0x1C, 0x1C, 0x08, 0x08, 0x00, 36 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 37 | 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x08, 0x00, 38 | 0x14, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 39 | 0x00, 0x24, 0x7E, 0x24, 0x24, 0x7E, 0x24, 0x00, 40 | 0x1C, 0x0A, 0x0A, 0x1C, 0x28, 0x28, 0x1C, 0x00, 41 | 0x43, 0x23, 0x10, 0x08, 0x04, 0x62, 0x61, 0x00, 42 | 0x0C, 0x12, 0x12, 0x5C, 0x22, 0x22, 0x5C, 0x00, 43 | 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 44 | 0x10, 0x08, 0x08, 0x08, 0x08, 0x08, 0x10, 0x00, 45 | 0x08, 0x10, 0x10, 0x10, 0x10, 0x10, 0x08, 0x00, 46 | 0x00, 0x24, 0x18, 0x18, 0x24, 0x00, 0x00, 0x00, 47 | 0x00, 0x00, 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00, 48 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x08, 49 | 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 50 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 51 | 0x20, 0x20, 0x10, 0x10, 0x08, 0x08, 0x04, 0x04, 52 | 0x1C, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1C, 0x00, 53 | 0x10, 0x18, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 54 | 0x1C, 0x20, 0x20, 0x10, 0x08, 0x04, 0x3C, 0x00, 55 | 0x1C, 0x20, 0x20, 0x1C, 0x20, 0x20, 0x1C, 0x00, 56 | 0x12, 0x12, 0x12, 0x12, 0x3C, 0x10, 0x10, 0x00, 57 | 0x3C, 0x04, 0x04, 0x1C, 0x20, 0x20, 0x1C, 0x00, 58 | 0x3C, 0x02, 0x02, 0x1E, 0x22, 0x22, 0x1C, 0x00, 59 | 0x3E, 0x20, 0x20, 0x10, 0x10, 0x08, 0x08, 0x00, 60 | 0x1C, 0x22, 0x22, 0x1C, 0x22, 0x22, 0x1C, 0x00, 61 | 0x1C, 0x22, 0x22, 0x3C, 0x20, 0x20, 0x1C, 0x00, 62 | 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 63 | 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x04, 64 | 0x20, 0x10, 0x08, 0x04, 0x08, 0x10, 0x20, 0x00, 65 | 0x00, 0x00, 0x3E, 0x00, 0x00, 0x3E, 0x00, 0x00, 66 | 0x04, 0x08, 0x10, 0x20, 0x10, 0x08, 0x04, 0x00, 67 | 0x1C, 0x20, 0x20, 0x18, 0x08, 0x00, 0x08, 0x00, 68 | 0x38, 0x44, 0x72, 0x4A, 0x32, 0x04, 0x38, 0x00, 69 | 0x1C, 0x22, 0x22, 0x3E, 0x22, 0x22, 0x22, 0x00, 70 | 0x1E, 0x22, 0x22, 0x1E, 0x22, 0x22, 0x1E, 0x00, 71 | 0x38, 0x04, 0x04, 0x04, 0x04, 0x04, 0x38, 0x00, 72 | 0x1E, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1E, 0x00, 73 | 0x3C, 0x04, 0x04, 0x3C, 0x04, 0x04, 0x3C, 0x00, 74 | 0x3C, 0x04, 0x04, 0x3C, 0x04, 0x04, 0x04, 0x00, 75 | 0x3C, 0x02, 0x02, 0x02, 0x32, 0x22, 0x3C, 0x00, 76 | 0x22, 0x22, 0x22, 0x3E, 0x22, 0x22, 0x22, 0x00, 77 | 0x1C, 0x08, 0x08, 0x08, 0x08, 0x08, 0x1C, 0x00, 78 | 0x3E, 0x08, 0x08, 0x08, 0x08, 0x08, 0x06, 0x00, 79 | 0x24, 0x24, 0x14, 0x0C, 0x14, 0x24, 0x24, 0x00, 80 | 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x3C, 0x00, 81 | 0x41, 0x63, 0x55, 0x49, 0x41, 0x41, 0x41, 0x00, 82 | 0x22, 0x26, 0x26, 0x2A, 0x32, 0x32, 0x22, 0x00, 83 | 0x3C, 0x42, 0x42, 0x42, 0x42, 0x42, 0x3C, 0x00, 84 | 0x1C, 0x22, 0x22, 0x1E, 0x02, 0x02, 0x02, 0x00, 85 | 0x1E, 0x21, 0x21, 0x21, 0x21, 0x29, 0x1E, 0x60, 86 | 0x1E, 0x22, 0x22, 0x1E, 0x12, 0x22, 0x22, 0x00, 87 | 0x38, 0x04, 0x04, 0x18, 0x20, 0x20, 0x1C, 0x00, 88 | 0x3E, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 89 | 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1C, 0x00, 90 | 0x22, 0x22, 0x22, 0x22, 0x14, 0x14, 0x08, 0x00, 91 | 0x41, 0x41, 0x49, 0x49, 0x49, 0x49, 0x36, 0x00, 92 | 0x22, 0x22, 0x14, 0x08, 0x14, 0x22, 0x22, 0x00, 93 | 0x22, 0x22, 0x14, 0x08, 0x08, 0x08, 0x08, 0x00, 94 | 0x3E, 0x20, 0x10, 0x08, 0x04, 0x02, 0x3E, 0x00, 95 | 0x18, 0x08, 0x08, 0x08, 0x08, 0x08, 0x18, 0x00, 96 | 0x04, 0x04, 0x08, 0x08, 0x10, 0x10, 0x20, 0x20, 97 | 0x18, 0x10, 0x10, 0x10, 0x10, 0x10, 0x18, 0x00, 98 | 0x08, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 99 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 100 | 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 101 | 0x00, 0x00, 0x1C, 0x22, 0x22, 0x22, 0x5C, 0x00, 102 | 0x04, 0x04, 0x1C, 0x24, 0x24, 0x24, 0x1C, 0x00, 103 | 0x00, 0x00, 0x18, 0x04, 0x04, 0x04, 0x18, 0x00, 104 | 0x20, 0x20, 0x38, 0x24, 0x24, 0x24, 0x38, 0x00, 105 | 0x00, 0x00, 0x18, 0x24, 0x1C, 0x04, 0x38, 0x00, 106 | 0x18, 0x04, 0x04, 0x0C, 0x04, 0x04, 0x04, 0x00, 107 | 0x00, 0x00, 0x18, 0x24, 0x38, 0x20, 0x24, 0x18, 108 | 0x04, 0x04, 0x04, 0x1C, 0x24, 0x24, 0x24, 0x00, 109 | 0x08, 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 110 | 0x10, 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x0C, 111 | 0x04, 0x04, 0x24, 0x14, 0x0C, 0x14, 0x24, 0x00, 112 | 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x18, 0x00, 113 | 0x00, 0x00, 0x36, 0x49, 0x49, 0x49, 0x41, 0x00, 114 | 0x00, 0x00, 0x0C, 0x12, 0x12, 0x12, 0x12, 0x00, 115 | 0x00, 0x00, 0x1C, 0x22, 0x22, 0x22, 0x1C, 0x00, 116 | 0x00, 0x00, 0x1C, 0x24, 0x24, 0x1C, 0x04, 0x04, 117 | 0x00, 0x00, 0x1C, 0x12, 0x12, 0x1C, 0x30, 0x10, 118 | 0x00, 0x00, 0x18, 0x04, 0x04, 0x04, 0x04, 0x00, 119 | 0x00, 0x00, 0x18, 0x04, 0x08, 0x10, 0x0C, 0x00, 120 | 0x00, 0x08, 0x08, 0x1C, 0x08, 0x08, 0x30, 0x00, 121 | 0x00, 0x00, 0x24, 0x24, 0x24, 0x24, 0x18, 0x00, 122 | 0x00, 0x00, 0x22, 0x22, 0x14, 0x14, 0x08, 0x00, 123 | 0x00, 0x00, 0x41, 0x41, 0x2A, 0x2A, 0x14, 0x00, 124 | 0x00, 0x00, 0x22, 0x14, 0x08, 0x14, 0x22, 0x00, 125 | 0x00, 0x00, 0x22, 0x22, 0x14, 0x08, 0x04, 0x02, 126 | 0x00, 0x00, 0x3E, 0x10, 0x08, 0x04, 0x3E, 0x00, 127 | 0x10, 0x08, 0x08, 0x04, 0x08, 0x08, 0x10, 0x00, 128 | 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 129 | 0x04, 0x08, 0x08, 0x10, 0x08, 0x08, 0x04, 0x00, 130 | 0x00, 0x00, 0x28, 0x14, 0x00, 0x00, 0x00, 0x00, 131 | 0x08, 0x14, 0x22, 0x41, 0x41, 0x41, 0x7F, 0x00, 132 | // TODO 133 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 134 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 135 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 136 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 137 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 138 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 139 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 140 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 141 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 142 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 143 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 144 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 145 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 146 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 147 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 148 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 149 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 150 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 151 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 152 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 153 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 154 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 155 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 156 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 157 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 158 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 159 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 160 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 161 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 162 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 163 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 164 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 165 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 166 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 167 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 168 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 169 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 170 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 171 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 172 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 173 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 174 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 175 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 176 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 177 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 178 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 179 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 180 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 181 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 182 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 183 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 184 | 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 185 | 0x10, 0x10, 0x10, 0x10, 0x1F, 0x10, 0x10, 0x10, 186 | 0x10, 0x10, 0x1F, 0x10, 0x1F, 0x10, 0x10, 0x10, 187 | 0x28, 0x28, 0x28, 0x28, 0x2F, 0x28, 0x28, 0x28, 188 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 189 | 0x00, 0x00, 0x1F, 0x10, 0x1F, 0x10, 0x10, 0x10, 190 | 0x28, 0x28, 0x2F, 0x20, 0x2F, 0x28, 0x28, 0x28, 191 | 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 192 | 0x00, 0x00, 0x3F, 0x20, 0x2F, 0x28, 0x28, 0x28, 193 | 0x28, 0x28, 0x2F, 0x20, 0x3F, 0x00, 0x00, 0x00, 194 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 195 | 0x10, 0x10, 0x1F, 0x10, 0x1F, 0x00, 0x00, 0x00, 196 | 0x00, 0x00, 0x00, 0x00, 0x1F, 0x10, 0x10, 0x10, 197 | 0x10, 0x10, 0x10, 0x10, 0xF0, 0x00, 0x00, 0x00, 198 | 0x10, 0x10, 0x10, 0x10, 0xFF, 0x00, 0x00, 0x00, 199 | 0x00, 0x00, 0x00, 0x00, 0xFF, 0x10, 0x10, 0x10, 200 | 0x10, 0x10, 0x10, 0x10, 0xF0, 0x10, 0x10, 0x10, 201 | 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 202 | 0x10, 0x10, 0x10, 0x10, 0xFF, 0x10, 0x10, 0x10, 203 | 0x10, 0x10, 0xF0, 0x10, 0xF0, 0x10, 0x10, 0x10, 204 | 0x28, 0x28, 0x28, 0x28, 0xE8, 0x28, 0x28, 0x28, 205 | 0x28, 0x28, 0xE8, 0x08, 0xF8, 0x00, 0x00, 0x00, 206 | 0x00, 0x00, 0xF8, 0x08, 0xE8, 0x28, 0x28, 0x28, 207 | 0x28, 0x28, 0xEF, 0x00, 0xFF, 0x00, 0x00, 0x00, 208 | 0x00, 0x00, 0xFF, 0x00, 0xEF, 0x28, 0x28, 0x28, 209 | 0x28, 0x28, 0xE8, 0x08, 0xE8, 0x28, 0x28, 0x28, 210 | 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 211 | 0x28, 0x28, 0xEF, 0x00, 0xEF, 0x28, 0x28, 0x28, 212 | 0x10, 0x10, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 213 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 214 | 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x10, 0x10, 0x10, 215 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 216 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 217 | 0x10, 0x10, 0xF0, 0x10, 0xF0, 0x00, 0x00, 0x00, 218 | 0x00, 0x00, 0xF0, 0x10, 0xF0, 0x10, 0x10, 0x10, 219 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 220 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 221 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 222 | 0x10, 0x10, 0x10, 0x10, 0x1F, 0x00, 0x00, 0x00, 223 | 0x00, 0x00, 0x00, 0x00, 0xF0, 0x10, 0x10, 0x10, 224 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 225 | 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 226 | 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 227 | 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 228 | 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 229 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 230 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 231 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 232 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 233 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 234 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 235 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 236 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 237 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 238 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 239 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 240 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 241 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 242 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 243 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 244 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 245 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 246 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 247 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 248 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 249 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 250 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 251 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 252 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 253 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 254 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 255 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 256 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 257 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 258 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 259 | 0x00, 0x00, 0x3C, 0x3C, 0x3C, 0x3C, 0x00, 0x00, 260 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 261 | }; -------------------------------------------------------------------------------- /core/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(PACECore INTERFACE) 2 | 3 | target_sources(PACECore INTERFACE 4 | AboveBoard.cpp 5 | CGACard.cpp 6 | CPU.cpp 7 | FixedDiskAdapter.cpp 8 | FloppyController.cpp 9 | SerialMouse.cpp 10 | System.cpp 11 | ) 12 | 13 | target_include_directories(PACECore INTERFACE ${CMAKE_CURRENT_LIST_DIR}) -------------------------------------------------------------------------------- /core/CPU.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | class System; 6 | 7 | class CPU final 8 | { 9 | public: 10 | 11 | CPU(System &sys); 12 | 13 | void reset(); 14 | 15 | void run(int ms); 16 | 17 | uint32_t getCycleCount() const {return cycleCount;} 18 | 19 | enum class Reg8 20 | { 21 | AL = 0, 22 | CL, 23 | DL, 24 | BL, 25 | AH, 26 | CH, 27 | DH, 28 | BH, 29 | }; 30 | 31 | enum class Reg16 32 | { 33 | AX = 0, 34 | CX, 35 | DX, 36 | BX, 37 | 38 | // index registers 39 | SP, 40 | BP, 41 | SI, 42 | DI, 43 | 44 | // program counter 45 | IP, 46 | 47 | // segment registers 48 | ES, 49 | CS, 50 | SS, 51 | DS, 52 | }; 53 | 54 | uint8_t reg(Reg8 r) const {return reinterpret_cast(regs)[((static_cast(r) & 3) << 1) + (static_cast(r) >> 2)];} 55 | uint8_t ®(Reg8 r) {return reinterpret_cast(regs)[((static_cast(r) & 3) << 1) + (static_cast(r) >> 2)];} 56 | uint16_t reg(Reg16 r) const {return regs[static_cast(r)];} 57 | uint16_t ®(Reg16 r) {return regs[static_cast(r)];} 58 | 59 | uint16_t getFlags() const {return flags;} 60 | void setFlags(uint16_t flags) {this->flags = flags;} 61 | 62 | void executeInstruction(); 63 | 64 | private: 65 | uint16_t readMem16(uint16_t offset, uint32_t segment); 66 | void writeMem16(uint16_t offset, uint32_t segment, uint16_t data); 67 | 68 | std::tuple getEffectiveAddress(int mod, int rm, int &cycles, bool rw, uint32_t addr); 69 | 70 | // R/M helpers 71 | 72 | uint8_t readRM8(uint8_t modRM, int &cycles, uint32_t addr); 73 | uint16_t readRM16(uint8_t modRM, int &cycles, uint32_t addr); 74 | 75 | void writeRM8(uint8_t modRM, uint8_t v, int &cycles, uint32_t addr, bool rw = false); 76 | void writeRM16(uint8_t modRM, uint16_t v, int &cycles, uint32_t addr, bool rw = false); 77 | 78 | // ALU helpers 79 | using ALUOp8 = uint8_t(*)(uint8_t, uint8_t, uint16_t &); 80 | using ALUOp16 = uint16_t(*)(uint16_t, uint16_t, uint16_t &); 81 | 82 | template 83 | void doALU8(uint32_t addr); 84 | template 85 | void doALU16(uint32_t addr); 86 | 87 | template 88 | void doALU8AImm(uint32_t addr); 89 | template 90 | void doALU16AImm(uint32_t addr); 91 | 92 | void cyclesExecuted(int cycles); 93 | 94 | void serviceInterrupt(uint8_t vector); 95 | 96 | static const uint32_t clockSpeed = 4772726; 97 | 98 | // internal state 99 | int cyclesToRun = 0; 100 | uint32_t cycleCount = 0; 101 | 102 | // registers 103 | uint16_t regs[13]; 104 | uint16_t flags; 105 | 106 | // enabling interrupts happens one opcode later 107 | bool delayInterrupt = false; 108 | 109 | Reg16 segmentOverride; 110 | 111 | // RAM 112 | System &sys; 113 | }; 114 | -------------------------------------------------------------------------------- /core/FIFO.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // FIFO helper using circular buffer 3 | 4 | template 5 | class FIFO final 6 | { 7 | public: 8 | void push(T val) 9 | { 10 | if(fullFlag) 11 | return; 12 | 13 | data[writeOff] = val; 14 | 15 | writeOff = (writeOff + 1) % size; 16 | 17 | fullFlag = readOff == writeOff; 18 | } 19 | 20 | T pop() 21 | { 22 | if(empty()) 23 | return T(0); 24 | 25 | auto ret = data[readOff]; 26 | 27 | readOff = (readOff + 1) % size; 28 | 29 | fullFlag = false; 30 | 31 | return ret; 32 | } 33 | 34 | T peek() 35 | { 36 | if(empty()) 37 | return T(0); 38 | 39 | return data[readOff]; 40 | } 41 | 42 | bool empty() const 43 | { 44 | return !fullFlag && readOff == writeOff; 45 | } 46 | 47 | bool full() const {return fullFlag;} 48 | 49 | int getCount() const 50 | { 51 | if(fullFlag) 52 | return size; 53 | 54 | if(writeOff >= readOff) 55 | return writeOff - readOff; 56 | 57 | return writeOff + size - readOff; 58 | } 59 | 60 | private: 61 | T data[size]; 62 | 63 | int readOff = 0, writeOff = 0; 64 | bool fullFlag = false; 65 | }; -------------------------------------------------------------------------------- /core/FixedDiskAdapter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "FixedDiskAdapter.h" 5 | 6 | FixedDiskAdapter::FixedDiskAdapter(System &sys) : sys(sys) 7 | { 8 | // technically generates IRQ5, but not in a way that requires updateForInterrupts (yet?) 9 | sys.addIODevice(0x3F0, 0x320, 0, this); 10 | } 11 | 12 | void FixedDiskAdapter::setIOInterface(FixedDiskIO *io) 13 | { 14 | this->io = io; 15 | } 16 | 17 | uint8_t FixedDiskAdapter::read(uint16_t addr) 18 | { 19 | switch(addr) 20 | { 21 | case 0x320: // read 22 | { 23 | auto ret = responseLen ? data[responseOffset++] : 0xFF; 24 | 25 | // when all bytes read 26 | if(responseOffset == responseLen) 27 | { 28 | responseOffset = responseLen = 0; 29 | 30 | // clear interrupt 31 | status &= ~(1 << 5); 32 | 33 | status &= ~(1 << 1 | 1 << 3); // clear IO mode, busy 34 | // and bus? 35 | } 36 | return ret; 37 | } 38 | 39 | case 0x321: // status 40 | { 41 | auto ret = status; 42 | 43 | // this is a hack so that the "init characteristics" command works 44 | if(commandDataOffset < commandDataLen) 45 | status |= (1 << 0); // set request 46 | return ret; 47 | } 48 | 49 | case 0x322: // drive settings? 50 | break; 51 | 52 | default: 53 | printf("FXD R %04X @~%04X\n", addr, sys.getCPU().reg(CPU::Reg16::IP)); 54 | } 55 | return 0xFF; 56 | } 57 | 58 | void FixedDiskAdapter::write(uint16_t addr, uint8_t data) 59 | { 60 | switch(addr) 61 | { 62 | case 0x320: // write 63 | { 64 | if(commandDataOffset < commandDataLen) 65 | this->data[commandDataOffset++] = data; 66 | else if(controlBlockOffset < 6) 67 | controlBlock[controlBlockOffset++] = data; 68 | 69 | if(controlBlockOffset == 6 && commandDataOffset == commandDataLen) 70 | { 71 | // always clear this 72 | // it needs to be set again before the BIOS will send the rest of the "init characteristics" data though... 73 | status &= ~(1 << 0); // clear request 74 | 75 | if(!commandDataLen) 76 | { 77 | if(controlBlock[0] == 0x0C/*init characteristics*/) 78 | { 79 | commandDataLen = 8; // has an extra 8 bytes 80 | break; 81 | } 82 | } 83 | 84 | // now we really have all the data 85 | 86 | status |= (1 << 5); // interrupt? 87 | if(dmaIntrMask & 2) 88 | sys.flagPICInterrupt(5); 89 | 90 | int drive = (controlBlock[1] >> 5) & 1; 91 | int head = controlBlock[1] & 0x1F; 92 | int sector = controlBlock[2] & 0x3F; 93 | int cylinder = controlBlock[3] | (controlBlock[2] & 0xC0) << 2; 94 | 95 | const int sectorsPerTrack = 17; 96 | const int sectorSize = 512; 97 | bool failed = false; 98 | 99 | auto outData = this->data; 100 | responseLen = 1; 101 | 102 | // don't clear sense if reading it 103 | if(controlBlock[0] != 0x03) 104 | sense[0] = 0; 105 | 106 | if(controlBlock[0] == 0x00) // test drive ready 107 | {} 108 | else if(controlBlock[0] == 0x01) // recalibrate 109 | { 110 | if(!io || !io->isPresent(drive)) 111 | { 112 | failed = true; 113 | sense[0] |= 6; // didn't reach track 0 114 | } 115 | } 116 | else if(controlBlock[0] == 0x03) // request sense 117 | { 118 | // copy sense data 119 | for(auto &b : sense) 120 | *outData++ = b; 121 | 122 | responseLen = 5; 123 | } 124 | else if(controlBlock[0] == 0x08) // read 125 | { 126 | // transfers data through DMA... 127 | // super-hack 128 | 129 | auto &dma = sys.dma; 130 | auto dmaSize = dma.currentWordCount[3] + 1; 131 | auto destAddr = dma.currentAddress[3]; 132 | auto destHigh = dma.highAddr[3] << 16; 133 | 134 | auto lba = ((cylinder * numHeads[drive] + head) * sectorsPerTrack) + sector; 135 | 136 | while(dmaSize && (dmaIntrMask & 1)) 137 | { 138 | uint8_t buf[512]; 139 | 140 | if(!io || !io->read(drive, buf, lba)) 141 | { 142 | failed = true; 143 | break; 144 | } 145 | 146 | for(int i = 0; i < sectorSize; i++) 147 | sys.writeMem(destHigh + destAddr + i, buf[i]); 148 | 149 | dmaSize -= sectorSize; 150 | destAddr += sectorSize; 151 | lba++; 152 | } 153 | 154 | sense[0] |= 1 << 7; // address valid 155 | 156 | // set an error if failed 157 | // TODO: set different errors for no drive and read fail 158 | if(failed) 159 | sense[0] |= 4; // not ready 160 | 161 | } 162 | else if(controlBlock[0] == 0x0A) // write 163 | { 164 | // hack the second 165 | auto &dma = sys.dma; 166 | auto dmaSize = dma.currentWordCount[3] + 1; 167 | auto srcAddr = dma.currentAddress[3]; 168 | auto destHigh = dma.highAddr[3] << 16; 169 | 170 | auto lba = ((cylinder * numHeads[drive] + head) * sectorsPerTrack) + sector; 171 | 172 | while(dmaSize && (dmaIntrMask & 1)) 173 | { 174 | uint8_t buf[512]; 175 | 176 | for(int i = 0; i < sectorSize; i++) 177 | buf[i] = sys.readMem(destHigh + srcAddr + i); 178 | 179 | if(!io || !io->write(drive, buf, lba)) 180 | { 181 | failed = true; 182 | break; 183 | } 184 | 185 | dmaSize -= sectorSize; 186 | srcAddr += sectorSize; 187 | lba++; 188 | } 189 | 190 | sense[0] |= 1 << 7; // address valid 191 | 192 | // set an error if failed 193 | // TODO: set different errors for no drive and read fail 194 | if(failed) 195 | sense[0] |= 4; // not ready 196 | } 197 | else if(controlBlock[0] == 0x0C) // init characteristics 198 | { 199 | // manual says byte 1 is ignored, BIOS suggests otherwise... 200 | numCylinders[drive] = this->data[0] << 8 | this->data[1]; 201 | numHeads[drive] = this->data[2]; 202 | } 203 | else if(controlBlock[0] == 0x0F) // write to sector buffer 204 | {} // TODO? 205 | else if(controlBlock[0] == 0xE0) // RAM diagnostic 206 | {} 207 | else if(controlBlock[0] == 0xE4) // controller diagnostic 208 | {} 209 | else 210 | { 211 | failed = true; 212 | sense[0] |= 0x20; // invalid command 213 | printf("FXD %02X (+%i)\n", controlBlock[0], commandDataOffset); 214 | } 215 | 216 | // needs to be set after "init characteristics" 217 | status |= (1 << 1); // IO mode 218 | 219 | status &= ~(1 << 0); // clear request 220 | 221 | controlBlockOffset = 0; 222 | commandDataOffset = commandDataLen = 0; 223 | 224 | responseOffset = 0; 225 | *outData = (controlBlock[1] & (1 << 5)) // drive number for status 226 | | (failed ? (1 << 1) : 0); 227 | 228 | // copy data for sense 229 | sense[1] = controlBlock[1]; 230 | sense[2] = controlBlock[2]; 231 | sense[3] = controlBlock[3]; 232 | } 233 | break; 234 | } 235 | case 0x321: // reset 236 | { 237 | controlBlockOffset = commandDataOffset = responseOffset = 0; 238 | commandDataLen = responseLen = 0; 239 | status = 0; 240 | dmaIntrMask = 0; 241 | break; 242 | } 243 | 244 | case 0x322: //controller select 245 | { 246 | // BIOS expects these bits 247 | status = 1 << 3/*busy*/ | 1 << 2/*bus*/ | 1 << 0/*req*/; 248 | 249 | // hack to recover after bad control blocks 250 | controlBlockOffset = 0; 251 | break; 252 | } 253 | 254 | case 0x323: // dma/interrupt mask 255 | { 256 | dmaIntrMask = data; 257 | break; 258 | } 259 | 260 | // these don't seem to do anything, but the BIOS likes to write to them 261 | case 0x327: 262 | case 0x32B: 263 | case 0x32F: 264 | break; 265 | 266 | default: 267 | printf("FXD W %04X = %02X @~%04X\n", addr, data, sys.getCPU().reg(CPU::Reg16::IP)); 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /core/FixedDiskAdapter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "System.h" 3 | 4 | class FixedDiskIO 5 | { 6 | public: 7 | // is drive connected to controller 8 | virtual bool isPresent(int unit) = 0; 9 | 10 | // reads a 512 byte sector 11 | virtual bool read(int unit, uint8_t *buf, uint32_t lba) = 0; 12 | virtual bool write(int unit, const uint8_t *buf, uint32_t lba) = 0; 13 | }; 14 | 15 | class FixedDiskAdapter final : public IODevice 16 | { 17 | public: 18 | FixedDiskAdapter(System &sys); 19 | 20 | void setIOInterface(FixedDiskIO *io); 21 | 22 | uint8_t read(uint16_t addr) override; 23 | void write(uint16_t addr, uint8_t data) override; 24 | 25 | void updateForInterrupts() override {}; 26 | int getCyclesToNextInterrupt(uint32_t cycleCount) override {return 0;} 27 | 28 | private: 29 | System &sys; 30 | 31 | uint8_t status = 0; 32 | uint8_t dmaIntrMask = 0; 33 | 34 | uint8_t controlBlock[6]; 35 | uint8_t controlBlockOffset = 0; 36 | uint8_t commandDataLen = 0, commandDataOffset = 0; 37 | 38 | uint8_t sense[4]; 39 | 40 | uint8_t data[8]; 41 | uint8_t responseOffset = 0, responseLen = 0; 42 | 43 | uint16_t numCylinders[2]; 44 | uint8_t numHeads[2]; 45 | 46 | FixedDiskIO *io = nullptr; 47 | }; -------------------------------------------------------------------------------- /core/FloppyController.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "FloppyController.h" 5 | 6 | FloppyController::FloppyController(System &sys) : sys(sys) 7 | { 8 | // technically generates IRQ6, but not in a way that requires updateForInterrupts (yet?) 9 | sys.addIODevice(0x3F8, 0x3F0, 0, this); 10 | } 11 | 12 | void FloppyController::setIOInterface(FloppyDiskIO *io) 13 | { 14 | this->io = io; 15 | } 16 | 17 | uint8_t FloppyController::read(uint16_t addr) 18 | { 19 | switch(addr) 20 | { 21 | case 0x3F4: // floppy main status 22 | { 23 | // no drives busy, dma-mode 24 | return 0 | 25 | (resultLen ? 1 << 4 : 0) | // fdc busy if there's a result to read 26 | (resultLen ? 1 << 6 : 0) | // fdc->cpu if we have result data else cpu->fdc 27 | 1 << 7; // ready 28 | } 29 | case 0x3F5: // floppy command/data 30 | { 31 | if(resultLen) 32 | { 33 | auto ret = result[resultOff++]; 34 | 35 | // end of result 36 | if(resultOff == resultLen) 37 | resultLen = 0; 38 | 39 | return ret; 40 | } 41 | 42 | return 0xFF; 43 | } 44 | 45 | case 0x3F7: // floppy digital input 46 | return 0; // TODO, but this makes 8088_bios boot 47 | 48 | } 49 | return 0xFF; 50 | } 51 | 52 | void FloppyController::write(uint16_t addr, uint8_t data) 53 | { 54 | switch(addr) 55 | { 56 | case 0x3F2: // floppy digital output 57 | { 58 | auto changed = digitalOutput ^ data; 59 | 60 | if((changed & (1 << 2)) && (data & (1 << 2))) 61 | { 62 | // leaving reset 63 | // because RDY is high, this generates an interrupt 64 | if(data & (1 << 3)) 65 | sys.flagPICInterrupt(6); 66 | 67 | readyChanged = 0xF; // all of them 68 | } 69 | 70 | digitalOutput = data; 71 | break; 72 | } 73 | 74 | case 0x3F5: // floppy command/data 75 | { 76 | if(commandLen == 0) 77 | { 78 | command[0] = data; 79 | 80 | if(data == 0x03) // specify 81 | commandLen = 3; 82 | else if(data == 0x04) // sense drive status 83 | commandLen = 2; 84 | else if((data & 0x1F) == 0x06) // read 85 | commandLen = 9; 86 | else if(data == 0x07) // recalibrate 87 | commandLen = 2; 88 | else if(data == 0x08) // sense interrupt status 89 | commandLen = 1; 90 | else if((data & 0x1F) == 0x0a) // read id 91 | commandLen = 2; 92 | else if(data == 0x0F) 93 | commandLen = 3; 94 | else 95 | printf("FCD = %02X\n", data); 96 | 97 | if(commandLen) 98 | commandOff = 1; 99 | } 100 | else 101 | command[commandOff++] = data; 102 | 103 | if(commandLen && commandOff == commandLen) 104 | { 105 | // got full command 106 | if(command[0] == 0x03) // specify 107 | { 108 | // auto stepRateTime = command[1] >> 4; 109 | // auto headUnloadTime = command[1] & 0xF; 110 | // auto headLoadTime = command[2] >> 1; 111 | // bool nonDMA = command[2] & 1; 112 | } 113 | else if(command[0] == 0x04) // sense drive status 114 | { 115 | int unit = command[1] & 3; 116 | // int head = (command[1] >> 2) & 1; 117 | 118 | bool track0 = presentCylinder[unit] == 0; 119 | 120 | resultLen = 1; 121 | result[0] = (track0 ? 1 << 4 : 0) | 1 << 5 /*ready*/; 122 | } 123 | else if((command[0] & 0x1F) == 0x06) // read 124 | { 125 | // multitrack mfm skip 126 | [[maybe_unused]] bool multiTrack = command[0] & (1 << 7); 127 | [[maybe_unused]] bool mfm = command[0] & (1 << 6); 128 | // bool skipDeleted = command[0] & (1 << 5); 129 | 130 | int unit = command[1] & 3; 131 | int head = (command[1] >> 2) & 1; 132 | 133 | auto cylinder = command[2]; 134 | [[maybe_unused]] auto headAgain = command[3]; 135 | auto record = command[4]; 136 | auto number = command[5]; 137 | auto endOfTrack = command[6]; 138 | //auto gapLength = command[7]; 139 | //auto dataLength = command[8]; 140 | 141 | assert(head == headAgain); 142 | assert(number == 2); 143 | assert(multiTrack); 144 | assert(mfm); 145 | 146 | auto sectorSize = 128 << number; 147 | 148 | // transfers data through DMA... 149 | // super-hack 150 | bool failed = false; 151 | auto &dma = sys.dma; 152 | auto dmaSize = dma.currentWordCount[2] + 1; 153 | auto destAddr = dma.currentAddress[2]; 154 | auto destHigh = dma.highAddr[2] << 16; 155 | while(dmaSize) 156 | { 157 | uint8_t buf[512]; 158 | 159 | if(!io || !io->read(unit, buf, cylinder, head, record)) 160 | { 161 | failed = true; 162 | break; 163 | } 164 | 165 | for(int i = 0; i < sectorSize; i++) 166 | sys.writeMem(destHigh + destAddr + i, buf[i]); 167 | 168 | dmaSize -= sectorSize; 169 | destAddr += sectorSize; 170 | 171 | // update offset 172 | record++; 173 | if(record > endOfTrack) 174 | { 175 | record = 1; 176 | if(head == 0) 177 | head = 1; 178 | else 179 | { 180 | head = 0; 181 | cylinder++; 182 | } 183 | } 184 | } 185 | 186 | // FIXME: if !auto else reload 187 | dma.currentAddress[2] = destAddr; 188 | dma.currentWordCount[2] = 0; 189 | // resets every time anyway... 190 | 191 | status[0] = unit | head << 2; 192 | 193 | if(failed) 194 | status[0] |= 1 << 6; 195 | 196 | resultLen = 7; 197 | result[0] = status[0]; 198 | result[1] = status[1]; 199 | result[2] = status[2]; 200 | result[3] = cylinder; 201 | result[4] = head; 202 | result[5] = record; 203 | result[6] = number; 204 | 205 | if(digitalOutput & (1 << 3)) 206 | sys.flagPICInterrupt(6); 207 | } 208 | else if(command[0] == 0x07) // recalibrate 209 | { 210 | int unit = command[1] & 3; 211 | 212 | status[0] = unit; 213 | 214 | if(!io || !io->isPresent(unit)) 215 | status[0] |= 1 << 6 | 1 << 4; // abnormal termination/equipment check 216 | else 217 | { 218 | presentCylinder[unit] = 0; 219 | status[0] |= 1 << 5; // set seek end 220 | } 221 | 222 | if(digitalOutput & (1 << 3)) 223 | sys.flagPICInterrupt(6); 224 | } 225 | else if(command[0] == 0x08) // sense interrupt status 226 | { 227 | if(readyChanged) 228 | { 229 | int unit = 0; 230 | while(!(readyChanged & 1 << unit)) 231 | unit++; 232 | 233 | readyChanged &= ~(1 << unit); 234 | 235 | status[0] = 0xC0 | unit; 236 | } 237 | 238 | result[0] = status[0]; 239 | result[1] = presentCylinder[status[0] & 3]; 240 | resultLen = 2; 241 | 242 | status[0] = 0; // clear status 243 | } 244 | else if((command[0] & 0x1F) == 0x0a) // read id 245 | { 246 | [[maybe_unused]] bool mfm = command[0] & (1 << 6); 247 | 248 | int unit = command[1] & 3; 249 | int head = (command[1] >> 2) & 1; 250 | 251 | assert(mfm); 252 | 253 | status[0] = unit | head << 2; 254 | 255 | resultLen = 7; 256 | result[0] = status[0]; 257 | result[1] = status[1]; 258 | result[2] = status[2]; 259 | result[3] = presentCylinder[unit]; 260 | result[4] = head; 261 | result[5] = 1; 262 | result[6] = 2; // ? 263 | 264 | if(digitalOutput & (1 << 3)) 265 | sys.flagPICInterrupt(6); 266 | } 267 | else if(command[0] == 0x0F) // seek 268 | { 269 | int unit = command[1] & 3; 270 | // int head = (command[1] >> 2) & 1; 271 | auto cylinder = command[2]; 272 | 273 | status[0] = unit; 274 | 275 | if(!io || !io->isPresent(unit)) 276 | status[0] |= 1 << 6 | 1 << 4; // abnormal termination/equipment check 277 | else 278 | { 279 | presentCylinder[unit] = cylinder; 280 | 281 | // set seek end 282 | status[0] |= 1 << 5; 283 | } 284 | 285 | if(digitalOutput & (1 << 3)) 286 | sys.flagPICInterrupt(6); 287 | } 288 | 289 | commandLen = 0; 290 | resultOff = 0; 291 | } 292 | 293 | break; 294 | } 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /core/FloppyController.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "System.h" 3 | 4 | class FloppyDiskIO 5 | { 6 | public: 7 | // is there a disk in the drive 8 | virtual bool isPresent(int unit) = 0; 9 | 10 | // reads a 512 byte sector 11 | virtual bool read(int unit, uint8_t *buf, uint8_t cylinder, uint8_t head, uint8_t sector) = 0; 12 | }; 13 | 14 | class FloppyController final : public IODevice 15 | { 16 | public: 17 | FloppyController(System &sys); 18 | 19 | void setIOInterface(FloppyDiskIO *io); 20 | 21 | uint8_t read(uint16_t addr) override; 22 | void write(uint16_t addr, uint8_t data) override; 23 | 24 | void updateForInterrupts() override {}; 25 | int getCyclesToNextInterrupt(uint32_t cycleCount) override {return 0;} 26 | 27 | private: 28 | System &sys; 29 | 30 | uint8_t digitalOutput = 0; 31 | 32 | uint8_t status[4] = {0, 0, 0, 0}; 33 | uint8_t presentCylinder[4]; 34 | 35 | uint8_t command[9]; 36 | uint8_t result[7]; 37 | uint8_t commandLen = 0, resultLen = 0; 38 | uint8_t commandOff, resultOff; 39 | 40 | uint8_t readyChanged; 41 | 42 | FloppyDiskIO *io = nullptr; 43 | }; -------------------------------------------------------------------------------- /core/Scancode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // break codes are | 0x80 4 | enum class XTScancode 5 | { 6 | Invalid = 0x00, 7 | 8 | A = 0x1E, 9 | B = 0x30, 10 | C = 0x2E, 11 | D = 0x20, 12 | E = 0x12, 13 | F = 0x21, 14 | G = 0x22, 15 | H = 0x23, 16 | I = 0x17, 17 | J = 0x24, 18 | K = 0x25, 19 | L = 0x26, 20 | M = 0x32, 21 | N = 0x31, 22 | O = 0x18, 23 | P = 0x19, 24 | Q = 0x10, 25 | R = 0x13, 26 | S = 0x1F, 27 | T = 0x14, 28 | U = 0x16, 29 | V = 0x2F, 30 | W = 0x11, 31 | X = 0x2D, 32 | Y = 0x15, 33 | Z = 0x2C, 34 | 35 | _1 = 0x02, 36 | _2 = 0x03, 37 | _3 = 0x04, 38 | _4 = 0x05, 39 | _5 = 0x06, 40 | _6 = 0x07, 41 | _7 = 0x08, 42 | _8 = 0x09, 43 | _9 = 0x0A, 44 | _0 = 0x0B, 45 | 46 | Return = 0x1C, 47 | Escape = 0x01, 48 | Backspace = 0x0E, 49 | Tab = 0x0F, 50 | Space = 0x39, 51 | 52 | Minus = 0x0C, 53 | Equals = 0x0D, 54 | LeftBracket = 0x1A, 55 | RightBracket = 0x1B, 56 | Backslash = 0x2B, 57 | Semicolon = 0x27, 58 | Apostrophe = 0x28, 59 | Grave = 0x29, 60 | Comma = 0x33, 61 | Period = 0x34, 62 | Slash = 0x35, 63 | 64 | CapsLock = 0x3A, 65 | 66 | F1 = 0x3B, 67 | F2 = 0x3C, 68 | F3 = 0x3D, 69 | F4 = 0x3E, 70 | F5 = 0x3F, 71 | F6 = 0x40, 72 | F7 = 0x41, 73 | F8 = 0x42, 74 | F9 = 0x43, 75 | F10 = 0x44, 76 | F11 = 0x57, 77 | F12 = 0x58, 78 | 79 | // PrintScreen = 0xE037, 80 | ScrollLock = 0x46, 81 | // Pause = 0xE11D45 E19DC5, // immediate break? 82 | 83 | Insert = 0xE052, 84 | Home = 0xE047, 85 | PageUp = 0xE049, 86 | Delete = 0xE053, 87 | End = 0xE04F, 88 | PageDown = 0xE051, 89 | Right = 0xE04D, 90 | Left = 0xE04B, 91 | Down = 0xE050, 92 | Up = 0xE048, 93 | 94 | NumLock = 0x45, 95 | 96 | KPDivide = 0xE035, 97 | KPMultiply = 0x37, 98 | KPMinus = 0x4A, 99 | KPPlus = 0x4E, 100 | KPEnter = 0xE01C, 101 | KP1 = 0x4F, 102 | KP2 = 0x50, 103 | KP3 = 0x51, 104 | KP4 = 0x4B, 105 | KP5 = 0x4C, 106 | KP6 = 0x4D, 107 | KP7 = 0x47, 108 | KP8 = 0x48, 109 | KP9 = 0x49, 110 | KP0 = 0x52, 111 | KPPeriod = 0x53, 112 | 113 | NonUSBackslash = 0x56, 114 | 115 | // Application = 0xE05D, 116 | // Power = 0xE05E, 117 | 118 | KPEquals = 0x59, 119 | 120 | // F13-24 are 64-6E and 76 121 | 122 | // skipping unassigned... 123 | 124 | KPComma = 0x7E, 125 | 126 | International1 = 0x73, 127 | International2 = 0x70, 128 | International3 = 0x7D, 129 | International4 = 0x79, 130 | International5 = 0x7B, 131 | International6 = 0x5C, 132 | Lang1 = 0xF2, 133 | Lang2 = 0xF1, 134 | Lang3 = 0x78, 135 | Lang4 = 0x77, 136 | Lang5 = 0x76, 137 | 138 | LeftCtrl = 0x1D, 139 | LeftShift = 0x2A, 140 | LeftAlt = 0x38, 141 | // LeftGUI = 0xE05B, 142 | RightCtrl = 0xE01D, 143 | RightShift = 0x36, 144 | RightAlt = 0xE038, 145 | // RightGUI = 0xE05C, 146 | 147 | // some media keys with two byte codes... 148 | }; -------------------------------------------------------------------------------- /core/SerialMouse.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "SerialMouse.h" 5 | 6 | // this is also a minimal serial card 7 | // (as those aren't implemented yet) 8 | 9 | SerialMouse::SerialMouse(System &sys) : sys(sys) 10 | { 11 | sys.addIODevice(0x3F8, 0x2F8, 1 << 3, this); 12 | 13 | lineStatus = (1 << 5)/*tx empty*/ | (1 << 6)/*tx shift empty*/; 14 | } 15 | 16 | void SerialMouse::addMotion(int x, int y) 17 | { 18 | xMotion += x; 19 | yMotion += y; 20 | } 21 | 22 | void SerialMouse::setButton(int button, bool state) 23 | { 24 | auto buttonMask = 1 << button; 25 | auto newState = (state ? 1 : 0) << button; 26 | 27 | if((buttons & buttonMask) != newState) 28 | { 29 | changedButtons |= buttonMask; 30 | buttons ^= buttonMask; 31 | } 32 | } 33 | 34 | void SerialMouse::sync() 35 | { 36 | if(!changedButtons && !xMotion && !yMotion) 37 | return; 38 | 39 | // make sure queue isn't full 40 | if(rxQueue.getCount() > 5) 41 | return; 42 | 43 | // TODO: clamp motion? 44 | 45 | rxQueue.push(0x40 | (buttons & 1) << 5 | (buttons & 2) << 3 | (yMotion & 0xC0) >> 4 | (xMotion & 0xC0) >> 6); 46 | rxQueue.push(xMotion & 0x3F); 47 | rxQueue.push(yMotion & 0x3F); 48 | 49 | changedButtons = 0; 50 | xMotion = 0; 51 | yMotion = 0; 52 | } 53 | 54 | void SerialMouse::update() 55 | { 56 | auto elapsed = sys.getCPU().getCycleCount() - lastUpdateCycle; 57 | 58 | if(!cpuCyclesPerWord) 59 | { 60 | // skip 61 | lastUpdateCycle += elapsed; 62 | return; 63 | } 64 | 65 | while(elapsed) 66 | { 67 | auto step = std::min(elapsed, wordCycleCounter); 68 | 69 | wordCycleCounter -= step; 70 | 71 | if(wordCycleCounter == 0) 72 | { 73 | // received word 74 | if(!rxQueue.empty()) 75 | { 76 | // TODO: if already set, set overrun 77 | lineStatus |= 1; // receive ready 78 | if(interruptEnable & 1) // data available 79 | sys.flagPICInterrupt(3); 80 | } 81 | 82 | wordCycleCounter = cpuCyclesPerWord; 83 | } 84 | 85 | lastUpdateCycle += step; 86 | elapsed -= step; 87 | } 88 | } 89 | 90 | uint8_t SerialMouse::read(uint16_t addr) 91 | { 92 | update(); 93 | 94 | switch(addr) 95 | { 96 | case 0x2F8: // RX buffer or divisor LSB 97 | { 98 | if(lineControl & (1 << 7)) 99 | return divisor; 100 | else 101 | { 102 | if(lineStatus & 1) 103 | { 104 | lineStatus &= ~1; // clear receive ready 105 | return rxQueue.pop(); 106 | } 107 | 108 | break; 109 | } 110 | } 111 | case 0x2F9: // interrupt enable or divisor MSB 112 | { 113 | if(lineControl & (1 << 7)) 114 | return divisor >> 8; 115 | else 116 | return interruptEnable; 117 | } 118 | 119 | case 0x2FB: // line control 120 | return lineControl; 121 | 122 | case 0x2FC: // modem control 123 | return modemControl; 124 | 125 | case 0x2FD: // line status: 126 | return lineStatus; 127 | 128 | case 0x2FE: // modem status 129 | return 0; 130 | 131 | default: 132 | printf("serial/mouse R %04X @~%04X\n", addr, sys.getCPU().reg(CPU::Reg16::IP)); 133 | } 134 | return 0xFF; 135 | } 136 | 137 | void SerialMouse::write(uint16_t addr, uint8_t data) 138 | { 139 | update(); 140 | 141 | switch(addr) 142 | { 143 | case 0x2F8: // TX buffer or divisor LSB 144 | { 145 | if(lineControl & (1 << 7)) 146 | { 147 | divisor = (divisor & 0xFF00) | data; 148 | updateTimings(); 149 | } 150 | else 151 | printf("serial/mouse tx %X\n", data); 152 | break; 153 | } 154 | case 0x2F9: // interrupt enable or divisor MSB 155 | { 156 | if(lineControl & (1 << 7)) 157 | { 158 | divisor = (divisor & 0xFF) | data << 8; 159 | updateTimings(); 160 | } 161 | else 162 | { 163 | interruptEnable = data; 164 | if(data) 165 | printf("serial/mouse interrupt enable %X\n", data); 166 | } 167 | break; 168 | } 169 | 170 | case 0x2FB: // line control 171 | lineControl = data; 172 | printf("serial/mouse line control %X\n", data); 173 | updateTimings(); 174 | break; 175 | case 0x2FC: // modem control 176 | { 177 | auto changed = modemControl ^ data; 178 | modemControl = data; 179 | printf("serial/mouse modem control %X\n", data); 180 | 181 | if(changed & 1) // DTR 182 | rxQueue.push('M'); 183 | 184 | if((changed & 2) && (data & 3) == 3) 185 | { 186 | // RTS went high while DTR on, end of reset (probably) 187 | 188 | while(!rxQueue.empty()) 189 | rxQueue.pop(); 190 | 191 | rxQueue.push('M'); 192 | } 193 | break; 194 | } 195 | 196 | default: 197 | printf("serial/mouse W %04X = %02X @~%04X\n", addr, data, sys.getCPU().reg(CPU::Reg16::IP)); 198 | } 199 | } 200 | 201 | void SerialMouse::updateForInterrupts() 202 | { 203 | if(!interruptEnable) 204 | return; 205 | 206 | auto elapsed = sys.getCPU().getCycleCount() - lastUpdateCycle; 207 | 208 | if(elapsed >= wordCycleCounter) 209 | update(); 210 | } 211 | 212 | int SerialMouse::getCyclesToNextInterrupt(uint32_t cycleCount) 213 | { 214 | if(!cpuCyclesPerWord) 215 | return 0x7FFFFFFF; 216 | 217 | auto passed = cycleCount - lastUpdateCycle; 218 | 219 | return wordCycleCounter - passed; 220 | } 221 | 222 | void SerialMouse::updateTimings() 223 | { 224 | if(divisor == 0) 225 | return; 226 | 227 | int bits = 5 + (lineControl & 3); // data bits 228 | bits += 1 + ((lineControl >> 2) & 1); // stop bits 229 | bits += (lineControl >> 3) & 1; // parity bit 230 | bits++; // start bit 231 | 232 | auto baud = 1843200 / divisor / 16; 233 | 234 | // get rough number of cpu cycles per word 235 | // (this emulator does not yet support multiple clocks...) 236 | cpuCyclesPerWord = 4772726 * bits / baud; 237 | 238 | sys.calculateNextInterruptCycle(sys.getCPU().getCycleCount()); 239 | } 240 | -------------------------------------------------------------------------------- /core/SerialMouse.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "FIFO.h" 3 | #include "System.h" 4 | 5 | class SerialMouse final : public IODevice 6 | { 7 | public: 8 | SerialMouse(System &sys); 9 | 10 | void addMotion(int x, int y); 11 | void setButton(int button, bool state); 12 | 13 | void sync(); 14 | 15 | void update(); 16 | 17 | uint8_t read(uint16_t addr) override; 18 | void write(uint16_t addr, uint8_t data) override; 19 | 20 | void updateForInterrupts() override; 21 | int getCyclesToNextInterrupt(uint32_t cycleCount) override; 22 | 23 | private: 24 | void updateTimings(); 25 | 26 | System &sys; 27 | 28 | uint16_t divisor = 0; 29 | uint8_t interruptEnable = 0; 30 | uint8_t lineControl = 0; 31 | uint8_t modemControl = 0; 32 | uint8_t lineStatus = 0; 33 | 34 | // hardware doesn't have a queue, but we're doing it anyway 35 | FIFO rxQueue; 36 | 37 | uint8_t buttons = 0; 38 | uint8_t changedButtons = 0; 39 | int xMotion = 0, yMotion = 0; 40 | 41 | uint32_t cpuCyclesPerWord; 42 | uint32_t wordCycleCounter = 0; 43 | uint32_t lastUpdateCycle = 0; 44 | }; -------------------------------------------------------------------------------- /core/System.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | #include "CPU.h" 7 | #include "FIFO.h" 8 | #include "Scancode.h" 9 | 10 | class IODevice 11 | { 12 | public: 13 | virtual uint8_t read(uint16_t addr) = 0; 14 | virtual void write(uint16_t addr, uint8_t data) = 0; 15 | 16 | virtual void updateForInterrupts() = 0; 17 | virtual int getCyclesToNextInterrupt(uint32_t cycleCount) = 0; 18 | }; 19 | 20 | class System 21 | { 22 | public: 23 | using MemRequestCallback = uint8_t *(*)(unsigned int block); 24 | 25 | System(); 26 | void reset(); 27 | 28 | CPU &getCPU() {return cpu;} 29 | 30 | void addMemory(uint32_t base, uint32_t size, uint8_t *ptr); 31 | void addReadOnlyMemory(uint32_t base, uint32_t size, const uint8_t *ptr); 32 | 33 | void removeMemory(unsigned int block); 34 | 35 | uint32_t *getMemoryDirtyMask(); 36 | bool getMemoryBlockDirty(unsigned int block) const; 37 | void setMemoryBlockDirty(unsigned int block); 38 | void clearMemoryBlockDirty(unsigned int block); 39 | 40 | void setMemoryRequestCallback(MemRequestCallback cb); 41 | MemRequestCallback getMemoryRequestCallback() const; 42 | 43 | void addIODevice(uint16_t mask, uint16_t value, uint8_t picMask, IODevice *dev); 44 | 45 | uint8_t readMem(uint32_t addr); 46 | void writeMem(uint32_t addr, uint8_t data); 47 | 48 | const uint8_t *mapAddress(uint32_t addr) const; 49 | 50 | uint8_t readIOPort(uint16_t addr); 51 | void writeIOPort(uint16_t addr, uint8_t data); 52 | 53 | void flagPICInterrupt(int index); 54 | 55 | void updateForInterrupts(); 56 | void updateForDisplay(); 57 | 58 | void calculateNextInterruptCycle(uint32_t cycleCount); 59 | uint32_t getNextInterruptCycle() const {return nextInterruptCycle;} 60 | 61 | bool hasInterrupt() const {return pic.request & ~pic.mask;} 62 | uint8_t acknowledgeInterrupt(); 63 | 64 | void sendKey(XTScancode scancode, bool down); 65 | 66 | bool hasSpeakerSample() const; 67 | int8_t getSpeakerSample(); 68 | 69 | static constexpr int getMemoryBlockSize() {return blockSize;} 70 | static constexpr int getNumMemoryBlocks() {return maxAddress / blockSize;} 71 | 72 | private: 73 | struct IORange 74 | { 75 | uint16_t ioMask, ioValue; 76 | uint8_t picMask; 77 | IODevice *dev; 78 | }; 79 | 80 | void updatePIT(); 81 | void calculateNextPITUpdate(); 82 | void updateSpeaker(uint32_t target); 83 | 84 | CPU cpu; 85 | 86 | static const int maxAddress = 1 << 20; 87 | static const int blockSize = 16 * 1024; 88 | 89 | uint8_t *memMap[maxAddress / blockSize]; 90 | uint32_t memDirty[maxAddress / blockSize / 32]; 91 | uint32_t memReadOnly[maxAddress / blockSize / 32]; 92 | 93 | MemRequestCallback memReqCb = nullptr; 94 | 95 | struct DMA 96 | { 97 | uint16_t baseAddress[4]; 98 | uint16_t baseWordCount[4]; 99 | uint16_t currentAddress[4]; 100 | uint16_t currentWordCount[4]; 101 | 102 | uint16_t tempAddress; 103 | uint16_t tempWordCount; 104 | uint8_t tempData; 105 | 106 | uint8_t status; 107 | uint8_t command; 108 | uint8_t request; 109 | 110 | uint8_t mode[4]; 111 | 112 | uint8_t mask = 0xF; 113 | 114 | bool flipFlop = false; 115 | 116 | uint8_t highAddr[4]; 117 | }; 118 | 119 | struct PIC 120 | { 121 | uint8_t initCommand[4]; 122 | int nextInit = 0; 123 | 124 | uint8_t request = 0; 125 | uint8_t service = 0; 126 | uint8_t mask = 0; 127 | 128 | uint8_t statusRead = 0; 129 | }; 130 | 131 | struct PIT 132 | { 133 | uint8_t control[3]{0, 0, 0}; 134 | uint8_t active = 0; 135 | 136 | uint16_t counter[3]; 137 | uint16_t reload[3]; 138 | uint16_t latch[3]; 139 | 140 | uint8_t latched = 0; 141 | uint8_t highByte = 0; // lo/hi access mode 142 | 143 | uint8_t outState = 0; 144 | uint8_t reloadNextCycle = 0; 145 | 146 | uint32_t lastUpdateCycle = 0; 147 | uint32_t nextUpdateCycle = 0; 148 | }; 149 | 150 | struct PPI 151 | { 152 | uint8_t mode = 0; 153 | uint8_t output[3]; 154 | }; 155 | 156 | 157 | DMA dma; 158 | 159 | PIC pic; 160 | 161 | PIT pit; 162 | 163 | PPI ppi; 164 | 165 | std::vector ioDevices; 166 | 167 | uint32_t nextInterruptCycle = 0; 168 | 169 | FIFO keyboardQueue; 170 | uint32_t keyboardClockLowCycle = 0; 171 | uint32_t keyboardTestReplyCycle = 0; 172 | int keyboardTestDelay = 0; // need to delay sending back test result 173 | 174 | uint32_t lastSpeakerUpdateCycle = 0; 175 | uint32_t speakerSampleTimer = 0; 176 | FIFO speakerQueue; // somewhat unsafe 177 | 178 | // because this is a giant pile of hacks, it needs to poke around in the DMA controller 179 | // FIXME: real DMA, remove this 180 | friend class FloppyController; 181 | friend class FixedDiskAdapter; 182 | }; -------------------------------------------------------------------------------- /font.py: -------------------------------------------------------------------------------- 1 | # python font.py 8x8font.png > CGAFont.h 2 | 3 | import sys 4 | from PIL import Image 5 | 6 | img = Image.open(sys.argv[1]).convert('1') 7 | w, h = img.size 8 | 9 | rows = h // 8 10 | cols = w // 8 11 | 12 | data = [] 13 | 14 | for row in range(rows): 15 | for col in range(cols): 16 | 17 | char_data = [] 18 | 19 | for y in range(8): 20 | byte = 0 21 | 22 | for x in range(8): 23 | if img.getpixel((x + col * 8, y + row * 8)) != 0: 24 | byte |= 1 << x 25 | 26 | char_data.append(byte) 27 | 28 | data.append(char_data) 29 | 30 | 31 | print('const uint8_t cgaFont[]\n{') 32 | print(',\n'.join([' ' + ', '.join([f'0x{b:02X}' for b in char]) for char in data])) 33 | print('};') -------------------------------------------------------------------------------- /minsdl/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # minimal SDL shell 2 | 3 | add_executable(PACE_SDL 4 | DiskIO.cpp 5 | Main.cpp 6 | ) 7 | 8 | find_package(SDL2 REQUIRED) 9 | 10 | target_link_libraries(PACE_SDL PACECore SDL2::SDL2) 11 | 12 | if(SDL2_SDL2main_FOUND) 13 | target_link_libraries(PACE_SDL SDL2::SDL2main) 14 | endif() 15 | 16 | install(TARGETS PACE_SDL) 17 | 18 | # install SDL2.dll on windows for convenience 19 | if(WIN32) 20 | get_target_property(SDL2_LOCATION SDL2::SDL2 IMPORTED_LOCATION_RELEASE) 21 | if(NOT SDL2_LOCATION) 22 | get_target_property(SDL2_LOCATION SDL2::SDL2 IMPORTED_LOCATION) 23 | endif() 24 | 25 | if(SDL2_LOCATION MATCHES ".dll$") 26 | install(FILES ${SDL2_LOCATION} DESTINATION bin) 27 | endif() 28 | endif() -------------------------------------------------------------------------------- /minsdl/DiskIO.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "DiskIO.h" 5 | 6 | bool FileFloppyIO::isPresent(int unit) 7 | { 8 | return unit < maxDrives && file[unit].is_open(); 9 | } 10 | 11 | bool FileFloppyIO::read(int unit, uint8_t *buf, uint8_t cylinder, uint8_t head, uint8_t sector) 12 | { 13 | if(unit >= maxDrives) 14 | return false; 15 | 16 | int heads = doubleSided[unit] ? 2 : 1; 17 | auto lba = ((cylinder * heads + head) * sectorsPerTrack[unit]) + sector - 1; 18 | 19 | file[unit].clear(); 20 | 21 | return file[unit].seekg(lba * 512).read(reinterpret_cast(buf), 512).gcount() == 512; 22 | } 23 | 24 | void FileFloppyIO::openDisk(int unit, std::string path) 25 | { 26 | if(unit >= maxDrives) 27 | return; 28 | 29 | file[unit].close(); 30 | 31 | file[unit].open(path); 32 | if(file[unit]) 33 | { 34 | file[unit].seekg(0, std::ios::end); 35 | auto fdSize = file[unit].tellg(); 36 | 37 | // try to work out geometry 38 | switch(fdSize / 1024) 39 | { 40 | case 160: 41 | doubleSided[unit] = false; 42 | sectorsPerTrack[unit] = 8; 43 | break; 44 | case 180: 45 | doubleSided[unit] = false; 46 | sectorsPerTrack[unit] = 9; 47 | break; 48 | case 360: 49 | doubleSided[unit] = true; 50 | sectorsPerTrack[unit] = 9; 51 | // could also be a single-sided 3.5-inch disk 52 | break; 53 | case 720: // 3.5 inch 54 | doubleSided[unit] = true; 55 | sectorsPerTrack[unit] = 9; 56 | break; 57 | case 1200: 58 | doubleSided[unit] = true; 59 | sectorsPerTrack[unit] = 15; 60 | break; 61 | default: 62 | std::cerr << "unhandled floppy image size " << fdSize << "(" << fdSize / 1024 << "k)\n"; 63 | // set... something 64 | doubleSided[unit] = false; 65 | sectorsPerTrack[unit] = 8; 66 | break; 67 | } 68 | 69 | std::cout << "using " << (doubleSided[unit] ? 2 : 1) << " head(s) " << sectorsPerTrack[unit] << " sectors/track for floppy image\n"; 70 | } 71 | } 72 | 73 | bool FileFixedIO::isPresent(int unit) 74 | { 75 | return unit < maxDrives && file[unit].is_open(); 76 | } 77 | 78 | bool FileFixedIO::read(int unit, uint8_t *buf, uint32_t lba) 79 | { 80 | if(unit >= maxDrives) 81 | return false; 82 | 83 | file[unit].clear(); 84 | 85 | return file[unit].seekg(lba * 512).read(reinterpret_cast(buf), 512).gcount() == 512; 86 | } 87 | 88 | bool FileFixedIO::write(int unit, const uint8_t *buf, uint32_t lba) 89 | { 90 | if(unit >= maxDrives) 91 | return false; 92 | 93 | file[unit].clear(); 94 | 95 | return file[unit].seekp(lba * 512).write(reinterpret_cast(buf), 512).good(); 96 | } 97 | 98 | void FileFixedIO::openDisk(int unit, std::string path) 99 | { 100 | if(unit >= maxDrives) 101 | return; 102 | 103 | if(!std::filesystem::exists(path)) 104 | { 105 | // new disk image 106 | file[unit].open(path, std::ios::in | std::ios::out | std::ios::trunc | std::ios::binary); 107 | 108 | // fill boot sector 109 | for(int i = 0; i < 512; i++) 110 | file[unit].put(0); 111 | } 112 | else 113 | file[unit].open(path, std::ios::in | std::ios::out | std::ios::binary); 114 | 115 | if(file[unit]) 116 | std::cout << "Loaded fixed-disk " << unit << ": " << path << "\n"; 117 | } 118 | -------------------------------------------------------------------------------- /minsdl/DiskIO.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "FixedDiskAdapter.h" 6 | #include "FloppyController.h" 7 | 8 | class FileFloppyIO final : public FloppyDiskIO 9 | { 10 | public: 11 | bool isPresent(int unit) override; 12 | bool read(int unit, uint8_t *buf, uint8_t cylinder, uint8_t head, uint8_t sector) override; 13 | 14 | void openDisk(int unit, std::string path); 15 | 16 | static const int maxDrives = 4; 17 | 18 | private: 19 | std::ifstream file[maxDrives]; 20 | 21 | bool doubleSided[maxDrives]; 22 | int sectorsPerTrack[maxDrives]; 23 | }; 24 | 25 | class FileFixedIO final : public FixedDiskIO 26 | { 27 | public: 28 | bool isPresent(int unit) override; 29 | bool read(int unit, uint8_t *buf, uint32_t lba) override; 30 | bool write(int unit, const uint8_t *buf, uint32_t lba) override; 31 | 32 | void openDisk(int unit, std::string path); 33 | 34 | static const int maxDrives = 2; 35 | 36 | private: 37 | std::fstream file[maxDrives]; 38 | 39 | bool doubleSided[maxDrives]; 40 | int sectorsPerTrack[maxDrives]; 41 | }; -------------------------------------------------------------------------------- /pico-shared/BIOS.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern char _binary_bios_xt_rom_start[]; 4 | extern char _binary_bios_xt_rom_end[]; 5 | 6 | extern char _binary_fixed_disk_bios_rom_start[]; 7 | extern char _binary_fixed_disk_bios_rom_end[]; -------------------------------------------------------------------------------- /pico-shared/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(PACEPicoShared INTERFACE) 2 | 3 | target_sources(PACEPicoShared INTERFACE 4 | fatfs/ff.c 5 | fatfs/ffunicode.c 6 | 7 | DiskIO.cpp 8 | Filesystem.cpp 9 | Storage.cpp 10 | USBHID.cpp 11 | ) 12 | 13 | target_include_directories(PACEPicoShared INTERFACE ${CMAKE_CURRENT_LIST_DIR}) 14 | target_link_libraries(PACEPicoShared INTERFACE PACECore hardware_pio tinyusb_host) 15 | 16 | pico_generate_pio_header(PACEPicoShared ${CMAKE_CURRENT_LIST_DIR}/spi.pio) 17 | 18 | #embed BIOS 19 | set(BIOS_FILE bios-xt.rom) 20 | set(BIOS_PATH ${CMAKE_CURRENT_LIST_DIR}/../) 21 | set(BIOS_OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}) 22 | add_custom_command( 23 | OUTPUT bios.o 24 | WORKING_DIRECTORY ${BIOS_PATH} 25 | COMMAND ${CMAKE_OBJCOPY} -I binary -O elf32-littlearm -B armv6s-m --rename-section .data=.rodata,alloc,load,readonly,data,contents ${BIOS_FILE} ${CMAKE_CURRENT_BINARY_DIR}/bios.o 26 | DEPENDS ${BIOS_PATH}${BIOS_FILE} 27 | ) 28 | 29 | add_library(PACEPicoBIOS bios.o) 30 | target_include_directories(PACEPicoBIOS INTERFACE ${CMAKE_CURRENT_LIST_DIR}) 31 | set_target_properties(PACEPicoBIOS PROPERTIES LINKER_LANGUAGE C) 32 | 33 | # embed disk adapter BIOS if found 34 | set(DISK_BIOS_FILE fixed-disk-bios.rom) 35 | if(EXISTS ${BIOS_PATH}/${DISK_BIOS_FILE}) 36 | add_custom_command( 37 | OUTPUT disk-bios.o 38 | WORKING_DIRECTORY ${BIOS_PATH} 39 | COMMAND ${CMAKE_OBJCOPY} -I binary -O elf32-littlearm -B armv6s-m --rename-section .data=.rodata,alloc,load,readonly,data,contents ${DISK_BIOS_FILE} ${CMAKE_CURRENT_BINARY_DIR}/disk-bios.o 40 | DEPENDS ${BIOS_PATH}${DISK_BIOS_FILE} 41 | ) 42 | 43 | message("Found fixed disk adapter BIOS") 44 | 45 | target_sources(PACEPicoBIOS PRIVATE disk-bios.o) 46 | target_compile_definitions(PACEPicoBIOS PUBLIC FIXED_DISK) 47 | endif() -------------------------------------------------------------------------------- /pico-shared/DiskIO.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "DiskIO.h" 4 | 5 | bool FileFixedIO::isPresent(int unit) 6 | { 7 | return unit < maxDrives;// && file[unit]; 8 | } 9 | 10 | bool FileFixedIO::read(int unit, uint8_t *buf, uint32_t lba) 11 | { 12 | if(unit >= maxDrives) 13 | return false; 14 | 15 | f_lseek(&file[unit], lba * 512); 16 | 17 | UINT read = 0; 18 | auto res = f_read(&file[unit], buf, 512, &read); 19 | 20 | return res == FR_OK && read == 512; 21 | } 22 | 23 | bool FileFixedIO::write(int unit, const uint8_t *buf, uint32_t lba) 24 | { 25 | if(unit >= maxDrives) 26 | return false; 27 | 28 | f_lseek(&file[unit], lba * 512); 29 | 30 | UINT written = 0; 31 | auto res = f_write(&file[unit], buf, 512, &written); 32 | 33 | return res == FR_OK && written == 512; 34 | } 35 | 36 | void FileFixedIO::openDisk(int unit, const char *path) 37 | { 38 | if(unit >= maxDrives) 39 | return; 40 | 41 | auto res = f_open(&file[unit], path, FA_READ | FA_WRITE | FA_OPEN_ALWAYS); 42 | 43 | if(res == FR_OK) 44 | printf("Loaded fixed-disk %i: %s\n", unit, path); 45 | } -------------------------------------------------------------------------------- /pico-shared/DiskIO.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "FixedDiskAdapter.h" 4 | 5 | #include "fatfs/ff.h" 6 | 7 | class FileFixedIO final : public FixedDiskIO 8 | { 9 | public: 10 | bool isPresent(int unit) override; 11 | bool read(int unit, uint8_t *buf, uint32_t lba) override; 12 | bool write(int unit, const uint8_t *buf, uint32_t lba) override; 13 | 14 | void openDisk(int unit, const char *path); 15 | 16 | static const int maxDrives = 1; 17 | 18 | private: 19 | FIL file[maxDrives]; 20 | }; -------------------------------------------------------------------------------- /pico-shared/Filesystem.cpp: -------------------------------------------------------------------------------- 1 | #include "fatfs/ff.h" 2 | #include "fatfs/diskio.h" 3 | 4 | #include "Storage.h" 5 | 6 | // fatfs/storage glue 7 | // guess where this is from... 8 | 9 | static bool initialised = false; 10 | 11 | // fatfs io funcs 12 | DSTATUS disk_initialize(BYTE pdrv) { 13 | initialised = storage_init(); 14 | return initialised ? RES_OK : STA_NOINIT; 15 | } 16 | 17 | DSTATUS disk_status(BYTE pdrv) { 18 | return initialised ? RES_OK : STA_NOINIT; 19 | } 20 | 21 | DRESULT disk_read(BYTE pdrv, BYTE *buff, LBA_t sector, UINT count) { 22 | static_assert(FF_MIN_SS == FF_MAX_SS); 23 | return storage_read(sector, 0, buff, FF_MIN_SS * count) == int32_t(FF_MIN_SS * count) ? RES_OK : RES_ERROR; 24 | } 25 | 26 | DRESULT disk_write(BYTE pdrv, const BYTE *buff, LBA_t sector, UINT count) { 27 | return storage_write(sector, 0, buff, FF_MIN_SS * count) == int32_t(FF_MIN_SS * count) ? RES_OK : RES_ERROR; 28 | } 29 | 30 | DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void* buff) { 31 | uint16_t block_size; 32 | uint32_t num_blocks; 33 | 34 | switch(cmd) { 35 | case CTRL_SYNC: 36 | return RES_OK; 37 | 38 | case GET_SECTOR_COUNT: 39 | get_storage_size(block_size, num_blocks); 40 | *(LBA_t *)buff = num_blocks; 41 | return RES_OK; 42 | 43 | case GET_BLOCK_SIZE: 44 | *(DWORD *)buff = 1; 45 | return RES_OK; 46 | } 47 | 48 | return RES_PARERR; 49 | } 50 | -------------------------------------------------------------------------------- /pico-shared/Storage.cpp: -------------------------------------------------------------------------------- 1 | // SPI SD card storage interface 2 | // "borrowed" from 32blit-pico 3 | // only change is replacing blit::debugf -> printf 4 | 5 | #include 6 | #include 7 | 8 | #include "Storage.h" 9 | 10 | #include "pico/time.h" 11 | #include "pico/binary_info.h" 12 | #include "hardware/clocks.h" 13 | 14 | #include "config.h" 15 | 16 | #include "spi.pio.h" 17 | 18 | #define SD_TIMEOUT 10 19 | 20 | static PIO sd_pio = pio1; 21 | static int sd_sm = 0; 22 | static bool sd_io_initialised = false; 23 | 24 | static uint32_t card_size_blocks = 0; 25 | static bool is_hcs = false; 26 | 27 | static void spi_write(const uint8_t *buf, size_t len) { 28 | size_t tx_remain = len, rx_remain = len; 29 | auto txfifo = (io_rw_8 *) &sd_pio->txf[sd_sm]; 30 | auto rxfifo = (io_rw_8 *) &sd_pio->rxf[sd_sm]; 31 | 32 | while (tx_remain || rx_remain) { 33 | if (tx_remain && !pio_sm_is_tx_fifo_full(sd_pio, sd_sm)) { 34 | *txfifo = *buf++; 35 | --tx_remain; 36 | } 37 | if (rx_remain && !pio_sm_is_rx_fifo_empty(sd_pio, sd_sm)) { 38 | (void) *rxfifo; 39 | --rx_remain; 40 | } 41 | } 42 | } 43 | 44 | static void spi_read(uint8_t *buf, size_t len) { 45 | size_t rx_remain = len; 46 | auto txfifo = (io_rw_8 *) &sd_pio->txf[sd_sm]; 47 | auto rxfifo = (io_rw_8 *) &sd_pio->rxf[sd_sm]; 48 | 49 | // assume FIFO is empty 50 | *txfifo = 0xFF; 51 | *txfifo = 0xFF; 52 | *txfifo = 0xFF; 53 | *txfifo = 0xFF; 54 | 55 | while(rx_remain) { 56 | if (!pio_sm_is_rx_fifo_empty(sd_pio, sd_sm)) { 57 | *buf++ = *rxfifo; 58 | if(--rx_remain > 3) 59 | *txfifo = 0xFF; 60 | } 61 | } 62 | } 63 | 64 | static uint8_t spi_transfer_byte(uint8_t b) { 65 | while(pio_sm_is_tx_fifo_full(sd_pio, sd_sm)); 66 | *(io_rw_8 *)&sd_pio->txf[sd_sm] = b; 67 | 68 | while(pio_sm_is_rx_fifo_empty(sd_pio, sd_sm)); 69 | return *(io_rw_8 *)&sd_pio->rxf[sd_sm]; 70 | } 71 | 72 | static bool sd_wait_ready() { 73 | absolute_time_t timeout_time = make_timeout_time_ms(SD_TIMEOUT); 74 | 75 | while(spi_transfer_byte(0xFF) != 0xFF) { 76 | if(absolute_time_diff_us(get_absolute_time(), timeout_time) <= 0) 77 | return false; 78 | } 79 | 80 | return true; 81 | } 82 | 83 | static bool sd_begin() { 84 | gpio_put(SD_CS, 0); 85 | 86 | // wait for ready 87 | if(!sd_wait_ready()) { 88 | gpio_put(SD_CS, 1); 89 | return false; 90 | } 91 | 92 | return true; 93 | } 94 | 95 | static void sd_end() { 96 | gpio_put(SD_CS, 1); 97 | spi_transfer_byte(0xFF); 98 | } 99 | 100 | static void sd_write_command(uint8_t cmd, uint32_t param, uint8_t crc) { 101 | uint8_t buf[]{ 102 | uint8_t(0x40 | cmd), 103 | uint8_t(param >> 24), 104 | uint8_t(param >> 16), 105 | uint8_t(param >> 8), 106 | uint8_t(param), 107 | crc 108 | }; 109 | 110 | spi_write(buf, sizeof(buf)); 111 | } 112 | 113 | uint8_t sd_read_response() { 114 | uint8_t ret = 0; 115 | int attempt = 0; 116 | while(((ret = spi_transfer_byte(0xFF)) & 0x80) && (attempt++ < 8)); 117 | 118 | return ret; 119 | } 120 | 121 | static uint8_t sd_command1(uint8_t cmd, uint32_t param, uint8_t crc = 1) { 122 | if(!sd_begin()) 123 | return 0xFF; 124 | 125 | sd_write_command(cmd, param, crc); 126 | uint8_t res = sd_read_response(); 127 | 128 | sd_end(); 129 | return res; 130 | } 131 | 132 | static uint8_t sd_command3_7(uint8_t cmd, uint32_t param, uint32_t &res_data, uint8_t crc = 1) { 133 | if(!sd_begin()) 134 | return 0xFF; 135 | 136 | sd_write_command(cmd, param, crc); 137 | 138 | uint8_t res = sd_read_response(); 139 | 140 | // no error bits 141 | if((res & 0xFE) == 0) { 142 | spi_read((uint8_t *)&res_data, 4); 143 | res_data = __builtin_bswap32(res_data); 144 | } 145 | 146 | sd_end(); 147 | return res; 148 | } 149 | 150 | static bool sd_read_block(uint8_t *buffer, int len) { 151 | // wait for start token 152 | absolute_time_t timeout_time = make_timeout_time_ms(SD_TIMEOUT * 10); 153 | 154 | while(spi_transfer_byte(0xFF) != 0xFE) { 155 | if(absolute_time_diff_us(get_absolute_time(), timeout_time) <= 0) 156 | return false; 157 | } 158 | 159 | spi_read(buffer, len); 160 | 161 | // crc 162 | spi_transfer_byte(0xFF); 163 | spi_transfer_byte(0xFF); 164 | 165 | return true; 166 | } 167 | 168 | static uint8_t sd_command_read_block(uint8_t cmd, uint32_t addr, uint8_t *buffer) { 169 | if(!sd_begin()) 170 | return 0xFF; 171 | 172 | sd_write_command(cmd, addr, 1); 173 | 174 | uint8_t res = sd_read_response(); 175 | 176 | if(res == 0) { 177 | int len = 512; 178 | if(cmd == 9 || cmd == 10) // CSD/CID are 16 bytes 179 | len = 16; 180 | else if(cmd == 6) // SWITCH 181 | len = 64; 182 | 183 | if(!sd_read_block(buffer, len)) 184 | return 0xFF; 185 | } 186 | 187 | sd_end(); 188 | 189 | return res; 190 | } 191 | 192 | static uint8_t sd_command_read_block_multiple(uint8_t cmd, uint32_t addr, uint8_t *buffer, int count, uint32_t &read) { 193 | if(!sd_begin()) 194 | return 0xFF; 195 | 196 | sd_write_command(cmd, addr, 1); 197 | 198 | uint8_t res = sd_read_response(); 199 | 200 | if(res == 0) { 201 | // likely only used with CMD 18 202 | const int len = 512; 203 | 204 | while(count--) { 205 | if(!sd_read_block(buffer, len)) 206 | return 0xFF; 207 | 208 | read += len; 209 | buffer += len; 210 | } 211 | 212 | // now CMD12 to end 213 | sd_write_command(12, 0, 1); // STOP_TRANSMISSION 214 | spi_transfer_byte(0xFF); // stuff byte 215 | sd_read_response(); 216 | } 217 | 218 | sd_end(); 219 | 220 | return res; 221 | } 222 | 223 | static uint8_t sd_command_write_block(uint8_t cmd, uint32_t addr, const uint8_t *buffer) { 224 | if(!sd_begin()) 225 | return 0xFF; 226 | 227 | sd_write_command(cmd, addr, 1); 228 | 229 | uint8_t res = sd_read_response(); 230 | 231 | if(res == 0) { 232 | if(!sd_wait_ready()) { 233 | sd_end(); 234 | return 0xFF; 235 | } 236 | 237 | spi_transfer_byte(0xFE); // start token (different for CMD25) 238 | 239 | int len = 512; 240 | 241 | for(int i = 0; i < len; i++) 242 | spi_transfer_byte(*buffer++); 243 | 244 | // crc 245 | spi_transfer_byte(0xFF); 246 | spi_transfer_byte(0xFF); 247 | 248 | auto dataRes = spi_transfer_byte(0xFF) & 0x1F; 249 | 250 | // check accepted 251 | if(dataRes != 0x5) { 252 | sd_end(); 253 | return 0xFF; 254 | } 255 | 256 | // wait for not busy 257 | while(spi_transfer_byte(0xFF) == 0); 258 | } 259 | 260 | sd_end(); 261 | return res; 262 | } 263 | 264 | bool storage_init() { 265 | bi_decl_if_func_used(bi_4pins_with_names(SD_MISO, "SD RX", SD_MOSI, "SD TX", SD_SCK, "SD SCK", SD_CS, "SD CS")); 266 | 267 | // this will be called again it it fails 268 | if(!sd_io_initialised) { 269 | int base = 0; 270 | #if SD_MOSI >= 32 || SD_MISO >= 32 || SD_SCK >= 32 271 | static_assert(SD_MOSI >= 16 && SD_MISO >= 16 && SD_SCK >= 16); 272 | pio_set_gpio_base(sd_pio, 16); 273 | base = 16; 274 | #endif 275 | 276 | uint offset = pio_add_program(sd_pio, &spi_cpha0_program); 277 | 278 | sd_sm = pio_claim_unused_sm(sd_pio, true); 279 | 280 | pio_sm_config c = spi_cpha0_program_get_default_config(offset); 281 | 282 | sm_config_set_out_pins(&c, SD_MOSI, 1); 283 | sm_config_set_in_pins(&c, SD_MISO); 284 | sm_config_set_sideset_pins(&c, SD_SCK); 285 | 286 | sm_config_set_out_shift(&c, false, true, 8); 287 | sm_config_set_in_shift(&c, false, true, 8); 288 | 289 | // MOSI, SCK output are low, MISO is input 290 | pio_sm_set_pins_with_mask64(sd_pio, sd_sm, 0, (1ull << SD_SCK) | (1ull << SD_MOSI)); 291 | pio_sm_set_pindirs_with_mask64(sd_pio, sd_sm, (1ull << SD_SCK) | (1ull << SD_MOSI), (1ull << SD_SCK) | (1ull << SD_MOSI) | (1ull << SD_MISO)); 292 | pio_gpio_init(sd_pio, SD_MOSI); 293 | pio_gpio_init(sd_pio, SD_MISO); 294 | pio_gpio_init(sd_pio, SD_SCK); 295 | 296 | gpio_pull_up(SD_MISO); 297 | 298 | // SPI is synchronous, so bypass input synchroniser to reduce input delay. 299 | hw_set_bits(&sd_pio->input_sync_bypass, 1u << (SD_MISO - base)); 300 | 301 | pio_sm_init(sd_pio, sd_sm, offset, &c); 302 | pio_sm_set_enabled(sd_pio, sd_sm, true); 303 | 304 | // CS 305 | gpio_init(SD_CS); 306 | gpio_set_dir(SD_CS, GPIO_OUT); 307 | gpio_put(SD_CS, 1); 308 | 309 | sd_io_initialised = true; 310 | } 311 | 312 | // go slow for init 313 | pio_sm_set_clkdiv(sd_pio, sd_sm, 250); 314 | 315 | uint8_t buf[]{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; 316 | spi_write(buf, sizeof(buf)); 317 | 318 | // send cmd0 319 | uint8_t res = 0xFF; 320 | for(int retry = 0; retry < 16 && res == 0xFF; retry++) 321 | res = sd_command1(0, 0, 0x95); // GO_IDLE_STATE 322 | 323 | if(res != 0x1) { 324 | printf("CMD0 failed (res %X)\n", res); 325 | return false; 326 | } 327 | 328 | // check voltage range / check if SDv2 329 | bool is_v2 = true; 330 | 331 | uint32_t data; 332 | res = sd_command3_7(8, 0x1AA, data, 0x87); // SEND_IF_COND 333 | 334 | if(res == 5) { 335 | // not supported, old card 336 | is_v2 = false; 337 | } else if(res != 1) { 338 | printf("CMD8 failed (res %X)\n", res); 339 | return false; 340 | } else if(data != 0x1AA) { 341 | printf("CMD8 returned unexpected %X!\n", data); 342 | return false; 343 | } 344 | 345 | // init 346 | while(res != 0) { 347 | res = sd_command1(55, 0, 0x65); // APP_CMD 348 | 349 | if(res == 0x1) 350 | res = sd_command1(41, is_v2 ? 0x40000000 : 0, is_v2 ? 0x77 : 0xE5); // APP_SEND_OP_COND 351 | else if(res != 0xFF) { 352 | printf("CMD55 failed (res %X)\n", res); 353 | return false; 354 | } 355 | 356 | if(res > 1 && res != 0xFF) { 357 | printf("ACMD41 failed (res %X)\n", res); 358 | return false; 359 | } 360 | } 361 | 362 | // defaults, but make sure 363 | sd_command1(59, 0); // CRC_ON_OFF 364 | sd_command1(16, 512); // SET_BLOCKLEN 365 | 366 | // read OCR 367 | res = sd_command3_7(58, 0, data); // READ_OCR 368 | if(res != 0) { 369 | printf("CMD58 failed (res %X)\n", res); 370 | return false; 371 | } 372 | 373 | is_hcs = false; 374 | if((data & 0xC0000000) == 0xC0000000) 375 | is_hcs = true; 376 | 377 | // read CSD 378 | uint8_t csd[16]; 379 | res = sd_command_read_block(9, 0, csd); // SEND_CSD 380 | 381 | if(res != 0) { 382 | printf("CMD9 failed (res %X)\n", res); 383 | return false; 384 | } 385 | 386 | // v1 387 | if((csd[0] >> 6) == 0) { 388 | int c_size = ((csd[6] & 0x3) << 10) | (csd[7] << 2) | (csd[8] >> 6); 389 | int c_size_mult = ((csd[9] & 0x3) << 1) | (csd[10] >> 7); 390 | int readBlLen = csd[5] & 0xF; 391 | 392 | uint32_t num_blocks = uint32_t(c_size + 1) * (2 << (c_size_mult + 1)); 393 | uint32_t size_bytes = num_blocks * (2 << (readBlLen - 1)); 394 | card_size_blocks = size_bytes / 512; 395 | } else { // v2 396 | // measured in 512k blocks 397 | card_size_blocks = (((int32_t(csd[7] & 0x3F) << 16) | (uint32_t(csd[8]) << 8) | csd[9]) + 1) * 1024; 398 | } 399 | 400 | printf("Detected %s card, size %i blocks\n", is_v2 ? (is_hcs ? "SDHC" : "SDv2") : "SDv1", card_size_blocks); 401 | 402 | // set speed (PIO program is 2 cycles/clock) 403 | 404 | // according to the SD specs high speed in SPI mode is "Same as SD mode", helpful. 405 | #if SD_SPI_OVERCLOCK 406 | // these are too fast, but usually okay and the best we can do with a 125MHz clock 407 | // 75MHz is definitely not okay (from 150MHz clock on RP2350) 408 | int clkdiv = std::ceil(clock_get_hz(clk_sys) / (31250000.0f * 2.0f)); 409 | int clkdiv_high_speed = std::ceil(clock_get_hz(clk_sys) / (62500000.0f * 2.0f)); 410 | #else 411 | int clkdiv = std::ceil(clock_get_hz(clk_sys) / (25000000.0f * 2.0f)); 412 | int clkdiv_high_speed = std::ceil(clock_get_hz(clk_sys) / (50000000.0f * 2.0f)); 413 | #endif 414 | 415 | // attempt high speed 416 | uint8_t switch_res[64]; 417 | if(sd_command_read_block(6, 0x80FFFFF1, switch_res) == 0) { // SWITCH 418 | if((switch_res[16] & 0xF) == 1) 419 | clkdiv = clkdiv_high_speed; // successful switch, use high speed 420 | } 421 | 422 | pio_sm_set_clkdiv(sd_pio, sd_sm, clkdiv); 423 | pio_sm_restart(sd_pio, sd_sm); 424 | 425 | return true; 426 | } 427 | 428 | void get_storage_size(uint16_t &block_size, uint32_t &num_blocks) { 429 | block_size = 512; 430 | num_blocks = card_size_blocks; 431 | } 432 | 433 | int32_t storage_read(uint32_t sector, uint32_t offset, void *buffer, uint32_t size_bytes) { 434 | // offset should be 0 (block size == msc buffer size) 435 | 436 | if(!is_hcs) 437 | sector *= 512; 438 | 439 | auto blocks = size_bytes / 512; 440 | 441 | if(blocks == 1) { 442 | if(sd_command_read_block(17, sector, (uint8_t *)buffer) != 0) // READ_SINGLE_BLOCK 443 | return 0; 444 | 445 | return size_bytes; 446 | } else { 447 | uint32_t read = 0; 448 | sd_command_read_block_multiple(18, sector, (uint8_t *)buffer, blocks, read); 449 | return read; 450 | } 451 | 452 | return size_bytes; 453 | } 454 | 455 | int32_t storage_write(uint32_t sector, uint32_t offset, const uint8_t *buffer, uint32_t size_bytes) { 456 | // offset should be 0 457 | 458 | if(!is_hcs) 459 | sector *= 512; 460 | 461 | auto blocks = size_bytes / 512; 462 | 463 | int32_t written = 0; 464 | 465 | // TODO: multi block writes 466 | while(blocks--) { 467 | if(sd_command_write_block(24, sector, (uint8_t *)buffer + written) != 0) // WRITE_SINGLE_BLOCK 468 | break; 469 | 470 | written += 512; 471 | if(!is_hcs) 472 | sector += 512; 473 | else 474 | sector++; 475 | } 476 | 477 | return written; 478 | } -------------------------------------------------------------------------------- /pico-shared/Storage.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | bool storage_init(); 5 | 6 | void get_storage_size(uint16_t &block_size, uint32_t &num_blocks); 7 | 8 | int32_t storage_read(uint32_t sector, uint32_t offset, void *buffer, uint32_t size_bytes); 9 | int32_t storage_write(uint32_t sector, uint32_t offset, const uint8_t *buffer, uint32_t size_bytes); -------------------------------------------------------------------------------- /pico-shared/USBHID.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "tusb.h" 4 | 5 | #include "Scancode.h" 6 | 7 | static const XTScancode scancodeMap[]{ 8 | XTScancode::Invalid, 9 | XTScancode::Invalid, 10 | XTScancode::Invalid, 11 | XTScancode::Invalid, 12 | 13 | XTScancode::A, 14 | XTScancode::B, 15 | XTScancode::C, 16 | XTScancode::D, 17 | XTScancode::E, 18 | XTScancode::F, 19 | XTScancode::G, 20 | XTScancode::H, 21 | XTScancode::I, 22 | XTScancode::J, 23 | XTScancode::K, 24 | XTScancode::L, 25 | XTScancode::M, 26 | XTScancode::N, 27 | XTScancode::O, 28 | XTScancode::P, 29 | XTScancode::Q, 30 | XTScancode::R, 31 | XTScancode::S, 32 | XTScancode::T, 33 | XTScancode::U, 34 | XTScancode::V, 35 | XTScancode::W, 36 | XTScancode::X, 37 | XTScancode::Y, 38 | XTScancode::Z, 39 | 40 | XTScancode::_1, 41 | XTScancode::_2, 42 | XTScancode::_3, 43 | XTScancode::_4, 44 | XTScancode::_5, 45 | XTScancode::_6, 46 | XTScancode::_7, 47 | XTScancode::_8, 48 | XTScancode::_9, 49 | XTScancode::_0, 50 | 51 | XTScancode::Return, 52 | XTScancode::Escape, 53 | XTScancode::Backspace, 54 | XTScancode::Tab, 55 | XTScancode::Space, 56 | 57 | XTScancode::Minus, 58 | XTScancode::Equals, 59 | XTScancode::LeftBracket, 60 | XTScancode::RightBracket, 61 | XTScancode::Backslash, 62 | XTScancode::Backslash, // same key 63 | XTScancode::Semicolon, 64 | XTScancode::Apostrophe, 65 | XTScancode::Grave, 66 | XTScancode::Comma, 67 | XTScancode::Period, 68 | XTScancode::Slash, 69 | 70 | XTScancode::CapsLock, 71 | 72 | XTScancode::F1, 73 | XTScancode::F2, 74 | XTScancode::F3, 75 | XTScancode::F4, 76 | XTScancode::F5, 77 | XTScancode::F6, 78 | XTScancode::F7, 79 | XTScancode::F8, 80 | XTScancode::F9, 81 | XTScancode::F10, 82 | XTScancode::F11, 83 | XTScancode::F12, 84 | 85 | XTScancode::Invalid, // PrintScreen 86 | XTScancode::ScrollLock, 87 | XTScancode::Invalid, // Pause 88 | XTScancode::Insert, 89 | 90 | XTScancode::Home, 91 | XTScancode::PageUp, 92 | XTScancode::Delete, 93 | XTScancode::End, 94 | XTScancode::PageDown, 95 | XTScancode::Right, 96 | XTScancode::Left, 97 | XTScancode::Down, 98 | XTScancode::Up, 99 | 100 | XTScancode::NumLock, 101 | 102 | XTScancode::KPDivide, 103 | XTScancode::KPMultiply, 104 | XTScancode::KPMinus, 105 | XTScancode::KPPlus, 106 | XTScancode::KPEnter, 107 | XTScancode::KP1, 108 | XTScancode::KP2, 109 | XTScancode::KP3, 110 | XTScancode::KP4, 111 | XTScancode::KP5, 112 | XTScancode::KP6, 113 | XTScancode::KP7, 114 | XTScancode::KP8, 115 | XTScancode::KP9, 116 | XTScancode::KP0, 117 | XTScancode::KPPeriod, 118 | 119 | XTScancode::NonUSBackslash, 120 | 121 | XTScancode::Invalid, // Application 122 | XTScancode::Invalid, // Power 123 | 124 | XTScancode::KPEquals, 125 | 126 | // F13-F24 127 | XTScancode::Invalid, 128 | XTScancode::Invalid, 129 | XTScancode::Invalid, 130 | XTScancode::Invalid, 131 | XTScancode::Invalid, 132 | XTScancode::Invalid, 133 | XTScancode::Invalid, 134 | XTScancode::Invalid, 135 | XTScancode::Invalid, 136 | XTScancode::Invalid, 137 | XTScancode::Invalid, 138 | XTScancode::Invalid, 139 | 140 | // no mapping 141 | XTScancode::Invalid, 142 | XTScancode::Invalid, 143 | XTScancode::Invalid, 144 | XTScancode::Invalid, 145 | XTScancode::Invalid, 146 | XTScancode::Invalid, 147 | XTScancode::Invalid, 148 | XTScancode::Invalid, 149 | XTScancode::Invalid, 150 | XTScancode::Invalid, 151 | XTScancode::Invalid, 152 | XTScancode::Invalid, 153 | XTScancode::Invalid, 154 | XTScancode::Invalid, 155 | XTScancode::Invalid, 156 | XTScancode::Invalid, 157 | XTScancode::Invalid, 158 | 159 | XTScancode::KPComma, 160 | XTScancode::Invalid, 161 | 162 | XTScancode::International1, 163 | XTScancode::International2, 164 | XTScancode::International3, 165 | XTScancode::International4, 166 | XTScancode::International5, 167 | XTScancode::International6, 168 | XTScancode::Invalid, // ...7 169 | XTScancode::Invalid, // ...8 170 | XTScancode::Invalid, // ...9 171 | XTScancode::Lang1, 172 | XTScancode::Lang2, 173 | XTScancode::Lang3, 174 | XTScancode::Lang4, 175 | XTScancode::Lang5, 176 | }; 177 | 178 | static const XTScancode modMap[] 179 | { 180 | XTScancode::LeftCtrl, 181 | XTScancode::LeftShift, 182 | XTScancode::LeftAlt, 183 | XTScancode::Invalid, // LeftGUI 184 | XTScancode::RightCtrl, 185 | XTScancode::RightShift, 186 | XTScancode::RightAlt, 187 | XTScancode::Invalid, // RightGUI 188 | }; 189 | 190 | static uint8_t lastKeys[6]{0, 0, 0, 0, 0}; 191 | static uint8_t lastKeyMod = 0; 192 | 193 | void update_key_state(XTScancode code, bool state); 194 | void update_mouse_state(int8_t x, int8_t y, bool left, bool right); 195 | 196 | void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_report, uint16_t desc_len) 197 | { 198 | // request report if it's a keyboard/mouse 199 | auto protocol = tuh_hid_interface_protocol(dev_addr, instance); 200 | 201 | if(protocol == HID_ITF_PROTOCOL_KEYBOARD || protocol == HID_ITF_PROTOCOL_MOUSE) 202 | tuh_hid_receive_report(dev_addr, instance); 203 | } 204 | 205 | void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len) 206 | { 207 | auto protocol = tuh_hid_interface_protocol(dev_addr, instance); 208 | 209 | if(protocol == HID_ITF_PROTOCOL_KEYBOARD) 210 | { 211 | auto keyboardReport = (hid_keyboard_report_t const*) report; 212 | 213 | // check for new keys down 214 | for(int i = 0; i < 6 && keyboardReport->keycode[i]; i++) 215 | { 216 | auto key = keyboardReport->keycode[i]; 217 | bool found = false; 218 | for(int j = 0; j < 6 && lastKeys[j] && !found; j++) 219 | found = lastKeys[j] == key; 220 | 221 | if(found) 222 | continue; 223 | 224 | if(key < std::size(scancodeMap) && scancodeMap[key] != XTScancode::Invalid) 225 | update_key_state(scancodeMap[key], true); 226 | else 227 | printf("key down %i %i\n", i, key); 228 | } 229 | 230 | // do the reverse and check for released keys 231 | for(int i = 0; i < 6 && lastKeys[i]; i++) 232 | { 233 | auto key = lastKeys[i]; 234 | bool found = false; 235 | for(int j = 0; j < 6 && keyboardReport->keycode[j] && !found; j++) 236 | found = keyboardReport->keycode[j] == key; 237 | 238 | if(found) 239 | continue; 240 | 241 | if(key < std::size(scancodeMap) && scancodeMap[key] != XTScancode::Invalid) 242 | update_key_state(scancodeMap[key], false); 243 | else 244 | printf("key up %i %i\n", i, key); 245 | } 246 | 247 | // ...and mods 248 | auto changedMods = lastKeyMod ^ keyboardReport->modifier; 249 | auto pressedMods = changedMods & keyboardReport->modifier; 250 | auto releasedMods = changedMods ^ pressedMods; 251 | 252 | for(int i = 0; i < 8; i++) 253 | { 254 | if(modMap[i] == XTScancode::Invalid) 255 | continue; 256 | 257 | if(pressedMods & (1 << i)) 258 | update_key_state(modMap[i], true); 259 | else if(releasedMods & (1 << i)) 260 | update_key_state(modMap[i], false); 261 | } 262 | 263 | memcpy(lastKeys, keyboardReport->keycode, 6); 264 | lastKeyMod = keyboardReport->modifier; 265 | 266 | tuh_hid_receive_report(dev_addr, instance); 267 | } 268 | else if(protocol == HID_ITF_PROTOCOL_MOUSE) 269 | { 270 | auto mouseReport = (hid_mouse_report_t const*) report; 271 | 272 | update_mouse_state(mouseReport->x, mouseReport->y, mouseReport->buttons & MOUSE_BUTTON_LEFT, mouseReport->buttons & MOUSE_BUTTON_RIGHT); 273 | 274 | tuh_hid_receive_report(dev_addr, instance); 275 | } 276 | } -------------------------------------------------------------------------------- /pico-shared/fatfs/diskio.h: -------------------------------------------------------------------------------- 1 | /*-----------------------------------------------------------------------/ 2 | / Low level disk interface modlue include file (C)ChaN, 2019 / 3 | /-----------------------------------------------------------------------*/ 4 | 5 | #ifndef _DISKIO_DEFINED 6 | #define _DISKIO_DEFINED 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif 11 | 12 | /* Status of Disk Functions */ 13 | typedef BYTE DSTATUS; 14 | 15 | /* Results of Disk Functions */ 16 | typedef enum { 17 | RES_OK = 0, /* 0: Successful */ 18 | RES_ERROR, /* 1: R/W Error */ 19 | RES_WRPRT, /* 2: Write Protected */ 20 | RES_NOTRDY, /* 3: Not Ready */ 21 | RES_PARERR /* 4: Invalid Parameter */ 22 | } DRESULT; 23 | 24 | 25 | /*---------------------------------------*/ 26 | /* Prototypes for disk control functions */ 27 | 28 | 29 | DSTATUS disk_initialize (BYTE pdrv); 30 | DSTATUS disk_status (BYTE pdrv); 31 | DRESULT disk_read (BYTE pdrv, BYTE* buff, LBA_t sector, UINT count); 32 | DRESULT disk_write (BYTE pdrv, const BYTE* buff, LBA_t sector, UINT count); 33 | DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff); 34 | 35 | 36 | /* Disk Status Bits (DSTATUS) */ 37 | 38 | #define STA_NOINIT 0x01 /* Drive not initialized */ 39 | #define STA_NODISK 0x02 /* No medium in the drive */ 40 | #define STA_PROTECT 0x04 /* Write protected */ 41 | 42 | 43 | /* Command code for disk_ioctrl fucntion */ 44 | 45 | /* Generic command (Used by FatFs) */ 46 | #define CTRL_SYNC 0 /* Complete pending write process (needed at FF_FS_READONLY == 0) */ 47 | #define GET_SECTOR_COUNT 1 /* Get media size (needed at FF_USE_MKFS == 1) */ 48 | #define GET_SECTOR_SIZE 2 /* Get sector size (needed at FF_MAX_SS != FF_MIN_SS) */ 49 | #define GET_BLOCK_SIZE 3 /* Get erase block size (needed at FF_USE_MKFS == 1) */ 50 | #define CTRL_TRIM 4 /* Inform device that the data on the block of sectors is no longer used (needed at FF_USE_TRIM == 1) */ 51 | 52 | /* Generic command (Not used by FatFs) */ 53 | #define CTRL_POWER 5 /* Get/Set power status */ 54 | #define CTRL_LOCK 6 /* Lock/Unlock media removal */ 55 | #define CTRL_EJECT 7 /* Eject media */ 56 | #define CTRL_FORMAT 8 /* Create physical format on the media */ 57 | 58 | /* MMC/SDC specific ioctl command */ 59 | #define MMC_GET_TYPE 10 /* Get card type */ 60 | #define MMC_GET_CSD 11 /* Get CSD */ 61 | #define MMC_GET_CID 12 /* Get CID */ 62 | #define MMC_GET_OCR 13 /* Get OCR */ 63 | #define MMC_GET_SDSTAT 14 /* Get SD status */ 64 | #define ISDIO_READ 55 /* Read data form SD iSDIO register */ 65 | #define ISDIO_WRITE 56 /* Write data to SD iSDIO register */ 66 | #define ISDIO_MRITE 57 /* Masked write data to SD iSDIO register */ 67 | 68 | /* ATA/CF specific ioctl command */ 69 | #define ATA_GET_REV 20 /* Get F/W revision */ 70 | #define ATA_GET_MODEL 21 /* Get model name */ 71 | #define ATA_GET_SN 22 /* Get serial number */ 72 | 73 | #ifdef __cplusplus 74 | } 75 | #endif 76 | 77 | #endif 78 | -------------------------------------------------------------------------------- /pico-shared/fatfs/ffsystem.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------*/ 2 | /* Sample Code of OS Dependent Functions for FatFs */ 3 | /* (C)ChaN, 2018 */ 4 | /*------------------------------------------------------------------------*/ 5 | 6 | 7 | #include "ff.h" 8 | 9 | 10 | #if FF_USE_LFN == 3 /* Dynamic memory allocation */ 11 | 12 | /*------------------------------------------------------------------------*/ 13 | /* Allocate a memory block */ 14 | /*------------------------------------------------------------------------*/ 15 | 16 | void* ff_memalloc ( /* Returns pointer to the allocated memory block (null if not enough core) */ 17 | UINT msize /* Number of bytes to allocate */ 18 | ) 19 | { 20 | return malloc(msize); /* Allocate a new memory block with POSIX API */ 21 | } 22 | 23 | 24 | /*------------------------------------------------------------------------*/ 25 | /* Free a memory block */ 26 | /*------------------------------------------------------------------------*/ 27 | 28 | void ff_memfree ( 29 | void* mblock /* Pointer to the memory block to free (nothing to do if null) */ 30 | ) 31 | { 32 | free(mblock); /* Free the memory block with POSIX API */ 33 | } 34 | 35 | #endif 36 | 37 | 38 | 39 | #if FF_FS_REENTRANT /* Mutal exclusion */ 40 | 41 | /*------------------------------------------------------------------------*/ 42 | /* Create a Synchronization Object */ 43 | /*------------------------------------------------------------------------*/ 44 | /* This function is called in f_mount() function to create a new 45 | / synchronization object for the volume, such as semaphore and mutex. 46 | / When a 0 is returned, the f_mount() function fails with FR_INT_ERR. 47 | */ 48 | 49 | //const osMutexDef_t Mutex[FF_VOLUMES]; /* Table of CMSIS-RTOS mutex */ 50 | 51 | 52 | int ff_cre_syncobj ( /* 1:Function succeeded, 0:Could not create the sync object */ 53 | BYTE vol, /* Corresponding volume (logical drive number) */ 54 | FF_SYNC_t* sobj /* Pointer to return the created sync object */ 55 | ) 56 | { 57 | /* Win32 */ 58 | *sobj = CreateMutex(NULL, FALSE, NULL); 59 | return (int)(*sobj != INVALID_HANDLE_VALUE); 60 | 61 | /* uITRON */ 62 | // T_CSEM csem = {TA_TPRI,1,1}; 63 | // *sobj = acre_sem(&csem); 64 | // return (int)(*sobj > 0); 65 | 66 | /* uC/OS-II */ 67 | // OS_ERR err; 68 | // *sobj = OSMutexCreate(0, &err); 69 | // return (int)(err == OS_NO_ERR); 70 | 71 | /* FreeRTOS */ 72 | // *sobj = xSemaphoreCreateMutex(); 73 | // return (int)(*sobj != NULL); 74 | 75 | /* CMSIS-RTOS */ 76 | // *sobj = osMutexCreate(&Mutex[vol]); 77 | // return (int)(*sobj != NULL); 78 | } 79 | 80 | 81 | /*------------------------------------------------------------------------*/ 82 | /* Delete a Synchronization Object */ 83 | /*------------------------------------------------------------------------*/ 84 | /* This function is called in f_mount() function to delete a synchronization 85 | / object that created with ff_cre_syncobj() function. When a 0 is returned, 86 | / the f_mount() function fails with FR_INT_ERR. 87 | */ 88 | 89 | int ff_del_syncobj ( /* 1:Function succeeded, 0:Could not delete due to an error */ 90 | FF_SYNC_t sobj /* Sync object tied to the logical drive to be deleted */ 91 | ) 92 | { 93 | /* Win32 */ 94 | return (int)CloseHandle(sobj); 95 | 96 | /* uITRON */ 97 | // return (int)(del_sem(sobj) == E_OK); 98 | 99 | /* uC/OS-II */ 100 | // OS_ERR err; 101 | // OSMutexDel(sobj, OS_DEL_ALWAYS, &err); 102 | // return (int)(err == OS_NO_ERR); 103 | 104 | /* FreeRTOS */ 105 | // vSemaphoreDelete(sobj); 106 | // return 1; 107 | 108 | /* CMSIS-RTOS */ 109 | // return (int)(osMutexDelete(sobj) == osOK); 110 | } 111 | 112 | 113 | /*------------------------------------------------------------------------*/ 114 | /* Request Grant to Access the Volume */ 115 | /*------------------------------------------------------------------------*/ 116 | /* This function is called on entering file functions to lock the volume. 117 | / When a 0 is returned, the file function fails with FR_TIMEOUT. 118 | */ 119 | 120 | int ff_req_grant ( /* 1:Got a grant to access the volume, 0:Could not get a grant */ 121 | FF_SYNC_t sobj /* Sync object to wait */ 122 | ) 123 | { 124 | /* Win32 */ 125 | return (int)(WaitForSingleObject(sobj, FF_FS_TIMEOUT) == WAIT_OBJECT_0); 126 | 127 | /* uITRON */ 128 | // return (int)(wai_sem(sobj) == E_OK); 129 | 130 | /* uC/OS-II */ 131 | // OS_ERR err; 132 | // OSMutexPend(sobj, FF_FS_TIMEOUT, &err)); 133 | // return (int)(err == OS_NO_ERR); 134 | 135 | /* FreeRTOS */ 136 | // return (int)(xSemaphoreTake(sobj, FF_FS_TIMEOUT) == pdTRUE); 137 | 138 | /* CMSIS-RTOS */ 139 | // return (int)(osMutexWait(sobj, FF_FS_TIMEOUT) == osOK); 140 | } 141 | 142 | 143 | /*------------------------------------------------------------------------*/ 144 | /* Release Grant to Access the Volume */ 145 | /*------------------------------------------------------------------------*/ 146 | /* This function is called on leaving file functions to unlock the volume. 147 | */ 148 | 149 | void ff_rel_grant ( 150 | FF_SYNC_t sobj /* Sync object to be signaled */ 151 | ) 152 | { 153 | /* Win32 */ 154 | ReleaseMutex(sobj); 155 | 156 | /* uITRON */ 157 | // sig_sem(sobj); 158 | 159 | /* uC/OS-II */ 160 | // OSMutexPost(sobj); 161 | 162 | /* FreeRTOS */ 163 | // xSemaphoreGive(sobj); 164 | 165 | /* CMSIS-RTOS */ 166 | // osMutexRelease(sobj); 167 | } 168 | 169 | #endif 170 | 171 | -------------------------------------------------------------------------------- /pico-shared/ffconf.h: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------------------/ 2 | / FatFs Functional Configurations 3 | /---------------------------------------------------------------------------*/ 4 | 5 | #define FFCONF_DEF 86631 /* Revision ID */ 6 | 7 | /*---------------------------------------------------------------------------/ 8 | / Function Configurations 9 | /---------------------------------------------------------------------------*/ 10 | 11 | #define FF_FS_READONLY 0 12 | /* This option switches read-only configuration. (0:Read/Write or 1:Read-only) 13 | / Read-only configuration removes writing API functions, f_write(), f_sync(), 14 | / f_unlink(), f_mkdir(), f_chmod(), f_rename(), f_truncate(), f_getfree() 15 | / and optional writing functions as well. */ 16 | 17 | 18 | #define FF_FS_MINIMIZE 0 19 | /* This option defines minimization level to remove some basic API functions. 20 | / 21 | / 0: Basic functions are fully enabled. 22 | / 1: f_stat(), f_getfree(), f_unlink(), f_mkdir(), f_truncate() and f_rename() 23 | / are removed. 24 | / 2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1. 25 | / 3: f_lseek() function is removed in addition to 2. */ 26 | 27 | 28 | #define FF_USE_FIND 0 29 | /* This option switches filtered directory read functions, f_findfirst() and 30 | / f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */ 31 | 32 | 33 | #define FF_USE_MKFS 1 34 | /* This option switches f_mkfs() function. (0:Disable or 1:Enable) */ 35 | 36 | 37 | #define FF_USE_FASTSEEK 0 38 | /* This option switches fast seek function. (0:Disable or 1:Enable) */ 39 | 40 | 41 | #define FF_USE_EXPAND 0 42 | /* This option switches f_expand function. (0:Disable or 1:Enable) */ 43 | 44 | 45 | #define FF_USE_CHMOD 0 46 | /* This option switches attribute manipulation functions, f_chmod() and f_utime(). 47 | / (0:Disable or 1:Enable) Also FF_FS_READONLY needs to be 0 to enable this option. */ 48 | 49 | 50 | #define FF_USE_LABEL 0 51 | /* This option switches volume label functions, f_getlabel() and f_setlabel(). 52 | / (0:Disable or 1:Enable) */ 53 | 54 | 55 | #define FF_USE_FORWARD 0 56 | /* This option switches f_forward() function. (0:Disable or 1:Enable) */ 57 | 58 | 59 | #define FF_USE_STRFUNC 0 60 | #define FF_PRINT_LLI 0 61 | #define FF_PRINT_FLOAT 0 62 | #define FF_STRF_ENCODE 0 63 | /* FF_USE_STRFUNC switches string functions, f_gets(), f_putc(), f_puts() and 64 | / f_printf(). 65 | / 66 | / 0: Disable. FF_PRINT_LLI, FF_PRINT_FLOAT and FF_STRF_ENCODE have no effect. 67 | / 1: Enable without LF-CRLF conversion. 68 | / 2: Enable with LF-CRLF conversion. 69 | / 70 | / FF_PRINT_LLI = 1 makes f_printf() support long long argument and FF_PRINT_FLOAT = 1/2 71 | makes f_printf() support floating point argument. These features want C99 or later. 72 | / When FF_LFN_UNICODE >= 1 with LFN enabled, string functions convert the character 73 | / encoding in it. FF_STRF_ENCODE selects assumption of character encoding ON THE FILE 74 | / to be read/written via those functions. 75 | / 76 | / 0: ANSI/OEM in current CP 77 | / 1: Unicode in UTF-16LE 78 | / 2: Unicode in UTF-16BE 79 | / 3: Unicode in UTF-8 80 | */ 81 | 82 | 83 | /*---------------------------------------------------------------------------/ 84 | / Locale and Namespace Configurations 85 | /---------------------------------------------------------------------------*/ 86 | 87 | #define FF_CODE_PAGE 850 88 | /* This option specifies the OEM code page to be used on the target system. 89 | / Incorrect code page setting can cause a file open failure. 90 | / 91 | / 437 - U.S. 92 | / 720 - Arabic 93 | / 737 - Greek 94 | / 771 - KBL 95 | / 775 - Baltic 96 | / 850 - Latin 1 97 | / 852 - Latin 2 98 | / 855 - Cyrillic 99 | / 857 - Turkish 100 | / 860 - Portuguese 101 | / 861 - Icelandic 102 | / 862 - Hebrew 103 | / 863 - Canadian French 104 | / 864 - Arabic 105 | / 865 - Nordic 106 | / 866 - Russian 107 | / 869 - Greek 2 108 | / 932 - Japanese (DBCS) 109 | / 936 - Simplified Chinese (DBCS) 110 | / 949 - Korean (DBCS) 111 | / 950 - Traditional Chinese (DBCS) 112 | / 0 - Include all code pages above and configured by f_setcp() 113 | */ 114 | 115 | 116 | #define FF_USE_LFN 1 117 | #define FF_MAX_LFN 255 118 | /* The FF_USE_LFN switches the support for LFN (long file name). 119 | / 120 | / 0: Disable LFN. FF_MAX_LFN has no effect. 121 | / 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe. 122 | / 2: Enable LFN with dynamic working buffer on the STACK. 123 | / 3: Enable LFN with dynamic working buffer on the HEAP. 124 | / 125 | / To enable the LFN, ffunicode.c needs to be added to the project. The LFN function 126 | / requiers certain internal working buffer occupies (FF_MAX_LFN + 1) * 2 bytes and 127 | / additional (FF_MAX_LFN + 44) / 15 * 32 bytes when exFAT is enabled. 128 | / The FF_MAX_LFN defines size of the working buffer in UTF-16 code unit and it can 129 | / be in range of 12 to 255. It is recommended to be set it 255 to fully support LFN 130 | / specification. 131 | / When use stack for the working buffer, take care on stack overflow. When use heap 132 | / memory for the working buffer, memory management functions, ff_memalloc() and 133 | / ff_memfree() exemplified in ffsystem.c, need to be added to the project. */ 134 | 135 | 136 | #define FF_LFN_UNICODE 0 137 | /* This option switches the character encoding on the API when LFN is enabled. 138 | / 139 | / 0: ANSI/OEM in current CP (TCHAR = char) 140 | / 1: Unicode in UTF-16 (TCHAR = WCHAR) 141 | / 2: Unicode in UTF-8 (TCHAR = char) 142 | / 3: Unicode in UTF-32 (TCHAR = DWORD) 143 | / 144 | / Also behavior of string I/O functions will be affected by this option. 145 | / When LFN is not enabled, this option has no effect. */ 146 | 147 | 148 | #define FF_LFN_BUF 255 149 | #define FF_SFN_BUF 12 150 | /* This set of options defines size of file name members in the FILINFO structure 151 | / which is used to read out directory items. These values should be suffcient for 152 | / the file names to read. The maximum possible length of the read file name depends 153 | / on character encoding. When LFN is not enabled, these options have no effect. */ 154 | 155 | 156 | #define FF_FS_RPATH 0 157 | /* This option configures support for relative path. 158 | / 159 | / 0: Disable relative path and remove related functions. 160 | / 1: Enable relative path. f_chdir() and f_chdrive() are available. 161 | / 2: f_getcwd() function is available in addition to 1. 162 | */ 163 | 164 | 165 | /*---------------------------------------------------------------------------/ 166 | / Drive/Volume Configurations 167 | /---------------------------------------------------------------------------*/ 168 | 169 | #define FF_VOLUMES 1 170 | /* Number of volumes (logical drives) to be used. (1-10) */ 171 | 172 | 173 | #define FF_STR_VOLUME_ID 0 174 | #define FF_VOLUME_STRS "RAM","NAND","CF","SD","SD2","USB","USB2","USB3" 175 | /* FF_STR_VOLUME_ID switches support for volume ID in arbitrary strings. 176 | / When FF_STR_VOLUME_ID is set to 1 or 2, arbitrary strings can be used as drive 177 | / number in the path name. FF_VOLUME_STRS defines the volume ID strings for each 178 | / logical drives. Number of items must not be less than FF_VOLUMES. Valid 179 | / characters for the volume ID strings are A-Z, a-z and 0-9, however, they are 180 | / compared in case-insensitive. If FF_STR_VOLUME_ID >= 1 and FF_VOLUME_STRS is 181 | / not defined, a user defined volume string table needs to be defined as: 182 | / 183 | / const char* VolumeStr[FF_VOLUMES] = {"ram","flash","sd","usb",... 184 | */ 185 | 186 | 187 | #define FF_MULTI_PARTITION 0 188 | /* This option switches support for multiple volumes on the physical drive. 189 | / By default (0), each logical drive number is bound to the same physical drive 190 | / number and only an FAT volume found on the physical drive will be mounted. 191 | / When this function is enabled (1), each logical drive number can be bound to 192 | / arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk() 193 | / funciton will be available. */ 194 | 195 | #define FF_MIN_SS 512 196 | #define FF_MAX_SS 512 197 | 198 | /* This set of options configures the range of sector size to be supported. (512, 199 | / 1024, 2048 or 4096) Always set both 512 for most systems, generic memory card and 200 | / harddisk, but a larger value may be required for on-board flash memory and some 201 | / type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is configured 202 | / for variable sector size mode and disk_ioctl() function needs to implement 203 | / GET_SECTOR_SIZE command. */ 204 | 205 | 206 | #define FF_LBA64 0 207 | /* This option switches support for 64-bit LBA. (0:Disable or 1:Enable) 208 | / To enable the 64-bit LBA, also exFAT needs to be enabled. (FF_FS_EXFAT == 1) */ 209 | 210 | 211 | #define FF_MIN_GPT 0x10000000 212 | /* Minimum number of sectors to switch GPT as partitioning format in f_mkfs and 213 | / f_fdisk function. 0x100000000 max. This option has no effect when FF_LBA64 == 0. */ 214 | 215 | 216 | #define FF_USE_TRIM 0 217 | /* This option switches support for ATA-TRIM. (0:Disable or 1:Enable) 218 | / To enable Trim function, also CTRL_TRIM command should be implemented to the 219 | / disk_ioctl() function. */ 220 | 221 | 222 | 223 | /*---------------------------------------------------------------------------/ 224 | / System Configurations 225 | /---------------------------------------------------------------------------*/ 226 | 227 | #define FF_FS_TINY 1 228 | /* This option switches tiny buffer configuration. (0:Normal or 1:Tiny) 229 | / At the tiny configuration, size of file object (FIL) is shrinked FF_MAX_SS bytes. 230 | / Instead of private sector buffer eliminated from the file object, common sector 231 | / buffer in the filesystem object (FATFS) is used for the file data transfer. */ 232 | 233 | 234 | #define FF_FS_EXFAT 0 235 | /* This option switches support for exFAT filesystem. (0:Disable or 1:Enable) 236 | / To enable exFAT, also LFN needs to be enabled. (FF_USE_LFN >= 1) 237 | / Note that enabling exFAT discards ANSI C (C89) compatibility. */ 238 | 239 | 240 | #define FF_FS_NORTC 1 241 | #define FF_NORTC_MON 1 242 | #define FF_NORTC_MDAY 1 243 | #define FF_NORTC_YEAR 2020 244 | /* The option FF_FS_NORTC switches timestamp functiton. If the system does not have 245 | / any RTC function or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable 246 | / the timestamp function. Every object modified by FatFs will have a fixed timestamp 247 | / defined by FF_NORTC_MON, FF_NORTC_MDAY and FF_NORTC_YEAR in local time. 248 | / To enable timestamp function (FF_FS_NORTC = 0), get_fattime() function need to be 249 | / added to the project to read current time form real-time clock. FF_NORTC_MON, 250 | / FF_NORTC_MDAY and FF_NORTC_YEAR have no effect. 251 | / These options have no effect in read-only configuration (FF_FS_READONLY = 1). */ 252 | 253 | 254 | #define FF_FS_NOFSINFO 0 255 | /* If you need to know correct free space on the FAT32 volume, set bit 0 of this 256 | / option, and f_getfree() function at first time after volume mount will force 257 | / a full FAT scan. Bit 1 controls the use of last allocated cluster number. 258 | / 259 | / bit0=0: Use free cluster count in the FSINFO if available. 260 | / bit0=1: Do not trust free cluster count in the FSINFO. 261 | / bit1=0: Use last allocated cluster number in the FSINFO if available. 262 | / bit1=1: Do not trust last allocated cluster number in the FSINFO. 263 | */ 264 | 265 | 266 | #define FF_FS_LOCK 2 267 | /* The option FF_FS_LOCK switches file lock function to control duplicated file open 268 | / and illegal operation to open objects. This option must be 0 when FF_FS_READONLY 269 | / is 1. 270 | / 271 | / 0: Disable file lock function. To avoid volume corruption, application program 272 | / should avoid illegal open, remove and rename to the open objects. 273 | / >0: Enable file lock function. The value defines how many files/sub-directories 274 | / can be opened simultaneously under file lock control. Note that the file 275 | / lock control is independent of re-entrancy. */ 276 | 277 | 278 | /* #include // O/S definitions */ 279 | #define FF_FS_REENTRANT 0 280 | #define FF_FS_TIMEOUT 1000 281 | #define FF_SYNC_t HANDLE 282 | /* The option FF_FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs 283 | / module itself. Note that regardless of this option, file access to different 284 | / volume is always re-entrant and volume control functions, f_mount(), f_mkfs() 285 | / and f_fdisk() function, are always not re-entrant. Only file/directory access 286 | / to the same volume is under control of this function. 287 | / 288 | / 0: Disable re-entrancy. FF_FS_TIMEOUT and FF_SYNC_t have no effect. 289 | / 1: Enable re-entrancy. Also user provided synchronization handlers, 290 | / ff_req_grant(), ff_rel_grant(), ff_del_syncobj() and ff_cre_syncobj() 291 | / function, must be added to the project. Samples are available in 292 | / option/syscall.c. 293 | / 294 | / The FF_FS_TIMEOUT defines timeout period in unit of time tick. 295 | / The FF_SYNC_t defines O/S dependent sync object type. e.g. HANDLE, ID, OS_EVENT*, 296 | / SemaphoreHandle_t and etc. A header file for O/S definitions needs to be 297 | / included somewhere in the scope of ff.h. */ 298 | 299 | 300 | 301 | /*--- End of configuration options ---*/ -------------------------------------------------------------------------------- /pico-shared/spi.pio: -------------------------------------------------------------------------------- 1 | .program spi_cpha0 2 | .side_set 1 3 | out pins, 1 side 0 4 | in pins, 1 side 1 -------------------------------------------------------------------------------- /pico-shared/tusb_config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2019 Ha Thach (tinyusb.org) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | * 24 | */ 25 | 26 | #ifndef _TUSB_CONFIG_H_ 27 | #define _TUSB_CONFIG_H_ 28 | 29 | #ifdef __cplusplus 30 | extern "C" { 31 | #endif 32 | 33 | //-------------------------------------------------------------------- 34 | // COMMON CONFIGURATION 35 | //-------------------------------------------------------------------- 36 | 37 | // defined by board.mk 38 | #ifndef CFG_TUSB_MCU 39 | #error CFG_TUSB_MCU must be defined 40 | #endif 41 | 42 | // RHPort number used for device can be defined by board.mk, default to port 0 43 | #ifndef BOARD_DEVICE_RHPORT_NUM 44 | #define BOARD_DEVICE_RHPORT_NUM 0 45 | #endif 46 | 47 | // RHPort max operational speed can defined by board.mk 48 | // Default to Highspeed for MCU with internal HighSpeed PHY (can be port specific), otherwise FullSpeed 49 | #ifndef BOARD_DEVICE_RHPORT_SPEED 50 | #if (CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX || \ 51 | CFG_TUSB_MCU == OPT_MCU_NUC505 || CFG_TUSB_MCU == OPT_MCU_CXD56) 52 | #define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_HIGH_SPEED 53 | #else 54 | #define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_FULL_SPEED 55 | #endif 56 | #endif 57 | 58 | // Device mode with rhport and speed defined by board.mk 59 | #if BOARD_DEVICE_RHPORT_NUM == 0 60 | #define CFG_TUSB_RHPORT0_MODE (OPT_MODE_HOST | BOARD_DEVICE_RHPORT_SPEED) 61 | #elif BOARD_DEVICE_RHPORT_NUM == 1 62 | #define CFG_TUSB_RHPORT1_MODE (OPT_MODE_HOST | BOARD_DEVICE_RHPORT_SPEED) 63 | #else 64 | #error "Incorrect RHPort configuration" 65 | #endif 66 | 67 | 68 | // This example doesn't use an RTOS 69 | #ifndef CFG_TUSB_OS 70 | #define CFG_TUSB_OS OPT_OS_NONE 71 | #endif 72 | 73 | // CFG_TUSB_DEBUG is defined by compiler in DEBUG build 74 | // #define CFG_TUSB_DEBUG 0 75 | 76 | /* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. 77 | * Tinyusb use follows macros to declare transferring memory so that they can be put 78 | * into those specific section. 79 | * e.g 80 | * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) 81 | * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) 82 | */ 83 | #ifndef CFG_TUSB_MEM_SECTION 84 | #define CFG_TUSB_MEM_SECTION 85 | #endif 86 | 87 | #ifndef CFG_TUSB_MEM_ALIGN 88 | #define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) 89 | #endif 90 | 91 | //-------------------------------------------------------------------- 92 | // DEVICE CONFIGURATION 93 | //-------------------------------------------------------------------- 94 | 95 | 96 | // Size of buffer to hold descriptors and other data used for enumeration 97 | #define CFG_TUH_ENUMERATION_BUFSIZE 256 98 | 99 | #define CFG_TUH_HUB 1 100 | #define CFG_TUH_CDC 1 101 | #define CFG_TUH_HID 4 // typical keyboard + mouse device can have 3-4 HID interfaces 102 | #define CFG_TUH_MSC 0 103 | #define CFG_TUH_VENDOR 0 104 | 105 | // max device support (excluding hub device) 106 | // 1 hub typically has 4 ports 107 | #define CFG_TUH_DEVICE_MAX (CFG_TUH_HUB ? 4 : 1) 108 | 109 | //------------- HID -------------// 110 | 111 | #define CFG_TUH_HID_EP_BUFSIZE 64 112 | 113 | 114 | #ifdef __cplusplus 115 | } 116 | #endif 117 | 118 | #endif /* _TUSB_CONFIG_H_ */ -------------------------------------------------------------------------------- /pico2/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(PACEPico2 2 | Display.cpp 3 | Main.cpp 4 | 5 | psram.c 6 | ) 7 | 8 | target_compile_definitions(PACEPico2 PRIVATE PICO_EMBED_XIP_SETUP=1) 9 | target_include_directories(PACEPico2 PRIVATE ${CMAKE_CURRENT_LIST_DIR}) 10 | target_link_libraries(PACEPico2 PACECore PACEPicoShared PACEPicoBIOS hardware_dma pico_multicore pico_stdlib) 11 | 12 | pico_enable_stdio_uart(PACEPico2 1) 13 | pico_add_extra_outputs(PACEPico2) 14 | 15 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/PACEPico2.uf2 16 | DESTINATION bin 17 | ) -------------------------------------------------------------------------------- /pico2/Display.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "hardware/clocks.h" 4 | #include "hardware/dma.h" 5 | #include "hardware/gpio.h" 6 | #include "hardware/irq.h" 7 | #include "hardware/resets.h" 8 | #include "hardware/structs/hstx_ctrl.h" 9 | #include "hardware/structs/hstx_fifo.h" 10 | 11 | #include "Display.h" 12 | 13 | #include "config.h" 14 | 15 | // DVI constants 16 | 17 | #define TMDS_CTRL_00 0x354u 18 | #define TMDS_CTRL_01 0x0abu 19 | #define TMDS_CTRL_10 0x154u 20 | #define TMDS_CTRL_11 0x2abu 21 | 22 | #define SYNC_V0_H0 (TMDS_CTRL_00 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20)) 23 | #define SYNC_V0_H1 (TMDS_CTRL_01 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20)) 24 | #define SYNC_V1_H0 (TMDS_CTRL_10 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20)) 25 | #define SYNC_V1_H1 (TMDS_CTRL_11 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20)) 26 | 27 | // mode 28 | // active area needs to be consistent with (2x) DISPLAY_WIDTH/_HEIGHT 29 | //#define MODE_H_SYNC_POLARITY 0 // unused, assumed to be active-low 30 | #define MODE_H_FRONT_PORCH 16 31 | #define MODE_H_SYNC_WIDTH 96 32 | #define MODE_H_BACK_PORCH 48 33 | #define MODE_H_ACTIVE_PIXELS 640 34 | 35 | //#define MODE_V_SYNC_POLARITY 0 36 | #define MODE_V_FRONT_PORCH 10 37 | #define MODE_V_SYNC_WIDTH 2 38 | #define MODE_V_BACK_PORCH 33 39 | #define MODE_V_ACTIVE_LINES 480 40 | 41 | /* 42 | #define MODE_H_TOTAL_PIXELS ( \ 43 | MODE_H_FRONT_PORCH + MODE_H_SYNC_WIDTH + \ 44 | MODE_H_BACK_PORCH + MODE_H_ACTIVE_PIXELS \ 45 | )*/ 46 | #define MODE_V_TOTAL_LINES ( \ 47 | MODE_V_FRONT_PORCH + MODE_V_SYNC_WIDTH + \ 48 | MODE_V_BACK_PORCH + MODE_V_ACTIVE_LINES \ 49 | ) 50 | 51 | #define HSTX_CMD_RAW (0x0u << 12) 52 | #define HSTX_CMD_RAW_REPEAT (0x1u << 12) 53 | #define HSTX_CMD_TMDS (0x2u << 12) 54 | #define HSTX_CMD_TMDS_REPEAT (0x3u << 12) 55 | #define HSTX_CMD_NOP (0xfu << 12) 56 | 57 | // HSTX command lists 58 | 59 | // Lists are padded with NOPs to be >= HSTX FIFO size, to avoid DMA rapidly 60 | // pingponging and tripping up the IRQs. 61 | 62 | static uint32_t vblank_line_vsync_off[] = { 63 | HSTX_CMD_RAW_REPEAT | MODE_H_FRONT_PORCH, 64 | SYNC_V1_H1, 65 | HSTX_CMD_RAW_REPEAT | MODE_H_SYNC_WIDTH, 66 | SYNC_V1_H0, 67 | HSTX_CMD_RAW_REPEAT | (MODE_H_BACK_PORCH + MODE_H_ACTIVE_PIXELS), 68 | SYNC_V1_H1, 69 | HSTX_CMD_NOP 70 | }; 71 | 72 | static uint32_t vblank_line_vsync_on[] = { 73 | HSTX_CMD_RAW_REPEAT | MODE_H_FRONT_PORCH, 74 | SYNC_V0_H1, 75 | HSTX_CMD_RAW_REPEAT | MODE_H_SYNC_WIDTH, 76 | SYNC_V0_H0, 77 | HSTX_CMD_RAW_REPEAT | (MODE_H_BACK_PORCH + MODE_H_ACTIVE_PIXELS), 78 | SYNC_V0_H1, 79 | HSTX_CMD_NOP, 80 | }; 81 | 82 | static const uint32_t vactive_line[] = { 83 | HSTX_CMD_RAW_REPEAT | MODE_H_FRONT_PORCH, 84 | SYNC_V1_H1, 85 | HSTX_CMD_RAW_REPEAT | MODE_H_SYNC_WIDTH, 86 | SYNC_V1_H0, 87 | HSTX_CMD_RAW_REPEAT | MODE_H_BACK_PORCH, 88 | SYNC_V1_H1, 89 | HSTX_CMD_TMDS | MODE_H_ACTIVE_PIXELS 90 | }; 91 | 92 | // DMA logic 93 | 94 | #define HSTX_DMA_CH_BASE 0 95 | #define HSTX_NUM_DMA_CHANNELS 3 96 | 97 | static uint8_t cur_dma_ch = HSTX_DMA_CH_BASE; 98 | 99 | // pixel/line repeat 100 | static uint8_t h_shift = 0; 101 | static uint8_t new_h_shift = 0; 102 | 103 | static volatile uint v_scanline = HSTX_NUM_DMA_CHANNELS; 104 | static uint32_t in_scanline = 0, last_in_scanline = 1; 105 | static uint32_t in_scanline_step = 1 << 16; 106 | static uint32_t new_scanline_step = 0; 107 | 108 | static bool started = false; 109 | static volatile bool need_mode_change = false; 110 | static uint8_t framebuffer[640 * 200]; 111 | 112 | // temp buffer for expanding lines (pixel double) 113 | // two scanlines + include the cmdlist(s) so we can avoid an irq 114 | static uint32_t scanline_buffer[(MODE_H_ACTIVE_PIXELS * sizeof(uint16_t) + sizeof(vactive_line)) / sizeof(uint32_t) * 2]; 115 | 116 | static constexpr uint16_t col_565(uint8_t r, uint8_t g, uint8_t b) { 117 | return (r >> 3) | ((g >> 2) << 5) | ((b >> 3) << 11); 118 | } 119 | 120 | static uint16_t cga_palette[16] 121 | { 122 | col_565(0x00, 0x00, 0x00), // black 123 | col_565(0x00, 0x00, 0xAA), // blue 124 | col_565(0x00, 0xAA, 0x00), // green 125 | col_565(0x00, 0xAA, 0xAA), // cyan 126 | col_565(0xAA, 0x00, 0x00), // red 127 | col_565(0xAA, 0x00, 0xAA), // magenta 128 | col_565(0xAA, 0x55, 0x00), // brown 129 | col_565(0xAA, 0xAA, 0xAA), // light grey 130 | 131 | col_565(0x55, 0x55, 0x55), // dark grey 132 | col_565(0x55, 0x55, 0xFF), // light blue 133 | col_565(0x55, 0xFF, 0x55), // light green 134 | col_565(0x55, 0xFF, 0xFF), // light cyan 135 | col_565(0xFF, 0x55, 0x55), // light red 136 | col_565(0xFF, 0x55, 0xFF), // light magenta 137 | col_565(0xFF, 0xFF, 0x55), // yellow 138 | col_565(0xFF, 0xFF, 0xFF), // white 139 | }; 140 | 141 | static void __scratch_x("") dma_irq_handler() { 142 | // cur_dma_ch indicates the channel that just finished, which is the one 143 | // we're about to reload. 144 | dma_channel_hw_t *ch = &dma_hw->ch[cur_dma_ch]; 145 | dma_hw->intr = 1u << cur_dma_ch; 146 | 147 | if(cur_dma_ch + 1 == HSTX_DMA_CH_BASE + HSTX_NUM_DMA_CHANNELS) 148 | cur_dma_ch = HSTX_DMA_CH_BASE; 149 | else 150 | cur_dma_ch++; 151 | 152 | if (v_scanline >= MODE_V_FRONT_PORCH && v_scanline < (MODE_V_FRONT_PORCH + MODE_V_SYNC_WIDTH)) { 153 | ch->read_addr = (uintptr_t)vblank_line_vsync_on; 154 | } else if (v_scanline < MODE_V_FRONT_PORCH + MODE_V_SYNC_WIDTH + MODE_V_BACK_PORCH) { 155 | ch->read_addr = (uintptr_t)vblank_line_vsync_off; 156 | ch->transfer_count = std::size(vblank_line_vsync_off); 157 | } else { 158 | auto display_line = in_scanline >> 16; 159 | in_scanline += in_scanline_step; 160 | bool first = display_line != last_in_scanline; 161 | last_in_scanline = display_line; 162 | 163 | const auto line_buf_size_words = sizeof(scanline_buffer) / sizeof(uint32_t) / 2; 164 | auto temp_ptr = scanline_buffer + (display_line & 1) * line_buf_size_words; 165 | ch->read_addr = (uintptr_t)temp_ptr; 166 | 167 | ch->transfer_count = std::size(vactive_line) + (MODE_H_ACTIVE_PIXELS * 2) / sizeof(uint32_t); 168 | 169 | // expand line if needed 170 | if(first) { 171 | auto w = MODE_H_ACTIVE_PIXELS >> h_shift; 172 | auto fb_line_ptr = framebuffer + display_line * w; 173 | 174 | temp_ptr += std::size(vactive_line); 175 | // palette lookup 176 | if(h_shift == 0) { 177 | for(int i = 0; i < w / 2; i++) { 178 | auto px = *fb_line_ptr++; 179 | *temp_ptr++ = cga_palette[px & 0xF] | cga_palette[px >> 4] << 16; 180 | } 181 | } else { 182 | for(int i = 0; i < w / 2; i++) { 183 | auto px = *fb_line_ptr++; 184 | *temp_ptr++ = cga_palette[px & 0xF] | cga_palette[px & 0xF] << 16; 185 | *temp_ptr++ = cga_palette[px >> 4] | cga_palette[px >> 4] << 16; 186 | } 187 | } 188 | } 189 | } 190 | 191 | v_scanline++; 192 | 193 | if(v_scanline == MODE_V_TOTAL_LINES) { 194 | v_scanline = 0; 195 | in_scanline = 0; 196 | } else if(v_scanline == 2) { 197 | // new frame 198 | // wait until scanline 2 so that there are no active lines in progress 199 | 200 | // set h/v shift 201 | if(need_mode_change) { 202 | h_shift = new_h_shift; 203 | in_scanline_step = new_scanline_step; 204 | 205 | need_mode_change = false; 206 | new_h_shift = 0xFF; 207 | new_scanline_step = 0; 208 | } 209 | } 210 | } 211 | 212 | void init_display() { 213 | // reset HSTX to make sure it's in a good state 214 | reset_unreset_block_num_wait_blocking(RESET_HSTX); 215 | 216 | // divide down if we're overclocking (we probably are) 217 | #if OVERCLOCK_250 218 | clock_configure(clk_hstx, 0, CLOCKS_CLK_HSTX_CTRL_AUXSRC_VALUE_CLK_SYS, 250000000, 125000000); 219 | #endif 220 | 221 | // Configure HSTX's TMDS encoder for RGB565 222 | // (it starts from bit 7) 223 | hstx_ctrl_hw->expand_tmds = 224 | 4 << HSTX_CTRL_EXPAND_TMDS_L2_NBITS_LSB | // R 225 | 29 << HSTX_CTRL_EXPAND_TMDS_L2_ROT_LSB | // 226 | 5 << HSTX_CTRL_EXPAND_TMDS_L1_NBITS_LSB | // G 227 | 3 << HSTX_CTRL_EXPAND_TMDS_L1_ROT_LSB | // 228 | 4 << HSTX_CTRL_EXPAND_TMDS_L0_NBITS_LSB | // B 229 | 8 << HSTX_CTRL_EXPAND_TMDS_L0_ROT_LSB; // 230 | 231 | // Pixels (TMDS) come in 2 16-bit chunks. Control symbols (RAW) are an 232 | // entire 32-bit word. 233 | hstx_ctrl_hw->expand_shift = 234 | 2 << HSTX_CTRL_EXPAND_SHIFT_ENC_N_SHIFTS_LSB | 235 | 16 << HSTX_CTRL_EXPAND_SHIFT_ENC_SHIFT_LSB | 236 | 1 << HSTX_CTRL_EXPAND_SHIFT_RAW_N_SHIFTS_LSB | 237 | 0 << HSTX_CTRL_EXPAND_SHIFT_RAW_SHIFT_LSB; 238 | 239 | // Serial output config: clock period of 5 cycles, pop from command 240 | // expander every 5 cycles, shift the output shiftreg by 2 every cycle. 241 | hstx_ctrl_hw->csr = 0; 242 | hstx_ctrl_hw->csr = 243 | HSTX_CTRL_CSR_EXPAND_EN_BITS | 244 | 5u << HSTX_CTRL_CSR_CLKDIV_LSB | 245 | 5u << HSTX_CTRL_CSR_N_SHIFTS_LSB | 246 | 2u << HSTX_CTRL_CSR_SHIFT_LSB | 247 | HSTX_CTRL_CSR_EN_BITS; 248 | 249 | 250 | // HSTX outputs 0 through 7 appear on GPIO 12 through 19. 251 | constexpr int HSTX_FIRST_PIN = 12; 252 | // Pinout on Pico DVI sock: 253 | // 254 | // GP12 D0+ GP13 D0- 255 | // GP14 CK+ GP15 CK- 256 | // GP16 D2+ GP17 D2- 257 | // GP18 D1+ GP19 D1- 258 | 259 | // Assign clock pair to two neighbouring pins: 260 | int bit = DVI_CLK_P - HSTX_FIRST_PIN; 261 | hstx_ctrl_hw->bit[bit ] = HSTX_CTRL_BIT0_CLK_BITS; 262 | hstx_ctrl_hw->bit[bit ^ 1] = HSTX_CTRL_BIT0_CLK_BITS | HSTX_CTRL_BIT0_INV_BITS; 263 | 264 | for(uint lane = 0; lane < 3; ++lane) { 265 | // For each TMDS lane, assign it to the correct GPIO pair based on the 266 | // desired pinout: 267 | static const int lane_to_output_bit[3] = { 268 | DVI_D0_P - HSTX_FIRST_PIN, 269 | DVI_D1_P - HSTX_FIRST_PIN, 270 | DVI_D2_P - HSTX_FIRST_PIN 271 | }; 272 | int bit = lane_to_output_bit[lane]; 273 | // Output even bits during first half of each HSTX cycle, and odd bits 274 | // during second half. The shifter advances by two bits each cycle. 275 | uint32_t lane_data_sel_bits = 276 | (lane * 10 ) << HSTX_CTRL_BIT0_SEL_P_LSB | 277 | (lane * 10 + 1) << HSTX_CTRL_BIT0_SEL_N_LSB; 278 | // The two halves of each pair get identical data, but one pin is inverted. 279 | hstx_ctrl_hw->bit[bit ] = lane_data_sel_bits; 280 | hstx_ctrl_hw->bit[bit ^ 1] = lane_data_sel_bits | HSTX_CTRL_BIT0_INV_BITS; 281 | } 282 | 283 | for(int i = 12; i <= 19; ++i) 284 | gpio_set_function(i, GPIO_FUNC_HSTX); 285 | 286 | // All channels are set up identically, to transfer a whole scanline and 287 | // then chain to the next channel. Each time a channel finishes, we 288 | // reconfigure the one that just finished, meanwhile the next channel 289 | // is already making progress. 290 | 291 | for(int i = 0; i < HSTX_NUM_DMA_CHANNELS; i++) { 292 | dma_channel_claim(HSTX_DMA_CH_BASE + i); 293 | dma_channel_config c; 294 | c = dma_channel_get_default_config(HSTX_DMA_CH_BASE + i); 295 | 296 | int next_chan = i == (HSTX_NUM_DMA_CHANNELS - 1) ? 0 : i + 1; 297 | 298 | channel_config_set_chain_to(&c, HSTX_DMA_CH_BASE + next_chan); 299 | channel_config_set_dreq(&c, DREQ_HSTX); 300 | dma_channel_configure( 301 | HSTX_DMA_CH_BASE + i, 302 | &c, 303 | &hstx_fifo_hw->fifo, 304 | vblank_line_vsync_off, 305 | count_of(vblank_line_vsync_off), 306 | false 307 | ); 308 | } 309 | 310 | const unsigned chan_mask = (1 << HSTX_NUM_DMA_CHANNELS) - 1; 311 | 312 | dma_hw->ints0 = (chan_mask << HSTX_DMA_CH_BASE); 313 | dma_hw->inte0 = (chan_mask << HSTX_DMA_CH_BASE); 314 | irq_set_exclusive_handler(DMA_IRQ_0, dma_irq_handler); 315 | irq_set_enabled(DMA_IRQ_0, true); 316 | 317 | // fill line headers 318 | const auto line_buf_size = sizeof(scanline_buffer) / 2; 319 | auto line_buf0 = scanline_buffer; 320 | auto line_buf1 = scanline_buffer + line_buf_size / sizeof(uint32_t); 321 | 322 | for(size_t i = 0; i < std::size(vactive_line); i++) 323 | *line_buf0++ = *line_buf1++ = vactive_line[i]; 324 | 325 | // set irq to highest priority 326 | irq_set_priority(DMA_IRQ_0, PICO_HIGHEST_IRQ_PRIORITY); 327 | } 328 | 329 | void set_display_size(int w, int h) { 330 | // set h shift/v scale 331 | new_h_shift = 0; 332 | 333 | new_scanline_step = (h << 16) / MODE_V_ACTIVE_LINES; 334 | 335 | while(MODE_H_ACTIVE_PIXELS >> new_h_shift > w) 336 | new_h_shift++; 337 | 338 | // check if we're actually changing scale 339 | if(new_scanline_step == in_scanline_step && new_h_shift == h_shift) { 340 | new_h_shift = 0xFF; 341 | new_scanline_step = 0; 342 | return; 343 | } 344 | 345 | // don't do it yet if already started 346 | // (will set need_mode_change after next render) 347 | if(started) 348 | return; 349 | 350 | h_shift = new_h_shift; 351 | in_scanline_step = new_scanline_step; 352 | new_h_shift = 0; 353 | } 354 | 355 | void update_display() { 356 | 357 | // start dma after first render 358 | if(!started) { 359 | started = true; 360 | dma_channel_start(HSTX_DMA_CH_BASE); 361 | } else if(new_h_shift != 0xFF) { 362 | need_mode_change = true; 363 | } 364 | 365 | // check if dma channels have encountered a read error and reset 366 | // usually this happens because of a breakpoint 367 | bool error = false; 368 | 369 | for(int i = 0; i < HSTX_NUM_DMA_CHANNELS; i++) 370 | error = error || (dma_hw->ch[HSTX_DMA_CH_BASE + i].al1_ctrl & DMA_CH0_CTRL_TRIG_READ_ERROR_BITS); 371 | 372 | if(error) { 373 | for(int i = 0; i < HSTX_NUM_DMA_CHANNELS; i++) 374 | hw_set_bits(&dma_hw->ch[HSTX_DMA_CH_BASE + i].al1_ctrl, DMA_CH0_CTRL_TRIG_READ_ERROR_BITS); 375 | 376 | // disable/enable HSTX 377 | hw_clear_bits(&hstx_ctrl_hw->csr, HSTX_CTRL_CSR_EN_BITS); 378 | hw_set_bits(&hstx_ctrl_hw->csr, HSTX_CTRL_CSR_EN_BITS); 379 | 380 | // restart DMA 381 | v_scanline = 2; 382 | cur_dma_ch = HSTX_DMA_CH_BASE; 383 | 384 | for(int i = 0; i < HSTX_NUM_DMA_CHANNELS; i++) { 385 | dma_channel_set_read_addr(HSTX_DMA_CH_BASE + i, vblank_line_vsync_off, false); 386 | dma_channel_set_trans_count(HSTX_DMA_CH_BASE + i, std::size(vblank_line_vsync_off), false); 387 | } 388 | 389 | dma_channel_start(HSTX_DMA_CH_BASE); 390 | } 391 | } 392 | 393 | bool display_in_first_half() { 394 | return started && v_scanline < MODE_V_TOTAL_LINES / 2; 395 | } 396 | 397 | bool display_in_second_half() { 398 | return started && v_scanline >= MODE_V_TOTAL_LINES / 2; 399 | } 400 | 401 | uint8_t *display_get_framebuffer() { 402 | return framebuffer; 403 | } 404 | -------------------------------------------------------------------------------- /pico2/Display.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | void init_display(); 5 | 6 | void set_display_size(int w, int h); 7 | 8 | void write_display(int x, int y, int count, uint8_t *data); 9 | void update_display(); 10 | 11 | bool display_in_first_half(); 12 | bool display_in_second_half(); 13 | 14 | uint8_t *display_get_framebuffer(); -------------------------------------------------------------------------------- /pico2/Main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "hardware/clocks.h" 4 | #include "hardware/irq.h" 5 | #include "hardware/timer.h" 6 | #include "hardware/vreg.h" 7 | #include "pico/multicore.h" 8 | #include "pico/stdlib.h" 9 | #include "pico/time.h" 10 | 11 | #include "tusb.h" 12 | 13 | #include "fatfs/ff.h" 14 | 15 | #include "config.h" 16 | #include "psram.h" 17 | 18 | #include "BIOS.h" 19 | #include "DiskIO.h" 20 | #include "Display.h" 21 | 22 | #include "AboveBoard.h" 23 | #include "CGACard.h" 24 | #include "FixedDiskAdapter.h" 25 | #include "FloppyController.h" 26 | #include "Scancode.h" 27 | #include "SerialMouse.h" 28 | #include "System.h" 29 | 30 | static FATFS fs; 31 | 32 | static System sys; 33 | 34 | static AboveBoard aboveBoard(sys); 35 | 36 | static CGACard cga(sys); 37 | static FloppyController fdc(sys); 38 | 39 | #ifdef FIXED_DISK 40 | static FixedDiskAdapter fixDisk(sys); 41 | #endif 42 | 43 | static SerialMouse mouse(sys); 44 | 45 | static FileFixedIO fixedIO; 46 | 47 | static uint32_t emu_time = 0, real_time = 0, sync_time = 0; 48 | 49 | static void scanlineCallback(const uint8_t *data, int line, int w) 50 | { 51 | // seems to be a bug sometimes when switching mode 52 | if(w > 640) 53 | w = 640; 54 | 55 | auto fb = display_get_framebuffer(); 56 | auto ptr = fb + line * w; 57 | 58 | if(line == 0) 59 | { 60 | // sync first half 61 | auto start = get_absolute_time(); 62 | while(display_in_first_half()) {}; 63 | sync_time += absolute_time_diff_us(start, get_absolute_time()); 64 | set_display_size(w, 200); 65 | } 66 | else if(line == 100) 67 | { 68 | // sync second half 69 | auto start = get_absolute_time(); 70 | while(display_in_second_half()) {}; 71 | sync_time += absolute_time_diff_us(start, get_absolute_time()); 72 | } 73 | 74 | // copy 75 | memcpy(ptr, data, w / 2); 76 | 77 | if(line == 199) 78 | { 79 | // end 80 | update_display(); 81 | } 82 | } 83 | 84 | static uint8_t *requestMem(unsigned int block) 85 | { 86 | auto addr = block * System::getMemoryBlockSize(); 87 | 88 | // this is only for mapping above board memory 89 | if(addr < 0x100000) 90 | return nullptr; 91 | 92 | addr -= 0x100000; 93 | 94 | // needs to be a multiple of 2MB and we're already using some 95 | if(addr >= 6 * 1024 * 1024) 96 | return nullptr; 97 | 98 | auto psram = reinterpret_cast(PSRAM_LOCATION); 99 | return psram + 640 * 1024 + addr; 100 | } 101 | 102 | static void alarmCallback(uint alarmNum) 103 | { 104 | // TODO: audio output 105 | while(sys.hasSpeakerSample()) 106 | sys.getSpeakerSample(); 107 | 108 | timer_hw->intr = 1 << alarmNum; 109 | hardware_alarm_set_target(alarmNum, make_timeout_time_ms(5)); 110 | } 111 | 112 | void update_key_state(XTScancode code, bool state) 113 | { 114 | sys.sendKey(code, state); 115 | } 116 | 117 | void update_mouse_state(int8_t x, int8_t y, bool left, bool right) 118 | { 119 | mouse.addMotion(x, y); 120 | mouse.setButton(0, left); 121 | mouse.setButton(1, right); 122 | mouse.sync(); 123 | } 124 | 125 | static void runEmulator(absolute_time_t &time) 126 | { 127 | auto now = get_absolute_time(); 128 | auto elapsed = absolute_time_diff_us(time, now) / 1000; 129 | 130 | if(elapsed) 131 | { 132 | if(elapsed > 10) 133 | elapsed = 10; 134 | 135 | auto start = get_absolute_time(); 136 | 137 | sys.getCPU().run(elapsed); 138 | time = delayed_by_ms(time, elapsed); 139 | 140 | cga.update(); 141 | sys.updateForDisplay(); 142 | 143 | // get "real" time taken 144 | auto update_time = absolute_time_diff_us(start, get_absolute_time()); 145 | 146 | emu_time += elapsed * 1000; 147 | real_time += update_time; 148 | 149 | // every 10s calculate speed 150 | if(emu_time >= 10000000) { 151 | int speed = uint64_t(emu_time) * 1000 / real_time; 152 | printf("speed %i.%i%% (%ims in %ims, sync %ims)\n", speed / 10, speed % 10, emu_time / 1000, real_time / 1000, sync_time / 1000); 153 | emu_time = real_time = sync_time = 0; 154 | } 155 | } 156 | } 157 | 158 | #ifdef EMULATOR_ON_CORE1 159 | static void core1Main() 160 | { 161 | auto time = get_absolute_time(); 162 | while(true) 163 | runEmulator(time); 164 | } 165 | #endif 166 | 167 | int main() 168 | { 169 | set_sys_clock_khz(250000, false); 170 | 171 | tusb_init(); 172 | 173 | stdio_init_all(); 174 | 175 | init_display(); 176 | set_display_size(320, 200); 177 | 178 | size_t psramSize = psram_init(PSRAM_CS_PIN); 179 | 180 | printf("detected %i bytes PSRAM\n", psramSize); 181 | 182 | // init storage/filesystem 183 | auto res = f_mount(&fs, "", 1); 184 | 185 | if(res != FR_OK) 186 | { 187 | printf("Failed to mount filesystem! (%i)\n", res); 188 | while(true); 189 | } 190 | 191 | // emulator init 192 | auto psram = reinterpret_cast(PSRAM_LOCATION); 193 | sys.addMemory(0, 640 * 1024, psram); 194 | sys.setMemoryRequestCallback(requestMem); 195 | cga.setScanlineCallback(scanlineCallback); 196 | 197 | auto bios = _binary_bios_xt_rom_start; 198 | auto biosSize = _binary_bios_xt_rom_end - _binary_bios_xt_rom_start; 199 | auto biosBase = 0x100000 - biosSize; 200 | sys.addReadOnlyMemory(biosBase, biosSize, (const uint8_t *)bios); 201 | 202 | #ifdef FIXED_DISK 203 | // size is wring, but mem mapping can't handle smaller 204 | sys.addReadOnlyMemory(0xC8000, 0x4000, (const uint8_t *)_binary_fixed_disk_bios_rom_start); 205 | 206 | fixedIO.openDisk(0, "hd0.img"); 207 | fixDisk.setIOInterface(&fixedIO); 208 | #endif 209 | 210 | sys.reset(); 211 | 212 | [[maybe_unused]] 213 | auto time = get_absolute_time(); 214 | 215 | // fake audio output 216 | // (since the core audio output is all over the place we can't just drain between updates) 217 | int alarmNum = hardware_alarm_claim_unused(true); 218 | hardware_alarm_set_callback(alarmNum, alarmCallback); 219 | hardware_alarm_set_target(alarmNum, make_timeout_time_ms(5)); 220 | irq_set_priority(TIMER0_IRQ_0 + alarmNum, PICO_LOWEST_IRQ_PRIORITY); 221 | 222 | #ifdef EMULATOR_ON_CORE1 223 | multicore_launch_core1(core1Main); 224 | #endif 225 | 226 | while(true) 227 | { 228 | tuh_task(); 229 | #ifndef EMULATOR_ON_CORE1 230 | runEmulator(time); 231 | #endif 232 | } 233 | 234 | return 0; 235 | } -------------------------------------------------------------------------------- /pico2/config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // FIXME: this is a leftover, it doesn't control the overclock 4 | #define OVERCLOCK_250 1 5 | 6 | #ifdef SOLDERPARTY_RP2350_STAMP_XL 7 | #define DVI_CLK_P 14 8 | #define DVI_D0_P 12 9 | #define DVI_D1_P 18 10 | #define DVI_D2_P 16 11 | 12 | // these are not the slot on the carrier, it conflicts with the PSRAM CS 13 | #define SD_SCK 39 14 | #define SD_MOSI 37 15 | #define SD_MISO 38 16 | #define SD_CS 36 17 | 18 | #define PSRAM_CS_PIN 8 19 | 20 | #elif defined(PIMORONI_PICO_PLUS2_RP2350) 21 | // as I was using a mess of jumper wires, there's not really a right answer here 22 | #define DVI_CLK_P 14 23 | #define DVI_D0_P 12 24 | #define DVI_D1_P 18 25 | #define DVI_D2_P 16 26 | 27 | #define SD_SCK 2 28 | #define SD_MOSI 4 29 | #define SD_MISO 3 30 | #define SD_CS 5 31 | 32 | #define PSRAM_CS_PIN PIMORONI_PICO_PLUS2_PSRAM_CS_PIN 33 | #else 34 | #error "No board configuration!" 35 | #endif 36 | -------------------------------------------------------------------------------- /pico2/psram.c: -------------------------------------------------------------------------------- 1 | #include "hardware/structs/ioqspi.h" 2 | #include "hardware/structs/qmi.h" 3 | #include "hardware/structs/xip_ctrl.h" 4 | #include "hardware/clocks.h" 5 | #include "hardware/sync.h" 6 | 7 | #include "psram.h" 8 | 9 | static size_t __no_inline_not_in_flash_func(psram_detect)() { 10 | int psram_size = 0; 11 | 12 | // Try and read the PSRAM ID via direct_csr. 13 | qmi_hw->direct_csr = 30 << QMI_DIRECT_CSR_CLKDIV_LSB | QMI_DIRECT_CSR_EN_BITS; 14 | 15 | // Need to poll for the cooldown on the last XIP transfer to expire 16 | // (via direct-mode BUSY flag) before it is safe to perform the first 17 | // direct-mode operation 18 | while ((qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) != 0) { 19 | } 20 | 21 | // Exit out of QMI in case we've inited already 22 | qmi_hw->direct_csr |= QMI_DIRECT_CSR_ASSERT_CS1N_BITS; 23 | 24 | // Transmit as quad. 25 | qmi_hw->direct_tx = QMI_DIRECT_TX_OE_BITS | QMI_DIRECT_TX_IWIDTH_VALUE_Q << QMI_DIRECT_TX_IWIDTH_LSB | 0xf5; 26 | 27 | while ((qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) != 0) { 28 | } 29 | 30 | (void)qmi_hw->direct_rx; 31 | 32 | qmi_hw->direct_csr &= ~(QMI_DIRECT_CSR_ASSERT_CS1N_BITS); 33 | 34 | // Read the id 35 | qmi_hw->direct_csr |= QMI_DIRECT_CSR_ASSERT_CS1N_BITS; 36 | uint8_t kgd = 0; 37 | uint8_t eid = 0; 38 | 39 | for (size_t i = 0; i < 7; i++) 40 | { 41 | if (i == 0) { 42 | qmi_hw->direct_tx = 0x9f; 43 | } else { 44 | qmi_hw->direct_tx = 0xff; 45 | } 46 | 47 | while ((qmi_hw->direct_csr & QMI_DIRECT_CSR_TXEMPTY_BITS) == 0) { 48 | } 49 | 50 | while ((qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) != 0) { 51 | } 52 | 53 | if (i == 5) { 54 | kgd = qmi_hw->direct_rx; 55 | } else if (i == 6) { 56 | eid = qmi_hw->direct_rx; 57 | } else { 58 | (void)qmi_hw->direct_rx; 59 | } 60 | } 61 | 62 | // Disable direct csr. 63 | qmi_hw->direct_csr &= ~(QMI_DIRECT_CSR_ASSERT_CS1N_BITS | QMI_DIRECT_CSR_EN_BITS); 64 | 65 | if (kgd == 0x5D) { 66 | psram_size = 1024 * 1024; // 1 MiB 67 | uint8_t size_id = eid >> 5; 68 | if (eid == 0x26 || size_id == 2) { 69 | psram_size *= 8; // 8 MiB 70 | } else if (size_id == 0) { 71 | psram_size *= 2; // 2 MiB 72 | } else if (size_id == 1) { 73 | psram_size *= 4; // 4 MiB 74 | } 75 | } 76 | 77 | return psram_size; 78 | } 79 | 80 | size_t __no_inline_not_in_flash_func(psram_init)(uint cs_pin) { 81 | gpio_set_function(cs_pin, GPIO_FUNC_XIP_CS1); 82 | 83 | uint32_t intr_stash = save_and_disable_interrupts(); 84 | 85 | size_t psram_size = psram_detect(); 86 | 87 | if (!psram_size) { 88 | return 0; 89 | } 90 | 91 | uint32_t clock_hz = clock_get_hz(clk_sys); 92 | 93 | // Enable direct mode, PSRAM CS, clkdiv of 10. 94 | qmi_hw->direct_csr = 10 << QMI_DIRECT_CSR_CLKDIV_LSB | \ 95 | QMI_DIRECT_CSR_EN_BITS | \ 96 | QMI_DIRECT_CSR_AUTO_CS1N_BITS; 97 | while (qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) { 98 | ; 99 | } 100 | 101 | // Enable QPI mode on the PSRAM 102 | const uint CMD_QPI_EN = 0x35; 103 | qmi_hw->direct_tx = QMI_DIRECT_TX_NOPUSH_BITS | CMD_QPI_EN; 104 | 105 | while (qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) { 106 | ; 107 | } 108 | 109 | // Set PSRAM timing for APS6404 110 | // 111 | // Using an rxdelay equal to the divisor isn't enough when running the APS6404 close to 133MHz. 112 | // So: don't allow running at divisor 1 above 100MHz (because delay of 2 would be too late), 113 | // and add an extra 1 to the rxdelay if the divided clock is > 100MHz (i.e. sys clock > 200MHz). 114 | const int max_psram_freq = 133000000; 115 | 116 | int divisor = (clock_hz + max_psram_freq - 1) / max_psram_freq; 117 | if (divisor == 1 && clock_hz > 100000000) { 118 | divisor = 2; 119 | } 120 | int rxdelay = divisor; 121 | if (clock_hz / divisor > 100000000) { 122 | rxdelay += 1; 123 | } 124 | 125 | // - Max select must be <= 8us. The value is given in multiples of 64 system clocks. 126 | // - Min deselect must be >= 18ns. The value is given in system clock cycles - ceil(divisor / 2). 127 | const int clock_period_fs = 1000000000000000ll / clock_hz; 128 | const int max_select = (125 * 1000000) / clock_period_fs; // 125 = 8000ns / 64 129 | const int min_deselect = (18 * 1000000 + (clock_period_fs - 1)) / clock_period_fs - (divisor + 1) / 2; 130 | 131 | qmi_hw->m[1].timing = 1 << QMI_M1_TIMING_COOLDOWN_LSB | 132 | QMI_M1_TIMING_PAGEBREAK_VALUE_1024 << QMI_M1_TIMING_PAGEBREAK_LSB | 133 | max_select << QMI_M1_TIMING_MAX_SELECT_LSB | 134 | min_deselect << QMI_M1_TIMING_MIN_DESELECT_LSB | 135 | rxdelay << QMI_M1_TIMING_RXDELAY_LSB | 136 | divisor << QMI_M1_TIMING_CLKDIV_LSB; 137 | 138 | // Set PSRAM commands and formats 139 | qmi_hw->m[1].rfmt = 140 | QMI_M0_RFMT_PREFIX_WIDTH_VALUE_Q << QMI_M0_RFMT_PREFIX_WIDTH_LSB | \ 141 | QMI_M0_RFMT_ADDR_WIDTH_VALUE_Q << QMI_M0_RFMT_ADDR_WIDTH_LSB | \ 142 | QMI_M0_RFMT_SUFFIX_WIDTH_VALUE_Q << QMI_M0_RFMT_SUFFIX_WIDTH_LSB | \ 143 | QMI_M0_RFMT_DUMMY_WIDTH_VALUE_Q << QMI_M0_RFMT_DUMMY_WIDTH_LSB | \ 144 | QMI_M0_RFMT_DATA_WIDTH_VALUE_Q << QMI_M0_RFMT_DATA_WIDTH_LSB | \ 145 | QMI_M0_RFMT_PREFIX_LEN_VALUE_8 << QMI_M0_RFMT_PREFIX_LEN_LSB | \ 146 | 6 << QMI_M0_RFMT_DUMMY_LEN_LSB; 147 | 148 | qmi_hw->m[1].rcmd = 0xEB; 149 | 150 | qmi_hw->m[1].wfmt = 151 | QMI_M0_WFMT_PREFIX_WIDTH_VALUE_Q << QMI_M0_WFMT_PREFIX_WIDTH_LSB | \ 152 | QMI_M0_WFMT_ADDR_WIDTH_VALUE_Q << QMI_M0_WFMT_ADDR_WIDTH_LSB | \ 153 | QMI_M0_WFMT_SUFFIX_WIDTH_VALUE_Q << QMI_M0_WFMT_SUFFIX_WIDTH_LSB | \ 154 | QMI_M0_WFMT_DUMMY_WIDTH_VALUE_Q << QMI_M0_WFMT_DUMMY_WIDTH_LSB | \ 155 | QMI_M0_WFMT_DATA_WIDTH_VALUE_Q << QMI_M0_WFMT_DATA_WIDTH_LSB | \ 156 | QMI_M0_WFMT_PREFIX_LEN_VALUE_8 << QMI_M0_WFMT_PREFIX_LEN_LSB; 157 | 158 | qmi_hw->m[1].wcmd = 0x38; 159 | 160 | // Disable direct mode 161 | qmi_hw->direct_csr = 0; 162 | 163 | // Enable writes to PSRAM 164 | hw_set_bits(&xip_ctrl_hw->ctrl, XIP_CTRL_WRITABLE_M1_BITS); 165 | 166 | restore_interrupts(intr_stash); 167 | 168 | return psram_size; 169 | } -------------------------------------------------------------------------------- /pico2/psram.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "pico/stdlib.h" 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | #define PSRAM_LOCATION _u(0x11000000) 10 | 11 | size_t psram_init(uint cs_pin); 12 | 13 | #ifdef __cplusplus 14 | } 15 | #endif -------------------------------------------------------------------------------- /pico_sdk_import.cmake: -------------------------------------------------------------------------------- 1 | # This is a copy of /external/pico_sdk_import.cmake 2 | 3 | # This can be dropped into an external project to help locate this SDK 4 | # It should be include()ed prior to project() 5 | 6 | if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) 7 | set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) 8 | message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") 9 | endif () 10 | 11 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) 12 | set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) 13 | message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") 14 | endif () 15 | 16 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) 17 | set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) 18 | message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") 19 | endif () 20 | 21 | set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the PICO SDK") 22 | set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of PICO SDK from git if not otherwise locatable") 23 | set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") 24 | 25 | if (NOT PICO_SDK_PATH) 26 | if (PICO_SDK_FETCH_FROM_GIT) 27 | include(FetchContent) 28 | set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) 29 | if (PICO_SDK_FETCH_FROM_GIT_PATH) 30 | get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") 31 | endif () 32 | FetchContent_Declare( 33 | pico_sdk 34 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 35 | GIT_TAG master 36 | ) 37 | if (NOT pico_sdk) 38 | message("Downloading PICO SDK") 39 | FetchContent_Populate(pico_sdk) 40 | set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) 41 | endif () 42 | set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) 43 | else () 44 | message(FATAL_ERROR 45 | "PICO SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." 46 | ) 47 | endif () 48 | endif () 49 | 50 | get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") 51 | if (NOT EXISTS ${PICO_SDK_PATH}) 52 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") 53 | endif () 54 | 55 | set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) 56 | if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) 57 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the PICO SDK") 58 | endif () 59 | 60 | set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the PICO SDK" FORCE) 61 | 62 | include(${PICO_SDK_INIT_CMAKE_FILE}) 63 | -------------------------------------------------------------------------------- /picovision/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(picovision_import.cmake) 2 | 3 | add_executable(PACEPicoVision 4 | Display.cpp 5 | Main.cpp 6 | ) 7 | 8 | target_include_directories(PACEPicoVision PRIVATE ${CMAKE_CURRENT_LIST_DIR}) 9 | target_link_libraries(PACEPicoVision PACECore PACEPicoShared PACEPicoBIOS hardware_i2c pico_stdlib aps6404 swd_load) 10 | 11 | pico_add_extra_outputs(PACEPicoVision) 12 | 13 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/PACEPicoVision.uf2 14 | DESTINATION bin 15 | ) -------------------------------------------------------------------------------- /picovision/Display.cpp: -------------------------------------------------------------------------------- 1 | // largely "borrowed" from 32blit driver 2 | 3 | #include 4 | 5 | #include "pico/stdlib.h" 6 | #include "hardware/i2c.h" 7 | 8 | #include "aps6404.hpp" 9 | #include "swd_load.hpp" 10 | #include "pico-stick.h" 11 | 12 | #include "Display.h" 13 | 14 | static const uint8_t cga_palette[16][3] 15 | { 16 | {0x00, 0x00, 0x00}, // black 17 | {0x00, 0x00, 0xAA}, // blue 18 | {0x00, 0xAA, 0x00}, // green 19 | {0x00, 0xAA, 0xAA}, // cyan 20 | {0xAA, 0x00, 0x00}, // red 21 | {0xAA, 0x00, 0xAA}, // magenta 22 | {0xAA, 0x55, 0x00}, // brown 23 | {0xAA, 0xAA, 0xAA}, // light grey 24 | 25 | {0x55, 0x55, 0x55}, // dark grey 26 | {0x55, 0x55, 0xFF}, // light blue 27 | {0x55, 0xFF, 0x55}, // light green 28 | {0x55, 0xFF, 0xFF}, // light cyan 29 | {0xFF, 0x55, 0x55}, // light red 30 | {0xFF, 0x55, 0xFF}, // light magenta 31 | {0xFF, 0xFF, 0x55}, // yellow 32 | {0xFF, 0xFF, 0xFF}, // white 33 | }; 34 | 35 | // pins 36 | static constexpr uint CS = 17; 37 | static constexpr uint D0 = 19; 38 | static constexpr uint VSYNC = 16; 39 | static constexpr uint RAM_SEL = 8; 40 | 41 | static constexpr uint I2C_SDA = 6; 42 | static constexpr uint I2C_SCL = 7; 43 | 44 | // i2c 45 | static constexpr uint I2C_ADDR = 0x0D; 46 | static constexpr uint I2C_REG_SET_RES = 0xFC; 47 | static constexpr uint I2C_REG_START = 0xFD; 48 | static constexpr uint I2C_REG_STOP = 0xFF; 49 | 50 | static constexpr uint32_t base_address = 0x10000; 51 | 52 | static pimoroni::APS6404 ram(CS, D0, pio1); 53 | static uint8_t ram_bank = 0; 54 | 55 | static bool display_enabled = false; 56 | static uint8_t need_mode_change = 2; 57 | 58 | static volatile bool do_render = true; 59 | 60 | static uint16_t cur_width = 640, cur_height = 240; 61 | 62 | static void vsync_callback(uint gpio, uint32_t events){ 63 | if(!do_render) { 64 | ram_bank ^= 1; 65 | gpio_put(RAM_SEL, ram_bank); 66 | 67 | do_render = true; 68 | } 69 | } 70 | 71 | static void write_frame_setup(uint16_t width, uint16_t height, int pixel_stride, uint8_t h_repeat, uint8_t v_repeat) { 72 | constexpr int buf_size = 32; 73 | uint32_t buf[buf_size]; 74 | 75 | int dv_format = 2; // 5 bit palette 76 | 77 | uint32_t full_width = width * h_repeat; 78 | buf[0] = 0x4F434950; // "PICO" 79 | 80 | // setup 81 | buf[1] = 0x01000101 + ((uint32_t)v_repeat << 16); 82 | buf[2] = full_width << 16; 83 | buf[3] = (uint32_t)height << 16; 84 | 85 | // frame table header 86 | buf[4] = 0x00000001; // 1 frame, start at frame 0 87 | buf[5] = 0x00010000 + height + ((uint32_t)ram_bank << 24); // frame rate divider 1 88 | buf[6] = 0x00000001; // 1 palette, don't advance, 0 sprites 89 | 90 | ram.write(0, buf, 7 * 4); 91 | ram.wait_for_finish_blocking(); 92 | 93 | // write frame table 94 | uint frame_table_addr = 4 * 7; 95 | 96 | for(int y = 0; y < height; y += buf_size) { 97 | int step = std::min(buf_size, height - y); 98 | for(int i = 0; i < step; i++) { 99 | // always use full width as stride to simplify access later 100 | uint32_t line_addr = base_address + (y + i) * full_width * pixel_stride; 101 | buf[i] = dv_format << 27 | h_repeat << 24 | line_addr; 102 | } 103 | 104 | ram.write(frame_table_addr, buf, step * 4); 105 | ram.wait_for_finish_blocking(); 106 | frame_table_addr += 4 * step; 107 | } 108 | 109 | // write palette 110 | ram.write(frame_table_addr, (uint32_t *)cga_palette, sizeof(cga_palette)); 111 | } 112 | 113 | void init_display() { 114 | gpio_init(RAM_SEL); 115 | gpio_put(RAM_SEL, 0); 116 | gpio_set_dir(RAM_SEL, GPIO_OUT); 117 | 118 | gpio_init(VSYNC); 119 | gpio_set_dir(VSYNC, GPIO_IN); 120 | gpio_set_irq_enabled_with_callback(VSYNC, GPIO_IRQ_EDGE_RISE, true, vsync_callback); 121 | 122 | sleep_ms(200); 123 | swd_load_program(section_addresses, section_data, section_data_len, std::size(section_data_len), 0x20000001, 0x15004000, true); 124 | 125 | // init RAM 126 | ram.init(); 127 | sleep_us(100); 128 | 129 | gpio_put(RAM_SEL, 1); 130 | ram.init(); 131 | sleep_us(100); 132 | 133 | gpio_put(RAM_SEL, 0); 134 | sleep_ms(100); 135 | 136 | // i2c init 137 | i2c_init(i2c1, 400000); 138 | gpio_set_function(I2C_SDA, GPIO_FUNC_I2C); 139 | gpio_pull_up(I2C_SDA); 140 | gpio_set_function(I2C_SCL, GPIO_FUNC_I2C); 141 | gpio_pull_up(I2C_SCL); 142 | 143 | uint8_t resolution = 0; // 640x480 144 | uint8_t buf[2] = {I2C_REG_SET_RES, resolution}; 145 | i2c_write_blocking(i2c1, I2C_ADDR, buf, 2, false); 146 | } 147 | 148 | void set_display_size(int w, int h) { 149 | if(w != cur_width || h != cur_height) { 150 | cur_width = w; 151 | cur_height = h; 152 | need_mode_change = 2; 153 | } 154 | } 155 | 156 | void write_display(int x, int y, int count, uint8_t *data) { 157 | ram.write(base_address + (x + y * 640) * 1, (uint32_t *)data, count); 158 | } 159 | 160 | void update_display() { 161 | 162 | ram.wait_for_finish_blocking(); 163 | 164 | // handle mode change 165 | if(need_mode_change) { 166 | uint8_t h_repeat = 640 / cur_width, v_repeat = 480 / cur_height; 167 | write_frame_setup(cur_width, cur_height, 1, h_repeat, v_repeat); 168 | need_mode_change--; 169 | } 170 | 171 | // enable display after first render 172 | if(!display_enabled) { 173 | // swap banks now 174 | ram_bank ^= 1; 175 | gpio_put(RAM_SEL, ram_bank); 176 | 177 | uint8_t buf[2] = {I2C_REG_START, 1}; 178 | i2c_write_blocking(i2c1, I2C_ADDR, buf, 2, false); 179 | display_enabled = true; 180 | } else 181 | do_render = false; 182 | } 183 | 184 | bool display_render_needed() { 185 | return do_render; 186 | } 187 | 188 | void display_wait_for_frame(int cur_bank) { 189 | if(!display_enabled){ 190 | // haven't started, just swap banks 191 | ram_bank ^= 1; 192 | gpio_put(RAM_SEL, ram_bank); 193 | return; 194 | } 195 | 196 | // the swap already happened 197 | if(ram_bank != cur_bank) 198 | return; 199 | 200 | // this is a last resort if we need the other RAM bank right now 201 | do_render = false; 202 | while(!do_render); 203 | } 204 | 205 | pimoroni::APS6404 &display_get_ram() { 206 | return ram; 207 | } 208 | 209 | int display_get_ram_bank() { 210 | return ram_bank; 211 | } -------------------------------------------------------------------------------- /picovision/Display.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace pimoroni 5 | { 6 | class APS6404; 7 | } 8 | 9 | void init_display(); 10 | 11 | void set_display_size(int w, int h); 12 | 13 | void write_display(int x, int y, int count, uint8_t *data); 14 | void update_display(); 15 | 16 | bool display_render_needed(); 17 | 18 | void display_wait_for_frame(int cur_bank); 19 | 20 | pimoroni::APS6404 &display_get_ram(); 21 | int display_get_ram_bank(); 22 | -------------------------------------------------------------------------------- /picovision/Main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "hardware/clocks.h" 4 | #include "hardware/irq.h" 5 | #include "hardware/timer.h" 6 | #include "hardware/vreg.h" 7 | #include "pico/stdlib.h" 8 | #include "pico/time.h" 9 | 10 | #include "tusb.h" 11 | 12 | #include "fatfs/ff.h" 13 | 14 | #include "aps6404.hpp" 15 | 16 | #include "BIOS.h" 17 | #include "DiskIO.h" 18 | 19 | #include "Display.h" 20 | 21 | #include "AboveBoard.h" 22 | #include "CGACard.h" 23 | #include "FixedDiskAdapter.h" 24 | #include "FloppyController.h" 25 | #include "Scancode.h" 26 | #include "SerialMouse.h" 27 | #include "System.h" 28 | 29 | struct MemBlockMapping 30 | { 31 | uint8_t cacheBlock; 32 | uint8_t windowBlock; // where expanded memory is mapped to 33 | uint16_t memBlock; 34 | }; 35 | 36 | static FATFS fs; 37 | 38 | static System sys; 39 | 40 | static AboveBoard aboveBoard(sys); 41 | 42 | static CGACard cga(sys); 43 | static FloppyController fdc(sys); 44 | 45 | #ifdef FIXED_DISK 46 | static FixedDiskAdapter fixDisk(sys); 47 | #endif 48 | 49 | static SerialMouse mouse(sys); 50 | 51 | static const int cacheBlocks = 12; 52 | static const int frameFlushThreshold = 4; // minimum dirty blocks before flushing at frame end 53 | static const int maxFlushBlocks = 2; // maximum blocks to flush at the end of a frame 54 | static const int totalPSRAMBlocks = 7 * 1024 * 1024 / System::getMemoryBlockSize(); // (1MB CPU address space + 6MB expanded) 55 | 56 | static uint8_t ramCache[cacheBlocks * System::getMemoryBlockSize()]; 57 | static std::forward_list ramCacheMap; 58 | static std::forward_list::iterator ramCacheEnd; 59 | static std::forward_list::iterator lastFlushedBlock; 60 | static uint32_t cacheUsedMem[totalPSRAMBlocks / 32]; 61 | 62 | static constexpr uint32_t psramBaseAddress = 0x100000; 63 | 64 | static uint8_t scanLineOutBuf[640]; 65 | static int firstFrames = 2; 66 | static bool discardFrame = false; 67 | 68 | static FileFixedIO fixedIO; 69 | 70 | static std::forward_list::iterator cacheFlush(); 71 | 72 | static void scanlineCallback(const uint8_t *data, int line, int w) 73 | { 74 | auto ptr = scanLineOutBuf; 75 | 76 | // seems to be a bug sometimes when switching mode 77 | if(w > 640) 78 | w = 640; 79 | 80 | for(int x = 0; x < w; x += 2) 81 | { 82 | int index = *data & 0xF; 83 | *ptr++ = index << 2; 84 | 85 | index = *data++ >> 4; 86 | *ptr++ = index << 2; 87 | } 88 | 89 | if(line == 0) 90 | { 91 | // sync 92 | while(!discardFrame && !display_render_needed()); 93 | discardFrame = false; 94 | 95 | set_display_size(w, 240); /*really 200*/ 96 | 97 | if(firstFrames) 98 | { 99 | // fill the bottom part of the screen of the first two frames 100 | for(int y = 200; y < 240; y++) 101 | write_display(0, y, 640, scanLineOutBuf); 102 | firstFrames--; 103 | } 104 | } 105 | 106 | if(!discardFrame) 107 | write_display(0, line, w, scanLineOutBuf); 108 | 109 | if(line == 199) 110 | { 111 | // attempt to flush some dirty blocks early 112 | auto firstDirty = cacheFlush(); 113 | int psramBank = display_get_ram_bank(); 114 | 115 | update_display(); 116 | 117 | // if we're flushing wait for the next frame now 118 | // which won't cause a glitch since we're at the end of the frame anyway 119 | if(firstDirty != ramCacheMap.end()) 120 | { 121 | display_wait_for_frame(psramBank); 122 | 123 | // finish writing blocks 124 | int count = maxFlushBlocks; 125 | 126 | for(auto it = firstDirty; it != ramCacheMap.end() && count; ++it) 127 | { 128 | if(!sys.getMemoryBlockDirty(it->windowBlock)) 129 | continue; 130 | 131 | const auto blockSize = System::getMemoryBlockSize(); 132 | display_get_ram().write(psramBaseAddress + it->memBlock * blockSize, (uint32_t *)(ramCache + it->cacheBlock * blockSize), blockSize); 133 | 134 | sys.clearMemoryBlockDirty(it->windowBlock); 135 | 136 | count--; 137 | } 138 | } 139 | } 140 | } 141 | 142 | static uint8_t *requestMem(unsigned int block) 143 | { 144 | const auto blockSize = System::getMemoryBlockSize(); 145 | 146 | block = aboveBoard.remapMemoryBlockFromWindow(block); 147 | 148 | // don't map over 640k 149 | if(block * blockSize > 0xA0000 && block * blockSize < 0x100000) 150 | return nullptr; 151 | 152 | // limit to PSRAM (rounded down a bit) 153 | if(block * blockSize >= 7 * 1024 * 1024) 154 | return nullptr; 155 | 156 | auto &psram = display_get_ram(); 157 | 158 | // find non-dirty block 159 | auto it = ramCacheMap.begin(); 160 | auto prevIt = ramCacheMap.before_begin(); 161 | for(; it != ramCacheMap.end(); prevIt = it++) 162 | { 163 | // unused block 164 | if(it->memBlock == 0xFFFF) 165 | break; 166 | 167 | if(!sys.getMemoryBlockDirty(it->windowBlock)) 168 | break; 169 | } 170 | 171 | if(it == ramCacheMap.end()) 172 | { 173 | // all cache dirty, force flush first 174 | it = ramCacheMap.begin(); 175 | prevIt = ramCacheMap.before_begin(); 176 | 177 | // we really don't want the bank switching here, so make sure the next frame has started 178 | while(!display_render_needed()); 179 | 180 | int psramBank = display_get_ram_bank(); 181 | 182 | // as we're going to block for an entire frame anyway, might as well use the time to flush all the blocks 183 | for(auto it2 = it; it2 != ramCacheMap.end(); ++it2) 184 | psram.write(psramBaseAddress + it2->memBlock * blockSize, (uint32_t *)(ramCache + it2->cacheBlock * blockSize), blockSize); 185 | 186 | psram.wait_for_finish_blocking(); 187 | // swap buf, write again 188 | // this will cause a display glitch 189 | display_wait_for_frame(psramBank); 190 | 191 | for(auto it2 = it; it2 != ramCacheMap.end(); ++it2) 192 | { 193 | psram.write(psramBaseAddress + it2->memBlock * blockSize, (uint32_t *)(ramCache + it2->cacheBlock * blockSize), blockSize); 194 | sys.clearMemoryBlockDirty(it->windowBlock); 195 | } 196 | 197 | // drop the rest of this frame, one broken frame is better than two... 198 | if(!cga.isInVBlank()) 199 | discardFrame = true; 200 | } 201 | 202 | // got a cache block, fill it 203 | bool used = (cacheUsedMem[block / 32] & (1 << (block % 32))); 204 | if(used) 205 | { 206 | // load if previously used 207 | psram.wait_for_finish_blocking(); 208 | psram.read_blocking(psramBaseAddress + block * blockSize, (uint32_t *)(ramCache + it->cacheBlock * blockSize), blockSize / 4); 209 | } 210 | else // otherwise just clear it 211 | memset(ramCache + it->cacheBlock * blockSize, 0, blockSize); 212 | 213 | // remove old mapping 214 | if(it->memBlock != 0xFFFF) 215 | sys.removeMemory(it->windowBlock); 216 | 217 | it->memBlock = block; 218 | it->windowBlock = aboveBoard.remapMemoryBlockToWindow(block); 219 | 220 | // the first time we use a block, mark it as dirty 221 | if(!used) 222 | { 223 | cacheUsedMem[block / 32] |= (1 << (block % 32)); 224 | sys.setMemoryBlockDirty(it->windowBlock); 225 | } 226 | 227 | if(it != ramCacheEnd) 228 | { 229 | // move to end of list 230 | ramCacheMap.splice_after(ramCacheEnd, ramCacheMap, prevIt); 231 | ramCacheEnd = it; 232 | } 233 | 234 | return ramCache + it->cacheBlock * blockSize; 235 | } 236 | 237 | static std::forward_list::iterator cacheFlush() 238 | { 239 | // this should be true at this point 240 | // but if it isn't we could end up switching bank part way through 241 | if(!display_render_needed()) 242 | return ramCacheMap.end(); 243 | 244 | const auto blockSize = System::getMemoryBlockSize(); 245 | 246 | // find dirty 247 | int dirtyBlocks = 0; 248 | auto firstDirty = lastFlushedBlock; 249 | 250 | // try to avoid blocks flushed last time 251 | for(; firstDirty != ramCacheMap.end(); ++firstDirty) 252 | { 253 | if(firstDirty != lastFlushedBlock && sys.getMemoryBlockDirty(firstDirty->windowBlock)) 254 | break; 255 | } 256 | 257 | // if there were no more dirty blocks, we'll search from the start while counting 258 | 259 | for(auto it = ramCacheMap.begin(); it != ramCacheMap.end(); ++it) 260 | { 261 | if(sys.getMemoryBlockDirty(it->windowBlock)) 262 | { 263 | dirtyBlocks++; 264 | // track first dirty block 265 | if(firstDirty == ramCacheMap.end()) 266 | firstDirty = it; 267 | } 268 | } 269 | 270 | if(dirtyBlocks < frameFlushThreshold) 271 | return ramCacheMap.end(); 272 | 273 | // now flush to the current PSRAM bank 274 | int count = maxFlushBlocks; 275 | 276 | for(auto it = firstDirty; it != ramCacheMap.end() && count; ++it) 277 | { 278 | if(!sys.getMemoryBlockDirty(it->windowBlock)) 279 | continue; 280 | 281 | display_get_ram().write(psramBaseAddress + it->memBlock * blockSize, (uint32_t *)(ramCache + it->cacheBlock * blockSize), blockSize); 282 | 283 | count--; 284 | lastFlushedBlock = it; 285 | } 286 | 287 | return firstDirty; 288 | } 289 | 290 | static void alarmCallback(uint alarmNum) 291 | { 292 | // TODO: audio output 293 | while(sys.hasSpeakerSample()) 294 | sys.getSpeakerSample(); 295 | 296 | timer_hw->intr = 1 << alarmNum; 297 | hardware_alarm_set_target(alarmNum, make_timeout_time_ms(5)); 298 | } 299 | 300 | void update_key_state(XTScancode code, bool state) 301 | { 302 | sys.sendKey(code, state); 303 | } 304 | 305 | void update_mouse_state(int8_t x, int8_t y, bool left, bool right) 306 | { 307 | mouse.addMotion(x, y); 308 | mouse.setButton(0, left); 309 | mouse.setButton(1, right); 310 | mouse.sync(); 311 | } 312 | 313 | int main() 314 | { 315 | vreg_set_voltage(VREG_VOLTAGE_1_20); 316 | sleep_ms(10); 317 | set_sys_clock_khz(250000, false); 318 | 319 | tusb_init(); 320 | 321 | stdio_init_all(); 322 | 323 | // init storage/filesystem 324 | auto res = f_mount(&fs, "", 1); 325 | 326 | if(res != FR_OK) 327 | { 328 | printf("Failed to mount filesystem! (%i)\n", res); 329 | while(true); 330 | } 331 | 332 | init_display(); 333 | 334 | // int ram map 335 | ramCacheMap.emplace_front(MemBlockMapping{0, 0xFF, 0xFFFF}); 336 | ramCacheEnd = ramCacheMap.begin(); 337 | 338 | for(uint8_t i = 1; i < cacheBlocks; i++) 339 | ramCacheEnd = ramCacheMap.emplace_after(ramCacheEnd, MemBlockMapping{i, 0xFF, 0xFFFF}); 340 | 341 | lastFlushedBlock = ramCacheMap.end(); 342 | 343 | // emulator init 344 | sys.setMemoryRequestCallback(requestMem); 345 | cga.setScanlineCallback(scanlineCallback); 346 | 347 | auto bios = _binary_bios_xt_rom_start; 348 | auto biosSize = _binary_bios_xt_rom_end - _binary_bios_xt_rom_start; 349 | auto biosBase = 0x100000 - biosSize; 350 | sys.addReadOnlyMemory(biosBase, biosSize, (const uint8_t *)bios); 351 | 352 | #ifdef FIXED_DISK 353 | // size is wring, but mem mapping can't handle smaller 354 | sys.addReadOnlyMemory(0xC8000, 0x4000, (const uint8_t *)_binary_fixed_disk_bios_rom_start); 355 | 356 | fixedIO.openDisk(0, "hd0.img"); 357 | fixDisk.setIOInterface(&fixedIO); 358 | #endif 359 | 360 | sys.reset(); 361 | 362 | auto time = get_absolute_time(); 363 | 364 | // fake audio output 365 | // (since the core audio output is all over the place we can't just drain between updates) 366 | int alarmNum = hardware_alarm_claim_unused(true); 367 | hardware_alarm_set_callback(alarmNum, alarmCallback); 368 | hardware_alarm_set_target(alarmNum, make_timeout_time_ms(5)); 369 | irq_set_priority(TIMER_IRQ_0 + alarmNum, PICO_LOWEST_IRQ_PRIORITY); 370 | 371 | while(true) 372 | { 373 | tuh_task(); 374 | 375 | auto now = get_absolute_time(); 376 | auto elapsed = absolute_time_diff_us(time, now) / 1000; 377 | 378 | if(elapsed) 379 | { 380 | if(elapsed > 10) 381 | elapsed = 10; 382 | 383 | sys.getCPU().run(elapsed); 384 | time = delayed_by_ms(time, elapsed); 385 | 386 | cga.update(); 387 | sys.updateForDisplay(); 388 | } 389 | } 390 | 391 | return 0; 392 | } -------------------------------------------------------------------------------- /picovision/config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // for SD code 4 | #define OVERCLOCK_250 1 5 | 6 | #define SD_SCK 10 7 | #define SD_MOSI 11 8 | #define SD_MISO 12 9 | #define SD_CS 15 -------------------------------------------------------------------------------- /picovision/picovision_import.cmake: -------------------------------------------------------------------------------- 1 | # This file can be dropped into a project to help locate the Picovision library 2 | # It will also set up the required include and module search paths. 3 | 4 | if (NOT PIMORONI_PICOVISION_PATH) 5 | if(PICO_SDK_PATH AND EXISTS "${PICO_SDK_PATH}/../picovision") 6 | set(PIMORONI_PICOVISION_PATH ${PICO_SDK_PATH}/../picovision) 7 | message("Defaulting PIMORONI_PICOVISION_PATH as sibling of PICO_SDK_PATH: ${PIMORONI_PICO_PATH}") 8 | else() 9 | set(PIMORONI_PICOVISION_PATH "../../picovision/") 10 | endif() 11 | endif() 12 | 13 | if(NOT IS_ABSOLUTE ${PIMORONI_PICOVISION_PATH}) 14 | get_filename_component( 15 | PIMORONI_PICOVISION_PATH 16 | "${CMAKE_CURRENT_BINARY_DIR}/${PIMORONI_PICOVISION_PATH}" 17 | ABSOLUTE) 18 | endif() 19 | 20 | if (NOT EXISTS ${PIMORONI_PICOVISION_PATH}) 21 | message(FATAL_ERROR "Directory '${PIMORONI_PICOVISION_PATH}' not found") 22 | endif () 23 | 24 | if (NOT EXISTS ${PIMORONI_PICOVISION_PATH}/picovision_import.cmake) 25 | message(FATAL_ERROR "Directory '${PIMORONI_PICOVISION_PATH}' does not appear to contain the Picovision library") 26 | endif () 27 | 28 | message("PIMORONI_PICOVISION_PATH is ${PIMORONI_PICOVISION_PATH}") 29 | 30 | set(PIMORONI_PICOVISION_PATH ${PIMORONI_PICOVISION_PATH} CACHE PATH "Path to the Picovision libraries" FORCE) 31 | 32 | list(APPEND CMAKE_MODULE_PATH ${PIMORONI_PICOVISION_PATH}) 33 | 34 | include(picovision) 35 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(PACETest Main.cpp) 2 | 3 | find_package(ZLIB REQUIRED) 4 | 5 | include(FetchContent) 6 | 7 | FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz) 8 | FetchContent_MakeAvailable(json) 9 | 10 | target_link_libraries(PACETest PRIVATE PACECore nlohmann_json::nlohmann_json ZLIB::ZLIB) -------------------------------------------------------------------------------- /test/Main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "System.h" 9 | 10 | using json = nlohmann::json; 11 | 12 | static System sys; 13 | static uint8_t ram[1024 * 1024]; 14 | 15 | static std::string readGZ(const std::string &path) 16 | { 17 | auto file = gzopen(path.c_str(), "r"); 18 | 19 | if(!file) 20 | return ""; 21 | 22 | std::string ret; 23 | 24 | while(true) 25 | { 26 | auto offset = ret.size(); 27 | 28 | const int chunkSize = 4066; 29 | 30 | ret.resize(ret.size() + chunkSize); 31 | 32 | int readLen = gzread(file, ret.data() + offset, chunkSize); 33 | 34 | if(readLen < chunkSize) 35 | { 36 | ret.resize(offset + readLen); 37 | break; 38 | } 39 | } 40 | 41 | gzclose(file); 42 | 43 | return ret; 44 | } 45 | 46 | static CPU::Reg16 getReg(const std::string &name) 47 | { 48 | if(name == "ax") 49 | return CPU::Reg16::AX; 50 | if(name == "bx") 51 | return CPU::Reg16::BX; 52 | if(name == "cx") 53 | return CPU::Reg16::CX; 54 | if(name == "dx") 55 | return CPU::Reg16::DX; 56 | if(name == "cs") 57 | return CPU::Reg16::CS; 58 | if(name == "ss") 59 | return CPU::Reg16::SS; 60 | if(name == "ds") 61 | return CPU::Reg16::DS; 62 | if(name == "es") 63 | return CPU::Reg16::ES; 64 | if(name == "sp") 65 | return CPU::Reg16::SP; 66 | if(name == "bp") 67 | return CPU::Reg16::BP; 68 | if(name == "si") 69 | return CPU::Reg16::SI; 70 | if(name == "di") 71 | return CPU::Reg16::DI; 72 | if(name == "ip") 73 | return CPU::Reg16::IP; 74 | 75 | std::cerr << "bad reg " << name << "\n"; 76 | return CPU::Reg16::AX; 77 | } 78 | 79 | static void doOpTest(const std::string &testData, uint16_t flagsMask) 80 | { 81 | auto &cpu = sys.getCPU(); 82 | 83 | auto testJson = json::parse(testData); 84 | 85 | int passCount = 0; 86 | 87 | for(auto &test : testJson) 88 | { 89 | sys.reset(); 90 | 91 | // setup initial data 92 | auto initialRegs = test["initial"]["regs"]; 93 | auto initialRAM = test["initial"]["ram"]; 94 | 95 | for(auto ® : initialRegs.items()) 96 | { 97 | if(reg.key() == "flags") 98 | cpu.setFlags(reg.value()); 99 | else 100 | cpu.reg(getReg(reg.key())) = reg.value(); 101 | } 102 | 103 | for(auto &ram : initialRAM) 104 | sys.writeMem(ram[0], ram[1]); 105 | 106 | cpu.executeInstruction(); 107 | 108 | // now test result 109 | auto finalRegs = test["final"]["regs"]; 110 | auto finalRAM = test["final"]["ram"]; 111 | 112 | std::cerr << std::hex; 113 | bool fail = false; 114 | 115 | for(auto ® : finalRegs.items()) 116 | { 117 | uint16_t expected = reg.value(); 118 | uint16_t actual; 119 | 120 | if(reg.key() == "flags") 121 | { 122 | actual = cpu.getFlags() & flagsMask; 123 | expected &= flagsMask; // let's not bother with the unspecified ones 124 | } 125 | else 126 | actual = cpu.reg(getReg(reg.key())); 127 | 128 | if(actual != expected) 129 | { 130 | if(!fail) 131 | std::cerr << "test "<< test["name"] << "\n"; 132 | std::cerr << "\tfail " << reg.key() << " " << actual << " != " << expected << "\n"; 133 | fail = true; 134 | } 135 | } 136 | 137 | for(auto &ram : finalRAM) 138 | { 139 | int expected = ram[1]; 140 | int actual = sys.readMem(ram[0]); 141 | 142 | int addr = ram[0]; 143 | 144 | if(actual != expected) 145 | { 146 | if(!fail) 147 | std::cerr << "test "<< test["name"] << "\n"; 148 | std::cerr << "\tfail ram " << addr << " " << actual << " != " << expected << "\n"; 149 | fail = true; 150 | } 151 | } 152 | 153 | std::cerr << std::dec; 154 | 155 | if(!fail) 156 | passCount++; 157 | } 158 | 159 | std::cout << "\tpassed " << passCount << "/" << testJson.size() << std::endl; 160 | } 161 | 162 | int main(int argc, char *argv[]) 163 | { 164 | sys.addMemory(0, sizeof(ram), ram); 165 | 166 | auto metadata = json::parse(std::ifstream("8088/v1/metadata.json")); 167 | 168 | for(auto &opcode : metadata["opcodes"].items()) 169 | { 170 | auto &value = opcode.value(); 171 | 172 | // skip undocumented 173 | if(value.contains("status") && value["status"] != "normal") 174 | continue; 175 | 176 | // skip unimpl 177 | // ... exit(1) was a bad move... 178 | auto k = opcode.key(); 179 | if(k == "0F" || k == "2F"/*DAS*/ || k == "37"/*AAA*/ || k == "3F"/*AAS*/ || k == "CC"/*INT 3*/ || k == "CE"/*INTO*/ || 180 | k == "E5" /*IN w*/ || k == "E7" /*OUT w*/ || k == "ED" /*IN w*/ || k == "EF" /*OUT w*/) 181 | { 182 | continue; 183 | } 184 | // 185 | 186 | // de-noise 187 | // segment mov fails due to not masking reg 188 | // AAM has wrong(undefined) flags on trap 189 | // IN fails due to emulating some ports 190 | if(k == "8C"/*seg*/ || k == "8E"/*seg*/ || k == "D4"/*AAM*/ || k == "E4" /*IN*/ || k == "EC"/*IN*/) 191 | continue; 192 | // 193 | 194 | uint16_t flagsMask = 0xFFFF; 195 | 196 | if(value.contains("reg")) 197 | { 198 | // sub-opcodes 199 | for(auto &subOpcode : value["reg"].items()) 200 | { 201 | auto &subValue = subOpcode.value(); 202 | 203 | if(subValue["status"] != "normal") 204 | continue; 205 | 206 | // unimpl 207 | if(k == "F6" && subOpcode.key() == "7") 208 | continue; 209 | // 210 | 211 | // de-noise 212 | // DIV has wrong(undefined) flags on trap 213 | if(((k == "F6" || k == "F7") && (subOpcode.key() == "6" || subOpcode.key() == "7"))) 214 | continue; 215 | // 216 | 217 | if(subValue.contains("flags-mask")) 218 | flagsMask = subValue["flags-mask"]; 219 | 220 | std::cout << "opcode " << opcode.key() << " r " << subOpcode.key() << std::endl; 221 | 222 | auto data = readGZ("8088/v1/" + opcode.key() + "." + subOpcode.key() + ".json.gz"); 223 | 224 | if(data.empty() && subOpcode.key() == "0") 225 | data = readGZ("8088/v1/" + opcode.key() + ".json.gz"); 226 | 227 | if(data.empty()) 228 | { 229 | std::cerr << "could not get data\n"; 230 | continue; 231 | } 232 | doOpTest(data, flagsMask); 233 | } 234 | } 235 | else 236 | { 237 | // no sub-opcodes 238 | std::cout << "opcode " << opcode.key() << std::endl; 239 | 240 | if(value.contains("flags-mask")) 241 | flagsMask = value["flags-mask"]; 242 | 243 | auto data = readGZ("8088/v1/" + opcode.key() + ".json.gz"); 244 | 245 | if(data.length()) 246 | doOpTest(data, flagsMask); 247 | else 248 | std::cerr << "could not get data\n"; 249 | } 250 | } 251 | 252 | return 0; 253 | } 254 | --------------------------------------------------------------------------------