Skip to content

Apple IIgs Programming

Eric Le Bras edited this page Apr 22, 2023 · 2 revisions

The Apple IIgs is an Apple II in every way when used in 8-bit emulation mode. Everything documented about using FujiNet with an Apple II still applies to the Apple IIgs, including everything written about programming 8-bit ProDOS applications. The examples provided in the Apple Programming section in cc65 or assembler, or the Applesoft Network extensions are applicable as is.

However, the full potential of the Apple IIgs can only be exploited by writing native 16-bit applications. This section of the wiki focuses on this mode of programming, providing the information needed to operate the FujiNet from 16-bit applications.

About Apple IIgs programming languages and development tools

Among the many development environment and languages available, the ORCA shell, tools and languages produced by The Byte Works really stand out:

  • A comprehensive, consistent and documented collection of assembler/compilers and programming tools.
  • ORCA/C is still under development in 2023 more than 35 years after its initial release.
  • All of ORCA can run on modern computers thanks to the Golden Gate compatibility layer. Combined with modern tools such as Visual Studio Code or make, it allows for an incredible power, comfort and speed, as compared to GS/OS native tools that were available back in the day!

ORCA/C source code and binaries, including its library, header files and linker are available free for private use from the Github repo. However, to effectively use it under Linux, macOS or Windows you will want to purchase Golden Gate. And if you want to effectively program on an Apple IIgs (which is still possible, and perhaps the most simple way to experiment and feel the real thing), you'll also need the ORCA shell, which is part of the Opus ][ distribution. Both – Golden Gate and Opus ][ – may be acquired through the Juiced.GS store.

About cc65: this C cross compiler produces 6502 binaries, not 65816 16-bit binaries. As such, it cannot be used to program native GS applications. However, code written with cc65 is mostly compatible with ORCA/C (given that you rewrite cc65 specific IO library routines, mainly those from conio.h which are used for instance in the CONFIG program).

About GS/OS Programs

GS/OS manages several categories of native programs (identified by their file type), mainly:

  • GS/OS applications (S16 or $B3). These include desktop applications as well as classic text mode applications.
  • GS/OS Shell applications (EXE or $B5). Designed to be launched under ORCA shell. Mainly text mode applications, but desktop is possible and exists (e.g. Prizm, the ORCA shell desktop C programming environment).
  • Permanent initialization files (PIF or $B6)
  • Temporary initialization files (TIF or $B7)
  • New desk accessories (NDA or $B8), small desktop applications managed through the Apple menu.
  • Classic desk accessory (CDA or $B9), text mode small programs accessed through the Ctrl-OA-Escape interrupt sequence. The advantage of these is that they may be used in nearly any circumstance, even under ProDOS 8.
  • Tools (TOL or $BA)
  • Device drivers (DRV or $BB)

Other exist: Object code ($B1), Libraries (LIB - $B2), Tun-time libraries (RTL - $B4).

Specific Apple IIgs SmartPort Information

Finding the SmartPort Dispatcher and Devices

Information on Finding The SmartPort Dispatcher or Finding SmartPort Devices is relevant for the Apple IIgs. Just take into account these specifics:

  • The method for finding the slot is relevant, however, be careful that some expansion cards can be wrongly detected as SmartPorts. This is the case with the MicroDrive/Turbo IDE card, and potentially others, which expose the same identification bytes. Nevertheless, keep in mind that the SmartPort will always sit in slot 5 on an Apple IIgs.
  • The PEEK macro is not defined in the ORCA/C headers. You may either:
    • add the peekpoke.h file from cc65 into your headers path.

    • or add the following to your program:

      #define PEEK(addr) (*(unsigned char*) (addr))

Issuing SmartPort Commands

This part is a little bit more tricky. Issuing SmartPort commands involves making firmware calls, as documented in the Apple IIgs Firmware Reference. However, firmware routines are designed for the 8-bit Apple II and as such, must be called with the 65816 8-bit emulation mode set. Of course after the call, the native mode must be restored, or the 16-bit application will encounter some problems. The Apple IIgs Firmware Reference aforementioned fully describes what this involves. This is the tricky part, where the Apple IIgs toolbox comes handy.

Specifically, the Miscellaneous Tool provides the FWEntry call which can be used in this context. However, FWEntry alone does not allow to pass the arguments required by the SmartPort dispatcher, the way the dispatcher expects them to be passed. The arguments have to be written in memory just after the JSR to the dispatcher. Moreover, the JSR to the dispatcher followed by its arguments, must be found in bank 0. This is also in bank 0 that the SmartPort will return data. One way of doing this from inside a native IIgs application, is to use the Memory Manager to allocate a memory block inside bank 0. The JSR followed by the arguments will be written in that block, and the address where the SmartPort will return data will also be located inside that block. The allocation of that memory block is done with the following function:

#include <memory.h>
#include <window.h>
#include <misctool.h>
#include <stdint.h>

#define SP_CMD_STATUS 0
#define SP_CMD_CONTROL 4
#define SP_CMD_OPEN 6
#define SP_CMD_CLOSE 7
#define SP_CMD_READ 8
#define SP_STATUS_PARAM_COUNT 3
#define SP_CONTROL_PARAM_COUNT 3
#define SP_OPEN_PARAM_COUNT 3
#define SP_CLOSE_PARAM_COUNT 3
#define SP_READ_PARAM_COUNT 4

Handle sp_handle;
uint8_t *sp_payload;
uint8_t *sp_cmdlist;
uint8_t *sp_instr;
uint16_t sp_count;
uint16_t sp_dispatch;
uint8_t sp_error;

void sp_get_buffer(void)
{
  sp_handle = NewHandle(0x500, MyID, 0xC011, 0L);
  if (sp_handle == NULL)
    SysErr();
  sp_payload = (uint8_t *)*sp_handle;
  sp_cmdlist = (uint8_t *)*sp_handle + 1024;
  sp_instr = (uint8_t *)*sp_handle + 1034;
}

The following functions issue the various SmartPort calls involved in using the Fuji and Network devices (sp_dispatch must have been initialized before, look for Finding The SmartPort Dispatcher):

int8_t sp_status(uint8_t dest, uint8_t statcode)
{
  sp_error = 0;
  /* build the command list */
  sp_cmdlist[0] = SP_STATUS_PARAM_COUNT;
  sp_cmdlist[1] = dest; /* set before calling sp_status(); */
  sp_cmdlist[2] = (uint8_t)((uint16_t)sp_payload & 0x00FF);
  sp_cmdlist[3] = (uint8_t)((uint16_t)sp_payload >> 8) & 0xFF;
  sp_cmdlist[4] = statcode;

  sp_cmdlist_low = (uint8_t)((uint16_t)sp_cmdlist & 0x00FF);
  sp_cmdlist_high = (uint8_t)((uint16_t)sp_cmdlist >> 8) & 0xFF;
  sp_dispatch_low = (uint8_t)((uint16_t)sp_dispatch & 0x00FF);
  sp_dispatch_high = (uint8_t)((uint16_t)sp_dispatch >> 8) & 0xFF;

  /* Make firmware call */
  sp_instr[0] = 0x20;	/* JSR */
  sp_instr[1] = sp_dispatch_low;
  sp_instr[2] = sp_dispatch_high;
  sp_instr[3] = SP_CMD_STATUS;
  sp_instr[4] = sp_cmdlist_low;
  sp_instr[5] = sp_cmdlist_high;
  sp_instr[6] = 0x60;	/* RTS */

  /* Call in emulation mode with FWEntry */
  fwRec = FWEntry(0, 0, 0, (Word)sp_instr);
  sp_count = (fwRec.yRegExit << 8) | fwRec.xRegExit;
  sp_error = fwRec.aRegExit;
  return sp_error;
}

int8_t sp_control(uint8_t dest, uint8_t ctrlcode)
{
  sp_error = 0;
  /* build the command list */
  sp_cmdlist[0] = SP_CONTROL_PARAM_COUNT;
  sp_cmdlist[1] = dest; /* set before calling sp_status(); */
  sp_cmdlist[2] = (uint8_t)((uint16_t)sp_payload & 0x00FF);
  sp_cmdlist[3] = (uint8_t)((uint16_t)sp_payload >> 8) & 0xFF;
  sp_cmdlist[4] = ctrlcode;

  sp_cmdlist_low = (uint8_t)((uint16_t)sp_cmdlist & 0x00FF);
  sp_cmdlist_high = (uint8_t)((uint16_t)sp_cmdlist >> 8) & 0xFF;
  sp_dispatch_low = (uint8_t)((uint16_t)sp_dispatch & 0x00FF);
  sp_dispatch_high = (uint8_t)((uint16_t)sp_dispatch >> 8) & 0xFF;

  /* Make firmware call */
  sp_instr[0] = 0x20;	/* JSR */
  sp_instr[1] = sp_dispatch_low;
  sp_instr[2] = sp_dispatch_high;
  sp_instr[3] = SP_CMD_CONTROL;
  sp_instr[4] = sp_cmdlist_low;
  sp_instr[5] = sp_cmdlist_high;
  sp_instr[6] = 0x60;	/* RTS */

  /* Call in emulation mode with FWEntry */
  fwRec = FWEntry(0, 0, 0, (Word)sp_instr);
  sp_error = fwRec.aRegExit; 
  return sp_error;                         
}

int8_t sp_open(uint8_t dest)
{
  sp_error = 0;
  /* build the command list */
  sp_cmdlist[0] = SP_OPEN_PARAM_COUNT;
  sp_cmdlist[1] = dest; /* set before calling sp_open(); */

  sp_cmdlist_low = (uint8_t)((uint16_t)sp_cmdlist & 0x00FF);
  sp_cmdlist_high = (uint8_t)((uint16_t)sp_cmdlist >> 8) & 0xFF;
  sp_dispatch_low = (uint8_t)((uint16_t)sp_dispatch & 0x00FF);
  sp_dispatch_high = (uint8_t)((uint16_t)sp_dispatch >> 8) & 0xFF;

  /* Make firmware call */
  sp_instr[0] = 0x20;	/* JSR */
  sp_instr[1] = sp_dispatch_low;
  sp_instr[2] = sp_dispatch_high;
  sp_instr[3] = SP_CMD_OPEN;
  sp_instr[4] = sp_cmdlist_low;
  sp_instr[5] = sp_cmdlist_high;
  sp_instr[6] = 0x60;	/* RTS */

  /* Call in emulation mode with FWEntry */
  fwRec = FWEntry(0, 0, 0, (Word)sp_instr);
  sp_error = fwRec.aRegExit;
  return sp_error;
}

int8_t sp_close(uint8_t dest)
{
  sp_error = 0;
  /* build the command list */
  sp_cmdlist[0] = SP_CLOSE_PARAM_COUNT;
  sp_cmdlist[1] = dest; /* set before calling sp_close(); */

  sp_cmdlist_low = (uint8_t)((uint16_t)sp_cmdlist & 0x00FF);
  sp_cmdlist_high = (uint8_t)((uint16_t)sp_cmdlist >> 8) & 0xFF;
  sp_dispatch_low = (uint8_t)((uint16_t)sp_dispatch & 0x00FF);
  sp_dispatch_high = (uint8_t)((uint16_t)sp_dispatch >> 8) & 0xFF;

  /* Make firmware call */
  sp_instr[0] = 0x20;	/* JSR */
  sp_instr[1] = sp_dispatch_low;
  sp_instr[2] = sp_dispatch_high;
  sp_instr[3] = SP_CMD_CLOSE;
  sp_instr[4] = sp_cmdlist_low;
  sp_instr[5] = sp_cmdlist_high;
  sp_instr[6] = 0x60;	/* RTS */

  /* Call in emulation mode with FWEntry */
  fwRec = FWEntry(0, 0, 0, (Word)sp_instr);
  sp_error = fwRec.aRegExit;
  return sp_error;
}

int8_t sp_read(uint8_t dest, uint16_t len)
{
  sp_error = 0;
  /* build the command list */
  sp_cmdlist[0] = SP_READ_PARAM_COUNT;
  sp_cmdlist[1] = dest; /* set before calling sp_close(); */
  sp_cmdlist[2] = (uint8_t)((uint16_t)sp_payload & 0x00FF);
  sp_cmdlist[3] = (uint8_t)((uint16_t)sp_payload >> 8) & 0xFF;
  sp_cmdlist[4] = len & 0xFF;
  sp_cmdlist[5] = len >> 8;
  sp_cmdlist[6] = 0;
  sp_cmdlist[7] = 0;
  sp_cmdlist[8] = 0;

  sp_cmdlist_low = (uint8_t)((uint16_t)sp_cmdlist & 0x00FF);
  sp_cmdlist_high = (uint8_t)((uint16_t)sp_cmdlist >> 8) & 0xFF;
  sp_dispatch_low = (uint8_t)((uint16_t)sp_dispatch & 0x00FF);
  sp_dispatch_high = (uint8_t)((uint16_t)sp_dispatch >> 8) & 0xFF;

  /* Make firmware call */
  sp_instr[0] = 0x20;	/* JSR */
  sp_instr[1] = sp_dispatch_low;
  sp_instr[2] = sp_dispatch_high;
  sp_instr[3] = SP_CMD_READ;
  sp_instr[4] = sp_cmdlist_low;
  sp_instr[5] = sp_cmdlist_high;
  sp_instr[6] = 0x60;	/* RTS */

  /* Call in emulation mode with FWEntry */
  fwRec = FWEntry(0, 0, 0, (Word)sp_instr);
  sp_count = (fwRec.yRegExit << 8) | fwRec.xRegExit;
  sp_error = fwRec.aRegExit;
  return sp_error;
}
Clone this wiki locally