Adventures in File IO and Commander X16

Chat about anything CX16 related that doesn't fit elsewhere
ZeroByte
Posts: 714
Joined: Wed Feb 10, 2021 2:40 pm

Adventures in File IO and Commander X16

Post by ZeroByte »



On 6/21/2022 at 8:55 PM, Greg King said:




buf should be increased before the test.  Otherwise, the current wrap-around won't be seen.  But, if tmp is zero, then there's no point in doing any increases/decreases.



Good catch - that is a bug indeed.

Right now, I'm wrestling with why VIA IRQs aren't behaving as I expect.... will fix that line right now tho.

ZeroByte
Posts: 714
Joined: Wed Feb 10, 2021 2:40 pm

Adventures in File IO and Commander X16

Post by ZeroByte »


I've uploaded a new version of my OPL music on X16 demo (VOPLDEMO) to the downloads section.


Now, you can play _all_ the songs from the shareware version, and it loads each one into memory dynamically as you switch between them (using the left/right arrow keys)

TomXP411
Posts: 1781
Joined: Tue May 19, 2020 8:49 pm

Adventures in File IO and Commander X16

Post by TomXP411 »


Ok. color me impressed. Well done!

 

ZeroByte
Posts: 714
Joined: Wed Feb 10, 2021 2:40 pm

Adventures in File IO and Commander X16

Post by ZeroByte »



On 6/21/2022 at 8:55 PM, Greg King said:




But, if tmp is zero, then there's no point in doing any increases/decreases.



Actually, there is a reason - I made my routine report results via a global struct containing the next-available memory location just like the Kernal's LOAD routine does. (only it's written into a struct and not passed back directly)

Also, since all the values are right there in the routine's hot little hands, I decided to save the caller the effort of calculating offsets, etc., and the struct contains total bytes transferred and a success/fail boolean. (which I suppose could be the final status byte, come to think of it)

bce
Posts: 3
Joined: Mon Jul 18, 2022 2:41 pm

Adventures in File IO and Commander X16

Post by bce »



On 6/21/2022 at 1:10 PM, ZeroByte said:




As promised, here's the code that I'm using to harness MACPTR. It's incomplete in that it doesn't fall back on ACPTR (cbm_load() in this case)  if MACPTR returns -1. I'm not happy with the return type being signed, and will probably fix this by either making it a signed log, or else just returning zero on failure and requiring that the calling code check status byte to know if it's zero because EOF or because of unsupported device.



Most likely, though, this is a case that will never happen with files, as the OS supports block reads from disk - but if you pass it an LFN that's connected to the screen or keyboard or whatever, then that would fail....



extern int __fastcall__ macptr(unsigned char numbytes, void* buffer);



int cx16_read(unsigned char lfn, void* buffer, unsigned int size) {

  int error = 0;

  char* buf = (char*)buffer;

  static unsigned int bytesread;

  static int tmp;



  /* if we can't change to the inputchannel #lfn then return an error */

  if (_oserror = cbm_k_chkin(lfn)) return -1;



  bytesread = 0;

  printf("\n");

  while (size > 0 && !cbm_k_readst()) {

    if (size>=512)

      tmp = macptr(0,buf);  // let MACPTR read as much as it wants

    else if (size>=256)

      tmp = macptr(255,buf); // If size 256..512, unlimited MACPTR would read past desired stopping point.

    else

      tmp = macptr((size), buf);

    if (tmp == -1) return -1;

    bytesread += tmp;

    size -= tmp;

    // wrap the buffer pointer back into the bank window

    // if it advances above 0xbfff. Note that MACPTR did

    // wrap banks correctly, but our calculation must match.

    // also note that MACPTR incremented the active bank,

    // so there is no need to do RAM_BANK++ here.

    if (buf >= (char*)0xc000) buf -= 0x2000;

    buf += tmp;

    if (cbm_k_readst() & 0xBF) break;

    if (tmp == 0) break;

  }



And the assembly is short:



MACPTR := $FF44



.import popa

.export _macptr ; the _ is important - C symbols all start with _



.proc _macptr: near

    phx

    tax

    jsr popa

    ply

    jsr MACPTR

    bcs macptr_unsupported

macptr_supported:

    txa ; C wants int in .AX not .XY

    phy

    plx

    rts

macptr_unsupported: ; return -1 on error

    ldx #$ff

    lda #$ff

    rts

.endproc



Note that the MACPTR function itself returning a signed int is fine, since MACPTR will return 512 bytes at most.



Good work ZeroByte!

This maybe a silly question, but how does it know when it reaches EOF? 

Is it one of these lines?

  if (cbm_k_readst() & 0xBF) break;

  if (tmp == 0) break;

What would be the proper way of calling cx16_read?

Maybe something like:

char buffer[255];

int filesize;

char lfn=?

filesize=cx16_read(lfn, buffer, 255);

lfn would be the number we used to open the file with cbm_open, correct?  Are there lfn numbers we should not use?

 

 

ZeroByte
Posts: 714
Joined: Wed Feb 10, 2021 2:40 pm

Adventures in File IO and Commander X16

Post by ZeroByte »


LFN needs to be 2..14 for sequential read. (which is what this is)

0 and 1 have special meanings that've been explained to me, and which I've read about, but I really haven't grasped what's truly going on - I just know use 0 for LOAD, use >=2 for sequential access. If you use 0 or 1, the call to send P: command to DOS will fail and you will not have seeked anywhere in the file.

And yes, tmp==0 is typically the case that will catch EOF. MACPTR returns a number 0-512, being the number of bytes read. EOF is something a little odd that I also haven't truly spent the time to dig in and completely understand. It's one of the bits in the status byte, and I believe (someone correct me if I'm wrong) that it isn't set after hitting EOF initially - only if you try to read when the starting point is EOF. So if you were 1 byte away from EOF and asked for 2 bytes, you'd get 1 and no errors, followed by a zero + EOF status the next time you tried to read.

And to call cx16_read(), you must have previously performed an open() on the file. You can use the cbm_k_X() kernal call wrappers for setnam, setlfs, and open, or else use the cbm.h convenience function.

e.g.:

cbm_open(LFN,DEVICE,SA,FILENAME);

cx16_read(LFN,buffer,numbytes);



or:

cbm_k_setnam("myfile.bin");

cbm_k_setlfs(2,8,0);

cbm_k_open();

BruceMcF
Posts: 1336
Joined: Fri Jul 03, 2020 4:27 am

Adventures in File IO and Commander X16

Post by BruceMcF »



On 7/18/2022 at 5:16 PM, ZeroByte said:




LFN needs to be 2..14 for sequential read. (which is what this is)



0 and 1 have special meanings that've been explained to me, and which I've read about, but I really haven't grasped what's truly going on - I just know use 0 for LOAD, use >=2 for sequential access. If you use 0 or 1, the call to send P: command to DOS will fail and you will not have seeked anywhere in the file.



Secondary address 0 for binary load, 1 for binary save, 2-14 for sequential read or write and 15 for the command channel of the device. Remember that the KERNAL was originally the underlying Basic Input/Output subroutines to support Microsoft 6502 Basic, and then it was generalized a bit by Commodore to better support assembly language programs. The binary save has to know its starting address and ending address, with the starting address saved at the start of the binary file, and then load either goes to the default Basic load location or to the location the binary file was saved from. Keyboard and screen printing output requires sequential byte at a time input and output respectively, so that is extended to sequential byte at a time read or write access to sequential files.

Since program load and save each do their thing then return, without staying open, only one secondary address is needed for each. Then, device command channel is a unique channel for each device, so only one command channel secondary address is needed. Since the KERNAL can maintain up to 10 open channels, that means 16 secondary addresses is enough for load, 0, save, 1, command channel, 15, and then "enough" secondary addresses in between, 2-14.

ZeroByte
Posts: 714
Joined: Wed Feb 10, 2021 2:40 pm

Adventures in File IO and Commander X16

Post by ZeroByte »


oh, and one other thing I mentioned earlier in the thread but forgot about here - for whatever reason, only values 2, 6, and 10 allow seeking in my experience. So if you want to just do sequential reads from start of file - no big deal I guess, but if you want to use the P command, you have to use 2 6 or 10, and the LFN needs to match the SA for some odd reason.

BruceMcF
Posts: 1336
Joined: Fri Jul 03, 2020 4:27 am

Adventures in File IO and Commander X16

Post by BruceMcF »



On 7/19/2022 at 1:07 PM, ZeroByte said:




oh, and one other thing I mentioned earlier in the thread but forgot about here - for whatever reason, only values 2, 6, and 10 allow seeking in my experience. So if you want to just do sequential reads from start of file - no big deal I guess, but if you want to use the P command, you have to use 2 6 or 10, and the LFN needs to match the SA for some odd reason.



AFAICT, the P command only works on Relative files. I haven't ever seen any limitation on the LFN or SA being 2, 6 or 10, but on the other hand, the relative files I used were used to implement Block files for a Commodore 64 port of fig-Forth, so back in the day, while I USED relative files heavily, I didn't do any manipulation of REL files in either Basic or Assembler -- they were always hiding behind the BLOCK word.

As far as LFN and SA matching, I recall everyone doing that anyway, except for normally opening the Command Channel with an LFN of 1. For one thing, that makes it easier to keep track, and for another, when you use a variable in the OPEN command, you can re-use the same variable in both the LFN and SA positions.

TomXP411
Posts: 1781
Joined: Tue May 19, 2020 8:49 pm

Adventures in File IO and Commander X16

Post by TomXP411 »



On 7/19/2022 at 1:58 PM, BruceMcF said:




AFAICT, the P command only works on Relative files. I haven't ever seen any limitation on the LFN or SA being 2, 6 or 10, but on the other hand, the relative files I used were used to implement Block files for a Commodore 64 port of fig-Forth, so back in the day, while I USED relative files heavily, I didn't do any manipulation of REL files in either Basic or Assembler -- they were always hiding behind the BLOCK word.



Let's be clear. In CMDR-DOS, the P file only works on Sequential files, because the system does not support REL files. 



In CBM-DOS, the P command only works in REL files, because that's what REL files are for.


On 7/19/2022 at 1:58 PM, BruceMcF said:




As far as LFN and SA matching, I recall everyone doing that anyway, except for normally opening the Command Channel with an LFN of 1. For one thing, that makes it easier to keep track, and for another, when you use a variable in the OPEN command, you can re-use the same variable in both the LFN and SA positions.



The LFN and SA don't need to match. I tested this while helping ZeroByte troubleshoot his C code. Whatever's going on there is a bug in the C runtime, not CMDR-DOS. 

I actually tested explicitly using different Logical File Number and Secondary Address, and P works just fine. (ie: OPEN 3,8,5). 

Post Reply