xlink
data transfer and control system for the commodore 64/128
xlink allows connecting a Commodore 64 or 128 to a PC using a custom build USB adapter or a simple parallel port cable. A command-line client is used on the PC to transfer data to and from the remote machine memory, run programs on the remote machine or to initiate a hardware reset.
An interrupt-driven server on the remote machine listens to and executes the commands send by the client. The server can be temporarily loaded on the remote machine, or it can be permanently installed using a customized kernal rom. The latter has the advantage of being instantly available after power-up or reset, which makes the xlink system well suited for fast and easy cross development using a PC and a real Commodore machine.
The implementation of the underlying functionality is distributed as a shared library, making the functionality provided by xlink readily available for use in other programs.
The xlink client software and library is supported under Linux, MacOSX and Windows.
- Downloads
- Installing binary packages
- Building and installing the Client from source
- Building the Hardware
- Building the Firmware
- Installing the Firmware
- Running the server on the target machine
- Usage
- API Documentation
- Feedback
- License
Downloads
Source
The source distribution includes the sources for the client, server, kernal and firmware as well as schematics and pcb layouts in KiCad format for building the hardware.
Latest stable is xlink-1.3.tar.gz.
All releases can be found under /download/xlink
Latest developments are available via github:
git clone https://github.com/hbekel/xlink
Binaries
-
xlink-firmware-1.3.hex (firmware for the at90usb162)
- xlink-1.2-macosx.tar.gz (MacOSX binary package)
-
xlink-1.3.msi (Windows 32-bit installer package)
- WinVICE-2.4.19-xlink-1.2-x86.zip (32bit build with xlink support)
- vice-2.4.19-r29590-xlink-1.2.patch (corresponding patch)
Installing binary packages
Windows
Simply run the xlink-1.3.msi
installer package and follow the on-screen instructions. All required
libraries will be installed and the xlink command line client will be
added to your PATH
.
In order to access the parallel port on Windows XP or later you need
to run xlink.exe
as Administrator once to install the necessary
drivers.
Now proceed to Installing the firmware.
MacOSX
Open a terminal, change to the directory containing the package file and run the following command:
$ sudo tar vxC / -f xlink-1.2-macosx.tar.gz
You will also need libusb. Use the homebrew package manager to install it:
$ brew install libusb
Now proceed to Installing the firmware.
Building and installing the Client from source
Linux & MacOSX
KickAssembler 3 and libusb must be installed.
On MacOSX, you can use the homebrew package manager to install libusb:
$ brew install libusb
On Linux you should use the package manager of your distribution to install libusb. If your distribution provides separate development packages, make sure you install the development package for libusb as well.
Also download KickAssembler 3 and
copy the KickAss.jar
file to a convenient location.
Extract the source tarball and check/adjust the following settings in
the Makefile
:
PREFIX=/usr/local
SYSCONFDIR=/etc
KASM?=java -jar /usr/share/kickassembler/KickAss.jar
-
PREFIX: The installation prefix, which defaults to
/usr/local
. Leave this unchanged on MacOSX. -
SYSCONFDIR: This is the global system configuration directory, which defaults to
/etc
. For any PREFIX other than/usr
this will result in$(PREFIX)/$(SYSCONFDIR)
. -
KASM: The command to run KickAssembler. Adjust the path to
KickAss.jar
accordingly.
Now run make
to build the client and library.
If all went well you can now run make install
as root. This will
result in:
$(PREFIX)/bin/xlink
$(PREFIX)/lib/libxlink.so
$(PREFIX)/include/xlink.h
$(SYSCONFDIR)/bash_completion.d/xlink
On Linux, the following udev rule will also be installed:
$(SYSCONFDIR)/udev/rules.d/10-xlink.rules
The standard DESTDIR
variable can be used for a staged install.
Windows
The windows version can be cross-compiled with mingw32 under Linux or Cygwin. Use your package manager to install the mingw32 toolchain and libusb (including development packages if your distribution provides them).
Download KickAssembler 3 and
copy the KickAss.jar
file to a convenient location.
You will also need the wget utility to be installed.
Now check/adjust the following settings in the Makefile
:
KASM?=java -jar /usr/share/kickassembler/KickAss.jar
MINGW32?=i686-w64-mingw32
-
KASM: The command to run KickAssembler. Adjust the path to
KickAss.jar
accordingly. -
MINGW32: The prefix used for the mingw32 binaries, e.g. if gcc was installed as
i686-w64-mingw32-gcc
, the prefix isi686-w64-mingw32
. This value depends on your distribution/architecture.
To install on Windows you will need to copy xlink.dll
and
inpout32.dll
to your 32-bit system directory. On 32-bit systems this
is C:\Windows\system32
while on 64-bit systems this is
C:\Windows\SysWOW64
.
You will also need to install libusb. Download
the “Latest Windows Binaries” package from the libusb website, extract
the archive and copy the MS32/dll/libusb-1.0.dll
to your 32-bit
system directory.
In order to access the parallel port on Windows XP or later you need
to run xlink.exe
as Administrator once to install the necessary
drivers.
Building the Hardware
USB adapter
The USB adapter is implemented as a full-speed USB 2.0 device using Dean Cameras LUFA library on an Atmel at90usb162 chip. Unfortunately this chip is only available in SMD packaging (TQFP-32), and thus building the adapter requires a proper pcb and some soldering skill.
Schematics and pcb layout in KiCad format are included in the source distribution
List of parts
Reference | Type | Value | Package/RM |
---|---|---|---|
C1 | Ceramic capacitor | 18pF | 2.5mm |
C2 | Ceramic capacitor | 18pF | 2.5mm |
C3 | Tantalum capacitor | 1uF | 2.5mm |
C4 | Ceramic capacitor | 100nF | 2.5mm |
C5 | Ceramic capacitor | 4.7nF | 2.5mm |
IC1 | Microprocessor | at90usb162 | TQFP-32 |
J1 | Userport connector | 2x12 | 3.96mm |
P1 | USB Socket | USB-A | |
P2 | Pin Header, 2 Jumpers | 2x2 | 2.54mm |
JP1 | Pin Header, 1 Jumper | 1x2 | 2.54mm |
R1 | Precision Resistor | 22Ω | 6.5mm, ∅ 2.5mm |
R2 | Precision Resistor | 22Ω | 6.5mm, ∅ 2.5mm |
R3 | Resistor | 1 MΩ | 6.5mm, ∅ 2.5mm |
R4 | Precision Resistor | 1.8 KΩ | 6.5mm, ∅ 2.5mm |
R5 | Precision Resistor | 1.8 KΩ | 6.5mm, ∅ 2.5mm |
R6 | Precision Resistor | 1.8 KΩ | 6.5mm, ∅ 2.5mm |
R7 | Precision Resistor | 1.8 KΩ | 6.5mm, ∅ 2.5mm |
R8 | Precision Resistor | 1.8 KΩ | 6.5mm, ∅ 2.5mm |
R9 | Precision Resistor | 1.8 KΩ | 6.5mm, ∅ 2.5mm |
R10 | Precision Resistor | 1.8 KΩ | 6.5mm, ∅ 2.5mm |
R11 | Precision Resistor | 1.8 KΩ | 6.5mm, ∅ 2.5mm |
R12 | Precision Resistor | 820 Ω | 6.5mm, ∅ 2.5mm |
R13 | Precision Resistor | 1.8 KΩ | 6.5mm, ∅ 2.5mm |
X1 | Quartz Crystal | 8Mhz | HC49/U-S |
For the resistors labelled “Precision resistor” use “metal film” or “precision” types.
For C3 an electrolytic 1uF capacitor should work as well.
Placement on board
(microprocessor excluded)
The microprocessor is mounted on the bottom side. All other components are mounted on the top side.
Assembly Instructions
First solder the microprocessor to the bottom side. When the bottom side is facing up and the usb connector is at the top, pin one of the microprocessor (designated by the corner dot on the case) needs to be located in the top left corner.
Then solder the small components (resistors, capacitors and crystal).
Note that the tantalum capacitor C3 is polarized. There should be a small plus sign printed on the body, denoting the positive lead. It must face towards the microprocessor.
Now add the USB socket. Solder the contacts first, making sure the socket sits flat and smooth on the pcb. Now slightly bend the tongues extending through the larger holes. Make sure they don’t touch the ground plane. Then simply fill the large holes with solder.
Now you may optionally solder the pin header P2. This header is only
required if you need to reprogram the microprocessor and the xlink
bootloader
command for some reason fails to enter the dfu bootloader.
In this case, enter the bootloader manually.
The pin header JP1 configures the adapter for use in the C64 or the C128. If you only plan to use the adapter in a C64 you may simply bridge this jumper with a piece of wire.
Finally solder the userport-connector. It has proven practical to first bend all the solder tongues towards each other until they touch in the middle of the connector. Then you can gradually “wiggle” the pcb in between the tongues. The tongues now hold the pcb tightly, making a good physical connection before soldering. Although not all tongues need to be soldered, it is recommended to do so to increase stability.
Configuring the adapter for the C128
The JP1 header needs to remain open if the adapter is used in a C128. This is necessary because the reset line provided at the C128 userport only resets peripheral devices but not the C128 as a whole.
To enable hardware resets, an additional wire has to be run from the top pin of the header to the real reset line of the C128. This line can be accessed at PIN 11 of U57 (located inside the metal box containing the VIC) or at the right leg of R23 (located just below the reset switch).
If you want to use the first or second revision of the adapter in a C128 you will need to desolder or cut the trace connecting the userport reset line and instead connect this trace to the real reset line.
You’re now ready to build and install the firmware.
Manually entering the bootloader
If for some reason the xlink bootloader
command fails to set the
microprocessor in dfu mode, you can force it to enter the bootloader
by setting jumpers on the pin header in the following sequence:
After that the microproccessor should be in dfu mode.
Parallel Port Cable
This is a simple 8-bit parallel transfer cable connecting the PC Parallel Port to the Userport of the Commodore:
PC Parallel Port | Commodore Userport |
---|---|
GND (18-25) | GND (1, 12, A, N) |
D0 (2) | PB0 (C) |
D1 (3) | PB1 (D) |
D2 (4) | PB2 (E) |
D3 (5) | PB3 (F) |
D4 (6) | PB4 (H) |
D5 (7) | PB5 (J) |
D6 (8) | PB6 (K) |
D7 (9) | PB7 (L) |
BUSY (11) | PA2 (M) |
STROBE (1) | /FLAG2 (B) |
A shielded cable is recommended. The cable shield should be connected to ground on both sides. A 0.1uF capacitor between shield and ground at the Userport side can also help to reduce noise. Improper shielding will most likely cause transfer errors.
Check that your parallel port is configured for bidirectional transfer
in your BIOS. This is either called PS/2
, EPP
, ECP
, EPP/ECP
or
simply bidirectional
. If in doubt, anything but standard
should
do.
Please note that there is no electrical protection of any kind. The parallel port pins are directly connected to port B of CIA2. To prevent damage to the parallel port or CIA chip you should always power down both the PC and Commodore before inserting or removing the cable. Also turn off the Commodore during boot, reboot or shutdown of the PC.
Reset Circuit
This optional circuit is required for the parallel port cable in order to perform a hardware reset of the target machine.
Line | Port | Pin |
---|---|---|
INIT | Parallel Port | 16 |
RESET | Userport | 3 |
VCC | Userport | 2 |
GND | Userport | 1, 12, A, N |
For use with a C128, the RESET signal needs to be connected to the real reset line on the C128 board (Pin 11 of U57 or right leg of R23).
This circuit holds the Commodore RESET line low as long as the
parallel port INIT line is held low. The parallel port lines
themselves may have been left in an unknown/random state after the PC
wakes up from suspend or after the port has been accessed by another
program. This means that if the INIT line happens to be permanently
low, the Commodore will remain in a state of permanent reset and
appear frozen or even dead (showing just a black screen after power
up). In this case running xlink reset
will bring the
parallel port lines back to a defined state and the Commodore will
resume operation.
Building the Firmware
You will need gcc-avr to build the firmware for the usb adapter.
Optionally you may want to export the XLINK_SERIAL
environment
variable before building. This will make the adapter report a specific
USB serial number.
Adding a serial number is only useful if you’re planning to connect more than one Commodore machine to the same PC via USB.
Now run make firmware
. This will create the file xlink.hex
in the subdirectory driver/at90usb162/
.
Installing the Firmware
Make sure the Commodore is turned off and insert the adapter into the userport. Connect it to the PC using the USB cable. Now power on the Commodore.
Linux & MacOSX
Listing USB devices
On Linux, the lsusb
command lists the usb devices. On MacOsx, the
command system_profiler SPUSBDataType
can be used. Alternatively, go
to Applications -> Utilities -> System profiler -> Hardware -> USB.
Programming the firmware
Make sure dfu-programmer is installed on your system.
List the usb devices to check that the adapter has been properly
enumerated. It should be reported as Atmel Corp. at90usb162 DFU
bootloader
.
Now install the firmware using the following commands:
$ dfu-programmer at90usb162 erase
$ dfu-programmer at90usb162 write xlink-firmware-1.1.hex
$ dfu-programmer at90usb162 reset
If you have build the firmware from source you can use make
firmware-install
.
If all went well, wait a few seconds and check the usb device list
again. The adapter should now appear as 1d50:60c8 OpenMoko, Inc.
.
On Linux, given that you have already installed the client software,
udev should now have created a symlink to the device at
/dev/xlink
. If not, install the client software and run the
following commands as root:
udevadm control --reload-rules
udevadm trigger
This will make sure that the udev rules installed for the adapter take effect.
The adapter is now ready to use.
Windows
Things are a bit more complicated on windows. First make sure that you have properly installed libusb as described in Building and installing the Client. The windows installer package does this for you.
When you power up the adapter for the first time, windows will ask for
a driver. Cancel the driver installation wizard. Instead, use
Zadig to register the device for use with
libusb-win32
.
Now install the firmware over USB using Atmels FLIP tool.
Once the firmware is installed windows should ask for a driver
again. (If not, power cycle the Commodore once to force
re-enumeration). Use the Zadig tool again to register the device, but
this time use the WinUSB
driver.
The adapter is now ready to use.
Running the server on the target machine
RAM based server
Use the client to create a server.prg
by using the server
command:
xlink server server.prg
The resulting server.prg
can be loaded on the C64 and started with
RUN. See xlink help server
for more information.
To create a server program for the C128, specify the machine type
using the --machine
option:
xlink server --machine=c128 server.prg
Manual bootstraping
If you have no initial means of transferring the server.prg
from your
PC to your target machine you can type in a short basic
program (C64, C128).
Just run it and follow the on-screen instructions.
ROM based server
Use the client to patch a kernal image to include the server:
xlink kernal kernal.rom xlink.rom
This will take the existing kernal.rom
image and patch it to create
a kernal image called xlink.rom
that you can install in your C64.
To patch a C128 kernal image, use the --machine
option to specify
the target machine:
xlink kernal --machine=c128 kernal.rom xlink.rom
The patch will always be applied to the last 8192 bytes of the input file. This allows patching combined 16 or 32k rom images of the C64C or C128, which contain the kernal code in the upper 8k.
The kernal includes the server and runs it as part of the system irq.
Note that tape io has been removed in favor of the server. Attempts to load or save to tape in direct mode will display the error message “TAPE IO DISABLED” on both machines.
To speed up development cycles the reset routines on both machines have been modified.
On the C64, the startup memory check has been made optional and is skipped by default. To perform a memory check you will have to hold down the Commodore key during powerup/reset.
On the C128, autoboot from disk has been made optional and is skipped by default. To perform an autoboot you will have to hold down the Control key during powerup/reset.
Usage
For an overview of available commands run xlink --help
xlink 1.3 Copyright (C) 2015 Henning Bekel <h.bekel@googlemail.com> Usage: xlink [<opts>] [<command> [<opts>] [<arguments>]]... Options: -h, --help : show this help -q, --quiet : show errors only -v, --verbose : show verbose debug output -d, --device <path> : transfer device (default: /dev/xlink) -M, --machine : machine type (default: C64) -m, --memory : C64/C128 memory config (default: 0x37/0x00) -b, --bank : C128 bank value (default: 15) -a, --address <start>[-<end>] : address/range (default: autodetect) -s, --skip <n> : Skip n bytes of file Commands: help [<command>] : show detailed help for command kernal <infile> <outfile> : patch kernal image to include server code server [-a<addr>] <file> : create server program and save to file relocate <addr> : relocate currently running server reset : reset machine (requires hardware support) ready : try to make sure the server is ready ping : check if the server is available identify : identify remote server and machine type load [<opts>] <file> : load file into memory save [<opts>] <file> : save memory to file poke [<opts>] <addr>,<val> : poke value into memory peek [<opts>] <addr> : read value from memory fill <range> <val> : fill memory range with value jump [<opts>] <addr> : jump to specified address run [<opts>] [<file>] : run program, optionally load it before <file>... : load file(s) and run last file benchmark [<opts>] : test/measure transfer speed bootloader : enter dfu-bootloader (at90usb162)
To get detailed help for a specific command use xlink help <command>
.
Device autodetection
When no device is specified, xlink will first try to connect via USB,
using the product and vendor id for the adapter. If no usb adapter is
found, it will try to use a parallel port instead. On Linux the
default parallel port is /dev/parport0
. On windows, the default
parallel port is assumed to be available at io port 0x378
.
Specifying the device
The --device
option can be used to specify a device on the
commandline. Alternatively, the environment variable XLINK_DEVICE
can be used.
On Linux, both the usb adapter and the parallel port are accessed via
their respective device nodes, i.e. /dev/xlink
for usb or
/dev/parport0
for the (first) parallel port.
On MacOSX, the usb device is specified by using the literal string
usb
(this is the default).
On Windows, the usb device is specified by using the literal string usb
. The
parallel port is specified via its port address, (usually) 0x378
.
The shared memory driver can be selected by using the literal string
shm
. This driver implements a virtual port in shared memory that can
be accessed by other programs to emulate the Commodore side (see Vice
emulation).
Specifying the target machine
The --machine
option can be used to specify the target
machine. Alternatively, the environment variable XLINK_MACHINE
can
be used. Legal values are c64
or c128
. The default value is c64
.
Setting the machine type to C64 when connecting to a C128 will cause
the reset
and ready
commands to change to C64 mode automatically.
Multiple usb devices
In order to use multiple usb adapters connected to the same PC, the
adapters need to be programmed with different usb serial numbers (See
Building the Firmware). On Linux you can
then use udev rules to create device symlinks based on those
serials. On windows, the desired serial number can be attached to the
device specification, e.g. to use the device with the serial number
12345
you’d use usb:12345
.
Vice emulation
The win32 vice build is based on vice 2.4.19 and includes xlink emulation support.
To build this version yourself, apply the corresponding patch to revision 29590 of the vice svn tree:
$ svn checkout svn://svn.code.sf.net/p/vice-emu/code/trunk@29590 vice $ cd vice $ patch -p0 -i vice-2.4.19-r29590-xlink-1.2.patch
The resulting source tree will likely not build under systems other than win32 or posix.
Add the following line to the vice config file:
UserportXlink=1
Alternatively pass the command line option -xlink
. Note that if you
save your settings in vice, the current xlink configuration will be
saved as well, even though it does not appear anywhere in the menus.
Now load the server program in vice or install an image of the xlink
kernal. To tell xlink to use the shared memory driver pass --device
shm
or export XLINK_DEVICE=shm
.
Note that the transfer speed will depend on the performance of your machine and the vice emulation speed. On modern systems, this should be on par with the speed obatained on real hardware. On older systems the transfer speed might be somewhat decreased. For example, on my trusty old 1Ghz single-core box the transfer speed is limited to about 13kb/s.
Commands in general
Multiple commands can be specified in sequence. For example, the
load
and run
commands can be combined like this:
$ xlink load myprog.prg run
This is equivalent to
$ xlink load myprog.prg $ xlink run
Execution is aborted when one of these commands fails, so that subsequent commands are not executed.
Handling of file arguments
Specifying a single file as the only argument will automatically ready the server, load the file and run it. Thus the following command
$ xlink myprog.prg
is equivalent to
$ xlink ready run myprog.prg
Specifying multiple files in sequence will automatically ready the server, load all files into memory and the run the last file. Thus the following command
$ xlink charset.prg sprites.prg main.prg
is equivalent to
$ xlink ready load charset.prg load sprites.prg run main.prg
Numeric arguments
Numeric arguments may be specified in decimal or hexadecimal notation,
where hexadecimal arguments must be prefixed with 0x
.
For example, --address 0xC000
is equivalent to --address 49152
.
Memory configuration
The effective memory configuration used on the remote machine prior to
reading and/or writing values into memory can be controlled via the
--memory
and --bank
options.
On the C64, the value specified by the --memory
option is applied
to the processor port at $01
. The --bank
value is ignored.
On the C128, the value specified by the --memory
option is applied
to the MMU control register at $ff00
. Alternatively, the
--bank
option can be used to specify one of the fifteen predefined
memory configurations of the C128. If both options are present,
--memory
takes precedence over --bank
.
Memory ranges
Memory ranges are specified with the start address inclusive and the end address exclusive. This means that when using a command like
$ xlink save -a 0xC000-0xD000 file.bin
the last value saved will be the content of the memory location $CFFF
.
To include the very last memory location use 0x10000
as the end
address.
Transfer Commands
Load
xlink load [--address <start>[-<end>] [--memory <mem>] [--skip <n>] <file>
Load the specified file into memory
If no start address is given it is assumed that the file is a PRG file and that its first two bytes contain the start address in little-endian order.
Otherwise, if a start address is given it is assumed that the file is a
plain binary file that does not contain a start address. In this case the
entire file is loaded to the specified address. The --skip
option may be
used to skip an arbitrary amount of bytes at the beginning of the file. Thus
you can load a file already containing a start address to a different
location by specifying --skip 2
in addition to --address
.
If an additional end address is specified, transfer will end as soon as the end address or the end of the file is reached, whichever comes first.
Optional memory and bank configuration values can be specified that will be applied on the remote machine before writing the transferred values to their destination.
If no memory or bank values are specified, values will be written using the default memory configuration for the respective machine. The only exception to this rule is that if the destination range overlaps with the io area, values will be written to the ram residing below the io area. This is a safety measure that prevents possible damage to the involved hardware io ports. In order to load data directly into the io area the memory configuration needs to be set explicitly.
On the C64, the value of --memory
is applied via the processor port
at $01
. The bank value has no effect.
On the C128, the value of --memory
is applied via the MMU configuration
register at $ff00
. Alternatively, the --bank
value can be used to chose one
of the fifteen predefined bank configurations of the C128. If both options
are specified, --memory
will take precedence over --bank
.
If the server on the remote machine is running from RAM and is located in the same memory area as the data to be loaded then an attempt is made to relocate the server to a different location beforehand.
Save
xlink save [--address <start>-<end>] [--memory <mem>] [--bank <bank>] file
Saves the specified C64 memory area to file.
If the destination filename ends with .prg then the destination file will be prefixed with the supplied start address. If no address range is specified, then the basic program currently residing in C64 memory will be saved.
For a description of the –memory and –bank options see the load
command.
Peek
xlink peek [--memory <mem>] [--bank <bank>] <address>
Read the byte at the specified C64 memory address and print it on standard output.
Poke
xlink poke [--memory <mem>] [--bank <bank>] <address>,<byte>
Poke the specified byte to the specified address.
If no memory or bank option is specified, then the default memory config for the respective machine will be used, so that values poked to the io area will have the expected effect.
Fill
xlink fill --address <start>-<end> <value>
Fill the specified memory area with <value>. The end address will
default to 0x10000
unless explicitly specified.
Control Commands
Jump
xlink jump [--memory <mem>] [--bank <bank>] <address>
Jump to the specified address in memory. The stack pointer, processor flags and registers will be reset prior to jumping.
The --memory
and --bank
options can be used to configure the memory
setup of the remote machine prior to jumping.
Run
xlink run [<file>]
Without argument, RUN the currently loaded basic program. With a file argument specified, load the file beforehand. If the file loads to the respective machines’ default basic start address, then assume its a basic program and RUN it, else assume it’s an ml program and jump to the files load address.
Reset
xlink reset
Reset the remote machine. Works without the server actually running on the remote side.
###Ping
xlink ping
Ping the server, exit successfully if the server responds.
Ready
xlink ready [<commands>...]
Makes sure that the server is ready. First the server is pinged. If it doesn’t respond immediately, the remote machine is reset. If the server responds to another ping within three seconds, then the remaining commands (if any) are executed.
This command requires the server to be installed permanently so that it is available after reset.
Maintenance Commands
Server
xlink server [--machine c64|c128] [--address <address>] <file>
Write a ram-based server program to <file>
. Use address to specify the start
address for the server code. If the address corresponds to the machines
default basic start address, then the server can be started with RUN. This
is the default if no address is specified.
Kernal
xlink kernal [--machine c64|c128] <infile> <outfile>
Patch the kernal image supplied via <infile>
to include an xlink server and
write the results to <outfile>
. Note that the resulting kernal will no
longer support tape IO.
The patch will always be applied to the last 8192 bytes of the input file. This allows patching the combined 16 or 32k roms of the C128, which contain the kernal code in the upmost 8k.
The reset procedures of the respective machines have been modified to speed up development cycles. On the C64, the memory check on startup has been made optional and is skipped by default unless the commodore key is held down during reset. On the C128, automatic boot from disk has been made optional and is skipped by default unless the control key is held down during reset.
Benchmark
xlink benchmark [--address <start>[-<end>] [--memory <mem>] [--bank <bank>]
Write random data into memory, then read it back and compare it to the original data while measuring the achieved transfer rates.
If no address range is specified, a default range of freely usable ram in the standard memory configuration is chosen for the respective machine.
If an address range is specified that overlaps with rom or io, the
data received will differ from the data send, and the comparison will
fail. Use the --memory
and --bank
options to disable rom
and/or io for such ranges.
Bootloader
xlink bootloader
Prepare USB device for firmware updates. Enters the atmel dfu-bootloader.
API Documentation
Hello World
Compile and link against xlink:
This program prints the message “HELLO WORLD!” into the upper left
corner of the C64 screen by using xlink_load() to load
the corresponding screencodes to the start of the screen memory area
at $0400
.
Since we didn’t specify the device to use beforehand, the device is autodetected as described device autodetection.
To find out which device has been detected, use xlink_get_device(). To find out whether a device has been detected at all, use xlink_has_device(). To set the device explicitly, use xlink_set_device().
Note that this program will fail silently if an error occurs. The next section describes how to handle errors.
Error handling
xlink_error
The global variable xlink_error points to a struct of type xlink_error_t which contains details about the last error that occurred.
Most xlink functions return true
on success and false
on
failure. In the case of failure, an error code and a human readable
error message can be obtained from xlink_error
. In case of success,
xlink_error
is reset.
xlink_error_t
code
contains a code denoting the general type of error, and can be one of:
message
contains a human readable error message.
For example, errors could be reported to the user using the following scheme:
Debug messages
If debugging is enabled, detailed debug messages will be written to stdout
.
xlink_set_debug()
Enable or disable debug messages.
Versioning
xlink_version()
Returns the library version as an unsigned char, where the high nibble contains the major version and the low nibble contains the minor version.
The API will remain consistent across major versions. Minor version bumps will only occur if new functions are added. Should functions be removed or other breaking changes become necessary this will be reflected by a bump of the major version.
Device handling
xlink_has_device()
Returns true
if a device has been sucessfully initialized.
xlink_get_device()
Returns a pointer to a string containing the device specification for the device currently in use. If no device has been initialized yet, the string will be empty. Do not modify the returned string.
xlink_set_device()
Sets the device specification to path
and tries to initialize the
device. Returns true if the device has been successfully initialized.
Server detection and control
xlink_ping()
Sends a strobe signal to the server and returns true
if the server
responds with an ack signal within 250ms.
Note that this only checks whether something on the C64 side has acknowledged the strobe. This might be an xlink server ready to receive commands, but it might also be an xlink server gone astray (hung in a previous transfer), or something else altogether.
To identify the server and its state reliably use xlink_identify().
xlink_identify()
Requests identification data from the server and populates the the
xlink_server_info_t
structure pointed to by server
with the
received results.
xlink_server_info_t
-
id
contains an identification string of up to 15 characters. The xlink server reportsXLINK
here. -
version
contains the server version, where the high nibble contains the major version and the low nibble contains the minor version. Currently the xlink server will respond with0x10
. -
machine
contains the remote machine type. Possible values areXLINK_MACHINE_C64
orXLINK_MACHINE_C128
. -
type
contains the server type, eitherXLINK_SERVER_TYPE_RAM
orXLINK_SERVER_TYPE_ROM
-
start
contains the start address of the server code. -
end
contains the end address of the server code. -
length
contains the length of the server code. -
memtop
contains the top of the lower memory area.
xlink_reset()
Resets the C64 by pulling its RESET line low for about 10ms. This works whether or not a server is running on the C64.
If a parallel port cable is used, additional circuity is required for this to work.
xlink_ready()
Try to make sure that the server is ready. First, the server is
pinged. If it doesn’t respond, the C64 is reset. If the server
responds to another ping within the next 3 seconds this function
returns true
.
If the initial ping succeeds and a basic program is found running on the C64, a basic warmstart (equivalent to pressing runstop-restore) is performed prior to returning successfully.
xlink_relocate()
Loads a ram-based server to the given address
and passes control to
it, effectively disabling the currently running server. Note that no
additional checks are performed. You have to make sure that the
server doesn’t end up below ROM or IO areas.
You can use xlink_identify() beforehand to determine whether relocation is required and what the size of the server code will be.
Memory transfers
Common arguments
Address
The address
argument specifies the source or destination address or
address range. For transfers involving more than one byte of data,
wrapping may occur if the size of the transfer exceeds the machines
address space. This means that if the last memory address $FFFF
has
been read or written to and there is still more data to transfer, the
transfer continues from address $0000
onwards.
Memory and Bank
The memory
and bank
arguments allow adjusting the target machines
memory configuration before reading/writing values to/from memory.
These arguments are equivalent to the memory configuration
options described above.
In addition, the most significant bit of the memory
value controls
whether screen blanking should occur during transfers. Set this bit to
prevent screen blanking for the load, save and fill operations
(i.e. binary OR the value with 0x80
).
xlink_load()
Load size
bytes of data obtained from the memory area pointed to by
data
to address
in the target machine memory.
xlink_save()
Read size
bytes of data beginning from address
in the target
machine memory and store the result in the memory area pointed to by
data
. The caller has to make sure that enough memory is allocated
for data
beforehand.
xlink_peek()
Read the byte at address
from the target machine memory and store it
in the memory location pointed to by value
.
xlink_poke()
Write value
to address
in the target machine memory.
xlink_fill()
Fill the target machine memory with size
bytes of the constant value
value
beginning at address
.
Program execution
xlink_jump()
Make the target machine jump to the specified address
. Prior to
jumping, the stack pointer is reset and the address of the basic REPL
(Read-Eval-Print-Loop) is pushed on the stack, followed by the actual
jump address. Then the supplied memory config is applied, clean
processor flags and registers are pushed onto the stack and the jump
is performed via RTI, leaving the current invocation of the IRQ
routine servicing the request.
When the code performs a final RTS, the target machine (should) return to the basic prompt.
See xlink_inject() for an alternative way of running code on the target machine.
xlink_run()
Runs the currently loaded basic program. The currently running server is uninstalled beforehand.
This is equivalent to issuing RUN
on the target machine.
Low level API
Overview
The low level API provides a way to implement custom commands or communication schemes in your own programs without requiring changes to the server code running on the users remote machine.
Instead, the code required to serve a custom command on the C64 can be injected into the context of the currently running server using xlink_inject(). This code is then responsible for performing the subsequent low level communication with the client. The client in turn can use the functions of the low level API to communicate with the server.
To begin a communication session, the client calls xlink_begin(). It then uses the communication primitives xlink_send() and xlink_receive() (and their variants) to transfer data back and forth between the two machines. It finally calls xlink_end() to signal the end of the communication session. This will be explained in greater detail later in this document.
Technical background
In order to implement the server side code serving custom commands it is necessary to understand the underlying technical implementation. In this section I will describe the implementation and its implications while offering a simple set of macros that can be used to implement a custom server on the C64. The section is concluded with a simple example for a custom server and a corresponding client.
Handshaking
Two distinct lines are used to implement the necessary handshaking between the two machines:
The first line is used to send a handshake signal from the PC to the
C64. On the C64, this line is connected to the userport FLAG line. The
occurrence of a falling edge on the FLAG line is reflected by bit 4 of
the interrupt control and status register of CIA2 at $dd0d
. This
bit is set when a falling edge occurs, and is cleared when the register
is read. Thus to send a handshake signal to the C64, the PC generates
a falling edge on the flag line. To detect a handshake from the PC, the
C64 reads $dd0d
and tests whether bit 4 is set.
On the C64, we can wait for the occurrence of a handshake signal using the following code:
We define a macro called wait
containing this code.
The second line is used to send a handshake signal from the C64 to the
PC. At the C64, this line is connected to bit 2 of Port A of CIA2 at
$dd00
. To send a signal to the PC, the C64 simply flips the value of
this bit. The PC can thus detect an incoming signal from the C64 by
listening for changes on this line.
On the C64, we can send a handshake signal to the PC using the following code:
We define two macros called strobe
and ack
containing this code.
This way our code will document our intent when sending a signal to the PC: we’ll use the strobe macro when we initially tell the PC that some condition has occurred, and we’ll use the ack macro if we merely acknowledge a previous signal we received from the PC.
Data port and direction
xlink uses Port B of CIA2 at $dd01
for 8-bit wide bidirectional data
transfers.
On the C64, we can set the port direction of the data port to input using:
We can set the port direction to output using:
We define to macros for this, called set_input
and
set_output
respectively.
Note that you should never use set_output
blindly without having
negotiated a change of transfer direction with the PC first. See
negotiating direction for details.
On the client side the port is always kept in input mode unless the port is actually used to send data to the server. On the server side, we should always make sure that the port remains in input mode unless output mode is actually required. Thus, the code we inject() should always initialize the port and our handshake lines correctly first:
We define a macro called init
for this.
Negotiating direction
The transfer direction of the data port must be negotiated with care between the two machines. If both ports were set to output, either port may be damaged by short circuits, depending on the actual levels the individual port lines are driven to. The logic needed for negotiation of transfer direction is abstracted away by the low level functions on the client side. On the server side, code must follow the assumptions made by the low level functions on the client side.
In order to negotiate the transfer direction and to switch the direction during a communication session the handshake lines are used for synchronization. When the direction should change, the currently receiving side may switch its port to output (and thus become the sender) only after having received a signal from the current sender acknowledging that is has already set its port to input (and thus has become the receiver).
So on the C64 we’ll have to wait for a signal from the PC before switching to output:
We define a macro called output
for this.
Likewise, if the C64 is currently sending, it must set its port to input first and then acknowledge the switch to the PC:
We define a macro called input
for this.
Transferring data
In order to transfer data between the two machines the following communication scheme is used:
- The sender writes a byte of data on the port
- The sender sends a signal to the receiver (it “strobes” the receiver)
-
The sender waits until the receiver acknowledges the receipt of the data
- The receiver waits for a signal from the sender (it is “strobed” by the sender)
- The receiver reads the data from the port
- The receiver sends a signal to acknowledge receipt (it “acks” to the sender)
If the C64 takes the role of the sender, we can use the following code to send one byte of data from the accumulator:
If the C64 takes the role of the receiver, we can use the following code to receive one byte of data and store it into the X register:
We define macros called send
and receive
for this.
Implementing a custom server and client
We now have the necessary macros to implement a custom server on the
C64: init
, input
, output
, send
and receive
.
For the sake of the example we’ll implement a simple injectable server that understands only two commands:
identify
will send the short identification string “CUSTOM”quit
will quit the server and return control to the xlink server
The code uses Kickassembler 3 syntax.
The corresponding client will first inject the above code using xlink_inject(). It will send an identify command and output the results, followed by a quit command to quit the injected server again. It then uses xlink_identify to verify that the custom server has actually quit by checking that the xlink server is active again.
Low level functions
Overview and rationale
The low level functions are based on a concept of communication sessions. The functions xlink_begin() and xlink_end() signal the begin and end of a single session to the library. This is necessary because at the begin of a session, the roles of the PC and the C64 are not defined yet: while xlink is based on the assumption that usually the PC is a client that sends commands that are served by the C64, a reversal of these roles is possible just as well: in this case the PC could take the role of the server, listening for and servicing commands send by the C64.
To make this possible, the initial setup of transfer direction does not require handshaking. Instead, it is assumed that both sides are in input mode at the beginning of a session, and that either side may initiate communication by simply setting its port to output and sending a command to the other side. After this initial transfer has been send though, any subsequent change of transfer direction requires proper negotiation through the exchange of handshake signals as described above. The low level send and receive functions automatically detect whether the transfer direction changes and perform the necessary negotiation.
Note that in order for the PC to become the server, it will need to keep listening for a command sent by the remote machine for a possibly indefinite amount of time. Although you could use xlink_receive_with_timeout() with a timeout value of zero for this, it is not recommended to do so. If a parallel port cable was used, this would be no problem, since the user could simply abort the server program on the PC. If the usb adapter is used though, a timeout value of zero will make the microprocessor on the adapter enter a possibly endless loop while waiting for a signal from the remote machine. If no signal ever occurs, the adapter will need a power-cycle to break the endless loop.
Thus it is recommended implement a listening loop on the PC like this:
xlink_inject()
Loads size
bytes of code
to address
and executes the code in the
context of the current invocation of the IRQ routine servicing the
request. An RTS from the code will return control back to the
xlink server. Since the code runs within the IRQ service
routine it should not preform a CLI instruction.
xlink_begin()
Signals the begin of a communication session with the remote machine and assures that the next call to either xlink_send() or xlink_receive() (or their variants) will not perform an additional handshake required to negotiate transfer direction. This assumes that both communication ports are set to input when the first call to these functions occurs. On the client side, this is assured by the implementation of these functions. On the server side, the servicing code is responsible for keeping the port in input mode unless output mode is strictly required.
xlink_send()
Sends size
bytes of data
to the server.
If this call implies a change of transfer direction within the current communication session (i.e. if the previous call was to either xlink_receive() or xlink_receive_with_timeout()), then this function first waits for a signal acknowledging that the other side has switched its port to input and then switches the port to output.
If no change of transfer direction is implied by this call (i.e. the previous call was to either xlink_begin(), xlink_send() or xlink_send_with_timeout()), then no additional handshake is expected before switching the port to output.
If the server fails to acknowledge a previous strobe performed during
the execution of this function within one second, this function will
return false
and xlink_error will be set
appropriately. Use xlink_send_with_timeout()
to specify a larger timeout.
xlink_send_with_timeout()
This function is equivalent to xlink_send() except that the timeout
for the transfer may be set explicitly. The timeout
is given in
seconds. A zero timeout value means no timeout at all.
xlink_receive()
Receives size
bytes of data
from the server. The caller has to
make sure that sufficient memory is allocated for data
beforehand.
If this call implies a change of transfer direction within the current communication session (i.e. if the previous call was to either xlink_send() or xlink_send_with_timeout()), then this function will first switch the port to input and then acknowledge this to the other side.
If no change of transfer direction is implied by this call (i.e. the previous call was to either xlink_begin(), xlink_receive() or xlink_receive_with_timeout()), then no additional handshake is expected before switching the port to input.
If the server fails to acknowledge a previous strobe performed during
the execution of this function within one second, this function will
return false
and xlink_error will be set
appropriately. Use
xlink_receive_with_timeout() to specify a
larger timeout.
xlink_receive_with_timeout()
This function is equivalent to xlink_receive() except
that the timeout
for the transfer may be set explicitly. The
timeout
is given in seconds. A zero timeout value means no timeout at all.
xlink_end()
Signals the end of a communication session with the remote machine. Although this call is functionally equivalent to xlink_begin() it is provided as a means to clarify the code.
Feedback
Please report bugs, issues, feature request etc. on the github issue tracker.
If you have problems building, installing or using this software, feel free to drop me a line at h.bekel@googlemail.com.
License
XLINK Hardware, Firmware, Client, Server and Library Copyright (c) 2015, Henning Bekel <h.bekel@googlemail.com> All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
LUFA Library Copyright (C) Dean Camera, 2013. dean [at] fourwalledcubicle [dot] com www.lufa-lib.org Permission to use, copy, modify, and distribute this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that the copyright notice and this permission notice and warranty disclaimer appear in supporting documentation, and that the name of the author not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. The author disclaims all warranties with regard to this software, including all implied warranties of merchantability and fitness. In no event shall the author be liable for any special, indirect or consequential damages or any damages whatsoever resulting from loss of use, data or profits, whether in an action of contract, negligence or other tortious action, arising out of or in connection with the use or performance of this software.
InpOut32 Library and Drivers Copyright Logix4U & Phillip Gibbons [Highresolution Enterprises] (for the x64 port) http://www.highrez.co.uk/Downloads/InpOut32/ The Author makes no guarantee that this software is free from bugs and will not harm your system. However, the author actively runs this software and all downloads have been checked for known viruses. This product is released as open source (Freeware).