Jump to content
IGNORED

#FujiNet - a WIP SIO Network Adapter for the Atari 8-bit


tschak909

Recommended Posts

4 minutes ago, GreyHobbit said:

I noticed that Pimoroni in the UK are selling these ESP32 development boards: https://shop.pimoroni.com/products/tinypico

 

Should FujiNet run on one of these?

 

Geoff

From what I see there, it should work. You'll need the pin spreadsheet showing what pins should go where.

https://docs.google.com/spreadsheets/d/1qEPPM3-gqzfHsogJYuVXqhbvSSsgxdQMb_KGfpoV9KI/edit#gid=351591914

 

-Thom

  • Like 1
Link to comment
Share on other sites

42 minutes ago, GreyHobbit said:

I noticed that Pimoroni in the UK are selling these ESP32 development boards: https://shop.pimoroni.com/products/tinypico

 

Should FujiNet run on one of these?

 

Geoff

I haven't seen this one before. It is small which is nice, but it has fewer available gpio than the espressif modules. It may have enough but I have not verified. The module does not have a RF shield and the price is 26 USD which is way too expensive. That said, if it has enough gpio available someone could build their own FujiNet with it. It is pretty cool though, tiny and has battery support builtin

  • Like 1
Link to comment
Share on other sites

3 hours ago, mozzwald said:

I haven't seen this one before. It is small which is nice, but it has fewer available gpio than the espressif modules. It may have enough but I have not verified. The module does not have a RF shield and the price is 26 USD which is way too expensive. That said, if it has enough gpio available someone could build their own FujiNet with it. It is pretty cool though, tiny and has battery support builtin

 

4 hours ago, GreyHobbit said:

I noticed that Pimoroni in the UK are selling these ESP32 development boards: https://shop.pimoroni.com/products/tinypico

 

Should FujiNet run on one of these?

 

Geoff

This board is missing GPIO 16 & 17 which are needed for SIO UART communication. Unfortunately, this board won't work as a FujiNet :( 

  • Sad 1
Link to comment
Share on other sites

19 hours ago, mozzwald said:

 

This board is missing GPIO 16 & 17 which are needed for SIO UART communication. Unfortunately, this board won't work as a FujiNet :( 

Just missing those two vital GPIOs.  Thanks for checking.  It is tiny though, it looks like it's not far off being able to fit into an SIO plug! :)

Link to comment
Share on other sites

I am currently implementing a test called tests/esp32/http-fetch, to demonstrate how to implement a CIO device that would allow the following:

RUN"N:HTTP://IRATA.ONLINE/HELLO.BAS"

To start, the firmware on the ESP needs to be amended, as you can see in this commit:

https://github.com/FujiNetWIFI/atariwifi/commit/2a794a9d790096b820354481c63f7617f4d7a5cf

 

This implements three SIO commands to open, get data, and close the http connection, that will be used by the cio handler in atari-cio/

 

-Thom 

  • Like 1
Link to comment
Share on other sites

Once the SIO commands are implemented, we can implement the CIO driver. I am doing this in CC65.

 

A custom atari.cfg linker file is used, specifying a fixed start address of $1D00 (putting it right above DOS 2.5 memlo with 2 drives), and the available zero-page usage is scooted towards offset $D0, so that we don't clobber BASIC's internal zero-page variables.

 

Notice also that the SYSCHK and its header bits have been removed, to make the binary smaller. We don't need it.

 

src/atari.cfg

FEATURES {
    STARTADDRESS: default = $1D00;
}
SYMBOLS {
    __EXEHDR__:          type = import;
    __AUTOSTART__:       type = import;  # force inclusion of autostart "trailer"
    __STACKSIZE__:       type = weak, value = $0800; # 2k stack
    __STARTADDRESS__:    type = export, value = %S;
    __RESERVED_MEMORY__: type = weak, value = $0000;
}
MEMORY {
    ZP:         file = "", define = yes, start = $00E5, size = $001A;

# file header, just $FFFF
    HEADER:     file = %O,               start = $0000, size = $0002;

# "main program" load chunk
    MAINHDR:    file = %O,               start = $0000, size = $0004;
    MAIN:       file = %O, define = yes, start = %S,    size = $BC20 - __STACKSIZE__ - __RESERVED_MEMORY__ - %S;
    TRAILER:    file = %O,               start = $0000, size = $0006;
}
SEGMENTS {
    ZEROPAGE:  load = ZP,         type = zp;
    EXTZP:     load = ZP,         type = zp,                optional = yes;
    EXEHDR:    load = HEADER,     type = ro;
    MAINHDR:   load = MAINHDR,    type = ro;
    STARTUP:   load = MAIN,       type = ro,  define = yes;
    LOWBSS:    load = MAIN,       type = rw,                optional = yes;  # not zero initialized
    LOWCODE:   load = MAIN,       type = ro,  define = yes, optional = yes;
    ONCE:      load = MAIN,       type = ro,                optional = yes;
    CODE:      load = MAIN,       type = ro,  define = yes;
    RODATA:    load = MAIN,       type = ro;
    DATA:      load = MAIN,       type = rw;
    INIT:      load = MAIN,       type = rw,                optional = yes;
    BSS:       load = MAIN,       type = bss, define = yes;
    AUTOSTRT:  load = TRAILER,    type = ro;
}
FEATURES {
    CONDES: type    = constructor,
            label   = __CONSTRUCTOR_TABLE__,
            count   = __CONSTRUCTOR_COUNT__,
            segment = ONCE;
    CONDES: type    = destructor,
            label   = __DESTRUCTOR_TABLE__,
            count   = __DESTRUCTOR_COUNT__,
            segment = RODATA;
    CONDES: type    = interruptor,
            label   = __INTERRUPTOR_TABLE__,
            count   = __INTERRUPTOR_COUNT__,
            segment = RODATA,
            import  = __CALLIRQ__;
}

main.c:

We need to set up the handler table (HATABS) to point to individual functions for open, close, get, put, for typical I/O transactions. There is also a pointer to "special" which is used for any other wanted CIO command, think XIO in BASIC for any commands above 16. There is also a cio_init() which I am still learning the semantics of, so can't comment on it too much.

 

Also, because this is a prototype, the handler loads into a fixed position in memory, and pushes MEMLO to a fixed position at the end of the initialized memory segment.

/**
 * http-fetch CIO driver
 */

#include <atari.h>
#include <6502.h>
#include <stdio.h>
#include <string.h>
#include "cio.h"

devhdl_t devhdl;
unsigned char packet[256];


extern void cio_open(void);
extern void cio_close(void);
extern void cio_get(void);
extern void cio_put(void);
extern void cio_status(void);
extern void cio_special(void);

unsigned char ret;
unsigned char err;

void cio_init(void)
{
}

void main(void)
{
  unsigned char i;
  // Populate a devhdl table for our new N: device.
  devhdl.open        = (char *)cio_open-1;
  devhdl.close       = (char *)cio_close-1;
  devhdl.get         = (char *)cio_get-1;
  devhdl.put         = (char *)cio_put-1;
  devhdl.status      = (char *)cio_status-1;
  devhdl.special     = (char *)cio_special-1;
  devhdl.init        = cio_init;

  // find next hatabs entry
  for (i=0;i<11;i++)
    {
      if (OS.hatabs[i].id==0x00)
        break;
    }

  // And inject our handler table into its slot.
  OS.hatabs[i].id='N';         // N: device
  OS.hatabs[i].devhdl=&devhdl; // handler table for N: device.

  // Manually setting memlo, is there a symbol that can get me actual top of data?
  OS.memlo=(void *)0x23A0;
}

You can see, that each function pointer is - 1. This is not a typo. CIO needs it this way, so we have to cast to a char* so that we can back up by one byte. We set up the device handle statically in memory, and then populate HATABS with a pointer to it. Now CIO knows about our N: device.

 

Here's an implementation of cio_close(). Since this has no parameters, we simply just do an SIO call to our created sio_http_close(), and return. CIO proper will take care of freeing the IOCB for us. 

 

cio-close.c:

/**
 * CIO Close call
 */

#include <atari.h>
#include <6502.h>
#include "sio.h"

extern unsigned char err;
extern unsigned char ret;

void _cio_close(void)
{
  OS.dcb.ddevic=0x70; // Network adapter
  OS.dcb.dunit=1;
  OS.dcb.dcomnd=0xE4;  // sio_http_close()
  OS.dcb.dstats=0x00; // no data
  OS.dcb.dtimlo=0x1f; // Timeout
  OS.dcb.dbyt=0;
  OS.dcb.dbuf=NULL; // A packet.
  OS.dcb.daux1=0;
  siov();

  err=OS.dcb.dstats;
}

Those sharp-eyed among you will notice that the C call is _cio_close(). This is because CIO expects its return parameters in a specific order which CC65 does not do, so we have to make a small 'shim' of assembler code around the C code, to call the C function, and then put the return values in the registers that CIO expects (the return value in A, the error code in Y), so we make a stubs file which contains the wrappers.

 

cio-stubs.s

        .include "atari.inc"

        .import _ret
        .import _err
        .import __cio_open, __cio_close, __cio_get, __cio_put, __cio_status, __cio_special
        .export _cio_open, _cio_close, _cio_get, _cio_put, _cio_status, _cio_special

_cio_close:
        jsr __cio_close
        lda _ret
        ldy _err
        rts

_cio_get:
        jsr __cio_get
        lda _ret
        ldy _err
        rts

_cio_open:
        jsr __cio_open
        lda _ret
        ldy _err
        rts

_cio_put:
        sta _ret
        jsr __cio_put
        lda _ret
        ldy _err
        rts

_cio_special:
        jsr __cio_special
        lda _ret
        ldy _err
        rts

_cio_status:
        jsr __cio_status
        lda _ret
        ldy _err
        rts

Note: In CA65, any function calls from CC65 are prepended with an underscore (_).

Link to comment
Share on other sites

10 minutes ago, tschak909 said:

and the available zero-page usage is scooted towards offset $D0, so that we don't clobber BASIC's internal zero-page variables.

This isn't going to work out too well for applications which don't use the FP pack and use the entire upper half of page zero for variables. There are about six page zero locations (IIRC) reserved for CIO handler usage (just as there are ten or so usable by SIO drivers and PBI device handlers).

 

Unless you cache anything else on the way in and restore it on the way out, you will have compatibility issues. Since CC65's runtime probably requires a number of page zero locations just to manage the call stack, it might be worth thinking about straight assembly language.

  • Like 3
Link to comment
Share on other sites

I know this is freaking out the assembler programmers on this thread.

 

I am doing it this way to make it clearer to demonstrate.

 

Just take a deep breath, :) and know that I said PROTOTYPE. Stefan Dorndorf ported my earlier CIO work to assembler, to give me a scaffold to work off of, so this will wind up in assembler. This is being done to demonstrate HOW I am adding functionality. 

 

  • Like 1
Link to comment
Share on other sites

Here is the cio-open() call.

 

cio-open.c

/**
 * CIO Open call
 */

#include <atari.h>
#include <6502.h>
#include <string.h>
#include "sio.h"

extern unsigned char err;
extern unsigned char ret;
extern unsigned char packet[256];

void _cio_open(void)
{
  char *p=(char *)OS.ziocb.buffer;

  // remove EOL
  p[OS.ziocb.buflen-1]=0x00;

  // Scoot buffer past the N:
  p+=2;

  // Copy into packet
  strcpy(packet,p);

  // Start setting up DCB.
  OS.dcb.ddevic=0x70; // Network card
  OS.dcb.dcomnd=0xE6;
  OS.dcb.dunit=1;     // device unit 1
  OS.dcb.dstats=0x80; // Write URL to peripheral
  OS.dcb.dbuf=&packet; // Packet
  OS.dcb.dtimlo=0x1F; // Timeout
  OS.dcb.daux=0;      // no aux byte

  siov();

  // Clear buffer
  memset(&packet,0x00,sizeof(packet));

  ret=err=OS.dcb.dstats;
}

For each call to CIOV, a copy of the IOCB that requested the operation is copied into zero-page (which we reference here as OS.ziocb), which makes it really easy to get the parameters we need.

 

for reference, an IOCB looks like:

/* I/O control block */

struct __iocb {
    unsigned char   handler;    /* handler index number (0xff free) */
    unsigned char   drive;      /* device number (drive) */
    unsigned char   command;    /* command */
    unsigned char   status;     /* status of last operation */
    void*           buffer;     /* pointer to buffer */
    void*           put_byte;   /* pointer to device's PUT BYTE routine */
    unsigned int    buflen;     /* length of buffer */
    unsigned char   aux1;       /* 1st auxiliary byte */
    unsigned char   aux2;       /* 2nd auxiliary byte */
    unsigned char   aux3;       /* 3rd auxiliary byte */
    unsigned char   aux4;       /* 4th auxiliary byte */
    unsigned char   aux5;       /* 5th auxiliary byte */
    unsigned char   spare;      /* spare byte */
};

A great deal of work is already handled in CIOV, it has figured out from the buffer passed in, that it needs to go to our handler (Because of the N:), and it needs to be routed to the cio_open command, due to the IOCB command being set to IOCB_OPEN. There are lots of other goodies such as the aux bits (notice there are 5 of them! only two are exposed in most high level languages, so if you want to use them, you have to set those values manually)

 

One of the things passed to our open, is a buffer containing the "filename" to open. This includes the device name and colon, so, if we were to execute this command from BASIC:

RUN"N:HTTP://IRATA.ONLINE/HELLO.BAS"

CIO would execute an OPEN, passing a pointer to the buffer in the IOCB, as well as the length of the above string, (in ICBAL/ICBAH and ICBLL/ICBLH respectively, which our C struct maps to something easier to handle). 

 

One thing to note, is that we are basically ignoring aux1, which is used by CIO OPEN to determine what type of open this needs to be, 4 = read, 8 = write, 12 = update, etc... For this particular test, we don't care, but normally, we'd be tweaking SIO parameters based on this value.

 

The buffer that is passed in is terminated with an EOL ($9B) character, that since we're dealing with it in C, we simply remove it by turning it to a null, before copying it into our destination buffer. Also, since we don't care about WHICH N: device we're receiving, we simply scoot the pointer to the buffer past those characters, and set that pointer to the call of our DCB, and set up the other particulars needed to call our function on the ESP, before calling siov().

 

For now, we are simply returning the dstats returned by the SIO call, back to our program. This basically limits our error messages to something SIO related, so, which means we basically get these error codes:

 

  • 1 - Everything okay, #FujiNet sent back a COMPLETE, and the computer was happy with any resulting data.
  • 138 - #FujiNet did not respond to command before timeout.
  • 139 - #FujiNet did a NAK on the command frame.
  • 140 -  Serial Input Framing error, computer couldn't synchronize to the start/stop bits.
  • 142 - Serial Input overrun - We got too many bits before a stop bit
  • 143 - Checksum error - The checksum of the data frame sent to the computer didn't match what was computed.
  • 144 - Device Done - #FujiNet sent an ERROR in response to a command completing.

There is also an ERROR 146, which can be emitted by CIO if a particular CIO operation isn't implemented (e.g. P: does not implement GET, but it does implement PUT)

  • Like 1
Link to comment
Share on other sites

The final bit that we need for this test, is an implementation of the CIO GET routine. Specifically, we only need GETCHR, so we don't need to implement GETREC. With no checks/etc in place, this gives us:

 

/**
 * CIO Get call
 */

#include <atari.h>
#include <6502.h>
#include "sio.h"

extern unsigned char err;
extern unsigned char ret;
extern unsigned char packet[256];

extern void _cio_status(void);

unsigned char packetlen;
unsigned char* p;

void _cio_get_chr(void)
{
  err=1;
  ret=*p++;
  packetlen--;
}

void _cio_get(void)
{

  // If buffer is empty, get next buffer from esp

  if (packetlen==0)
    {
      packetlen=256;
      OS.dcb.ddevic=0x70;
      OS.dcb.dunit=1;
      OS.dcb.dcomnd=0xE5;
      OS.dcb.dstats=0x40;
      OS.dcb.dtimlo=0x1f;
      OS.dcb.dbyt=256;
      OS.dcb.dbuf=&packet;
      OS.dcb.daux=0;
      siov();
      err=OS.dcb.dstats;
      p=&packet[0];
    }

  _cio_get_chr();
}

We are relying on the fact that we have pre-initialized the ESP's buffer with nulls, and that we will always return a 256 byte buffer, to simplify things.

Also BASIC will do a GETCHR for precisely the amount of bytes it needs for a given pass (it derives this information from header of the program being loaded.)

 

Ideally, there would be some more defensive measures, checking for errors, etc. but I wanted to keep this concise.

 

I did make a couple of small bugs, that I did go back and correct:

 

* Needed to change the EOL substitution code slightly in OPEN.

* dbyt for OPEN needed to be set

* CLOSE needed to reset the packetlen to 0 to force a buffer re-load.

 

I'm recording a video showing this test, right now.

-Thom

Link to comment
Share on other sites

So, I'm getting a very painful introduction to the inconsistent internals of various FMSes, and their handling of CIO filenames. Jeebus.

 

To try and deal with some of this, I created a command, Set Base URL, which can be used as an XIO:

 

XIO 20,#1,0,0,"N:http://35.239.67.240/"

Subsequently, you can reference a file at that URL by doing:
 

N:BURIEDBU.COM

This seems to help, somewhat, I can load BASIC programs that start at that URL by doing:

RUN"N:HELLO.BAS"

but I can't load binary load files, in this manner, it just keeps reading past the end of the file, regardless of DUP only specifying a certain number of bytes in the IOCB. I have limited debugging information here, because I don't have access to Altirra's debugger while I'm looking at this, but I do wonder:

 

Is emitting an ERROR 136 important when loading a well formed binary load file? 

 

@phaeron @flashjazzcat do you guys have any insight into this?

 

-Thom

Link to comment
Share on other sites

Absolutely, since DOS will just keep reading segment headers until it gets EOF. BASIC programs - I guess - have the file length encoded in the header.
Yup. Ok. So i will implement a file size SIO call for the ESP.

Meanwhile I figured out that disk based SpartaDOS requires CIO drivers to implement XIO 40 for binary load. :)

All of this crazy exercise is being documentedm

Sent from my SM-G920F using Tapatalk

Link to comment
Share on other sites

As part of research on fleshing out the N: device, I have opened up a page on the wiki detailing filename size limitations in various DOSes.

https://github.com/FujiNetWIFI/atariwifi/wiki/N:-Device-Filename-Size-Limitations

 

This is important, as the data in these pages will go a long way to making a compatible file-level sharing mechanism that will work across the N: CIO device. 

 

Feel free to read it. 

 

If you can add to it, please do. I have opened up access to the Wiki so that other people can help! The page has a testing/recording procedure that you can follow to add your favorite DOS.

 

-Thom

Edited by tschak909
Link to comment
Share on other sites

8 minutes ago, phaeron said:

You'll also want to implement the Y=$03 success return code when the last data in the file is read. This is documented in section 5 of the OS Manual, on page 79.

 

Which OS manual, got a link? 

 

Also, I have my err variable set to return in Y. so I should return a 3 instead of a 136 on success?

 

-Thom

Edited by tschak909
Link to comment
Share on other sites

1 hour ago, tschak909 said:

Which OS manual, got a link? 

 

Also, I have my err variable set to return in Y. so I should return a 3 instead of a 136 on success?

 

-Thom

The Operating System User's Manual: https://archive.org/details/bitsavers_atari40080mputerTechnicalReferenceNotes1982_20170986/page/n63/mode/2up

 

You want to return $01 if the entire read was successful and there is still file data remaining, $03 if the entire read was successful and there is no more file data remaining, and $88 if the end of file was reached before the whole read request could be fulfilled.

  • Like 5
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...