]> git.sur5r.net Git - cc65/blobdiff - libsrc/atari/shadow_ram_handlers.s
Add sample linker configurations for Atari binary output in C.
[cc65] / libsrc / atari / shadow_ram_handlers.s
index e2de0c31936f83a2dcef21799fb095e04f972238..a8ba611b6ca45407f15c465d9fd0ca63d906008b 100644 (file)
 ; Christian Groessler, chris@groessler.org, 2013
 ;
 
-.if .defined(__ATARIXL__)
+;DEBUG           =       1
+CHKBUF          =       1       ; check if bounce buffering is needed (bounce buffering is always done if set to 0)
 
+.ifdef __ATARIXL__
+
+SHRAM_HANDLERS  =       1
         .include        "atari.inc"
-       .include        "save_area.inc"
-       .import         __CHARGEN_START__
-
-       .export         sram_init
-       .export         KEYBDV_wrapper
-
-.macro disable_rom
-       lda     PORTB
-       and     #$fe
-       sta     PORTB
-       lda     #>__CHARGEN_START__
-       sta     CHBAS
-       sta     CHBASE
-.endmacro
-.macro enable_rom
-       lda     PORTB
-       ora     #1
-       sta     PORTB
-       lda     #$E0
-       sta     CHBAS
-       sta     CHBASE
-.endmacro
+        .include        "save_area.inc"
+        .include        "zeropage.inc"
+        .include        "romswitch.inc"
+
+        .import         __CHARGEN_START__
 
-.segment "INIT"
+        .export         sram_init
+        .export         KEYBDV_handler
+        .export         CIO_handler
+        .export         SIO_handler
+        .export         SETVBV_handler
+        .export         XMOVE_handler
+
+BUFSZ           =       128     ; bounce buffer size
+BUFSZ_SIO       =       256
+
+.segment "ONCE"
 
 ; Turn off ROMs, install system and interrupt wrappers, set new chargen pointer
 
 sram_init:
 
 ; disable all interrupts
-       sei
-       ldx     #0
-       stx     NMIEN           ; disable NMI
+        ldx     #0
+        stx     NMIEN           ; disable NMI
+        sei
 
 ; disable ROMs
-       disable_rom
+        disable_rom
 
 ; setup interrupt vectors
-       lda     #<my_IRQ_han
-       sta     $fffe
-       lda     #>my_IRQ_han
-       sta     $ffff
-
-       lda     #<my_RESET_han
-       sta     $fffc
-       lda     #>my_RESET_han
-       sta     $fffd
-
-       lda     #<my_NMI_han
-       sta     $fffa
-       lda     #>my_NMI_han
-       sta     $fffb
-
-; setup pointers to CIOV and SIOV wrappers
-       lda     #$4C            ; JMP opcode
-       sta     CIOV
-       lda     #<my_CIOV
-       sta     CIOV+1
-       lda     #>my_CIOV
-       sta     CIOV+2
-       lda     #$4C            ; JMP opcode
-       sta     SIOV
-       lda     #<my_SIOV
-       sta     SIOV+1
-       lda     #>my_SIOV
-       sta     SIOV+2
+        lda     #<my_IRQ_han
+        sta     $fffe
+        lda     #>my_IRQ_han
+        sta     $ffff
+
+        lda     #<my_RESET_han
+        sta     $fffc
+        lda     #>my_RESET_han
+        sta     $fffd
+
+        lda     #<my_NMI_han
+        sta     $fffa
+        lda     #>my_NMI_han
+        sta     $fffb
 
 ; enable interrupts
-       lda     #$40
-       sta     NMIEN
-       cli
+        cli
+        lda     #$40
+        sta     NMIEN
+
+        rts
+
+.segment        "EXTZP" : zeropage
+
+zpptr1: .res    2
 
-       rts
+
+.segment "LOWBSS"
+
+; bounce buffers for CIO and SIO calls
+bounce_buffer:  .res    BUFSZ_SIO
 
 
 .segment "LOWCODE"
 
-.macro int_wrap orgvec
-       .local  ret
-       pha
-       enable_rom
-       lda     #>ret
-       pha
-       lda     #<ret
-       pha
-       php
-       jmp     (orgvec)
-ret:   disable_rom
-       pla
-       rti
+
+; Interrupt handlers
+; ------------------
+
+; The interrupt handlers don't look at the current state of PORTB and
+; unconditionally disable the ROMs on exit.
+; Please note that this works, since if the ROMs are enabled we anyway
+; aren't being called here because the vectors are pointing to their
+; original ROM locations.
+
+.macro  int_wrap orgvec
+        .local  ret
+        pha
+        enable_rom_quick
+        lda     #>ret
+        pha
+        lda     #<ret
+        pha
+        php
+        jmp     (orgvec)
+ret:    disable_rom_quick
+        pla
+        rti
 .endmacro
 
 my_IRQ_han:
-       int_wrap IRQ_save
+.ifdef DEBUG
+        php
+        pha
+        tya
+        pha
+        ldy     #0
+        lda     (SAVMSC),y
+        clc
+        adc     #1
+        sta     (SAVMSC),y
+        pla
+        tay
+        pla
+        plp
+.endif
+        int_wrap $FFFE
 
 my_NMI_han:
-       int_wrap NMI_save
+.ifdef DEBUG
+        php
+        pha
+        tya
+        pha
+        ldy     #39
+        lda     (SAVMSC),y
+        clc
+        adc     #1
+        sta     (SAVMSC),y
+        pla
+        tay
+        pla
+        plp
+.endif
+; set I bit to interrupted value
+        pha
+        txa
+        pha
+        tsx
+        lda     $103,x
+        pha
+        plp
+        pla
+        tax
+        pla
+        int_wrap $FFFA
 
 my_RESET_han:
-       int_wrap RESET_save
-
-
-my_CIOV:
-       pha
-       enable_rom
-       pla
-       jsr     CIOV_org
-       php
-       pha
-       disable_rom
-       pla
-       plp
-       rts
-
-my_SIOV:
-       pha
-       enable_rom
-       pla
-       jsr     SIOV_org
-       php
-       pha
-       disable_rom
-       pla
-       plp
-       rts
-
-KEYBDV_wrapper:
-       lda     #>(kret-1)
-       pha
-       lda     #<(kret-1)
-       pha
-       enable_rom
-       lda     KEYBDV+5
+        enable_rom
+        jmp     ($FFFC)
+
+
+; System request handlers
+; -----------------------
+
+
+; for filenames we assume they will fit into our bounce buffer
+
+; one filename, terminated by "invalid character", located at ICBAL/ICBAH
+
+CIO_filename:
+.if CHKBUF
+        jsr     chk_CIO_buf_fn
+        bcc     CIO_call_a
+.endif
+        jsr     setup_zpptr1_y0
+        jsr     copy_filename
+CIO_fn_cont:
+        jsr     bncbuf_to_iocb
+        ldy     CIO_y
+        jsr     CIO_call_a              ; call CIO (maybe A isn't needed, then we could call CIO_call)
+        php
+        pha
+        jsr     restore_icba            ; restore original ICBAL/ICBAH
+        pla
+        plp
+        rts                             ; back to application
+
+
+; two filenames, terminated and separated by "invalid character", located at ICBAL/ICBAH
+
+CIO_filename2:
+.if CHKBUF
+        jsr     chk_CIO_buf_fn2
+        bcc     CIO_call_a
+.endif
+        jsr     setup_zpptr1_y0
+        jsr     copy_filename
+        iny
+        jsr     copy_filename
+        jmp     CIO_fn_cont
+
+
+
+; CIO handler
+; We have buffer pointer and length entries in the IOCB, but their
+; usage depends on the function.
+; Some functions don't care about any of them (pointer and length),
+; and some only use the pointer (like e.g. OPEN), and some use both.
+; So we need function specific handlers to correctly deal with
+; buffers which are overlapping with the ROM area.
+;
+; FIXME: Currently only the requests used by the runtime lib are handled.
+
+CIO_handler:
+
+; @@@ TODO: check X for valid IOCB index ((X < $80) and ((X & $F) == 0))
+
+        sta     CIO_a
+        sty     CIO_y
+        stx     CIO_x
+
+        lda     ICCOM,x                 ; get function
+        cmp     #OPEN
+        beq     CIO_filename            ; filename as input parameter in buffer, length not used
+        cmp     #PUTREC
+        bcc     CIO_read                ; input (GETREC or GETCHR)
+        cmp     #CLOSE
+        bcc     CIO_write_jmp           ; output (PUTREC or PUTCHR)
+        beq     CIO_call_a              ; pass through, buffer not used
+        cmp     #RENAME                 ; 2 filenames as input parameters in buffer, length not used
+        beq     CIO_filename2
+        cmp     #GETCWD
+        bcc     CIO_filename            ; filename as input parameter in buffer, length not used
+        beq     CIO_invalid             ; GETCWD not supported yet
+        bcs     CIO_call_a              ; other commands: assume no buffer
+; not reached
+
+; enable ROM, call CIO, disable ROM
+
+CIO_call_a:
+        lda     CIO_a
+
+CIOV_call:
+        pha
+        lda     PORTB
+        sta     cur_CIOV_PORTB
+        enable_rom
+        pla
+        jsr     CIOV_org
+        php
+        pha
+        disable_rom_val cur_CIOV_PORTB
+        pla
+        plp
+        rts
+
+
+CIO_write_jmp:
+        jmp     CIO_write
+
+CIO_invalid:
+        lda     CIO_a
+        ldy     #DINVCM
+        rts
+
+; READ handler
+; ------------
+
+CIO_read:
+        lda     ICBLL,x
+        ora     ICBLH,x
+        beq     CIO_call_a              ; special I/O through A register in case buffer length is 0
+
+.if CHKBUF
+        jsr     chk_CIO_buf
+        bcc     CIO_call_a
+.endif
+
+; If the data length is larger than our bounce buffer, we have to split the request into smaller ones.
+; Otherwise we can get away with one call and a copy to the final destination afterwards.
+
+        lda     ICBLH,x                 ; get high byte of length
+        bne     big_read                ; not zero -> data too large for our buffers
+                                        ; CHANGE HERE TO SUPPORT BOUNCE BUFFERS > 255 BYTES
+        lda     #<BUFSZ
+        cmp     ICBLL,x
+        bcc     big_read
+
+; Data size fits into bounce buffer
+
+        jsr     setup_zpptr1
+        jsr     bncbuf_to_iocb
+        jsr     CIO_call_a              ; call CIO
+        php
+        bpl     @no_err
+        cpy     #EOFERR
+        beq     @no_err
+        pha
+        jsr     restore_icba
+        pla
+        plp
+        rts                             ; return with error
+
+@no_err:
+        sta     CIO_a
+        sty     CIO_y
+
+        jsr     copy_to_user            ; copy data into user buffer
+        jsr     restore_icba
+
+        lda     CIO_a
+        ldy     CIO_y
+        plp
+        rts                             ; return with success
+
+; Data size does not fit into bounce buffer
+
+big_read:
+        lda     #0
+        sta     retlen                  ; initialize return length
+        sta     retlen+1
+        jsr     iocblen_to_orig_len
+        jsr     iocbptr_to_orig_ptr
+        jsr     setup_zpptr1
+        jsr     bncbuf_to_iocb          ; let ICBAL/ICBAH point to bounce buffer
+
+br_loop:
+        jsr     cmp_orig_len_bnc_bufsz  ; is transfer length > bounce buffer size?
+        bcs     br_last                 ; no, last transfer, use remaining size
+
+        lda     #>BUFSZ
+        sta     ICBLH,x                 ; set data length
+        lda     #<BUFSZ
+        sta     ICBLL,x
+        bne     br_cont
+
+br_last:
+        lda     orig_len+1
+        sta     ICBLH,x                 ; set data length
+        lda     orig_len
+        sta     ICBLL,x
+
+br_cont:
+        sta     req_len                 ; remember length of this request
+        lda     ICBLH,x
+        sta     req_len+1
+        jsr     CIO_call_a              ; do the request
+        php
+        bpl     br_no_err
+        cpy     #EOFERR
+        beq     br_no_err
+
+        pha
+        jsr     restore_icba
+        pla
+        plp
+        rts                             ; return with error
+
+br_no_err:
+        sta     CIO_a
+        sty     CIO_y
+        pla
+        sta     CIO_p
+        jsr     copy_to_user
+
+; update retlen
+        clc
+        lda     retlen
+        adc     ICBLL,x
+        sta     retlen
+        lda     retlen+1
+        adc     #0
+        sta     retlen+1
+
+; if the request read less bytes than requested, we're done
+        lda     ICBLL,x
+        cmp     req_len
+        bne     br_done
+        lda     ICBLH,x
+        cmp     req_len+1
+        bne     br_done
+
+; update user buffer pointer (zpptr1)
+        clc
+        lda     zpptr1
+        adc     ICBLL,x
+        sta     zpptr1
+        lda     zpptr1+1
+        adc     #0
+        sta     zpptr1+1
+
+; update remaining length
+        sec
+        lda     orig_len
+        sbc     ICBLL,x
+        sta     orig_len
+        lda     orig_len+1
+        sbc     #0
+        sta     orig_len+1
+
+; still something left to do (remaining length != 0)?
+        lda     orig_len
+        ora     orig_len+1
+        beq     br_done
+        jmp     br_loop
+
+; done, write original buffer pointer and total transfer length to IOCB and return to application
+br_done:
+        lda     retlen
+        sta     ICBLL,x
+        lda     retlen+1
+        sta     ICBLH,x
+        jsr     orig_ptr_to_iocbptr
+        lda     CIO_p
+        pha
+        lda     CIO_a
+        ldy     CIO_y
+        plp
+        rts                             ; return with success
+
+
+
+CIO_call_a_jmp:
+        jmp     CIO_call_a
+
+
+
+; WRITE handler
+; -------------
+
+
+CIO_write:
+        lda     ICBLL,x
+        ora     ICBLH,x
+        beq     CIO_call_a_jmp          ; special I/O through A register in case buffer length is 0
+
+.if CHKBUF
+        jsr     chk_CIO_buf
+        bcc     CIO_call_a_jmp
+.endif
+
+; If the data length is larger than our bounce buffer, we have to split the request into smaller ones.
+; Otherwise we can get away with a copy to the bounce buffer and the call.
+
+        lda     ICBLH,x                 ; get high byte of length
+        bne     big_write               ; not zero -> data too large for our buffers
+                                        ; CHANGE HERE TO SUPPORT BOUNCE BUFFERS > 255 BYTES
+        lda     #<BUFSZ
+        cmp     ICBLL,x
+        bcc     big_write
+
+
+; Data size fits into bounce buffer
+
+        jsr     setup_zpptr1
+        jsr     bncbuf_to_iocb
+        jsr     copy_from_user
+        ldy     CIO_y
+        jsr     CIO_call_a
+        php
+        pha
+        jsr     restore_icba
+        pla
+        plp
+        rts                             ; return to application
+
+
+; Data size does not fit into bounce buffer
+
+big_write:
+        lda     #0
+        sta     retlen                  ; initialize return length
+        sta     retlen+1
+        jsr     iocblen_to_orig_len
+        jsr     iocbptr_to_orig_ptr
+        jsr     setup_zpptr1
+        jsr     bncbuf_to_iocb          ; let ICBAL/ICBAH point to bounce buffer
+
+bw_loop:
+        jsr     cmp_orig_len_bnc_bufsz  ; is transfer length > bounce buffer size?
+        bcs     bw_last                 ; no, last transfer, use remaining size
+
+        lda     #>BUFSZ
+        sta     ICBLH,x                 ; set data length
+        lda     #<BUFSZ
+        sta     ICBLL,x
+        bne     bw_cont
+
+bw_last:
+        lda     orig_len+1
+        sta     ICBLH,x                 ; set data length
+        lda     orig_len
+        sta     ICBLL,x
+
+bw_cont:
+        sta     req_len                 ; remember length of this request
+        lda     ICBLH,x
+        sta     req_len+1
+        jsr     copy_from_user
+        jsr     CIO_call_a              ; do the request
+        php
+        bpl     bw_no_err
+
+        plp
+        rts                             ; error return
+
+bw_no_err:
+        sta     CIO_a
+        sty     CIO_y
+        pla
+        sta     CIO_p
+
+; update retlen
+        clc
+        lda     retlen
+        adc     ICBLL,x
+        sta     retlen
+        lda     retlen+1
+        adc     #0
+        sta     retlen+1
+
+; if the request wrote less bytes than requested, we're done
+        lda     ICBLL,x
+        cmp     req_len
+        bne     bw_done
+        lda     ICBLH,x
+        cmp     req_len+1
+        bne     bw_done
+
+; update user buffer pointer (zpptr1)
+        clc
+        lda     zpptr1
+        adc     ICBLL,x
+        sta     zpptr1
+        lda     zpptr1+1
+        adc     #0
+        sta     zpptr1+1
+
+; update remaining length
+        sec
+        lda     orig_len
+        sbc     ICBLL,x
+        sta     orig_len
+        lda     orig_len+1
+        sbc     #0
+        sta     orig_len+1
+
+; still something left to do (remaining length != 0)?
+        lda     orig_len
+        ora     orig_len+1
+        beq     bw_done
+        jmp     bw_loop
+
+bw_done:
+        lda     retlen
+        sta     ICBLL,x
+        lda     retlen+1
+        sta     ICBLH,x
+        jsr     orig_ptr_to_iocbptr
+        lda     CIO_p
+        pha
+        lda     CIO_a
+        ldy     CIO_y
+        plp
+        rts                             ; return with success
+
+
+
+; check if length is larger than bounce buffer size
+; input:   orig_len - length
+; output:         A - destroyed
+;                CF - 0/1 for larger/not larger
+cmp_orig_len_bnc_bufsz:
+        sec
+        lda     #<BUFSZ
+        sbc     orig_len
+        lda     #>BUFSZ
+        sbc     orig_len+1
+        rts
+
+
+; copy data from bounce buffer into user buffer
+; input:   X - IOCB index
+;     zpptr1 - pointer to user buffer
+; output:  A - destroyed
+;          Y - 0
+copy_to_user:
+        ldy     ICBLL,x                 ; get # of bytes read (CHANGE HERE TO SUPPORT BOUNCE BUFFERS > 255 BYTES)
+        beq     @copy_done
+@copy:  dey
+        lda     bounce_buffer,y
+        sta     (zpptr1),y
+        cpy     #0
+        bne     @copy
+@copy_done:
+        rts
+
+
+; copy data from user buffer into bounce buffer
+; input:   X - IOCB index
+;     zpptr1 - pointer to user buffer
+; output:  A - destroyed
+;          Y - 0
+copy_from_user:
+        ldy     ICBLL,x                 ; get # of bytes to write (CHANGE HERE TO SUPPORT BOUNCE BUFFERS > 255 BYTES)
+        beq     @copy_done
+@copy:  dey
+        lda     (zpptr1),y
+        sta     bounce_buffer,y
+        cpy     #0
+        bne     @copy
+@copy_done:
+        rts
+
+
+; copy ICBLL/ICBLH to 'orig_len'
+; input:   X - IOCB index
+; output:  A - destroyed
+iocblen_to_orig_len:
+        lda     ICBLL,x
+        sta     orig_len
+        lda     ICBLH,x
+        sta     orig_len+1
+        rts
+
+
+; copy ICBAL/ICBAH to 'orig_ptr'
+; input:   X - IOCB index
+; output:  A - destroyed
+iocbptr_to_orig_ptr:
+        lda     ICBAL,x
+        sta     orig_ptr
+        lda     ICBAH,x
+        sta     orig_ptr+1
+        rts
+
+
+; copy 'orig_ptr' to ICBAL/ICBAH
+; input:   X - IOCB index
+; output:  A - destroyed
+orig_ptr_to_iocbptr:
+        lda     orig_ptr
+        sta     ICBAL,x
+        lda     orig_ptr+1
+        sta     ICBAH,x
+        rts
+
+
+; restore original contents of ICBAL/ICBAH from 'zpptr1'
+; input:   X - IOCB index
+; output:  A - destroyed
+restore_icba:
+        lda     zpptr1
+        sta     ICBAL,x
+        lda     zpptr1+1
+        sta     ICBAH,x
+        rts
+
+
+; put bounce buffer address into ICBAL/ICBAH
+; input:   X - IOCB index
+; output:  A - destroyed
+bncbuf_to_iocb:
+        lda     #<bounce_buffer
+        sta     ICBAL,x
+        lda     #>bounce_buffer
+        sta     ICBAH,x
+        rts
+
+
+; copy file name pointed to by 'zpptr1' to 'bounce_buffer'
+; input:   Y - index into file name buffer and bounce_buffer
+; output:  Y - points to first invalid byte after file name
+;          A - destroyed
+copy_filename:
+        lda     (zpptr1),y
+        sta     bounce_buffer,y
+        beq     copy_fn_done
+        iny
+        cmp     #ATEOL
+        bne     copy_filename
+        dey
+copy_fn_done:
+        rts
+
+
+; write IOCB buffer address into zpptr1
+; input:   X - IOCB index
+; output:  Y - 0 (for setup_zpptr1_y0, else unchanged)
+;          A - destroyed
+setup_zpptr1_y0:
+        ldy     #0
+setup_zpptr1:
+        lda     ICBAL,x                 ; put buffer address into zp pointer
+        sta     zpptr1
+        lda     ICBAH,x
+        sta     zpptr1+1
+        rts
+
+
+.if CHKBUF
+
+; get length of file name pointed to by 'zpptr1'
+; input:   Y - index into file name
+; output:  Y - length
+;          A - destroyed
+get_fn_len:
+        lda     (zpptr1),y
+        beq     @done
+        iny
+        cmp     #ATEOL
+        bne     get_fn_len
+        dey
+@done:
+        rts
+
+
+chk_CIO_buf_fn2:
+        tya
+        pha
+        lda     ICBLL,x
+        pha
+        lda     ICBLH,x
+        pha
+        jsr     setup_zpptr1_y0
+        jsr     get_fn_len
+        iny                     ; include terminating zero
+        bne     fn_cont
+
+chk_CIO_buf_fn:
+        tya
+        pha
+        lda     ICBLL,x
+        pha
+        lda     ICBLH,x
+        pha
+        jsr     setup_zpptr1_y0
+fn_cont:jsr     get_fn_len
+        iny                     ; include terminating zero
+        tya
+        sta     ICBLL,x
+        lda     #0
+        sta     ICBLH,x
+        jsr     chk_CIO_buf
+        pla     
+        sta     ICBLH,x
+        pla     
+        sta     ICBLL,x
+        pla
+        tay
+        rts
+
+
+; check if a CIO input/output buffer overlaps with ROM area (>= $C000)
+; input:                      X - IOCB index
+;       ICBAL/ICBAH/ICBLL/ICBLH - buffer address and length
+; output:                    CF - 1/0 for overlap/no overlap
+;                             A - destroyed
+
+chk_CIO_buf:
+        lda     ICBAH,x
+        cmp     #$c0
+        bcc     @cont
+@ret:   
+.ifdef DEBUG
+        jsr     CIO_buf_noti
+.endif
+        rts
+
+@cont:  lda     ICBAL,x
+        clc
+        adc     ICBLL,x
+        lda     ICBAH,x
+        adc     ICBLH,x
+        bcs     @ret            ; ??? wraparound
+        cmp     #$c0
+.ifdef DEBUG
+        jsr     CIO_buf_noti
+.endif
+        rts
+
+.ifdef DEBUG
+; write to screen memory on 2nd line:
+; pos 0: # of accesses without buffering
+; pos 1: # of accesses with buffering
+CIO_buf_noti:
+        php
+        pha
+        tya
+        pha
+        bcc     @nobuf
+
+        inc     CIObnval_dobuf
+        jmp     @cont
+
+@nobuf: inc     CIObnval_nobuf
+
+@cont:  ldy     #40
+        lda     CIObnval_nobuf
+        sta     (SAVMSC),y
+        ldy     #41
+        lda     CIObnval_dobuf
+        sta     (SAVMSC),y
+
+        pla
+        tay
+        pla
+        plp
+        rts
+
+CIObnval_dobuf:
+        .byte   0
+CIObnval_nobuf:
+        .byte   0
+.endif
+
+.endif  ; .if CHKBUF
+
+;---------------------------------------------------------
+
+; SIO handler
+; We only handle SIO_STAT, SIO_READ, SIO_WRITE, and SIO_WRITEV.
+; These are the only functions used by the runtime library currently.
+; For other function we return NVALID status code.
+
+SIO_handler:
+        lda     DCOMND                  ; get command
+        cmp     #SIO_STAT
+        beq     SIO_stat
+        cmp     #SIO_READ
+        beq     SIO_read
+        cmp     #SIO_WRITE
+        beq     SIO_write
+        cmp     #SIO_WRITEV
+        beq     SIO_write
+
+        ; unhandled command
+        lda     #NVALID
+SIO_err:sta     DSTATS
+        rts
+
+; SIO_STAT is always called with a low buffer (by the runtime)
+SIO_stat:
+        ; fall thru
+
+SIO_call:
+        lda     PORTB
+        sta     cur_SIOV_PORTB
+        enable_rom
+        jsr     SIOV_org
+        php
+        pha
+        disable_rom_val cur_SIOV_PORTB
+        pla
+        plp
+        rts
+
+
+; SIO read handler
+; ----------------
+
+SIO_read:
+
+.if CHKBUF
+        jsr     chk_SIO_buf
+        bcc     SIO_call
+.endif
+
+; we only support transfers <= bounce buffer size
+        jsr     cmp_sio_len_bnc_bufsz
+        bcs     sio_read_len_ok
+
+        lda     #DERROR         ; don't know a better status code for this
+        bne     SIO_err
+
+sio_read_len_ok:
+        lda     DBUFLO
+        sta     zpptr1          ; remember destination buffer address
+        lda     DBUFHI
+        sta     zpptr1+1
+
+        jsr     bncbuf_to_dbuf  ; put bounce buffer address to DBUFLO/DBUFHI
+
+        jsr     SIO_call        ; do the operation
+        pha
+        lda     DSTATS          ; get status
+        bmi     sio_read_ret    ; error
+
+        ; copy data to user buffer
+sio_read_ok:
+        lda     DBYTHI          ; could be 1 for 256 bytes
+        beq     srok1
+        ldy     #0
+        beq     srok2
+srok1:  ldy     DBYTLO
+srok2:  dey
+sio_read_copy:
+        lda     bounce_buffer,y
+        sta     (zpptr1),y
+        dey
+        cpy     #$ff
+        bne     sio_read_copy
+
+sio_read_ret:
+        jsr     orgbuf_to_dbuf
+
+        pla
+        rts                     ; success return
+
+
+; SIO write handler
+; -----------------
+
+SIO_write:
+
+.if CHKBUF
+        jsr     chk_SIO_buf
+        bcc     SIO_call
+.endif
+
+; we only support transfers <= bounce buffer size
+        jsr     cmp_sio_len_bnc_bufsz
+        bcs     sio_write_len_ok
+
+        lda     #DERROR         ; don't know a better status code for this
+        jmp     SIO_err
+
+sio_write_len_ok:
+        lda     DBUFLO
+        sta     zpptr1          ; get source buffer address
+        lda     DBUFHI
+        sta     zpptr1+1
+
+        ; copy data from user buffer to bounce buffer
+        lda     DBYTHI          ; could be 1 for 256 bytes
+        beq     swok1
+        ldy     #0
+        beq     swok2
+swok1:  ldy     DBYTLO
+swok2:  dey
+sio_write_copy:
+        lda     (zpptr1),y
+        sta     bounce_buffer,y
+        dey
+        cpy     #$ff
+        bne     sio_write_copy
+
+        jsr     bncbuf_to_dbuf  ; put bounce buffer address to DBUFLO/DBUFHI
+
+        jsr     SIO_call        ; do the operation
+        pha
+        jsr     orgbuf_to_dbuf
+        pla
+        rts
+
+
+; check if SIO length is larger than bounce buffer size
+; input:   orig_len - length
+; output:         A - destroyed
+;                CF - 0/1 for larger/not larger
+cmp_sio_len_bnc_bufsz:
+        sec
+        lda     #<BUFSZ_SIO
+        sbc     DBYTLO
+        lda     #>BUFSZ_SIO
+        sbc     DBYTHI
+        rts
+
+; put bounce buffer address into DBUFLO/DBUFHI
+; input:   (--)
+; output:  A - destroyed
+bncbuf_to_dbuf:
+        lda     #<bounce_buffer
+        sta     DBUFLO
+        lda     #>bounce_buffer
+        sta     DBUFHI
+        rts
+
+; put original buffer address into DBUFLO/DBUFHI
+; input:   zpptr1 - original pointer
+; output:  A      - destroyed
+orgbuf_to_dbuf:
+        lda     zpptr1
+        sta     DBUFLO
+        lda     zpptr1+1
+        sta     DBUFHI
+        rts
+
+
+.if CHKBUF
+
+; check if a SIO input/output buffer overlaps with ROM area (>= $C000)
+; input: DBUFLO/DBUFHI/DBYTLO/DBYTHI - buffer address and length
+; output:                         CF - 1/0 for overlap/no overlap
+;                                  A - destroyed
+
+chk_SIO_buf:
+        lda     DBUFHI
+        cmp     #$c0
+        bcc     @cont
+@ret:
+.ifdef DEBUG
+        jsr     SIO_buf_noti
+.endif
+        rts
+
+@cont:  lda     DBUFLO
+        clc
+        adc     DBYTLO
+        lda     DBUFHI
+        adc     DBYTHI
+        bcs     @ret            ; ??? wraparound
+        cmp     #$c0
+.ifdef DEBUG
+        jsr     SIO_buf_noti
+.endif
+        rts
+
+.ifdef DEBUG
+; write to screen memory on 2nd line:
+; pos 38: # of accesses without buffering
+; pos 39: # of accesses with buffering
+SIO_buf_noti:
+        php
+        pha
+        tya
+        pha
+        bcc     @nobuf
+
+        inc     SIObnval_dobuf
+        jmp     @cont
+
+@nobuf: inc     SIObnval_nobuf
+
+@cont:  ldy     #78
+        lda     SIObnval_nobuf
+        sta     (SAVMSC),y
+        ldy     #79
+        lda     SIObnval_dobuf
+        sta     (SAVMSC),y
+
+        pla
+        tay
+        pla
+        plp
+        rts
+
+SIObnval_dobuf:
+        .byte   0
+SIObnval_nobuf:
+        .byte   0
+.endif
+
+.endif  ; .if CHKBUF
+
+;---------------------------------------------------------
+
+KEYBDV_handler:
+
+        lda     #>(kret-1)
+        pha
+        lda     #<(kret-1)
+        pha
+        lda     PORTB
+        sta     cur_KEYBDV_PORTB
+        enable_rom
+        lda     KEYBDV+5
         pha
         lda     KEYBDV+4
         pha
-       rts             ; call keyboard handler
-kret:  pha
-       disable_rom
-       pla
-       rts
+        rts             ; call keyboard handler
+kret:   pha
+        disable_rom_val cur_KEYBDV_PORTB
+        pla
+        rts
+
+;---------------------------------------------------------
+
+SETVBV_handler:
+
+        pha
+        lda     PORTB
+        sta     cur_SETVBV_PORTB
+        enable_rom
+        pla
+        jsr     SETVBV_org
+        php
+        pha
+        disable_rom_val cur_SETVBV_PORTB
+        pla
+        plp
+        rts
+
+;---------------------------------------------------------
+
+XMOVE_handler:
+
+        pha
+        lda     PORTB
+        sta     cur_XMOVE_PORTB
+        enable_rom
+        pla
+        jsr     XMOVE_org
+        php
+        pha
+        disable_rom_val cur_XMOVE_PORTB
+        pla
+        plp
+        rts
+
+
+CIO_a:                  .res    1
+CIO_x:                  .res    1
+CIO_y:                  .res    1
+CIO_p:                  .res    1
+cur_CIOV_PORTB:         .res    1
+cur_SIOV_PORTB:         .res    1
+cur_KEYBDV_PORTB:       .res    1
+cur_SETVBV_PORTB:       .res    1
+cur_XMOVE_PORTB:        .res    1
+orig_ptr:               .res    2
+orig_len:               .res    2
+req_len:                .res    2
+retlen:                 .res    2
 
-.endif ; .if .defined(__ATARIXL__)
+.endif  ; .ifdef __ATARIXL__