Saturday, July 30, 2016

Boot block for PRODOS or PRODOS1

Note: This project has been superseded by John Brooks' ProDOS 2.4 at http://www.callapple.org/uncategorized/announcing-prodos-2-4-for-all-apple-ii-computers/

My previous project - the huge PRODOS that contains both PRODOS 1.9 and 2.0.3, to boot the latest ProDOS 8 for any Apple II - was a bit hasty.

Certainly nobody wants a longer boot time, though this was not a big problem to me. (We pay for convenience.)

Having created a mutant PRODOS is confusing. There's no way to know what's in it without booting twice, or finding the file sizes in the file. This is annoying but not horrible.

What pushed me in another direction was the extra wear and tear on vintage hardware.

I had also broken an old server adage: Thou shall not perform unnecessary I/O.

The result was a boot block for PRODOS/PRODOS1 which will boot PRODOS on a //e compatible with a 65C02, otherwise it boots PRODOS1. If '1' was pressed, PRODOS1 is selected.

A boot block by itself is not at all user friendly, so a Boot Block Utility was cobbled together. Boot Block Utility contains 3 boot blocks:
  • common PRODOS,
  • GS/OS era, and
  • PRODOS/PRODOS1
Existing boot blocks can be read and will be compared against the 3 loaded boot blocks. Any of the boot blocks can be written to disk.

bootblockutil.dsk has PRODOS/PRODOS1 as its boot block, but the utility is primarily BASIC displaying lowercase so doesn't actually run on an Apple ][+.

Sorry about the Release being set to 2. Read on for the saga.


Warning: 6502 microprocessor knowledge is useful for the rest of this piece.

The purpose of the ProDOS boot block is to find a file called PRODOS in the volume (root) directory of a disk, load it, then JMP to it. A block is 512 bytes.

My first thought was to write a little PRODOS that turned around and loaded PRODOS1 or PRODOS2. To do this would require what I had been putting off: dissembling the PRODOS boot block then copying a bunch of it.

Eventually I settled on da65 in http://cc65.github.io/cc65/ for the disassembler. I use the https://sourceforge.net/projects/dev65/ cross assembler. (da65 does not produce entirely Dev65 friendly source.)

Walking through the disassembly started me wondering how many corners I could cut with my boot director. Remembering to avoid unnecessary I/O, it looked like I could use the volume directory that the boot loader had already read.

For those unfamiliar with Apple II 5.25 inch floppy I/O, it is quite low level. There is direct control of the read/write head movement. The data from the drive is 6 data bits per byte which must be read every 32 cycles. (Most 6502 instructions take from 2 to 7 cycles.) The boot ROM reads the 256 byte sector at track 0, sector 0 and executes it. ProDOS uses 512 byte blocks. So the PRODOS boot loader needs a) to read another sector for rest of the boot block and b) needs it's own 5.25 disk routine to read the PRODOS file from the disk. (Later disk devices are expected to have ROM based drivers to do the I/O.)

Uh, so mucking around with the 5.25 code was something I really truly wanted to avoid.

After looking at the boot code it seemed possible to make changes to it instead of writing a separate boot director.

This would also require two copies of ProDOS. I'm assuming that the number of II+ and unenhanced //e users is a minority of the Apple II user base, so PRODOS should be ProDOS 2 and PRODOS1 would be for the older machines.

Over 20 bytes would be needed to check for ProDOS 2 requirements (//e compatibility and a 65C02) and act accordingly. Checking for '1' being pressed to select PRODOS1 would take another 12 bytes.

Most of the code in the boot block is fairly tight but there are a few easy areas of opportunity.

The first looks like an odd signature at the beginning.

L0800:  .byte   1       ; indicates a boot block/sector
        sec
        bcs     L0807   ; branch always taken
        jmp     LA132   ; someplace in RAM
       
L0807:
 

There is some orphaned code, likely for the Apple /// from which ProDOS borrows heavily from. (da65 didn't give it any labels, so it didn't find any references to it in the block.)

        lda     #$9F
        pha
        lda     #$FF
        pha             ; an RTS would branch to $A000
        lda     #$01
        ldx     #$00
        jmp     LF479   ; somewhere in BASIC?!


Then there is the end of the boot block itself...which contains the beginning of the 5.25 disk reader.

L09F2:  lda     $C08C,x
        bpl     L09F2
        brk
        brk
        brk
        brk
        brk
        brk
        brk
        brk
        brk


If booting from a 5.25, the BRK instructions are overwritten by the boot loader with a copy some of the disk ROM.

Shortening the "*** UNABLE TO LOAD PRODOS ***" message would also be easy pickings.

5.25 disk code is notoriously timing sensitive. Fortunately shifting the code down the eliminate the BRK instructions in the boot block was not a problem. 


The testing of the PRODOS/PRODOS1 boot block went well. (Code details follow.)

It then struck me that I would need to provide something that I haven't heard of before: boot block maintenance. Figured that I should be able to squeak by with BASIC and a bit of assembler in page 3.

PRODOS/PRODOS1 boot block Release 1 and Boot Block Utility Release 1 were put together. (They have separate release numbers because I've been around long enough to appreciate that these things have lives of their own.) Feeling confident, I created an SVN tag.

Shortly afterwards I found a bug in Boot Block Utility - an improperly quoted string. There was a "Save boot block to file" and not a "Load boot block from file", so I fixed that too.

The PRODOS boot blocks I looked at included the one on a disk with PRODOS 1.0 and another formatted with Apple's System Utilities. No change.

On a lark, I checked the boot block of a GS/OS 6.0.1 disk that was handy. Of course GS/OS 6.0.1 had a different boot block.

The major difference between the older PRODOS boot block and the GS/OS era boot block is support for "sparse" files. Some versions of ProDOS may skip writing a block that contains all binary zeros and indicate that this by the data block number being zero. (Block zero, the boot block, is always reserved for boot code so can never be used for data.) Binary zeros must be written to memory when the file loaded. This takes some bytes of code...well, at least the NOPs?! can go.

        ldx     #$01
        lda     #$00
        tay
L0900:  sta     ($60),y
        iny
        bne     L0900
        inc     $61
        nop     ; is this padding?!
        nop     ; at least it's not in the inner loop
        dex
        bpl     L0900


The address in the JMP of the odd signature had changed. So apparently it really is a signature, or maybe a checksum.

L0800:  .byte   1
        sec
        bcs     L0807
        jmp     L091C   ; someplace in the boot block

L0807:


Interrupts are disabled after the signature and are enabled before JMPing to the loaded code. (I decided to leave interrupts disabled.)

The orphaned code was also removed.

The failure message had its asterisks removed leaving "UNABLE TO LOAD PRODOS".

The 5.25 code was in the same place so those BRK instructions at the end of the block could go. Actually the load and branch instructions were replaced by BRKs. The GS/OS boot block copies more of the disk ROM than its predecessor. So 5 more bytes that can get moved out of the boot block.

L09F2:  brk
        brk
        brk
        brk
        brk
        brk
        brk
        brk
        brk
        brk
        brk
        brk
        brk
        brk



The "structured assembly" features of https://sourceforge.net/projects/dev65/ made me chuckle a bit at first, but I have really come to like it. Structured assembly is about removing annoying branch labels, allowing indentation for clarity. The added functionality for the boot block is:

                lda     kbd
                cmp     #'1'+$80
                if      eq
                 sta    kbdstrobe
                 beq    prodos1
                endif
                lda     rom_machid
                cmp     #06
                if      eq      ; //e compatible
                 lda    #$99
                 clc
                 sed
                 adc    #1
                 cld
                 if     pl      ; 65C02
                  dec   stoname
                  dec   unable_len
                 endif
                endif
prodos1:


The data is setup to normally boot PRODOS1.

Crunching the code enough for the changes was a bit of work. Saving bytes resulted in a complete clash of coding styles.

                if      eq
unable:          jsr    home
                 ldy    #$15
unable_len:      .equ   $-1
                 repeat
                  lda   unablemsg,y ; "UNABLE TO LOAD PRODOS1"
                  sta   $05B1,y
                  dey
                 until  mi
                 repeat
                 until  pl     ; infinite loop

readinc:                       ; ugly place to insert a subroutine
                 jsr    L0927  ; read block
                 bcs    unable ; < 128 bytes away
                 inc    $61    ; high byte address of block to read
                 inc    $61
                 rts
                endif


Sadly I did not find enough bytes to calculate the number of data blocks correctly. A file whose size is a multiple of 512 may have an additional block read or the following block in memory zeroed.

Having 4 bytes free, a check was added for the file size. Due to a possible off by one for the number of blocks, the PRODOS/PRODOS1 boot block restricts the size of the file to 39.5K. (Mostly. The number of blocks is calculated by shifting the second byte of the size; there weren't enough bytes to check the third byte of the size. Theoretically a file between 64K and 103.5K will be partially loaded.)

The boot code reads all 4 directory volume blocks. I didn't think there are enough bytes left to do reads when necessary.

There are still a bytes that can be freed up but I skeptical that there are enough to do anything useful.

The result is Boot Block Utility Release 2 which includes PRODOS/PRODOS1 boot block Release 2.

The PRODOS/PRODOS1 boot block source.
The assembler listing.

Saturday, July 9, 2016

Booting the right ProDOS 8 for any Apple II

Note: this has been superseded by Boot block for PRODOS or PRODOS1

Apple II ProDOS 8 version 2 requires:
  • At least 64K of RAM,
  • A //e compatible machine, and
  • A 65C02 microprocessor.
(The original) Apple ][ and Apple ][+ users must use ProDOS 1.x.

So continuing the time honored technology tradition of creating solutions in search of a problem,  I created a front end to start one of the two copies of ProDOS in the PRODOS system file.

When a //e compatible (which includes the //c and IIgs) with a 65C02 is found, ProDOS 2 is started. Otherwise ProDOS 1 is started.

Pressing '1' or '2' during the considerably longer boot time forces the start of the selected ProDOS.

A CiderPress friendly copy is at PRODOS#ff0000 which is over 33K (67 blocks) and contains ProDOS 1.9 and 2.0.3.

A zipped 140K disk image is at prodos1and2.po.zip and contains:
  • The aforementioned PRODOS (1.9 and 2.0.3)
  • BASIC.SYSTEM
  • PRODOS1AND2.BIN - the front end
  • PRODOS1AND2.HLP - BASIC program with the help text for PRODOS1AND2
  • PRODOS1AND2 - ugly little BASIC program to create your own PRODOS from two other copies of PRODOS
    • Warning: Two copies of ProDOS 2 probably won't work; there isn't enough RAM.
Note that PRODOS1AND2 and PRODOS1AND2.HLP display upper and lower case.

Thanks to Andrew Jacobs for his Dev65 cross assembler.

This is intended for those wanting to create Apple ][+ friendly ProDOS disks.