From bc56ffa990cf0ba9b1a3baabab2ede363746393a Mon Sep 17 00:00:00 2001 From: Martin Thierer Date: Mon, 28 Aug 2023 22:55:22 +0200 Subject: [PATCH] Bitfire loader support Most productions from CSDb crediting Bitbreaker for the loader have been tested and work. See the table at the end of doc/bitfire.txt for details. --- NEWS | 2 +- README | 32 +-- configs/config-arm2iec1 | 1 + configs/config-example | 3 + configs/config-larsp | 1 + configs/config-mbed | 1 + configs/config-sw1 | 1 + configs/config-sw2 | 1 + configs/config-uIEC | 1 + configs/config-uIEC3 | 1 + doc/bitfire.txt | 388 +++++++++++++++++++++++++ scripts/Makefile.main | 1 + src/doscmd.c | 43 +++ src/fastloader-ll.h | 5 + src/fastloader.c | 4 +- src/fastloader.h | 12 + src/fl-bitfire.c | 616 ++++++++++++++++++++++++++++++++++++++++ 17 files changed, 1094 insertions(+), 19 deletions(-) create mode 100644 doc/bitfire.txt create mode 100644 src/fl-bitfire.c diff --git a/NEWS b/NEWS index c0be1d96..475a42c8 100644 --- a/NEWS +++ b/NEWS @@ -20,7 +20,7 @@ - New fastloader: Hypra-Load - create D64 images in the FAT FS with N: if no image is mounted - Krill's IRQ loader - - Demo loaders: BoozeLoader, Spindle + - Demo loaders: Bitfire, BoozeLoader, Spindle 2012-02-26 - release 0.10.3 - Bugfix: Un-break I2C display communication diff --git a/README b/README index 3719caae..42076cd9 100644 --- a/README +++ b/README @@ -776,22 +776,22 @@ Note: Using sd2iec without an external crystal or similiar precise loader were tested. Other productions might use configuration options which are not supported and therefore might not work. - BoozeLoader - ----------- - - The loader uses sector addressing in combination with a non-standard - directory and therefore only works from the original D64 images, not - from the FAT filesytem. - - Spindle - ------- - - The loader only works from the original D64 images, not from the FAT - filesytem. - - Like Krill's Loader, Spindle >= 3.0 includes an ATN responder, which - activates "Sleep Mode" in a sd2iec present on the bus as a passive - device. + Demo loaders: Bitfire, BoozeLoader, Spindle + ------------------------------------------- + + All these loaders use sector addressing and non-standard directories + and therefore only work from the original D64 images, not from the + FAT filesytem. + + Like Krill's Loader, Bitfire >= 1.0 and Spindle >= 3.0 include an + ATN responder which activates "Sleep Mode" in a sd2iec present on + the bus as a passive device. + + With a few exceptions, only productions released on CSDb which + credit the respective author for the loader were tested. Others + might not work. For a list of known productions using these loaders + and known issues, see the tables at the end of the respective + documents in the "doc" directory of this repository. JiffyDOS: ========= diff --git a/configs/config-arm2iec1 b/configs/config-arm2iec1 index 9a73a94b..4b4c0f62 100644 --- a/configs/config-arm2iec1 +++ b/configs/config-arm2iec1 @@ -75,3 +75,4 @@ CONFIG_LOADER_HYPRALOAD=n CONFIG_LOADER_KRILL=n CONFIG_LOADER_BOOZE=n CONFIG_LOADER_SPINDLE=n +CONFIG_LOADER_BITFIRE=n diff --git a/configs/config-example b/configs/config-example index 3d0ff26d..af911870 100644 --- a/configs/config-example +++ b/configs/config-example @@ -132,6 +132,9 @@ CONFIG_LOADER_BOOZE=n # Enable Spindle loader CONFIG_LOADER_SPINDLE=n +# Enable Bitfire loader +CONFIG_LOADER_BITFIRE=n + # Enable DolphinDOS parallel speeder CONFIG_PARALLEL_DOLPHIN=y diff --git a/configs/config-larsp b/configs/config-larsp index 8b81f8b2..83ad149a 100644 --- a/configs/config-larsp +++ b/configs/config-larsp @@ -71,3 +71,4 @@ CONFIG_LOADER_HYPRALOAD=y CONFIG_LOADER_KRILL=y CONFIG_LOADER_BOOZE=y CONFIG_LOADER_SPINDLE=y +CONFIG_LOADER_BITFIRE=y diff --git a/configs/config-mbed b/configs/config-mbed index 9e899963..1c247e08 100644 --- a/configs/config-mbed +++ b/configs/config-mbed @@ -51,6 +51,7 @@ CONFIG_LOADER_HYPRALOAD=n CONFIG_LOADER_KRILL=n CONFIG_LOADER_BOOZE=n CONFIG_LOADER_SPINDLE=n +CONFIG_LOADER_BITFIRE=n CONFIG_HARDWARE_VARIANT=100 CONFIG_HARDWARE_NAME=sd2iec-mbed CONFIG_SD_AUTO_RETRIES=10 diff --git a/configs/config-sw1 b/configs/config-sw1 index 92321aaa..72f81b8b 100644 --- a/configs/config-sw1 +++ b/configs/config-sw1 @@ -69,3 +69,4 @@ CONFIG_LOADER_HYPRALOAD=y CONFIG_LOADER_KRILL=y CONFIG_LOADER_BOOZE=y CONFIG_LOADER_SPINDLE=y +CONFIG_LOADER_BITFIRE=y diff --git a/configs/config-sw2 b/configs/config-sw2 index e81594c0..c68d6edf 100644 --- a/configs/config-sw2 +++ b/configs/config-sw2 @@ -71,3 +71,4 @@ CONFIG_LOADER_HYPRALOAD=y CONFIG_LOADER_KRILL=y CONFIG_LOADER_BOOZE=y CONFIG_LOADER_SPINDLE=y +CONFIG_LOADER_BITFIRE=y diff --git a/configs/config-uIEC b/configs/config-uIEC index e1b8f14e..cca9e139 100644 --- a/configs/config-uIEC +++ b/configs/config-uIEC @@ -73,3 +73,4 @@ CONFIG_LOADER_HYPRALOAD=y CONFIG_LOADER_KRILL=y CONFIG_LOADER_BOOZE=y CONFIG_LOADER_SPINDLE=y +CONFIG_LOADER_BITFIRE=y diff --git a/configs/config-uIEC3 b/configs/config-uIEC3 index 1f84d065..2510b627 100644 --- a/configs/config-uIEC3 +++ b/configs/config-uIEC3 @@ -71,3 +71,4 @@ CONFIG_LOADER_HYPRALOAD=y CONFIG_LOADER_KRILL=y CONFIG_LOADER_BOOZE=y CONFIG_LOADER_SPINDLE=y +CONFIG_LOADER_BITFIRE=y diff --git a/doc/bitfire.txt b/doc/bitfire.txt new file mode 100644 index 00000000..ce385738 --- /dev/null +++ b/doc/bitfire.txt @@ -0,0 +1,388 @@ +Bitfire Loader Protocol +============================= +& sd2iec Implementation Notes + +Documented by Martin Thierer + +Bitfire is an IRQ-loader written by Bitbreaker [*], which is mostly +used for demos. You can find a list of known productions using it at +the end of this document. + +*) Bitbreaker https://csdb.dk/scener/?id=1678 + +Loader Revisions +================ + +Various revisions have been publicly released, but not all seem to +be actually used by any production. Some productions use pre-release +versions of 0.7 with significant differences to both 0.6 and 0.7. + +0.1 https://csdb.dk/release/?id=133975 +0.2 https://csdb.dk/release/?id=134613 +0.3 https://csdb.dk/release/?id=138322 +0.4 https://csdb.dk/release/?id=141245 +0.5 https://csdb.dk/release/?id=145333 protocol matches 0.4 +0.6 https://csdb.dk/release/?id=146211 +0.7 no release on CSDb, only tagged in git +1.0 https://csdb.dk/release/?id=201237 +1.1 https://csdb.dk/release/?id=214232 + +git https://github.com/bboxy/bitfire.git (only >= 0.6) + +Note: Parts of this document refer to a version "1.2". Right now, no + such version exists, neither as an official release or tagged in + git. It's the loader used by Next Level [007] and Deep Space 64 + [002]/[005], which contains changes incompatible with 1.1. They + might or might not represent what goes into a future, official + release, whatever its version will be called. + +Byte-Transfer Protocols +======================= + +All protocols use a clock line, which is always driven by the host. + +1-Bit Receive Protocol +---------------------- + +Used for both drivecode download and commands. Most loader revisions +use DATA as clock line and CLK for the data. + +The byte transfer starts on a negative edge of the respective clock +line. Bytes are sent LSB-first and for some loader revisions with the +bit values inverted (data line low is bit == 1 and vice versa). + +The drivecode download always uses CLK for data and the data is never +inverted. + + | Clock | Data | inv +------------|--------|--------|----- + <= 0.5 | DATA | CLK | + 0.6 | DATA | CLK | x + 0.7 | CLK | DATA | x + 1.0 | DATA | CLK | x + 1.1 | DATA | CLK | + 1.2 | CLK | DATA | +------------|--------|--------|----- +dc download | DATA | CLK | + +The host acknowledges each bit with a clock edge. After 8 bits the +clock line is in the same state as in the beginning of the byte +transfer. + +2-Bit Send Protocol +------------------- + +Used during file transfers for sending both the payload and the +metadata bytes. This protocol is identical for all loader revisions. + +ATN is used as the clock line and CLK & DATA are the data lines. The +transfer starts on a negative clock edge and ends on a positive edge. + + c0/d0 | c1/d1 | c2/d2 | c3/d3 +---------|---------|---------|--------- + b0/b1 | b2/b3 | b4/b5 | b6/b7 + +Installation +============ + +All revisions transfer an installer stub using M-W commands. It is +started using a M-E command without any additional payload and +downloads the rest of the drivecode from the host. + +Drivecode Download +------------------ + +The drive sets either CLK (<= 0.4) or DATA (> 0.4) when it's ready to +receive. The host then starts the transfer with a short ATN low pulse. + +The bytes are transferred using the 1-bit receive protocol (see "Byte- +Transfer Protocols", above). Note this always uses DATA as clock line +and sends the data bit non-inverted, even for loader revisions which +use a slightly different protocol to transmit commands. + +Revisions < 0.6 set ATN between bytes and the first bit is valid with +the positive ATN edge. Later versions just use the clock line (DATA). + +Revisions > 0.6 set ATN at the end of the drivecode transfer. + +ATN Responder +------------- + +If the loader detects additional drives on the bus, revisions < 1.0 +show a warning and refuse to start until this is corrected. + +Later revisions install an "ATN responder" on the other drives, which +monitors the ATN IN signal and sets ATNA accordingly, to minimize the +effect on the DATA line. + +If used as a passive device on the bus, the sd2iec firmware also +detects this and enters "sleep mode", which is indicated by the static +error LED and which can be deactivated by a long press of the disk +change button. + +Directory Layout +================ + +All revisions can handle random file requests, for which a custom +directory is used (so bitfire "files" are not referenced from the CBM +DOS directory). + +The first directory sector is always $12/$12, extending to sectors $11 +and $10, if necessary. + +The directory sector layout differs between 0.x and 1.x revisions. +Each file entry is 6 bytes for the 0.x versions and 4 bytes for the +1.x versions, as these don't store the individual start sectors for +each file (see "Sector Chains", below). All versions can handle a +maximum of 126 files, which would use 3 sectors in 0.x and 2 sectors +in 1.x revisions (42*3 == 63*2 == 126). + + | $00 .. $fb file entries | $fc $fd $fe $ff | + --------|----------------------------|-----------------| + 0.x | [ TR ST AL AH LL LH ] * 42 | ID | + 1.0/1.1 | [ AL AH LL LH ] * 63 | FT FS FO ID | + + | $00 $01 $02 $03 | $04.. $ff file entries | + --------|-----------------|----------------------------| + 1.2 | FT FS FO ID | [ AL AH LL LH ] * 63 | + + TR/ST file start sector (0.x only) + AL/AH file load address low/high (-$100 for 1.2) + LL/LH file length-1 (!) low/high + FT/FS/FO first (!) entry's track, sector- and byte-offset (1.x only) + ID disk id + +Note: In revision 1.2 the load address stored in the directory entry + is the actual load address - $100. + +Sector Chains +============= + +The individual sectors of each file are not linked using the first +two bytes of each sector (like in CBM DOS) but instead all 256 bytes +are used for payload data and the next sector is found using the +following algorithm: + + next_sector = current_sector + sector_interleave + + if next_sector >= number_of_sectors_on_current_track: + next_sector = (next_sector % sector_interleave) + 1 + + if next_sector == sector_interleave: // done with track + next_sector = 0 + track = track + 1 // +2 if track == $11 (skip directory track) + +All known productions use an interleave of 4 sectors. + +The files on the disk are stored contiguous, so a block might contain +data of (at least) two files. + +1.x revisions don't store the start sector for every file, but only +for the first entry of each directory sector (see "Directory Layout", +above). For random access, the start sector and the start offset in +this sector (see "File Transfer", below) have to be calculated by +iterating over the preceding entries in that directory sector and +adding up the file sizes. (This isn't necessary for sequential access, +because the start position is still known as the byte following the +previous file's last byte). + +Request Handling +================ + +After initialization, the drive enters the job loop and waits for +one-byte commands from the host. + +Which bus line is set by the host to start the request differs between +loader versions: 0.1 sets ATN, the other versions the same line which +is used as clock line for the 1-bit receive protocol (DATA for 0.2 to +0.6, 1.0 and 1.1, CLK otherwise). + +The command byte is transferred using the 1-bit receive protocol (see +"Byte-Transfer Protocols", above). + +The command normally indicates the (zero-based) index of the requested +file. As all versions support a maximum of 126 files, valid values are +$00 to $7d, plus some special commands: + + $80 custom drivecode upload (not supported on sd2iec) + $ef next file (index of previous file + 1 or 0 if first request) + $fx disk id; wait for disk with this id to be inserted + $ff drive reset (quits job loop) + +After the command was received, the drive sets DATA to indicate that +it's busy. + +File Transfer +------------- + +Every block is preceded by a few bytes of metadata ("preamble"), which +differs between the various loader revisions. + +For older revisions, the file's load address is only sent for the +first block, but 1.0 and later instead send each block's start +address. This is necessary, because these revisions always only +transfer the relevant portion of the file's first block (remember, +file data is stored contiguous), so the low byte of the subsequent +blocks' load address differs from the file's load address. + + | file's first block | subsequent blocks | + ------------|---------------------|-------------------| + 0.1 | AH AL BI LN | BI LN | + 0.2/0.3 | 00 AH AL BH LN | 00 BH LN | + 0.4/0.5 | BD AH AL BH LN | BD BH LN | + 0.6/0.7pre | BD AL AH BH LN | BD BH LN | + 0.7 | BD AL AH BR BH LN | 00 BR BH LN | + 1.x | 00 BR AH AL LN | 80 BR BH BL LN | + + AL/AH file's load address low/high. + BL/BH current block's load address low/high. + BI zero-based block index in file. + BR "barrier"; high byte of the highest contiguous block's + end address (+1 for 0.7) + BD delta of number of contiguous blocks so far ($ff for first) + shifted left by 2 bits so that CLK and DATA are always set + in the first bitpair. + LN length of current block in bytes. + +The barrier byte is needed for the decruncher when sending blocks out +of order, like the loader does. As the sd2iec implementation sends all +blocks in order, it is always set to the current block's load address +high byte (which also seems to work just fine for 0.7). + +As a special case, some productions using pre-release versions of 0.7 +were built with the BITFIRE_DEBUG flag set. These additionally send +the current file's index as the second byte of the first block's +header (between BD and AL). + +Bus Lock +-------- + +If the host sets ATN in a loader revision that supports it (>= 0.7 +pre-releases), the drive ignores all activity on the other bus lines +until ATN is released again. This makes it possible for the host to +modify $dd00 without confusing the drive. + +Disk Change +----------- + +If the host requests a disk change (command value $f0 to $fe), the +drive keeps reading the first directory sector ($12/$12) and checks +the byte at either sector offset $ff (<= 1.1) or $03 (1.2) for the +requested disk id. + +For loader versions <= 0.3 there is an extra handshake, where the +drive sets CLK after the correct disk has been identified and the host +acknowledges with either one (<= 0.2) or two (0.3) short ATN low +pulses. + +Known Productions using this Loader (as of August 2023) +======================================================= + +Note: The table below is compiled from releases on CSDb crediting + Bitbreaker for the loader, plus a few others found during + testing. It might not be complete and productions not listed + have not been tested and therefore might not work with the + sd2iec implementation. + +Reference: + pr = 0.7 pre-release version + db = 0.7 pre-release version built with BITFIRE_DEBUG + ! = See specific comment at end of table + 4 = 40 Track diskimage + +Ref | Title | CSDb | Rev | N +----|--------------------------------------------|--------|-------|--- +000 | The Space is Broken | 234768 | 0.7db | +001 | The Scroll of Antonius | 234200 | 0.7db | +002 | Deep Space 64 (final) | 233840 | 1.2 | +003 | Looking for Atlantis | 233693 | 0.7db | +004 | That Thing I Hate About Myself | 233555 | 0.7db | +005 | Deep Space 64 | 232987 | 1.2 | +006 | Wonderland XIV | 232980 | 1.1 | 4 +007 | Next Level | 232976 | 1.2 | +008 | Multiverse | 232973 | 1.1 | +009 | Hues | 232960 | 0.7db | +010 | Cinquanta | 231055 | 0.7 | +011 | Cinque | 226347 | 0.7 | +012 | Formula Petscii | 225762 | 1.1 | +013 | Vandalism News #73 Headlines | 225024 | 1.1 | +014 | Logo Graphics Compo 2022 Results | 224154 | 1.1 | +015 | PöSö | 220371 | 1.1 | +016 | Cowboy's Dream | 218369 | 1.1 | +017 | Vinyl Tribute #2 | 212342 | 1.0 | +018 | Unity | 203390 | 0.7 | +019 | Thirty | 200575 | 0.7 | +020 | XMAS 2020 | 198281 | 0.6 | +021 | !dead | 197878 | 0.7 | +022 | 2600 | 197187 | 0.7db | +023 | SID Chip Club | 193111 | 0.7 | +024 | Stacked | 187977 | 0.7pr | +025 | ExAc20 Aftershow | 187559 | 0.7 | +026 | In a Hurry | 187525 | 0.7pr | +027 | The Last Truckstop 3 | 180321 | 0.7db | 4 +028 | Two Sided | 180320 | 0.7pr | +029 | Thera | 179130 | 0.7pr | +030 | 1337 Karate | 177033 | 0.7 | ! +031 | The 21st | 177032 | 0.7 | +032 | Rivalry | 177023 | 0.7db | 4 +033 | Monomania | 175657 | 0.7db | +034 | Christmas18 | 173069 | 0.6 | +035 | C=Bit 18 | 170950 | 0.7 | +036 | Old Men in Used Cars | 170944 | 0.6 | +037 | Xcusemo | 170933 | 0.7 | +038 | The Star Wars Demo | 170922 | 0.6 | 4 +039 | Jump | 167225 | 0.7 | +040 | Fopcycle | 166968 | 0.7db | +041 | @ | 166967 | 0.7pr | +042 | Manorexic | 166742 | 0.7db | +043 | Call The Hidden | 165914 | 0.7pr | +044 | We Come in Peace | 163427 | 0.7db | 4 +045 | Krush Groovin' | 162872 | 0.6 | 4 +046 | Honey | 162542 | 0.7pr | +047 | Pain In The Asm | 161582 | 0.7db | +048 | Stoned Dragon | 161136 | 0.7 | +049 | Feliz Navidad | 161022 | 0.7db | +050 | Private Parts | 160050 | 0.7 | +051 | 40 | 158951 | 0.7pr | +052 | Quad Core 100% | 158909 | 0.6 | +053 | Quad Core | 158773 | 0.6 | +054 | Single Core | 158768 | 0.6 | +055 | Beats | 158642 | 0.7pr | +056 | K9 V Orange Main Sequence | 158641 | 0.7db | +057 | Modern Love Classics | 157489 | 0.7pr | +058 | Reluge 101% | 156960 | 0.7pr | +059 | SSDPCM1-Super | 156958 | 0.7 | 4 +060 | Reluge | 155521 | 0.7pr | +061 | The Shores of Reflection | 153526 | 0.6 | +062 | The Last Hope | 153523 | 0.7pr | +063 | Datastorm Leftovers | 153112 | 0.7db | +064 | Frodigi 8 | 152997 | 0.7pr | +065 | Mazinger Z Sing-Along | 152996 | 0.7pr | 4 +066 | Algo Dreams | 151630 | 0.6 | +067 | Ammonite | 151281 | 0.6 | +068 | Area 64 | 151276 | 0.7db | +069 | Wonderland XIII | 151275 | 0.6 | +070 | Concert | 151274 | 0.7pr | +071 | Incoherent Nightmare | 151257 | 0.7pr | +072 | 25 Years Atlantis | 151253 | 0.7db | +073 | 25 Years | 151250 | 0.7pr | +074 | 20+1 Years | 149770 | 0.6 | +075 | We/Shades | 149205 | 0.6 | +076 | Watch My Balls in Action! | 146727 | 0.6 | +077 | 50 Bytes of Sylvia | 144862 | 0.4 | +078 | Sample Blaster | 143938 | 0.4 | 4 +079 | Comaland 100% | 139278 | 0.3 | +080 | Fantasmolytic | 139263 | 0.3 | +081 | P0 Snake [sales version 64k] +3PD | 139055 | 0.3 | +082 | P0 Snake [sales version 64k] +3PD | 139000 | 0.3 | +083 | Frodigi 5 | 135265 | 0.3 | 4 +084 | Strip-a-Minute | 135099 | 0.3 | 4 +085 | Comaland | 133940 | 0.1 | +086 | Oxy Rock | 132062 | 0.1 | + +Production-specific Remarks +--------------------------- + +[030] 1337 Karate + Doesn't load beyond the game selection menu because it uses + unsupported custom drivecode. diff --git a/scripts/Makefile.main b/scripts/Makefile.main index f2442926..8c9f089b 100644 --- a/scripts/Makefile.main +++ b/scripts/Makefile.main @@ -60,6 +60,7 @@ ifeq ($(CONFIG_HAVE_IEC),y) SRC += fl-mmzak.c fl-nippon.c fl-turbodisk.c fl-ulm3.c SRC += fl-n0sdos.c fl-samsjourney.c fl-ultraboot.c SRC += fl-hypraload.c fl-krill.c fl-booze.c fl-spindle.c + SRC += fl-bitfire.c endif ifneq ($(CONFIG_NO_SD),y) diff --git a/src/doscmd.c b/src/doscmd.c index cad210b4..2da0a676 100644 --- a/src/doscmd.c +++ b/src/doscmd.c @@ -78,6 +78,11 @@ enum { RXTX_KRILL_DATA, RXTX_KRILL_CLOCK, RXTX_KRILL_RESEND, + + RXTX_BITFIRE_DATA, + RXTX_BITFIRE_IDATA, + RXTX_BITFIRE_CLOCK, + RXTX_BITFIRE_ICLK, }; typedef uint8_t (*fastloader_rx_t)(void); @@ -111,6 +116,12 @@ static const PROGMEM struct fastloader_rxtx_s fl_rxtx_table[] = { [RXTX_KRILL_RESEND] = { krill_get_byte_clk_data, krill_send_byte_resend }, [RXTX_KRILL_CLOCK] = { krill_get_byte_data_clk, krill_send_byte_atn }, #endif +#ifdef CONFIG_LOADER_BITFIRE + [RXTX_BITFIRE_DATA] = { bitfire_get_byte_clk_data, NULL }, + [RXTX_BITFIRE_IDATA] = { bitfire_get_byte_clk_data_inv, NULL }, + [RXTX_BITFIRE_CLOCK] = { bitfire_get_byte_data_clk, NULL }, + [RXTX_BITFIRE_ICLK] = { bitfire_get_byte_data_clk_inv, NULL }, +#endif }; struct fastloader_crc_s { @@ -249,11 +260,30 @@ static const PROGMEM struct fastloader_crc_s fl_crc_table[] = { { 0x40c3, FL_KRILL_SLEEP, RXTX_NONE }, // r184 { 0x5088, FL_KRILL_SLEEP, RXTX_NONE }, // r164 { 0x1fdc, FL_SPINDLE_SLEEP, RXTX_NONE }, + { 0x955d, FL_BITFIRE_SLEEP, RXTX_NONE }, #endif #ifdef CONFIG_LOADER_BOOZE { 0x0c48, FL_BOOZE, RXTX_NONE }, { 0x5f66, FL_BOOZE, RXTX_NONE }, #endif +#ifdef CONFIG_LOADER_BITFIRE + { 0x7cd6, FL_BITFIRE_01, RXTX_BITFIRE_CLOCK }, + { 0xf1ec, FL_BITFIRE_01, RXTX_BITFIRE_CLOCK }, + { 0x2b10, FL_BITFIRE_03, RXTX_BITFIRE_CLOCK }, + { 0xb0f4, FL_BITFIRE_04, RXTX_BITFIRE_CLOCK }, + { 0xaf44, FL_BITFIRE_06, RXTX_BITFIRE_ICLK }, + { 0x1f43, FL_BITFIRE_07PRE, RXTX_BITFIRE_IDATA }, + { 0xb2dd, FL_BITFIRE_07PRE, RXTX_BITFIRE_IDATA }, + { 0x809f, FL_BITFIRE_07DBG, RXTX_BITFIRE_IDATA }, + { 0x3046, FL_BITFIRE_07, RXTX_BITFIRE_IDATA }, + { 0xb8e6, FL_BITFIRE_07, RXTX_BITFIRE_IDATA }, + { 0xc83a, FL_BITFIRE_10, RXTX_BITFIRE_ICLK }, + { 0x0453, FL_BITFIRE_11, RXTX_BITFIRE_CLOCK }, + { 0x7c59, FL_BITFIRE_11, RXTX_BITFIRE_CLOCK }, + { 0xa45a, FL_BITFIRE_11, RXTX_BITFIRE_CLOCK }, + { 0x1c3d, FL_BITFIRE_11, RXTX_BITFIRE_CLOCK }, + { 0x8d3a, FL_BITFIRE_12, RXTX_BITFIRE_DATA }, +#endif { 0, FL_NONE, 0 }, // end marker }; @@ -337,6 +367,7 @@ static const PROGMEM struct fastloader_handler_s fl_handler_table[] = { { 0x0205, FL_KRILL_SLEEP, bus_sleep_krill, 0 }, // < r192 ATN responder { 0x020b, FL_NONE, bus_sleep_krill, 1 }, // >= r192 ATN responder { 0x0403, FL_SPINDLE_SLEEP, bus_sleep, 0 }, + { 0x0205, FL_BITFIRE_SLEEP, bus_sleep, 0 }, #endif #if defined(CONFIG_LOADER_KRILL) || defined(CONFIG_BUS_SILENCE_REQ) { 0x0205, FL_NONE, drvchkme_krill, 1 }, // < r192 drvchkme @@ -390,6 +421,18 @@ static const PROGMEM struct fastloader_handler_s fl_handler_table[] = { #ifdef CONFIG_LOADER_SPINDLE { 0x0205, FL_NONE, load_spindle, 0 }, #endif +#ifdef CONFIG_LOADER_BITFIRE + { 0x0700, FL_BITFIRE_01, load_bitfire, 0 }, + { 0x0700, FL_BITFIRE_03, load_bitfire, 1 }, + { 0x0700, FL_BITFIRE_04, load_bitfire, 2 }, + { 0x0700, FL_BITFIRE_06, load_bitfire, 3 }, + { 0x0700, FL_BITFIRE_07PRE, load_bitfire, 3 }, + { 0x0700, FL_BITFIRE_07DBG, load_bitfire, 4 }, + { 0x0700, FL_BITFIRE_07, load_bitfire, 5 }, + { 0x0700, FL_BITFIRE_10, load_bitfire, 6 }, + { 0x0700, FL_BITFIRE_11, load_bitfire, 6 }, + { 0x0700, FL_BITFIRE_12, load_bitfire, 6 }, +#endif { 0, FL_NONE, NULL, 0 }, // end marker }; diff --git a/src/fastloader-ll.h b/src/fastloader-ll.h index 0bcf25ec..6fe220e4 100644 --- a/src/fastloader-ll.h +++ b/src/fastloader-ll.h @@ -75,6 +75,11 @@ uint8_t krill_send_byte_58pre(uint8_t byte); uint8_t krill_send_byte_atn(uint8_t byte); uint8_t krill_send_byte_resend(uint8_t byte); +uint8_t bitfire_get_byte_data_clk(void); +uint8_t bitfire_get_byte_data_clk_inv(void); +uint8_t bitfire_get_byte_clk_data(void); +uint8_t bitfire_get_byte_clk_data_inv(void); + typedef enum { PARALLEL_DIR_IN = 0, PARALLEL_DIR_OUT } parallel_dir_t; diff --git a/src/fastloader.c b/src/fastloader.c index 8c1bc337..d8135015 100644 --- a/src/fastloader.c +++ b/src/fastloader.c @@ -105,7 +105,7 @@ uint16_t command_crc(const uint8_t start_offset, const uint8_t end_offset) { } #endif -#if defined(CONFIG_LOADER_KRILL) || defined(CONFIG_LOADER_BOOZE) || defined(CONFIG_LOADER_SPINDLE) +#if defined(CONFIG_LOADER_KRILL) || defined(CONFIG_LOADER_BOOZE) || defined(CONFIG_LOADER_SPINDLE) || defined(CONFIG_LOADER_BITFIRE) /** * Wait for ATN low with a variable (but not very precise) timeout. * @@ -236,7 +236,7 @@ uint8_t clocked_read_byte(iec_bus_t clk, iec_bus_t data, uint16_t to) { } #endif -#if defined(CONFIG_LOADER_KRILL) || defined(CONFIG_LOADER_BOOZE) || defined(CONFIG_LOADER_SPINDLE) +#if defined(CONFIG_LOADER_KRILL) || defined(CONFIG_LOADER_BOOZE) || defined(CONFIG_LOADER_SPINDLE) || defined(CONFIG_LOADER_BITFIRE) /* Search a (loader-specfic) file quirks table for an entry with the given */ /* crc. Returns the pointer to the entry, if found, or NULL otherwise. */ const file_quirks_t *get_file_quirks(const file_quirks_t *fq_table, uint16_t crc) { diff --git a/src/fastloader.h b/src/fastloader.h index f6d406d3..5dd8dee6 100644 --- a/src/fastloader.h +++ b/src/fastloader.h @@ -82,6 +82,17 @@ typedef enum { FL_SPINDLE_22, FL_SPINDLE_23, FL_SPINDLE_3, + FL_BITFIRE_SLEEP, + FL_BITFIRE_01, + FL_BITFIRE_03, + FL_BITFIRE_04, + FL_BITFIRE_06, + FL_BITFIRE_07PRE, // 0.7 without barrier byte in header + FL_BITFIRE_07DBG, // 0.7 without barrier byte and compiled with BITFIRE_DEBUG + FL_BITFIRE_07, + FL_BITFIRE_10, + FL_BITFIRE_11, + FL_BITFIRE_12, } fastloaderid_t; typedef struct { @@ -127,6 +138,7 @@ bool bus_sleep_krill(uint8_t); bool load_krill(uint8_t); bool load_booze(uint8_t); bool load_spindle(uint8_t); +bool load_bitfire(uint8_t); int16_t dolphin_getc(void); uint8_t dolphin_putc(uint8_t data, uint8_t with_eoi); diff --git a/src/fl-bitfire.c b/src/fl-bitfire.c new file mode 100644 index 00000000..9453a3ae --- /dev/null +++ b/src/fl-bitfire.c @@ -0,0 +1,616 @@ +/* sd2iec - SD/MMC to Commodore serial bus interface/controller + Copyright (C) 2007-2022 Ingo Korb + Final Cartridge III, DreamLoad, ELoad fastloader support: + Copyright (C) 2008-2011 Thomas Giesel + Nippon Loader support: + Copyright (C) 2010 Joerg Jungermann + Bitfire support: + Copyright (C) 2023 Martin Thierer + + Inspired by MMC2IEC by Lars Pontoppidan et al. + + FAT filesystem access based on code from ChaN and Jim Brain, see ff.c|h. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License only. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + fl-bitfire.c: Handling of Bitfire loader + +*/ + + +#include +#include "config.h" +#include "crc.h" +#include "buffers.h" +#include "d64ops.h" +#include "doscmd.h" +#include "errormsg.h" +#include "iec-bus.h" +#include "parser.h" +#include "timer.h" +#include "fastloader.h" + + +/* First dir sector on track 18 (next: DIR_START-1, DIR_START-2) */ +#define INIT_TRACK 18 +#define DIR_START 18 + +/* 0.x: 6 bytes per entry / 42 entries per sector / 3 sectors max. */ +/* 1.x: 4 bytes per entry / 63 entries per sector / 2 sectors max. */ +/* All have a maximum of 126 files in total (3*42 == 2*63 == 126). */ +#define V0_DIR_ENTRIES 42 +#define V1_DIR_ENTRIES 63 +#define MAX_FILES 126 + +typedef struct { + uint8_t track, sector; + uint16_t addr, length; +} dir_entry_v0_t; + +typedef struct dir_entry_v1 { + uint16_t addr, length; +} dir_entry_v1_t; + +typedef union { + dir_entry_v0_t v0; + dir_entry_v1_t v1; +} dir_entry_t; + +/* all known productions use an interleave of 4 sectors */ +#define INTERLEAVE 4 + +#define LOAD_NEXT_CMD 0xef +#define RESET_CMD 0xff + +/* block header ("preamble") fields */ +typedef enum { + IMM0, // 0x00 value + + LDLO, // file load address low-byte - first block only + LDHI, // file load address high-byte - first block only + + BALO, // block load address low-byte + BAHI, // block load address high-byte + BIDX, // zero-based block index + + BARR, // barrier (high-byte of last contiguous block's address + 1) + BRDT, // barrier delta (shifted << 2) + + BLST, // block status + BLEN, // block length + + FNUM, // file number (debug builds only) +} hdr_field_t; + +#define MAX_HDR_LEN 6 + +/* The last byte is always the block length and can therefore */ +/* (for now) be used as an implicit end marker. */ +static const PROGMEM hdr_field_t hdr_fields[][MAX_HDR_LEN] = { + { LDHI, LDLO, BIDX, BLEN }, // 0.1 + { IMM0, LDHI, LDLO, BAHI, BLEN }, // 0.2/0.3 + { BRDT, LDHI, LDLO, BAHI, BLEN }, // 0.4/0.5 + { BRDT, LDLO, LDHI, BAHI, BLEN }, // 0.6 and 0.7 pre-releases + { BRDT, FNUM, LDLO, LDHI, BAHI, BLEN }, // 0.7 pre-release debug builds + { BRDT, LDLO, LDHI, BARR, BAHI, BLEN }, // 0.7 + { BLST, BARR, BAHI, BALO, BLEN }, // 1.x +}; + +typedef struct { + buffer_t *dir_buf; + uint8_t dir_sector; // loaded dir sector + uint8_t next_file; // file index for "load next" + uint8_t track, sector; + uint8_t offset; // byte offset from dir entry, if applicable + uint16_t file_crc; + const uint8_t (*hdr_layout)[MAX_HDR_LEN]; +} session_t; + +/* block delays as hacks to make specific releases work */ +static const PROGMEM file_quirks_t file_quirks[] = { + { 0x3393, 40 }, // stacked / file $0a at $0b/$0a + { 0x2b90, 60 }, // beats / file $0f at $0c/$03 + + { 0, 0 } // end marker +}; + +static uint8_t get_block_delay(uint16_t crc) { + const file_quirks_t *fq; + + fq = get_file_quirks(file_quirks, crc); + + return fq != NULL ? pgm_read_byte(&fq->block_delay) : 0; +} + +/* Can't use clocked_read_byte(), as we need to be able to */ +/* read the first bit immediately, without waiting for a */ +/* clock edge. Also it's convenient to handle bus lock here. */ +static uint8_t get_byte_1bit(iec_bus_t clk, iec_bus_t data) { + uint8_t tc, i, b = 0; + +bus_locked: + while (!IEC_ATN); + + for (i = 8; i != 0; i--) { + tc = 9; +timeout_loop: + start_timeout(10000); + + /* wait for respective clock edge */ + while (((iec_bus_read() & clk) != 0) != (i&1)) { + if (!IEC_ATN) + goto bus_locked; + + if (has_timed_out()) { + /* Abort if the clock line hasn't changed for 90ms (9 * 10ms) */ + if (--tc == 0) + return 0; + + goto timeout_loop; + } + } + + delay_us(2); + + b = b >> 1 | (iec_bus_read() & data ? 0x80 : 0); + } + + /* try to prevent invalid timeouts, see comment in clocked_read_byte() */ + start_timeout(256); + + return b; +} + +/* used by 1.2 */ +uint8_t bitfire_get_byte_clk_data(void) { + return get_byte_1bit(IEC_BIT_CLOCK, IEC_BIT_DATA); +} + +/* used by 0.7 including pre-releases */ +uint8_t bitfire_get_byte_clk_data_inv(void) { + return ~bitfire_get_byte_clk_data(); +} + +/* used by <0.6 and 1.1 */ +uint8_t bitfire_get_byte_data_clk(void) { + return get_byte_1bit(IEC_BIT_DATA, IEC_BIT_CLOCK); +} + +/* used by 0.6 and 1.0 */ +uint8_t bitfire_get_byte_data_clk_inv(void) { + return ~bitfire_get_byte_data_clk(); +} + +static uint8_t load_drivecode(void) { + iec_bus_t eob; + + /* which bus line to wait for to be set at the end of each byte */ + eob = detected_loader < FL_BITFIRE_06 ? IEC_BIT_ATN : IEC_BIT_DATA; + + /* <= 0.5 wait for CLK, >= 0.6 for DATA; we just set both */ + set_clock(0); + set_data(0); + + if (wait_atn_low(1000)) + return 1; // timed out + + ATOMIC_BLOCK( ATOMIC_FORCEON ) { + set_clock(1); + set_data(1); + + while (true) { + /* drivecode download always uses CLK for data */ + bitfire_get_byte_data_clk(); + if (has_timed_out()) + goto done; + + start_timeout(150); + while ((iec_bus_read() & eob) == eob) { + if (has_timed_out()) + goto done; + } + } + } + +done: + set_data(0); + + return 0; +} + +static uint8_t load_dir(session_t *s, uint8_t sector) { + dir_changed = 0; + + read_sector(s->dir_buf, current_part, INIT_TRACK, DIR_START-sector); + if (current_error != ERROR_OK) + return 1; + + s->dir_sector = sector; + + return 0; +} + +/* Makes sure the dir sector for "file" is loaded. */ +/* Returns "file" adjusted to this dir sector, or */ +/* 0xff if an error occured reading the sector. */ +static uint8_t update_dir(session_t *s, uint8_t file) { + uint8_t eps, ds; + + /* entries per dir sector differ between versions */ + eps = detected_loader >= FL_BITFIRE_10 ? V1_DIR_ENTRIES : V0_DIR_ENTRIES; + + /* calculate dir sector and index into that sector for given file */ + for (ds = 0; file >= eps; file -= eps, ds++); + + if (s->dir_sector != ds) { + if (load_dir(s, ds)) + return 0xff; + } + + /* return file number adjusted to dir sector */ + return file; +} + +/* Iterate one sector. Advances track if end of current track was reached. */ +static void iterate_sector(session_t *s) { + s->sector += INTERLEAVE; + if (s->sector >= d64_sectors_per_track(current_part, s->track)) { + while (s->sector >= INTERLEAVE) // avoid division for modulo op + s->sector -= INTERLEAVE; + s->sector++; + if (s->sector == INTERLEAVE) { // track done + s->sector = 0; + while (++s->track == INIT_TRACK); // advance track but skip dir track + } + } +} + +/* 1.x directory sectors only hold the start-position (track, logical */ +/* sector and byte-offset into this sector) of the sector's first file. */ +/* Every time a random file is requested, its start position has to be */ +/* calculated by iterating over the lengths of the preceding entries. */ +static void iterate_file(session_t *s, uint8_t file) { + dir_entry_v1_t *e; + uint8_t i; + uint16_t l; + + /* init values start at offset 0x00 (1.2) or 0xfc (1.0/1.1) */ + /* dir entries start at offset 0x04 (1.2) or 0x00 (1.0/1.1) */ + e = (dir_entry_v1_t *)s->dir_buf->data; + if (detected_loader >= FL_BITFIRE_12) { + i = 0x00; + e++; + } else { + i = 0xfc; + } + + s->track = s->dir_buf->data[i]; + s->offset = s->dir_buf->data[i+2]; + + /* find the *first* file's start sector by iterating */ + /* the specified number of sectors from sector 0... */ + s->sector = 0; + i = s->dir_buf->data[i+1]; + while (i--) + iterate_sector(s); + + /* ... and further iterate to the *requested* file's */ + /* start sector and byte-offset from there. */ + for (i = 0; i < file; i++, e++) { + for (l = e->length + s->offset + 1; l >= 256; l -= 256) + iterate_sector(s); + + s->offset = l; + } +} + +static uint8_t load_file(session_t *s, uint8_t file) { + buffer_t *buf; + dir_entry_t *dir_entry; + uint8_t bi, i, hlen, bdel; + uint16_t addr, blen, flen; + hdr_field_t hf; + uint8_t hd[MAX_HDR_LEN]; + + if (file == LOAD_NEXT_CMD) + file = s->next_file; + + if (file >= MAX_FILES) + return 1; // invalid file index + + bdel = get_block_delay(s->file_crc); + s->file_crc = 0xffff; + + /* update_dir() makes sure the correct dir sector for the */ + /* requested file is loaded and returns the file index */ + /* adjusted to that sector (or 0xff if an error occured). */ + i = update_dir(s, file); + if (i == 0xff) + return 1; + + if (detected_loader >= FL_BITFIRE_10) { + dir_entry = (dir_entry_t *)(s->dir_buf->data + i*sizeof(dir_entry->v1)); + + if (detected_loader == FL_BITFIRE_12) { + addr = 0x100; // load adress needs an 0x100 offset + /* adjust dir entry pointer; the first dir entry starts at offset 4 */ + dir_entry = (dir_entry_t *)((uint8_t *)dir_entry + 4); + } else { + addr = 0; + } + addr += dir_entry->v1.addr; + flen = dir_entry->v1.length + 1; + + /* calculate track/sector/offset if either the first or a random file */ + if (file != s->next_file || s->next_file == 0) + iterate_file(s, i); + } else { /* pre 1.0 dir layout */ + dir_entry = (dir_entry_t *)(s->dir_buf->data + i*sizeof(dir_entry->v0)); + s->track = dir_entry->v0.track; + s->sector = dir_entry->v0.sector; + s->offset = 0; + addr = dir_entry->v0.addr; + flen = dir_entry->v0.length + 1; + } + + buf = alloc_buffer(); + if (!buf) + return 1; + + delay_ms(30); // needed at least by Incoherent Nightmare + + for (bi = 0;; bi++) { + read_sector(buf, current_part, s->track, s->sector); + if (current_error != ERROR_OK) + return 1; + + if (bdel > 0) + delay_ms(bdel); + + blen = s->offset + flen > 0x100 ? (uint16_t)0x100 - s->offset : flen; + + /* prepare header fields according to table for active protocol */ + for (i = 0, hf = 0, hlen = 0; hf != BLEN; i++) { + hf = (hdr_field_t)pgm_read_byte((uint8_t *)s->hdr_layout+i); + + switch (hf) { + case IMM0: + hd[hlen++] = 0; + break; + + case LDLO: // file load address low; only for first block + if (bi > 0) + break; + // falls through + case BALO: // block load address low + hd[hlen++] = addr & 0xff; + break; + + case LDHI: // file load address high; only for first block + if (bi > 0) + break; + // falls through + case BAHI: // block load address high + case BARR: // as we send in order, barrier is == block high address + hd[hlen++] = addr >> 8; + break; + + case BIDX: // zero-based block index + hd[hlen++] = bi; + break; + + case BRDT: // barrier delta; shifted << 2, so bits 0&1 are always 0 + switch (bi) { + case 0: + hd[hlen++] = (uint8_t)(0xff << 2); + break; + case 1: // compensate for the first block (always 0xff) + hd[hlen++] = 0x02 << 2; + break; + default: + hd[hlen++] = 0x01 << 2; + break; + } + break; + + case BLST: // block status + hd[hlen++] = bi > 0 ? 0x80 : 0x00; + break; + + case BLEN: // block length + hd[hlen++] = blen & 0xff; + break; + + case FNUM: // file number (debug builds only) + if (bi == 0) + hd[hlen++] = file; + break; + } + } + + ATOMIC_BLOCK( ATOMIC_FORCEON ) { + set_clock(0); + + if (detected_loader == FL_BITFIRE_01) { + /* 0.1 sends an ATN low pulse instead of the shifted first byte */ + if (wait_atn_low(1000)) + return 1; // timeout + while (!IEC_ATN); + } + + /* send prepared header */ + for (i = 0; i < hlen; i++) { + if (clocked_write_byte(hd[i], NULL, 1000)) + return 1; // timeout + } + + /* payload bytes are sent in reverse order */ + for (i = s->offset + blen - 1;; i--) { + if (clocked_write_byte(buf->data[i], NULL, 1000)) + return 1; // timeout + s->file_crc = crc16_update(s->file_crc, buf->data[i]); + if (i == s->offset) + break; + } + + /* clocked_write_byte() exits with the last bitpair not yet ackowledged */ + while (!IEC_ATN); + + set_clock(1); + set_data(0); + } + + /* Update byte-position and move to the next sector, if */ + /* all of the current sector's data has been processed. */ + s->offset = (s->offset + blen) & 0xff; + if (s->offset == 0) + iterate_sector(s); + + flen -= blen; // remaining file length + if (flen == 0) + break; // done with this file + + addr += blen; // update load address for next sector + } + + s->next_file = file + 1; // prepare for a potential "load next" command + + free_buffer(buf); + + return 0; +} + +static uint8_t turn_disk(session_t *s, uint8_t disk_id) { + uint8_t offs; + + /* disk id is located at offset 0x03 oder 0xff, depending on the revision */ + offs = detected_loader >= FL_BITFIRE_12 ? 0x03 : 0xff; + + while (true) { + /* load_dir() resets dir_changed */ + if (load_dir(s, 0)) + return 1; + + if (s->dir_buf->data[offs] == disk_id) + break; + + /* wrong disk; wait for disk change, host reset or user abort */ + while (!dir_changed) { + if (!IEC_ATN || check_keys()) // exit on host reset or long key press + return 1; + } + + /* disk changed, check again */ + } + + s->next_file = 0; + + return 0; +} + +bool load_bitfire(uint8_t proto) { + session_t session; + iec_bus_t req_line; + uint8_t cmd; + + memset(&session, 0, sizeof(session)); + session.file_crc = 0xffff; + session.hdr_layout = &hdr_fields[proto]; + + session.dir_buf = alloc_system_buffer(); + if (session.dir_buf == NULL) + goto exit; + + set_atn_irq(0); + + if (load_dir(&session, 0)) + goto exit; + + if (load_drivecode()) + goto exit; + + /* wait for >= 0.7 to release ATN */ + while (!IEC_ATN); + + if (detected_loader != FL_BITFIRE_01) { + if (fast_get_byte == bitfire_get_byte_data_clk || + fast_get_byte == bitfire_get_byte_data_clk_inv) { + /* 0.2 to 0.6, 1.0, 1.1 */ + req_line = IEC_BIT_DATA; + } else { + /* 0.7 including pre-releases and 1.2 */ + req_line = IEC_BIT_CLOCK; + } + } else { + /* 0.1 */ + req_line = IEC_BIT_ATN; + } + + while (true) { + set_clock(1); + set_data(1); + delay_us(2); + + /* wait for host request while checking for abort and diskchange */ + while (iec_bus_read() & req_line) { + if (check_keys()) /* exit loop on long key press */ + goto exit; + } + + cmd = fast_get_byte(); + if (has_timed_out()) + goto exit; // timeout during receive; probably host reset + set_data(0); + + if (cmd < 0xf0) { + if (cmd == 0x80) // custom drivecode upload (not supported) + goto exit; + if (load_file(&session, cmd)) + goto exit; // error + } else { // cmd >= 0xf0; reset or disk change + if (cmd == RESET_CMD) + goto exit; + + if (turn_disk(&session, cmd)) + goto exit; // error + + if (detected_loader <= FL_BITFIRE_03) { + /* Disk change acknowledge for <= 0.3: Consume */ + /* one (<= 0.2) or two (0.3) ATN low pulses. */ + set_clock(0); // disk changed + + while (IEC_ATN); + while (!IEC_ATN); + if (detected_loader == FL_BITFIRE_03) { // 0.3: two pulses + if (wait_atn_low(10)) + goto exit; // probably host reset + while (!IEC_ATN); + } + } + } + } + +exit: + /* dir buffer will be cleaned up by iec loop */ + + set_clock(1); + set_data(1); + set_atn_irq(1); + + /* loader no longer active past this point */ + detected_loader = FL_NONE; + + return true; +}