ip65 technical reference
File : drivers/cbm_disk_access.s
C64 disk access routines
functions
function | description |
---|
io_read_catalogue | routine to catalogue disk (filenames only)
io_device_number set to specify drive to use ($00 = same as last time, $01 = first disk (i.e. #8), $02 = 2nd disk (drive #9))
AX - address of buffer to read catalogue into
outputs:
on errror, carry flag is set.
otherwise, buffer will be filled with asciiz filenames (and an extra zero at the end of the last filename)
|
io_read_catalogue_ex | routine to catalogue disk (with filename, filetype, filesize)
io_device_number set to specify drive to use ($00 = same as last time, $01 = first disk (i.e. #8), $02 = 2nd disk (drive #9))
AX - address of buffer to read catalogue into
outputs:
on errror, carry flag is set.
otherwise, buffer will be filled with asciiz filenames,followed by 1 byte filetype, followed by 2 byte file length (in 256 byte sectors)
there is an extra zero at the end of the last file.
|
io_read_file | routine to read a file
inputs:
io_device_number - specifies drive to use ($00 = same as last time, $01 = first disk (i.e. #8), $02 = 2nd disk (drive #9))
io_filename - specifies filename to open
AX - address of buffer to read file into (set to $0000 to treat first 2 bytes as load address)
outputs:
on errror, carry flag is set
otherwise, io_filesize will be set to size of file and io_load_address will be set to actual load address used.
|
io_read_file_with_callback | routine to read a file with a callback after each 256 byte sector
inputs:
io_device_number - specifies drive to use ($00 = same as last time, $01 = first disk (i.e. #8), $02 = 2nd disk (drive #9))
io_filename - specifies filename to open
io_callback - address of routine to be called after each sector is read
AX - address of buffer to read sector into
outputs:
on errror, carry flag is set
|
io_read_sector | routine to read a sector (credited to "Graham")
cribbed from http://codebase64.org/doku.php?id=base:reading_a_sector_from_disk
inputs:
io_device_number set to specify drive to use ($00 = same as last time, $01 = first disk (i.e. #8), $02 = 2nd disk (drive #9))
io_sector_no - set to sector number to be read
io_track_no - set to track number to be read (only lo byte is used)
AX - address of buffer to read sector into
outputs:
on errror, carry flag is set. otherwise buffer will be filled with 256 bytes
|
io_write_sector | routine to write a sector
cribbed from http://codebase64.org/doku.php?id=base:writing_a_sector_to_disk (credited to "Graham")
inputs:
io_device_number set to specify drive to use ($00 = same as last time, $01 = first disk (i.e. #8), $02 = 2nd disk (drive #9))
io_sector_no - set to sector number to be written
io_track_no - set to track number to be written (only lo byte is used)
AX - address of buffer to to write to sector
outputs:; on errror, carry flag is set.
|
variables
variable | description | size (bytes) |
---|
io_filename | | 2 |
io_filesize | although a file on disk can be >64K, io_filesize is only used when loading into RAM hence file must be <64K
| 2 |
io_load_address | | 2 |
io_sector_no | | 1 |
io_track_no | | 2 |
constants
constants | description | value |
---|
io_callback | | jmp_to_callback+1
|
io_device_no | | 0
|
io_error_buffer | | error_buffer
|
implementation
;C64 disk access routines
;
.ifndef KPR_API_VERSION_NUMBER
.define EQU =
.include "../inc/kipper_constants.i"
.endif
.include "../inc/common.i"
.export io_device_no
.export io_sector_no
.export io_track_no
.export io_read_sector
.export io_write_sector
.export io_read_catalogue
.export io_read_catalogue_ex
.export io_read_file
.export io_read_file_with_callback
.export io_filename
.export io_filesize
.export io_load_address
.export io_callback
.export io_error_buffer
.importzp copy_src
.import ip65_error
.import output_buffer
.importzp copy_dest
io_error_buffer=error_buffer
;reuse the copy_src zero page location
buffer_ptr = copy_src
;######### KERNEL functions
CHKIN = $ffc6
CHKOUT = $ffc9
CHRIN = $ffcf
CHROUT = $ffd2
CLRCHN = $ffcc
CLALL = $FFE7
CLOSE = $ffc3
OPEN = $ffc0
READST = $ffb7
SETNAM = $ffbd
SETLFS = $ffba
.segment "SELF_MODIFIED_CODE"
io_track_no: .res 2
io_sector_no: .res 1
io_device_no: .byte 0
io_filename: .res 2
io_filesize: .res 2 ;although a file on disk can be >64K, io_filesize is only used when loading into RAM hence file must be <64K
io_load_address: .res 2
error_buffer = output_buffer + 256
command_buffer = error_buffer+128
sector_buffer_address: .res 2
buffer_counter: .res 1
extended_catalogue_flag: .res 1
drive_id: .byte 08 ;default to drive 8
jmp_to_callback:
jmp $ffff
io_callback=jmp_to_callback+1
write_byte_to_buffer:
tmp_buffer_ptr=write_byte_to_buffer+1
sta $ffff
inc tmp_buffer_ptr
bne :+
inc tmp_buffer_ptr+1
:
rts
.code
;routine to read a file
; inputs:
; io_device_number - specifies drive to use ($00 = same as last time, $01 = first disk (i.e. #8), $02 = 2nd disk (drive #9))
; io_filename - specifies filename to open
; AX - address of buffer to read file into (set to $0000 to treat first 2 bytes as load address)
; outputs:
; on errror, carry flag is set
; otherwise, io_filesize will be set to size of file and io_load_address will be set to actual load address used.
;
io_read_file:
stax io_load_address
sta sector_buffer_address
stx sector_buffer_address+1 ;this also sets the Z flag
bne @sector_buffer_address_set
;if we get here, X was $00 so we need to use first 2 bytes of file as load address
ldax #output_buffer
stax sector_buffer_address
@sector_buffer_address_set:
ldax #read_file_callback
stax io_callback
lda #0
sta io_filesize
sta io_filesize+1
ldax sector_buffer_address
jsr io_read_file_with_callback
rts
read_file_callback:
sty io_filesize ;only 1 (the last) sector can ever be !=$100 bytes
bne @not_full_sector
inc io_filesize+1
inc sector_buffer_address +1
@not_full_sector:
lda io_load_address+1 ;is the high byte of the address $00?
bne @done
ldax output_buffer ;if we get here we must have used downloaded into the static output buffer, so the
;first 2 bytes there are the real load address
stax copy_dest ;now copy the rest of the sector
stax sector_buffer_address
stax io_load_address
dey
dey
@copy_one_byte:
dey
lda output_buffer+2,y
sta (copy_dest),y
inc sector_buffer_address
bne :+
inc sector_buffer_address+1
:
tya
bne @copy_one_byte
@done:
rts
;routine to read a file with a callback after each 256 byte sector
; inputs:
; io_device_number - specifies drive to use ($00 = same as last time, $01 = first disk (i.e. #8), $02 = 2nd disk (drive #9))
; io_filename - specifies filename to open
; io_callback - address of routine to be called after each sector is read
; AX - address of buffer to read sector into
; outputs:
; on errror, carry flag is set
io_read_file_with_callback:
stax sector_buffer_address
jsr CLALL
jsr parse_filename
jsr SETNAM
jsr set_drive_id
lda #$02 ; file number 2
ldx drive_id
ldy #02 ; secondary address 2
jsr SETLFS
jsr OPEN
bcs @device_error ; if carry set, the device could not be addressed
;we should now check for file access errors
jsr open_error_channel
@no_error_opening_error_channel:
jsr check_error_channel
lda #$30
cmp error_buffer
beq @was_not_an_error
@readerror:
lda #KPR_ERROR_FILE_ACCESS_FAILURE
sta ip65_error
sec
rts
@was_not_an_error:
@get_next_sector:
ldx #$02 ;file number 2
jsr CHKIN ;file 2 now used as input
ldax sector_buffer_address
stax buffer_ptr
lda #$00
sta buffer_counter
@get_next_byte:
jsr READST
bne @eof
jsr CHRIN
ldy buffer_counter
sta (buffer_ptr),y
inc buffer_counter
bne @get_next_byte
ldy #$00;= 256 bytes
jsr jmp_to_callback
jmp @get_next_sector
@eof:
and #$40 ; end of file?
beq @readerror
;we have part loaded a sector
ldy buffer_counter
beq @empty_sector
jsr jmp_to_callback
@empty_sector:
@close:
jmp close_filenumber_2
@device_error:
lda #KPR_ERROR_DEVICE_FAILURE
sta ip65_error
ldx #$00
jsr CHKIN
sec
rts
;io_filename is null-terminated.
;this routines sets up up A,X,Y as needed by kernal routines i.e. XY=pointer to name, A = length of name
parse_filename:
ldax io_filename
stax buffer_ptr
ldy #$ff
@next_byte:
iny
lda (buffer_ptr),y
bne @next_byte
tya
ldx buffer_ptr
ldy buffer_ptr+1
rts
;routine to catalogue disk (with filename, filetype, filesize)
; io_device_number set to specify drive to use ($00 = same as last time, $01 = first disk (i.e. #8), $02 = 2nd disk (drive #9))
; AX - address of buffer to read catalogue into
; outputs:
; on errror, carry flag is set.
; otherwise, buffer will be filled with asciiz filenames,followed by 1 byte filetype, followed by 2 byte file length (in 256 byte sectors)
; there is an extra zero at the end of the last file.
io_read_catalogue_ex:
stax tmp_buffer_ptr
lda #1
bne extended_catalogue_flag_set
;routine to catalogue disk (filenames only)
; io_device_number set to specify drive to use ($00 = same as last time, $01 = first disk (i.e. #8), $02 = 2nd disk (drive #9))
; AX - address of buffer to read catalogue into
; outputs:
; on errror, carry flag is set.
; otherwise, buffer will be filled with asciiz filenames (and an extra zero at the end of the last filename)
io_read_catalogue:
stax tmp_buffer_ptr
lda #0
extended_catalogue_flag_set:
sta extended_catalogue_flag
;get the BAM
lda #$12
sta io_track_no
lda #00
sta io_sector_no
ldax #output_buffer
jsr io_read_sector
bcs @end_catalogue
@get_next_catalogue_sector:
clc
lda output_buffer
beq @end_catalogue
sta io_track_no
lda output_buffer+1
sta io_sector_no
ldax #output_buffer
jsr io_read_sector
bcs @end_catalogue
ldy #0
@read_one_file:
tya
pha
lda output_buffer+2,y ;file type
and #$7f
beq @skip_to_next_file
@get_next_char:
lda output_buffer+5,y ;file name
beq @end_of_filename
cmp #$a0
beq @end_of_filename
jsr write_byte_to_buffer
iny
jmp @get_next_char
@end_of_filename:
lda #0
jsr write_byte_to_buffer
pla
pha
tay ;get Y back to start of this file entry
lda extended_catalogue_flag ;do we need to include the 'extended' data?
beq @skip_to_next_file
lda output_buffer+2,y ;file type
jsr write_byte_to_buffer
lda output_buffer+30,y ;lo byte of file length in sectors
jsr write_byte_to_buffer
lda output_buffer+31,y ;hi byte of file length in sectors
jsr write_byte_to_buffer
@skip_to_next_file:
pla
clc
adc #$20
tay
bne @read_one_file
jmp @get_next_catalogue_sector
@end_catalogue:
lda #0
jsr write_byte_to_buffer
jsr write_byte_to_buffer
rts
set_drive_id:
lda io_device_no
beq @drive_id_set
clc
adc #07 ;so 01->08, 02->09 etc
sta drive_id
@drive_id_set:
rts
;routine to write a sector
;cribbed from http://codebase64.org/doku.php?id=base:writing_a_sector_to_disk (credited to "Graham")
;inputs:
; io_device_number set to specify drive to use ($00 = same as last time, $01 = first disk (i.e. #8), $02 = 2nd disk (drive #9))
; io_sector_no - set to sector number to be written
; io_track_no - set to track number to be written (only lo byte is used)
; AX - address of buffer to to write to sector
; outputs:; on errror, carry flag is set.
io_write_sector:
stax sector_buffer_address
jsr set_drive_id
jsr CLALL
lda #$32 ;"2"
jsr make_user_command
lda #1
ldx #cname
jsr SETNAM
lda #02
ldx drive_id
ldy #02
jsr SETLFS
jsr OPEN
bcs @error
lda #7
ldx #bpname
jsr SETNAM
lda #15
ldx drive_id
ldy #15
jsr SETLFS
jsr OPEN
bcs @error
jsr check_error_channel
lda #$30
cmp error_buffer
bne @error
ldx #$02 ; filenumber 2
jsr CHKOUT ; file 2 now used as output
ldax sector_buffer_address
stax buffer_ptr
ldy #0
:
lda (buffer_ptr),y ;get next byte in sector
jsr CHROUT ;write it out
iny
bne :-
ldx #$0F ; filenumber 15
jsr CHKOUT ; file 15 now used as output
ldy #$00
:
lda command_buffer,y
beq :+
jsr CHROUT ;write byte to command channel
iny
bne :-
:
lda #$0d ; carriage return, required to start command
jsr CHROUT ;write it out
jsr check_error_channel
lda #$30
cmp error_buffer
bne @error
@close:
jsr close_filenumbers_2_and_15
jsr CLRCHN
clc
rts
@error:
lda #KPR_ERROR_DEVICE_FAILURE
sta ip65_error
jsr @close
sec
rts
close_filenumbers_2_and_15:
lda #15 ; filenumber 15
jsr CLOSE
close_filenumber_2:
lda #$02 ; filenumber 2
jsr CLOSE
ldx #$00 ; filenumber 0 = keyboard
jsr CHKIN ;(keyboard now input device again)
clc
rts
;routine to read a sector (credited to "Graham")
;cribbed from http://codebase64.org/doku.php?id=base:reading_a_sector_from_disk
;inputs:
; io_device_number set to specify drive to use ($00 = same as last time, $01 = first disk (i.e. #8), $02 = 2nd disk (drive #9))
; io_sector_no - set to sector number to be read
; io_track_no - set to track number to be read (only lo byte is used)
; AX - address of buffer to read sector into
; outputs:
; on errror, carry flag is set. otherwise buffer will be filled with 256 bytes
io_read_sector:
stax sector_buffer_address
jsr set_drive_id
jsr CLALL
lda #$31 ;"1"
jsr make_user_command
lda #1
ldx #cname
jsr SETNAM
lda #02
ldx drive_id
ldy #02
jsr SETLFS
jsr OPEN
bcs @error
ldx #command_buffer
lda #12
jsr SETNAM
lda #15
ldx $BA ;use whatever was last device #
ldy #15
jsr SETLFS
jsr OPEN
bcs @error
jsr check_error_channel
lda #$30
cmp error_buffer
bne @error
ldx #$02 ; filenumber 2
jsr CHKIN ;(file 2 now used as input)
lda sector_buffer_address
sta buffer_ptr
lda sector_buffer_address+1
sta buffer_ptr+1
ldy #$00
@loop:
jsr CHRIN ;(get a byte from file)
sta (buffer_ptr),Y ; write byte to memory
iny
bne @loop ; next byte, end when 256 bytes are read
@close:
jmp close_filenumbers_2_and_15
@error:
lda #KPR_ERROR_DEVICE_FAILURE
sta ip65_error
jsr @close
sec
rts
open_error_channel:
lda #$00 ; no filename
tax
tay
jsr SETNAM
lda #$0f ;file number 15
ldx drive_id
ldy #$0f ; secondary address 15 (error channel)
jsr SETLFS
jsr OPEN
rts
check_error_channel:
LDX #$0F ; filenumber 15
JSR CHKIN ;(file 15 now used as input)
LDY #$00
@loop:
JSR READST ;(read status byte)
BNE @eof ; either EOF or read error
JSR CHRIN ;(get a byte from file)
sta error_buffer,y
iny
JMP @loop ; next byte
@eof:
lda #0
sta error_buffer,y
LDX #$00 ; filenumber 0 = keyboard
JSR CHKIN ;(keyboard now input device again)
rts
make_user_command:
;fill command buffer with "U " command, where "x" is passed in via A
;i.e. A=1 makes command to read in track & sector
;A=2 makes command to write track & sector
;returns length of command in Y
pha
ldy #0
lda #85 ;"U"
sta command_buffer,y
iny
pla
sta command_buffer,y
iny
lda #$20 ;" "
sta command_buffer,y
iny
lda #$32 ;"2" - file number
sta command_buffer,y
iny
lda #$20 ;" "
sta command_buffer,y
iny
lda #$30 ;"0" - drive number
sta command_buffer,y
iny
lda #$20 ;" "
sta command_buffer,y
iny
lda io_track_no
jsr byte_to_ascii
pha
txa
sta command_buffer,y
pla
iny
sta command_buffer,y
iny
lda #$20 ;" "
sta command_buffer,y
iny
lda io_sector_no
jsr byte_to_ascii
pha
txa
sta command_buffer,y
pla
iny
sta command_buffer,y
iny
lda #0
sta command_buffer,y ;make it ASCIIZ so we can print it
rts
byte_to_ascii:
cmp #30
bmi @not_30
ldx #$33
clc
adc #18
rts
@not_30:
cmp #20
bmi @not_20
ldx #$32
clc
adc #28
rts
@not_20:
cmp #10
bmi @not_10
ldx #$31
clc
adc #38
rts
@not_10:
ldx #$30
clc
adc #48
rts
.rodata
cname: .byte '#'
bpname: .byte "B-P 2 0"
;-- LICENSE FOR c64_disk_access.s --
; The contents of this file are subject to the Mozilla Public License
; Version 1.1 (the "License"); you may not use this file except in
; compliance with the License. You may obtain a copy of the License at
; http://www.mozilla.org/MPL/
;
; Software distributed under the License is distributed on an "AS IS"
; basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
; License for the specific language governing rights and limitations
; under the License.
;
; The Original Code is ip65.
;
; The Initial Developer of the Original Code is Jonno Downes,
; jonno@jamtronix.com.
; Portions created by the Initial Developer are Copyright (C) 2009
; Jonno Downes. All Rights Reserved.
; -- LICENSE END --