Jump to content





Part 11 -- Simple Assembly for Atari BASIC (The End) - Binary File I/O Part 2 (XIO Is Broken)

Posted by kenjennings, 03 August 2016 · 657 views

6502 Assembly Mac/65 Atari 8-bit Atari BASIC

Binary File I/O (Part 2 of 2)
 
==============================================================  
 
Part 1 - Introduction
http://atariage.com/...or-atari-basic/
 
Part 2 - Learn 82.7% of Assembly Language in About Three Pages
http://atariage.com/...or-atari-basic/
 
Part 3 - The World Inside a USR() Routine 
http://atariage.com/...or-atari-basic/
 
Part 4 - Implement DPEEK() 
http://atariage.com/...or-atari-basic/
 
Part 5 - Implement DPOKE  
http://atariage.com/...or-atari-basic/
 
Part 6 - Various Bit Manipulations
http://atariage.com/...or-atari-basic/
 
Part 7 - Convert Integer to Hex String 
http://atariage.com/...or-atari-basic/
 
Part 8 - Convert Integer to Bit String
http://atariage.com/...or-atari-basic/
 
Part 9 - Memory Copy 
http://atariage.com/...or-atari-basic/
 
Part 10 - Binary File I/O  Part 1 (XIO is Broken) 
http://atariage.com/...or-atari-basic/
 
Part 11 - Binary File I/O  Part 2 (XIO is Broken)
http://atariage.com/...-basic-the-end/
 
==============================================================
 
 
New XIO in Mac/65 Assembler Code
 
Many articles on this subject go by a simple route – use BASIC code to set up all the IOCB values for the 7/Get Bytes or 11/Put Bytes commands, and then provide a minimal assembly routine that simply calls the CIO vector.  While BASIC mangles the binary read/write functions, XIO itself also is sufficiently broken to justify a complete machine language USR() routine that exercises CIO commands the way they were intended.  For example, ICAX values are not always needed or even wanted, but XIO requires the values.  In fact, ICAX values are rarely needed outside of the 3/Open command.  Similarly, the filespec/buffer is often not necessary.  

 
This routine will perform the same purpose of XIO, but allow a variable number of arguments, adding more arguments only as needed:


Always required (2 arguments):

 
1) Channel Number – Only low byte values 0 to 7 accepted.  The high byte is ignored.


2) CIO Command – Only the low byte of the argument will be used.
 

Optional (4 arguments):


3) Filespec/Buffer address – 16-bit value


4) Filespec/Buffer length – 16-bit value


Additionally optional when the Filespec/Buffer is provided (5 arguments):
 

5) ICAX1 – the low byte of this argument is used.  High byte is ignored.
 

Additionally optional when the Filespec/Buffer and ICAX1 are provided (6 arguments):
 

6) ICAX2 – the low byte of this argument is used.  High byte is ignored.
 

Since the routine accepts 2, 4, 5, or 6 arguments it can support any of the following:
 
 
 

 

 
2)  USR(NEWXIO,6,18) – Perform command 18/Fill for screen device (assuming channel 6)


4)  USR(NEWXIO,1,254,ADR(F$),LEN(F$)) - Use channel 1 to format (254) the disk drive described by F$.  This would also be the format/number of arguments needed for performing CIO Commands 7/Get Bytes and 11/Put Bytes.


5)  USR(NEWXIO,5,3,ADR(F$),LEN(F$),6) -  Use channel 5 to open (3) the disk directory (read/4 + directory/2 = 6) described by F$


6)  USR(NEWXIO,6,3,ADR(F$),LEN(F$),28,7) – Use channel 6 to open (3) as graphics mode 7 with a text window (read/4 + write/8 + window/16 = 28) assuming F$ describes “S:”
 

Variable number of arguments means this program is organized differently from the prior utilities that copy stack arguments to Page Zero.  Since this is just an interface for putting values into the IOCB it doesn't make use of Page Zero beyond returning a status value to BASIC.  
 
0100 ; NXIO.M65
0105 ;
0110 ; NEW CIO/XIO INTERFACE
0115 ;
0120 ; SETUP IOCB WITH THE SUPPLIED
0125 ; ARGUMENTS AND CALL CIO
0130 ;
0135 ; USR 2, 4, 5, or 6 ARGUMENTS:
0140 ; CHANNEL == IOCB CHANEL (LOW BYTE)
0145 ; COMMAND == CIO COMMAND (LOW BYTE)
0150 ; BUF ADR == ADDRESS OF BUFFER
0155 ; BUF LEN == LENGTH OF BUFFER
0160 ; ICAX1   == CIO ICAX1 VALUE (LOW BYTE)
0165 ; ICAX2   == CIO ICAX2 VALUE (LOW BYTE)
0170 ;
0175 ; RETURN VALUE IS CIOV RESULT IN Y REG
0180 ;
0185 ZRET =  $D4     ; FR0 $D4/$D5 Return Value
0190 ;
0195 CIOV =  $E456   ; CIO Vector
0200 ;
0205 IOCB =  $0340   ; Base IO Control Block
0210 ICHID = IOCB+$00 ; Handler ID
0215 ICDNO = IOCB+$01 ; Device number
0220 ICCMD = IOCB+$02 ; ** CIO Command
0225 ICSTA = IOCB+$03 ; CIO Status
0230 ICBAL = IOCB+$04 ; ** Buffer address (low)
0235 ICBAH = IOCB+$05 ; ** Buffer address (high)
0240 ICPTL = IOCB+$06 ; Put char routine (low)
0245 ICPTH = IOCB+$07 ; Put char routine (high)
0250 ICBLL = IOCB+$08 ; ** Buffer length (low)
0255 ICBLH = IOCB+$09 ; ** Buffer length (high)  
0260 ICAX1 = IOCB+$0A ; ** Aux Byte 1
0265 ICAX2 = IOCB+$0B ; ** Aux Byte 2  
0270 ICAX3 = IOCB+$0C ; Aux Byte 3  
0275 ICAX4 = IOCB+$0D ; Aux Byte 4  
0280 ICAX5 = IOCB+$0E ; Aux Byte 5  
0285 ICAX6 = IOCB+$0F ; Aux Byte 6  
0290 ;
0295     .OPT OBJ
0300 ;
0305     *=  $9000   ; Arbitrary. this is relocatable
0310 ;
0315 INIT
0320     LDY #$FF    ; Make the return
0325     STY ZRET    ; value -1 ($FFFF)
0330     STY ZRET+1  ; by default.
0335 ;
0340     PLA         ; Get argument count
0345     BEQ BYE     ; Shortcut for no args.
0350 ;
0355     TAY
0360 ;
0365     CMP #$01    ; One arg is not enough.
0370     BEQ DISPOSE
0375 ;
0380     CMP #$03    ; Three args is not supported.
0385     BEQ DISPOSE
0390 ;
0395     CMP #$07    ; More than six is not valid.
0400     BCC DO_CHANNEL ; All good. Ready to pull args.
0405 ;
0410 ; Bad arg count.  Clean up for exit.
0415 ;
0420 DISPOSE ;       Any number of arguments
0425     PLA
0430     PLA
0435     DEY
0440     BNE DISPOSE
0445     RTS         ; Abandon ship.
0450 ;
0455 ; Pull channel and multiply times 16
0460 ;
0465 DO_CHANNEL ;    Arg 1 = Channel
0470     DEY         ; subtract one arg
0475     PLA         ; discard high byte
0480     PLA         ; Channel number
0485     CMP #$08    ; More than 7 channels
0490     BCS DISPOSE ; is invalid.
0495     ASL A       ; * 2
0500     ASL A       ; * 4
0505     ASL A       ; * 8
0510     ASL A       ; * 16
0515     TAX
0520 ;
0525 DO_ICCMD ;      Arg 2 = Command
0530     PLA         ; discard high byte
0535     PLA         ; command byte
0540     STA ICCMD,X ; Store Command in IOCB
0545     DEY         ; subtract one arg
0550     BEQ DO_CIO
0555 ;
0560 DO_ICBA ;       Arg 3 = Buffer Address
0565     PLA         ; Address high byte
0570     STA ICBAH,X
0575     PLA         ; Address low byte
0580     STA ICBAL,X
0585     DEY         ; subtract one arg  
0590 ;
0595 DO_ICBL ;       Arg 4 = Buffer Length
0600     PLA         ; Length high byte
0605     STA ICBLH,X
0610     PLA         ; Length low byte
0615     STA ICBLL,X
0620     DEY         ; subtract one arg  
0625     BEQ DO_CIO
0630 ;
0635 DO_ICAX1 ;      Arg 5 = Aux Byte 1
0640     PLA         ; discard high byte
0645     PLA         ; Aux byte
0650     STA ICAX1,X ; Store AUX1 in IOCB
0655     DEY         ; subtract one arg
0660     BEQ DO_CIO
0665 ;
0670 DO_ICAX2 ;      Arg 6 = Aux Byte 2
0675     PLA         ; discard high byte
0680     PLA         ; Aux byte
0685     STA ICAX2,X ; Store AUX2 in IOCB
0690     DEY         ; This should be zero args now...
0695 ;
0700 DO_CIO ;        IOCB is set, now execute...
0705     STY ZRET    ; Clear return value low
0710     STY ZRET+1  ; and high byte.
0715     JSR CIOV    ; Engage, Mr Crusher.
0720     BPL BYE     ; No error
0725     STY ZRET    ; Copy Y to return value
0730 ;
0735 BYE
0740     RTS
0745 ;
0750     .END
 
The initialization is similar to prior utilities.   It begins by setting the return value to a known value ($FFFF) that cannot be returned by a successful exit.  Then it pulls the argument count and does a series of value checks to identify any invalid number of arguments.  If the code identifies an issue here it branches to cleaning the stack and then exits.  One difference in the stack argument management is that this utility does not double the argument count to derive the number of bytes on the stack, because it will not be looping to copy the stack values into Page Zero.
 
The channel handling is more involved than other arguments: 
 
0455 ; Pull channel and multiply times 16
0460 ;
0465 DO_CHANNEL ;    Arg 1 = Channel
0470     DEY         ; subtract one arg
0475     PLA         ; discard high byte
0480     PLA         ; Channel number
0485     CMP #$08    ; More than 7 channels
0490     BCS DISPOSE ; is invalid.
0495     ASL A       ; * 2
0500     ASL A       ; * 4
0505     ASL A       ; * 8
0510     ASL A       ; * 16
0515     TAX
The channel is pulled from the low byte of the argument.  If the value exceeds the range of available channels, then it diverts to the stack cleanup to dispose of the remaining arguments and exits. 
 
Recall the earlier discussion about identifying the IOCB for a specific channel -- multiply the channel number times 16 and add to $340.  Here the code multiplies the channel number by 16 allowing use of the value as an index to load values into the correct IOCB.
 
The remaining arguments are handled similarly: 
0525 DO_ICCMD ;      Arg 2 = Command
0530     PLA         ; discard high byte
0535     PLA         ; command byte
0540     STA ICCMD,X ; Store Command in IOCB
0545     DEY         ; subtract one arg
0550     BEQ DO_CIO
The values are pulled from the stack and stored in the corresponding IOCB field.  Then the argument counter is decremented.  At the end of processing the arguments for command, buffer length, and ICAX1 (arguments 2, 4, and 5) the argument count is tested if it has reached zero.  If this occurs then the program skips over the work for processing any subsequent arguments. 
 
Finally, it gets down to business: 
0700 DO_CIO ;        IOCB is set, now execute...
0705     STY ZRET    ; Clear return value low
0710     STY ZRET+1  ; and high byte.
0715     JSR CIOV    ; Engage, Mr Crusher.
0720     BPL BYE     ; No error
0725     STY ZRET    ; Copy Y to return value
 
Recall that the Y register is used to count arguments and by the time the routine reaches this point the Y register is guaranteed to contain zero.  So, this is a convenient source to clear the high byte of the return value for BASIC.  Next, the code calls the CIO Vector ($E456).  When the CIO routine returns the error code is in the Y register and the utility copies that value to the low byte of the return value. 
 
Let's go over a couple implications when this is used in BASIC: 
 
1. The function accepts an absolute address and a length allowing access to any part of memory.  While this is much more flexible than XIO it also means that this routine cannot directly accept a BASIC string.  This routine can use a string passed by its address via ADR().  It is also up to the BASIC program to pass the correct length.  LEN() is correct only when the string has defined content, so a BASIC program must fill or pad out the string to its expected length. 
 
2. Since this is a USR() routine it is not integrated in BASIC's error handling. Therefore TRAP cannot trap any Input/Output errors.  The BASIC program must check the return value of the NXIO routine or use the STATUS command to identify  problems. 
 
 
 
Testing New XIO 
 
Now that we have some experience using files for binary data we're going to start with something different.  The Atari BASIC program below, MAKENXIO.BAS, creates a binary file containing the machine language code for the NXIO routine.  
1 REM MAKENXIO.BAS
5 REM CREATE NXIO.BIN FILE
10 OPEN #1,8,0,"H1:NXIO.BIN"
15 FOR I=1 TO 94
20 READ D:PUT #1,D
25 NEXT I
30 FOR I=95 TO 255
35 PUT #1,0
40 NEXT I
45 CLOSE #1
50 END
21996 REM H1:NXIO.OBJ
21997 REM SIZE  = 94
21998 REM START = 36864
21999 REM END   = 36957
22000 DATA 160,255,132,212,132,213,104,240
22001 DATA 84,168,201,1,240,8,201,3
22002 DATA 240,4,201,7,144,6,104,104
22003 DATA 136,208,251,96,136,104,104,201
22004 DATA 8,176,243,10,10,10,10,170
22005 DATA 104,104,157,66,3,136,240,34
22006 DATA 104,157,69,3,104,157,68,3
22007 DATA 136,104,157,73,3,104,157,72
22008 DATA 3,136,240,14,104,104,157,74
22009 DATA 3,136,240,6,104,104,157,75
22010 DATA 3,136,132,212,132,213,32,86
22011 DATA 228,16,2,132,212,96
22012 DATA 67,3,133,212,96
This will make managing the utility easier, allowing the test program (and any other BASIC program) to load the utility directly from the file without reading DATA statements.  Note that the program purposely pads the output to 255 bytes, so that a BASIC program can use the (broken) XIO command to load the binary data. 
 
Next, is the test program that exercises the features of NXIO.  It begins by loading the NXIO machine language routine into a string using XIO.  This is acceptable for the tightly confined usage here – the program does only one operation to read a file of 255 bytes.  The remaining CIO activity in the program is run by the shiny, new NXIO routine:   
100 REM TSTNXIO1.BAS
105 REM TEST THE NEW XIO USR ROUTINE
110 POKE 82,0:GRAPHICS 0
115 DIM NXIO$(255):NXIO$(255)=" "
120 NXIO=ADR(NXIO$)
125 REM READ NXIO FROM FILE
130 OPEN #1,4,0,"H1:NXIO.BIN"
135 XIO 7,#1,4,0,NXIO$
140 CLOSE #1
145 REM
150 REM TEST THE BAD ARG EXIT
155 REM TEST BAD ARGS 0
160 ? "TESTING BAD ARGUMENTS..."
165 BADARG=USR(NXIO)
170 ? "BAD ARGS 0 = ";BADARG
175 REM TEST BAD ARGS 1
180 BADARG=USR(NXIO,3)
185 ? "BAD ARGS 1 = ";BADARG
190 REM TEST BAD ARGS 3
195 BADARG=USR(NXIO,3,3,32000)
200 ? "BAD ARGS 3 = ";BADARG
205 REM TEST BAD ARGS 7
210 BADARG=USR(NXIO,3,3,32000,2,3,3,3)
215 ? "BAD ARGS 7 = ";BADARG
220 GOSUB 595
225 REM
230 REM DO CIO 3/OPEN TO SET UP
235 REM A GRAPHICS MODE AND RUN
240 REM THE 18/FILL COMMAND.
245 REM FORCE "S:" CLOSED
250 CLOSE #6
255 REM OPEN AS GR MODE 5, NO WINDOW
260 GROPEN=USR(NXIO,6,3,ADR("S:"),2,12,5)
265 REM SAME AS EARLIER XIO FILL DEMO
270 COLOR 3
275 PLOT 70,45:DRAWTO 50,10
280 DRAWTO 30,10:POSITION 10,45
285 POKE 765,3
290 XFILL=USR(NXIO,6,18)
295 GOSUB 600:REM WAIT FOR A KEY
300 REM REPORT THE RESULTS
305 GRAPHICS 0
310 ? "GROPEN = ";GROPEN
315 ? "XFILL  = ";XFILL
320 GOSUB 595
325 REM
330 REM GAMES WITH BINARY FILES
335 REM LOAD THE 8 BYTE MEMORY FILE
340 DIM D$(8):D$="!!!!!!!!"
345 ? "LOADING 8 BYTE MEMORY.BIN..."
350 OPEN #1,4,0,"H1:MEMORYT0.BIN"
355 XREAD8=USR(NXIO,1,7,ADR(D$),8)
360 CLOSE #1
365 FOR I=1 TO 8
370 ? ASC(D$(I,I)),
375 NEXT I
380 ? :? "XREAD8 = ";XREAD8
385 GOSUB 595
390 REM
395 REM SAVE THE ROM CHARACTER SET
400 CR=57344:REM ROM SET $E000
405 ? "SAVING ROM CHARACTER SET..."
410 OPEN #1,8,0,"H1:CSET.BIN"
415 XSAVE=USR(NXIO,1,11,CR,1024)
420 CLOSE #1
425 ? "XSAVE = ";XSAVE
430 GOSUB 595
435 REM
440 REM GAMES WITH THE BINARY LOAD
445 REM SETUP SCREEN FIRST...
450 GRAPHICS 0:POSITION 0,12
455 SC=PEEK(88)+256*PEEK(89)
460 FOR Y=0 TO 7
465 FOR X=0 TO 31
470 POKE SC+Y*40+X,Y*32+X
475 NEXT X
480 NEXT Y
485 ? "NORMAL CSET DISPLAYED"
490 ? "TO LOAD SOFT SET"
495 GOSUB 595
500 REM
505 REM LOAD THE SOFT SET IN
510 REM FLIPPED HALF SETS
515 CH=36864:REM SOFT SET $9000
520 POKE 756,144
525 OPEN #1,4,0,"H1:CSET.BIN"
530 CSLOAD1=USR(NXIO,1,7,CH+512,512)
535 CSLOAD2=USR(NXIO,1,7,CH,512)
540 CLOSE #1
545 ? "SWAPPED, SOFT CSET CSET DISPLAYED"
550 GOSUB 595
555 REM
560 REM THE END
565 GRAPHICS 0
570 ? "CSLOAD1 = ";CSLOAD1
575 ? "CSLOAD2 = ";CSLOAD2
580 END
585 REM
590 REM WAIT FOR A KEY
595 ? "PRESS A KEY"
600 OPEN #1,4,0,"K:"
605 POKE 764,255
610 GET #1,A
615 CLOSE #1
620 RETURN 
 
The program begins by reading the machine language routine via XIO into a string 255 characters long.   Yes, the routine is actually only  94 bytes long, so it wastes a bit of space.  Such is life when using XIO
 
The first round of tests validates the argument management.  There is a separate test for each bad argument possibility – 0, 1, 3, and 7 (or greater).  Each failure to start results in error code 65535 from NXIO: 
 
Attached Image  
 
The next round of tests uses NXIO with all the supported arguments to open a graphics mode 5 display with no text window.  Then it draws a shape and uses NXIO to execute the 18/Fill command:  
 
Attached Image  
 
After the fill completes press a key to continue and then the program prints the NXIO exit codes for the Graphics Open and the Fill: 
 
Attached Image  
 
Press a key again and the program runs the next test which uses 7/Get Characters to read the 8-byte MEMORYT0.BIN file created earlier.  After loading the file the program prints the ATASCII codes for the bytes in the string.  It should report the values below, and then print the exit code from NXIO for the 8-byte read.: 
 
Attached Image  
 
Press a key after this to run the next test.   This will use the 11/Put Characters to save the entire 1,024 bytes of the Atari's ROM character set to a file.  Then it will print the exit code from the NXIO routine for the operation: 
 
 Attached Image
 
Press a key to run the next test.  This will prepare the screen to demonstrate loading the character set from the file into memory.  The characters are displayed in the internal order.  Note the order of the characters. 
 
  Attached Image
 
Press a key to continue the test.   The program will use 7/Get Characters to load the first 512 bytes from the file into the second 512 bytes of the soft character set in memory, and then it loads the second set of 512 bytes from the file  into the first 512 bytes of the soft character set in memory.   This effectively swaps the images of the first half of the character set with the second half.   Observe the “changed” order of the characters: 
 
  Attached Image
 
Finally, press a key to return the display to the normal character set and the program will display the return codes from NXIO for the loading activities and then it ends: 
 
  Attached Image
 
The tests all work as expected, especially all the uses of 7/Get Characters and 11/Put Characters.  So, there is no problem with CIO.  The problem really is that BASIC's XIO command unnecessarily manages the interface to CIO commands.  Correct use of the CIO commands is so simple and the behavior XIO implements is so involved and complicated that it is difficult to consider it simply an accident.
 
What could be the reason for XIO's bizarrely over-engineered behavior?  Perhaps at the time Atari BASIC was designed there was an expectation that these I/O operations must work in increments no less and no more than 255 bytes.  Perhaps a misunderstanding between OS design and BASIC design?  Perhaps design requirements were in motion and it was too late to fix the behavior.  Truly weird.
 
Below is a list of the source files and test examples from the New XIO discussion available in the disk image and archive.  The files are listed in the order presented during the discussion.  The BASIC programs come in the tokenized BASIC format ending in .BAS.  Two listings in text format are also provided: Atari ATASCII format as .LIS and unix/linux text format ending in .TLS
 
 
New XIO File List: 
 
MSAVEDAT    BASIC program to PRINT eight values to a file.
MSAVEDT0   BASIC program to PUT eight bytes to a file.
MLOADDT0    BASIC program to GET eight bytes from a file.
XIOFILL     BASIC program performing the XIO fill command using device “Q:”
MSAVEDT2    BASIC program using XIO for 11/Put Bytes to write a file.
MSAVEDT3    BASIC program using XIO for 11/Put Bytes to write data to a file with a trailing string to identify the source of excess file data.
MLOADDT      BASIC program using XIO for 7/Get Bytes.
MLOADDTX    BASIC program using XIO for 7/Get Bytes with a trailing string to detect excess data read from the file.
MSAVE512    BASIC program to generate a file containing 512 bytes.
MLOAD512    BASIC program using XIO for 7/Get Bytes attempting to load 512 bytes from a file.
MLOAD8        BASIC program using XIO for 7/Get Bytes attempting to load bytes from a file containing 8 bytes.
 
NXIO.M65    Saved Mac/65 source
NXIO.L65    Mac/65 source listing
NXIO.T65    Mac/65 source listed to H6: (linux)
NXIO.ASM    Mac/65 assembly listing
NXIO.TSM    Mac/65 assembly listing to H6: (linux)
NXIO.OBJ    Mac/65 assembled machine language program (with load segments)
NXIO.BIN    Assembled machine language program without load segments with additional data padded to the end of the file to make it 255 bytes long.
NXIO.DAT    LISTed DATA statements for NXIO machine language routine.
 
MAKENXIO    BASIC program to create the BIN file with padding to 255 bytes so the file can be loaded using XIO to read the binary data.
TSTNXIO1    BASIC program testing the NXIO  USR() routines for various CIO commands.
 
 
 
ZIP archive of files:
 
 Attached File  NXIO_Disk.zip (29.92KB)
downloads: 33
 
Tar archive of files (remove the .zip after download)
 
Attached File  NXIO_Disk.tgz.zip (19.27KB)
downloads: 27
 
 
 
 
 
 
 
 
Game Over
 
The movie really is over this time.  Thanks for playing.  Enjoy the new toys.  Finally, welcome to the new world of assembly language.  Try to think of new ways to accelerate and improve BASIC programs.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

 - End -

 
 
 
 
Blessed is the man who walks not in the counsel of the wicked, nor stands in the way of sinners, nor sits in the seat of scoffers;
Psalm 1:1