Adventures in File IO and Commander X16
Posted: Mon Jun 20, 2022 4:20 pm
I have to say that working with the filesystem is one of the most challenging aspects of the Commander X16 that I've personally run across. There are tons of permutations of "Host FS vs SD image", emulator/ROM version, bugs, compiler/system library behavior, etc.
One thing that's been updated since R39 is that the hyper load/save hooks have changed a bit, if my understanding is correct - and now instead of just intercepting LOAD and SAVE, the emulator(s) now intercept calls to MACPTR and ACPTR (or some other similar choke point) in order to allow more operations to work with the host FS.
It appears that whatever the current methodology is, it doesn't catch everything, because I've recently started working with sequential access to files instead of just bulk loading them into (V)RAM/HIRAM. I've found that this doesn't work unless you're using an SD image. I find SD images to be very inconvenient to work with but hey, I guess that's just the nature of working with something like the X16 in pre-release state (life on the frontier - there's no Dominos and no running water).
One thing I've run across is cc65's cbm.h library, which provides what it calls "BASIC-like" interfaces to the low-level kernal file handling routines:
cbm_open(LFN, DEVICE, SA, filename)
cbm_close(LFN)
cbm_read(LFN, buffer* , num_bytes)
cbm_write(LFN, buffer*, num_bytes)
Where LFN = logical file number, DEVICE = 8 (for disk drive), and SA = that bizarre, multi-purpose byte that is essentially an "argument" for various low-level routines like LOAD or OPEN....
So here we are in cc65 space. The include file also provides three defines to use for the SA in cbm_open():
Quote
/* Constants to use with cbm_open() for openning a file for reading or
** writing without the need to append ",r" or ",w" to the filename.
**
** e.g., cbm_open(2, 8, CBM_READ, "0:data,s");
*/
#define CBM_READ 0 /* default is ",p" */
#define CBM_WRITE 1 /* ditto */
#define CBM_SEQ 2 /* default is ",r" -- or ",s" when writing */
This leads me to believe that cbm_open() might be doing a little bit of spying on the SA value before calling the underlying Kernal routines.
In BASIC, you can use any combo of LFN and SA as long as LFN >= 2 as 0 and 1 have special meaning. I'm going to dig a little deeper into the cc65 sources to see if it's intercepting and interpreting the SA and LFN or not.
But the interesting thing I've discovered is that in order for sequential access to work properly, you must use the same value for LFN and SA - and furthermore, these must be 2, 6, or 10 in order for file seeking to work. (I notice that these result in bit 1 of the SA/LFN value being set - hmm)
Oh, and seeking - stdio.h implements fopen() fseek() and fclose() - but fseek() won't build on X16 as there's no underlying code for it in cx16.lib - and there's no cbm_seek() function because CBM machines don't naturally support seek (well, without add-ons like SD2IEC or DOS extenders) so the library doesn't do it.
To do a seek, I wrote my own function with some advice from @TomXP411 - if you open the command channel to device 8 and send a "P" command (position) while the file is open, Commander-DOS will do a seek.
The P command epects binary values to follow it, so here's how to do it in cc65:
int8_t cx16_fseek(uint8_t channel, uint32_t offset) {
#define SETNAM 0xFFBD
static struct cmd {
char p;
uint8_t lfn;
uint32_t offset;
} cmd;
// open command channel to DOS and send P command.
// P u8 u32 (no spaces) u8 is LFN to seek(), and u32 = offset.
cmd.p='p';
cmd.lfn=channel;
cmd.offset=offset;
// can't call cbm_open because the P command is binary and may
// contain zeros, which C would interpret as null terminator.
//
// Roll-your-own inline asm call to SETNAM:
__asm__ ("lda #6");
__asm__ ("ldx #<%v",cmd);
__asm__ ("ldy #>%v",cmd);
__asm__ ("jsr %w",SETNAM);
cbm_k_setlfs(15,8,15);
cbm_k_open(); // this sends the CMD bytes..
cbm_k_close(15); // close the command channel
return 0;
// TODO: ERROR HANDLING!!!!!
}
Note that this code uses cbm_k_open() and not cbm_open() - the k version is a direct Kernal call, so it's not monkeying around with anything like cbm_open might be.
Anyway, if anyone has any tips, insights, or whatever regarding file IO, I thought that would be a useful topic for people to be able to hit upon.