text:
CPP=g++
CC=gcc
DTC=dtc
BIN=bin_pi

clean:
	rm -f ${BIN}/a314d-td
	rm -f ${BIN}/a314d-cp
	rm -f ${BIN}/a314d-fe
	rm -f ${BIN}/spi-a314.dtbo
	rm -f ${BIN}/start_gpclk
	rmdir ${BIN}

${BIN}/a314d-td: a314d/a314d.cc
	${CPP} -DMODEL_TD=1 a314d/a314d.cc -O3 -o ${BIN}/a314d-td

${BIN}/a314d-cp: a314d/a314d.cc
	${CPP} -DMODEL_CP=1 a314d/a314d.cc -O3 -o ${BIN}/a314d-cp

${BIN}/a314d-fe: a314d/a314d.cc
	${CPP} -DMODEL_FE=1 a314d/a314d.cc -O3 -o ${BIN}/a314d-fe

${BIN}/spi-a314.dtbo: a314d/spi-a314-overlay.dts
	${DTC} -I dts -O dtb -o ${BIN}/spi-a314.dtbo a314d/spi-a314-overlay.dts

${BIN}/start_gpclk: a314d/start_gpclk.c
	${CC} a314d/start_gpclk.c -I/opt/vc/include -L/opt/vc/lib -lbcm_host -o ${BIN}/start_gpclk


text:
# A314 Software

The A314 software supports multiple variants of A314 hardware.
The variants of the software use the following acronyms:

- a314-td (trapdoor), works with A314-500 and A314-600
- a314-cp (clockport), works with A314-cp
- a314-fe (front expansion), works with A314-1000

## How to install

Software needs to be built and installed for both the Raspberry Pi side and the Amiga side.

### Raspberry Pi

To build and install the software on the Raspberry Pi side, do the following:

- Install Raspberry Pi OS (I tend to use Raspberry Pi OS Lite 64-bit,
  but any recent version should work):
  <https://www.raspberrypi.com/software/operating-systems/>
- Install Dependencies: `sudo apt install python3-dev python3-distutils python3-pip build-essential git`
- Obtain a copy of the repository, either:
  - Clone the a314 repository: `git clone https://github.com/niklasekstrom/a314.git`, or
  - Download the sources from a release. Pick a release from
    <https://github.com/niklasekstrom/a314/releases>, download the file linked to as
    Source code (tar.gz), and extract with `tar xvf <filename>.tar.gz`.
- Change into the Software directory: `cd a314/Software`
- Run the installer script: `sudo ./install-pi.sh <model>`, where `<model>` is
  `td`, `cp` or `fe` depending on which variant of A314 is used.
- Reboot the Raspberry Pi: `sudo reboot now`

### Amiga

The binaries for the Amiga side are already built and are available as part of a release on the GitHub page: <https://github.com/niklasekstrom/a314/releases>

There are multiple sub directories in the release archive, and the files in those sub directories should be copied to the corresponding sub directories in the AmigaOS system volume.

Note that in the Devs directory there are multiple different versions of a314.device:
`a314-td.device`, `a314-cp.device`, `a314-fe.device`.
Copy only the version that matches your A314 hardware and rename it to `a314.device`.

After these files are copied to the Amiga it should be possible to run the pi command.
The pi command can be invoked by `pi` without any arguments to get an interactive bash terminal,
or with arguments to run a particular Linux command.

#### Building

It is also possible to build the Amiga binaries from source, either on the Raspberry Pi or on a Linux PC.
To do so, follow these steps:

- Download and install VBCC:
  - On the bottom of this page, <http://www.compilers.de/vbcc.html>, there are archives with VBCC
    pre-compiled for different plattforms such as Linux (x64) and Raspberry Pi
  - Download the appropriate archive, e.g.: `wget http://www.ibaug.de/vbcc/vbcc_linux_x64.tar.gz`
  - Unpack to some suitable directory: `tar xvf vbcc_linux_x64.tar.gz -C ~/amiga/`
  - Set the VBCC environment variable: `export VBCC=~/amiga/vbcc`
  - Add VBCC to PATH: `export PATH=$VBCC/bin:$PATH`
- Download and add NDK3.2:
  - Download archive from Aminet: `wget http://aminet.net/dev/misc/NDK3.2.lha`
  - Install lha extraction software: `sudo apt install lhasa`
  - Extract archive: `lha xw=~/amiga/NDK3.2 NDK3.2.lha`
  - Add environment variable: `export NDK32=~/amiga/NDK3.2`
- Clone the a314 repo: `git clone https://github.com/niklasekstrom/a314.git ~/amiga/a314`
- Change into the Software directory: `cd ~/amiga/a314/Software`
- Build binaries by running: `make -f Makefile-amiga`

The binaries are now available in the sub directories of the `bin_amiga` directory.

repo_language: C

mime_type: text/xml


text:
/*
 * Copyright (c) 2020 Niklas Ekström
 */

#include <exec/types.h>
#include <exec/ports.h>
#include <exec/tasks.h>
#include <exec/nodes.h>
#include <exec/lists.h>
#include <exec/execbase.h>
#include <exec/memory.h>

#include <devices/input.h>
#include <devices/inputevent.h>
#include <libraries/dos.h>

#include <proto/alib.h>
#include <proto/exec.h>

#include <stdio.h>
#include <string.h>
#include <time.h>

#include "../a314device/a314.h"

struct MsgPort *mp;
ULONG socket;

struct A314_IORequest *cmsg;
struct A314_IORequest *rmsg;
struct A314_IORequest *wmsg;

UBYTE arbuf[256];
UBYTE awbuf[256];

BOOL pending_a314_read = FALSE;
BOOL pending_a314_write = FALSE;
BOOL pending_a314_reset = FALSE;

BOOL stream_closed = FALSE;

struct MsgPort *id_mp;
struct IOStdReq *id_req;
struct InputEvent generated_event;

void start_a314_cmd(struct A314_IORequest *msg, UWORD command, char *buffer, int length)
{
	msg->a314_Request.io_Command = command;
	msg->a314_Request.io_Error = 0;

	msg->a314_Socket = socket;
	msg->a314_Buffer = buffer;
	msg->a314_Length = length;

	SendIO((struct IORequest *)msg);
}

LONG a314_connect(char *name)
{
	socket = time(NULL);
	start_a314_cmd(cmsg, A314_CONNECT, name, strlen(name));
	return WaitIO((struct IORequest *)cmsg);
}

void start_a314_read()
{
	start_a314_cmd(rmsg, A314_READ, arbuf, 255);
	pending_a314_read = TRUE;
}

void start_a314_write(int length)
{
	start_a314_cmd(wmsg, A314_WRITE, awbuf, length);
	pending_a314_write = TRUE;
}

LONG sync_a314_write(int length)
{
	start_a314_write(length);
	pending_a314_write = FALSE;
	return WaitIO((struct IORequest *)wmsg);
}

void start_a314_reset()
{
	start_a314_cmd(cmsg, A314_RESET, NULL, 0);
	pending_a314_reset = TRUE;
}

LONG sync_a314_reset()
{
	start_a314_reset();
	pending_a314_reset = FALSE;
	return WaitIO((struct IORequest *)cmsg);
}

WORD last_b = 0;

void send_generated_mouse_event(WORD dx, WORD dy, WORD b)
{
	generated_event.ie_NextEvent = NULL;
	generated_event.ie_Class = IECLASS_RAWMOUSE;
	generated_event.ie_SubClass = 0;

	UWORD code = IECODE_NOBUTTON;
	if (!(last_b & 1) && (b & 1))
		code = IECODE_LBUTTON;
	else if ((last_b & 1) && !(b & 1))
		code = IECODE_UP_PREFIX | IECODE_LBUTTON;
	else if (!(last_b & 2) && (b & 2))
		code = IECODE_RBUTTON;
	else if ((last_b & 2) && !(b & 2))
		code = IECODE_UP_PREFIX | IECODE_RBUTTON;
	generated_event.ie_Code = code;

	// Should include keyboard qualifiers.
	// Where do we get them from?

	UWORD qual = IEQUALIFIER_RELATIVEMOUSE;
	if (b & 1)
		qual |= IEQUALIFIER_LEFTBUTTON;
	if (b & 2)
		qual |= IEQUALIFIER_RBUTTON;
	generated_event.ie_Qualifier = qual;

	generated_event.ie_X = dx;
	generated_event.ie_Y = dy;

	id_req->io_Data = (void *)&generated_event;
	id_req->io_Command = IND_WRITEEVENT;
	id_req->io_Flags = 0;
	id_req->io_Length = sizeof(struct InputEvent);
	DoIO((struct IORequest *)id_req);
}

void handle_a314_read_completed()
{
	pending_a314_read = FALSE;

	if (stream_closed)
		return;

	int res = rmsg->a314_Request.io_Error;
	if (res == A314_READ_OK)
	{
		int length = rmsg->a314_Length;

		WORD dx = *(WORD *)&arbuf[0];
		WORD dy = *(WORD *)&arbuf[2];
		WORD b = *(BYTE *)&arbuf[4];

		send_generated_mouse_event(dx, dy, b);
		last_b = b;

		start_a314_read();
	}
	else if (res == A314_READ_EOS)
	{
		start_a314_reset();
		stream_closed = TRUE;
	}
	else if (res == A314_READ_RESET)
		stream_closed = TRUE;
}

int main()
{
	LONG old_priority = SetTaskPri(FindTask(NULL), 60);

	mp = CreatePort(NULL, 0);
	cmsg = (struct A314_IORequest *)CreateExtIO(mp, sizeof(struct A314_IORequest));

	if (OpenDevice(A314_NAME, 0, (struct IORequest *)cmsg, 0) != 0)
	{
		printf("Unable to open a314.device\n");
		goto fail_out1;
	}

	wmsg = (struct A314_IORequest *)CreateExtIO(mp, sizeof(struct A314_IORequest));
	rmsg = (struct A314_IORequest *)CreateExtIO(mp, sizeof(struct A314_IORequest));
	memcpy(wmsg, cmsg, sizeof(struct A314_IORequest));
	memcpy(rmsg, cmsg, sizeof(struct A314_IORequest));

	if (a314_connect("remote-mouse") != A314_CONNECT_OK)
	{
		printf("Unable to connect to remote-mouse service\n");
		goto fail_out2;
	}

	id_mp = CreatePort(NULL, 0);
	id_req = (struct IOStdReq *)CreateExtIO(id_mp, sizeof(struct IOStdReq));
	OpenDevice("input.device", 0, (struct IORequest *)id_req, 0);

	start_a314_read();

	ULONG portsig = 1 << mp->mp_SigBit;

	printf("Press ctrl-c to exit...\n");

	while (TRUE)
	{
		ULONG signal = Wait(portsig | SIGBREAKF_CTRL_C);

		if (signal & portsig)
		{
			struct Message *msg;
			while (msg = GetMsg(mp))
			{
				if (msg == (struct Message *)rmsg)
					handle_a314_read_completed();
				else if (msg == (struct Message *)wmsg)
					pending_a314_write = FALSE;
				else if (msg == (struct Message *)cmsg)
					pending_a314_reset = FALSE;
			}
		}

		if (signal & SIGBREAKF_CTRL_C)
		{
			start_a314_reset();
			stream_closed = TRUE;
		}

		if (stream_closed && !pending_a314_read && !pending_a314_write && !pending_a314_reset)
			break;
	}

	CloseDevice((struct IORequest *)id_req);
	DeleteExtIO((struct IORequest *)id_req);
	DeletePort(id_mp);
fail_out2:
	CloseDevice((struct IORequest *)cmsg);
	DeleteExtIO((struct IORequest *)rmsg);
	DeleteExtIO((struct IORequest *)wmsg);
fail_out1:
	DeleteExtIO((struct IORequest *)cmsg);
	DeletePort(mp);
	SetTaskPri(FindTask(NULL), old_priority);
	return 0;
}


text: vc remote-mouse.c -lamiga -o RemoteMouse


text:
#!/usr/bin/python3
# -*- coding: utf-8 -*-

# Copyright (c) 2020 Niklas Ekström

import logging
import os
import select
import socket
import struct
import sys
import time

logging.basicConfig(format = '%(levelname)s, %(asctime)s, %(name)s, line %(lineno)d: %(message)s')
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

MSG_REGISTER_REQ        = 1
MSG_REGISTER_RES        = 2
MSG_DEREGISTER_REQ      = 3
MSG_DEREGISTER_RES      = 4
MSG_READ_MEM_REQ        = 5
MSG_READ_MEM_RES        = 6
MSG_WRITE_MEM_REQ       = 7
MSG_WRITE_MEM_RES       = 8
MSG_CONNECT             = 9
MSG_CONNECT_RESPONSE    = 10
MSG_DATA                = 11
MSG_EOS                 = 12
MSG_RESET               = 13

def wait_for_msg():
    header = b''
    while len(header) < 9:
        data = drv.recv(9 - len(header))
        if not data:
            logger.error('Connection to a314d was closed, terminating.')
            exit(-1)
        header += data
    (plen, stream_id, ptype) = struct.unpack('=IIB', header)
    payload = b''
    while len(payload) < plen:
        data = drv.recv(plen - len(payload))
        if not data:
            logger.error('Connection to a314d was closed, terminating.')
            exit(-1)
        payload += data
    return (stream_id, ptype, payload)

def send_register_req(name):
    m = struct.pack('=IIB', len(name), 0, MSG_REGISTER_REQ) + name
    drv.sendall(m)

def send_read_mem_req(address, length):
    m = struct.pack('=IIBII', 8, 0, MSG_READ_MEM_REQ, address, length)
    drv.sendall(m)

def read_mem(address, length):
    send_read_mem_req(address, length)
    stream_id, ptype, payload = wait_for_msg()
    if ptype != MSG_READ_MEM_RES:
        logger.error('Expected MSG_READ_MEM_RES but got %s. Shutting down.', ptype)
        exit(-1)
    return payload

def send_write_mem_req(address, data):
    m = struct.pack('=IIBI', 4 + len(data), 0, MSG_WRITE_MEM_REQ, address) + data
    drv.sendall(m)

def write_mem(address, data):
    send_write_mem_req(address, data)
    stream_id, ptype, payload = wait_for_msg()
    if ptype != MSG_WRITE_MEM_RES:
        logger.error('Expected MSG_WRITE_MEM_RES but got %s. Shutting down.', ptype)
        exit(-1)

def send_connect_response(stream_id, result):
    m = struct.pack('=IIBB', 1, stream_id, MSG_CONNECT_RESPONSE, result)
    drv.sendall(m)

def send_data(stream_id, data):
    m = struct.pack('=IIB', len(data), stream_id, MSG_DATA) + data
    drv.sendall(m)

def send_eos(stream_id):
    m = struct.pack('=IIB', 0, stream_id, MSG_EOS)
    drv.sendall(m)

def send_reset(stream_id):
    m = struct.pack('=IIB', 0, stream_id, MSG_RESET)
    drv.sendall(m)

current_stream_id = None
done = False
rbuf = b''
DEV_NAME = '/dev/input/mice'

last_buttons = 0
next_send = time.time()
buffered_movement = None
MIN_SEND_INTERVAL = 0.02 # 20 ms.

def process_drv_msg(stream_id, ptype, payload):
    global current_stream_id, buffered_movement

    if ptype == MSG_CONNECT:
        if payload == b'remote-mouse' and current_stream_id is None:
            logger.info('Amiga connected')
            current_stream_id = stream_id
            send_connect_response(stream_id, 0)
        else:
            send_connect_response(stream_id, 3)
    elif current_stream_id == stream_id:
        if ptype == MSG_DATA:
            pass
        elif ptype == MSG_EOS:
            pass
        elif ptype == MSG_RESET:
            current_stream_id = None
            buffered_movement = None
            logger.info('Amiga disconnected')

try:
    idx = sys.argv.index('-ondemand')
except ValueError:
    idx = -1

if idx != -1:
    fd = int(sys.argv[idx + 1])
    drv = socket.socket(fileno=fd)
else:
    drv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    drv.connect(('localhost', 7110))
    drv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)

    send_register_req(b'remote-mouse')
    _, _, payload = wait_for_msg()
    if payload[0] != 1:
        logger.error('Unable to register remote-mouse with driver, shutting down')
        drv.close()
        done = True

if not done:
    try:
        dev_fd = os.open(DEV_NAME, os.O_RDONLY | os.O_NONBLOCK)
    except:
        logger.error('Unable to open mouse device at ' + DEV_NAME)
        done = True

if not done:
    logger.info('remote-mouse service is running')

while not done:
    sel_fds = [drv, dev_fd]

    if idx == -1:
        sel_fds.append(sys.stdin)

    timeout = 5.0

    if buffered_movement:
        now = time.time()
        if next_send <= now:
            send_data(current_stream_id, struct.pack('>hhb', *buffered_movement, last_buttons))
            buffered_movement = None
            next_send = now + MIN_SEND_INTERVAL
        else:
            timeout = next_send - now

    rfd, wfd, xfd = select.select(sel_fds, [], [], timeout)

    for fd in rfd:
        if fd == sys.stdin:
            line = sys.stdin.readline()
            if not line or line.startswith('quit'):
                if current_stream_id is not None:
                    send_reset(current_stream_id)
                drv.close()
                done = True
        elif fd == drv:
            buf = drv.recv(1024)
            if not buf:
                if current_stream_id is not None:
                    send_reset(current_stream_id)
                drv.close()
                done = True
            else:
                rbuf += buf
                while True:
                    if len(rbuf) < 9:
                        break

                    (plen, stream_id, ptype) = struct.unpack('=IIB', rbuf[:9])
                    if len(rbuf) < 9 + plen:
                        break

                    rbuf = rbuf[9:]
                    payload = rbuf[:plen]
                    rbuf = rbuf[plen:]

                    process_drv_msg(stream_id, ptype, payload)
        elif fd == dev_fd:
            data = os.read(dev_fd, 32)

            if len(data) == 0:
                os.close(dev_fd)
                if current_stream_id is not None:
                    send_reset(current_stream_id)
                drv.close()
                done = True
            elif len(data) == 3:
                flags, dx, dy = data
                buttons = flags & 3

                if current_stream_id is not None:
                    if flags & 0x10:
                        dx = dx - 256
                    if flags & 0x20:
                        dy = dy - 256
                    dy = -dy

                    if buffered_movement is None:
                        buffered_movement = (dx, dy)
                    else:
                        bdx, bdy = buffered_movement
                        buffered_movement = (bdx + dx, bdy + dy)

                    now = time.time()

                    if buttons != last_buttons or next_send <= now:
                        send_data(current_stream_id, struct.pack('>hhb', *buffered_movement, buttons))
                        buffered_movement = None
                        next_send = now + MIN_SEND_INTERVAL

                last_buttons = buttons


text:
/*
 * Copyright (c) 2021 Niklas Ekström
 */

#include <exec/types.h>
#include <exec/execbase.h>
#include <exec/devices.h>
#include <exec/errors.h>
#include <exec/ports.h>
#include <devices/timer.h>
#include <devices/trackdisk.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <libraries/filehandler.h>
#include <libraries/expansion.h>
#include <libraries/expansionbase.h>

#include <proto/alib.h>
#include <proto/exec.h>
#include <proto/expansion.h>

#include <string.h>
#include <stdarg.h>
#include <stdint.h>

#include "../a314device/a314.h"
#include "../a314device/proto_a314.h"

#include "debug.h"

// VBCC unnecessairly warns when a loop has a single iteration.
#pragma dontwarn 208

#define SysBase (*(struct ExecBase **)4)
#define A314Base (dev->a314_base)

#ifndef TD_GETGEOMETRY
// Needed to compile with AmigaOS 1.3 headers.
#define TD_GETGEOMETRY    (CMD_NONSTD+13)

#define DG_DIRECT_ACCESS    0
#define DGF_REMOVABLE       1

struct DriveGeometry
{
    ULONG    dg_SectorSize;
    ULONG    dg_TotalSectors;
    ULONG    dg_Cylinders;
    ULONG    dg_CylSectors;
    ULONG    dg_Heads;
    ULONG    dg_TrackSectors;
    ULONG    dg_BufMemType;
    UBYTE    dg_DeviceType;
    UBYTE    dg_Flags;
    UWORD    dg_Reserved;
};
#endif

// Defines.

#define DEVICE_NAME             "a314disk.device"
#define SERVICE_NAME            "disk"

#define TASK_PRIORITY           10
#define TASK_STACK_SIZE         2048

#define UNIT_COUNT              4
#define AUTOBOOT_UNIT_COUNT     1

#define SIGB_A314               30
#define SIGB_OP_REQ             29

#define SIGF_A314               (1UL << SIGB_A314)
#define SIGF_OP_REQ             (1UL << SIGB_OP_REQ)

#define BOOT_PRIORITY           -5

// TRACK_SIZE = 5632
#define TRACK_SIZE              (NUMSECS * TD_SECTOR)

#define MSG_READ_TRACK_REQ      1
#define MSG_WRITE_TRACK_REQ     2
#define MSG_READ_TRACK_RES      3
#define MSG_WRITE_TRACK_RES     4
#define MSG_INSERT_NOTIFY       5
#define MSG_EJECT_NOTIFY        6
#define MSG_SET_GEOMETRY        7

#define OP_RES_OK               0
#define OP_RES_NOT_PRESENT      1
#define OP_RES_WRITE_PROTECTED  2

// Structs.

struct DriveState
{
    struct IOStdReq *change_int;
    struct Interrupt *remove_int;
    ULONG *env_vec;
    volatile ULONG change_num;
    volatile ULONG opencnt;
    volatile BOOL present;
    volatile BOOL write_protected;
    UWORD unit;
    UWORD heads;
    UWORD sectors_per_track;
    ULONG cylinders;
};

#pragma pack(push, 1)
struct RequestMsg
{
    UBYTE kind;
    UBYTE unit;
    UWORD length;
    ULONG offset;
    ULONG address;
};
#pragma pack(pop)

#pragma pack(push, 1)
struct ResponseMsg
{
    UBYTE kind;
    UBYTE unit;
    union
    {
        UBYTE error; // READ_/WRITE_TRACK_RES
        UBYTE writable; // INSERT_NOTIFY
        struct // SET_GEOMETRY
        {
            UBYTE heads;
            UBYTE sectors_per_track;
            ULONG cylinders;
        } geometry;
    } u;
};
#pragma pack(pop)

struct DiskDevice
{
    struct Library lib;

    BPTR saved_seg_list;

    struct MsgPort op_req_mp;
    struct MsgPort a314_mp;

    struct A314_IORequest read_ior;
    struct A314_IORequest write_ior;
    struct A314_IORequest reset_ior;

    struct Library *a314_base;

    ULONG track_buffer_address;

    struct DriveState drive_states[UNIT_COUNT];

    void *task_stack;
    struct Task task;

    struct List request_queue;
    volatile struct IOStdReq *pending_operation;

    LONG a314_socket;

    BOOL pending_connect;
    BOOL pending_read;
    BOOL pending_write;
    BOOL pending_reset;
    BOOL connection_resetted;

    struct RequestMsg req_msg;
    struct ResponseMsg res_msg;
};

// Constants.

const char device_name[] = DEVICE_NAME;
const char id_string[] = DEVICE_NAME " 1.0 (29 April 2021)";

static const char service_name[] = SERVICE_NAME;
static const char a314_device_name[] = A314_NAME;

// Procedures.

void NewList(struct List *l)
{
    l->lh_Head = (struct Node *)&(l->lh_Tail);
    l->lh_Tail = NULL;
    l->lh_TailPred = (struct Node *)&(l->lh_Head);
}

static void send_a314_cmd(struct DiskDevice *dev, struct A314_IORequest *ior, UWORD cmd, char *buffer, int length)
{
    ior->a314_Request.io_Command = cmd;
    ior->a314_Request.io_Error = 0;
    ior->a314_Socket = dev->a314_socket;
    ior->a314_Buffer = buffer;
    ior->a314_Length = length;
    SendIO((struct IORequest *)ior);
}

static void send_request_if_possible(struct DiskDevice *dev)
{
    if (dev->connection_resetted || dev->pending_operation)
        return;

    struct IOStdReq *ior = NULL;

    while (TRUE)
    {
        ior = (struct IOStdReq *)RemHead(&dev->request_queue);
        if (!ior)
            return;

        struct DriveState *ds = (struct DriveState *)ior->io_Unit;
        if (ds->present)
        {
            dev->req_msg.unit = ds->unit;
            break;
        }
        else
        {
            ior->io_Error = TDERR_DiskChanged;
            ReplyMsg(&ior->io_Message);
        }
    }

    dev->pending_operation = ior;

    // TODO: Should handle that a read/write request can be larger
    // than the track buffer, by splitting up transfer.

    ULONG buf_address = TranslateAddressA314(ior->io_Data);

    switch (ior->io_Command)
    {
    case CMD_READ:
        dev->req_msg.kind = MSG_READ_TRACK_REQ;
        break;

    case TD_FORMAT:
    case CMD_WRITE:
        if (buf_address == INVALID_A314_ADDRESS)
            WriteMemA314(dev->track_buffer_address, ior->io_Data, ior->io_Length);
        dev->req_msg.kind = MSG_WRITE_TRACK_REQ;
        break;
    }

    if (buf_address == INVALID_A314_ADDRESS)
        buf_address = dev->track_buffer_address;

    dev->req_msg.length = ior->io_Length;
    dev->req_msg.offset = ior->io_Offset;
    dev->req_msg.address = buf_address;

    send_a314_cmd(dev, &dev->write_ior, A314_WRITE, (char *)&dev->req_msg, sizeof(struct RequestMsg));
    dev->pending_write = TRUE;
}

static void handle_eject_msg(struct DriveState *ds)
{
    ds->change_num++;
    ds->present = FALSE;

    struct Interrupt *remove_int = ds->remove_int;
    if (remove_int)
        Cause(remove_int);

    struct IOStdReq *change_int = ds->change_int;
    if (change_int)
        Cause((struct Interrupt *)change_int->io_Data);
}

static void handle_insert_msg(struct DriveState *ds, struct ResponseMsg *msg)
{
    ds->write_protected = msg->u.writable == 0;
    ds->change_num++;
    ds->present = TRUE;

    struct Interrupt *remove_int = ds->remove_int;
    if (remove_int)
        Cause(remove_int);

    struct IOStdReq *change_int = ds->change_int;
    if (change_int)
        Cause((struct Interrupt *)change_int->io_Data);
}

static void handle_set_geometry_msg(struct DriveState *ds, struct ResponseMsg *msg)
{
    ds->heads = msg->u.geometry.heads;
    ds->sectors_per_track = msg->u.geometry.sectors_per_track;
    ds->cylinders = msg->u.geometry.cylinders;

    ULONG *ev = ds->env_vec;
    if (ev)
    {
        ev[DE_NUMHEADS] = msg->u.geometry.heads;
        ev[DE_BLKSPERTRACK] = msg->u.geometry.sectors_per_track;
        ev[DE_UPPERCYL] = msg->u.geometry.cylinders - 1;
    }
}

static void handle_response_msg(struct DiskDevice *dev)
{
    struct DriveState *ds = &dev->drive_states[dev->res_msg.unit];

    switch (dev->res_msg.kind)
    {
    case MSG_READ_TRACK_RES:
    case MSG_WRITE_TRACK_RES:
        switch (dev->res_msg.u.error)
        {
        case OP_RES_OK:
            if (dev->res_msg.kind == MSG_READ_TRACK_RES &&
                    TranslateAddressA314(dev->pending_operation->io_Data) == INVALID_A314_ADDRESS)
                ReadMemA314(dev->pending_operation->io_Data, dev->track_buffer_address, dev->pending_operation->io_Length);
            dev->pending_operation->io_Actual = dev->pending_operation->io_Length;
            break;

        case OP_RES_NOT_PRESENT:
            dev->pending_operation->io_Error = TDERR_DiskChanged;
            break;

        case OP_RES_WRITE_PROTECTED:
            dev->pending_operation->io_Error = TDERR_WriteProt;
            break;
        }

        ReplyMsg(&dev->pending_operation->io_Message);
        dev->pending_operation = NULL;
        break;

    case MSG_EJECT_NOTIFY:
        handle_eject_msg(ds);
        break;

    case MSG_INSERT_NOTIFY:
        handle_insert_msg(ds, &dev->res_msg);
        break;

    case MSG_SET_GEOMETRY:
        handle_set_geometry_msg(ds, &dev->res_msg);
        break;
    }
}

static void handle_a314_reply(struct DiskDevice *dev, struct A314_IORequest *ior)
{
    switch (ior->a314_Request.io_Command)
    {
    case A314_CONNECT:
        dev->pending_connect = FALSE;
        if (ior->a314_Request.io_Error != A314_CONNECT_OK)
            dev->connection_resetted = TRUE;
        break;

    case A314_READ:
        dev->pending_read = FALSE;
        if (dev->read_ior.a314_Request.io_Error == A314_READ_OK)
            handle_response_msg(dev);
        else if (dev->read_ior.a314_Request.io_Error == A314_READ_RESET)
            dev->connection_resetted = TRUE;
        break;

    case A314_WRITE:
        dev->pending_write = FALSE;
        if (dev->write_ior.a314_Request.io_Error != A314_WRITE_OK)
            dev->connection_resetted = TRUE;
        break;

    case A314_RESET:
        dev->pending_reset = FALSE;
        break;
    }

    // TODO: If connection resets (Pi shuts down or crashes)
    // then should eject disks from all drives automatically,
    // and reply to any pending requests.

    if (!dev->connection_resetted && !dev->pending_connect && !dev->pending_read)
    {
        send_a314_cmd(dev, &dev->read_ior, A314_READ, (char *)&dev->res_msg, sizeof(struct ResponseMsg));
        dev->pending_read = TRUE;
    }
}

static void task_main()
{
    struct DiskDevice *dev = (struct DiskDevice *)FindTask(NULL)->tc_UserData;

    send_a314_cmd(dev, &dev->read_ior, A314_CONNECT, (char *)service_name, strlen(service_name));
    dev->pending_connect = TRUE;

    while (TRUE)
    {
        ULONG sigs = Wait(SIGF_A314 | SIGF_OP_REQ);

        if (sigs & SIGF_A314)
        {
            struct A314_IORequest *ior;
            while ((ior = (struct A314_IORequest *)GetMsg(&dev->a314_mp)))
                handle_a314_reply(dev, ior);
        }

        if (sigs & SIGF_OP_REQ)
        {
            struct IOStdReq *ior;
            while ((ior = (struct IOStdReq *)GetMsg(&dev->op_req_mp)))
                AddTail(&dev->request_queue, &ior->io_Message.mn_Node);
        }

        send_request_if_possible(dev);
    }
}

static void init_task(struct DiskDevice *dev)
{
    char *stack = dev->task_stack;
    struct Task *task = &dev->task;
    task->tc_Node.ln_Type = NT_TASK;
    task->tc_Node.ln_Pri = TASK_PRIORITY;
    task->tc_Node.ln_Name = (char *)device_name;
    task->tc_SPLower = (APTR)stack;
    task->tc_SPUpper = (APTR)(stack + TASK_STACK_SIZE);
    task->tc_SPReg = (APTR)(stack + TASK_STACK_SIZE);
    NewList(&task->tc_MemEntry);
    task->tc_UserData = (void *)dev;

    AddTask(task, (void *)task_main, 0);
}

static void init_message_port(struct MsgPort *mp, UBYTE sig_bit, struct Task *sig_task)
{
    mp->mp_Node.ln_Type = NT_MSGPORT;
    mp->mp_Flags = PA_SIGNAL;
    mp->mp_SigBit = sig_bit;
    mp->mp_SigTask = sig_task;
    NewList(&mp->mp_MsgList);
}

static void init_a314_ioreq(struct A314_IORequest *ior, struct MsgPort *mp)
{
    ior->a314_Request.io_Message.mn_ReplyPort = mp;
    ior->a314_Request.io_Message.mn_Length = sizeof(struct A314_IORequest);
    ior->a314_Request.io_Message.mn_Node.ln_Type = NT_REPLYMSG;
}

static void get_geometry(struct DriveState *ds, struct DriveGeometry *geom)
{
    ULONG numtracks = ds->cylinders * ds->heads;
    geom->dg_SectorSize = TD_SECTOR;
    geom->dg_TotalSectors = numtracks * ds->sectors_per_track;
    geom->dg_Cylinders = ds->cylinders;
    geom->dg_CylSectors = ds->sectors_per_track * ds->heads;
    geom->dg_Heads = ds->heads;
    geom->dg_TrackSectors = ds->sectors_per_track;
    geom->dg_BufMemType = 0;
    geom->dg_DeviceType = DG_DIRECT_ACCESS;
    geom->dg_Flags = DGF_REMOVABLE;
}

static void begin_io(__reg("a6") struct DiskDevice *dev, __reg("a1") struct IOStdReq *ior)
{
    if (!ior || !ior->io_Unit)
        return;

    struct DriveState *ds = (void *)ior->io_Unit;

    dbg("begin_io, unit=$b, command=$w, flags=$w", ds->unit, ior->io_Command, ior->io_Flags);

    ior->io_Error = 0;
    ior->io_Actual = 0;

    switch (ior->io_Command)
    {
    case CMD_RESET:
    case CMD_CLEAR:
    case CMD_UPDATE:
    case TD_MOTOR:
    case TD_SEEK:
        break;

    case TD_REMOVE:
        ds->remove_int = (struct Interrupt *)ior->io_Data;
        break;

    case TD_PROTSTATUS:
        ior->io_Actual = ds->write_protected;
        break;

    case TD_CHANGESTATE:
        ior->io_Actual = ds->present ? 0 : 1;
        break;

    case TD_CHANGENUM:
        ior->io_Actual = ds->change_num;
        break;

    case TD_GETDRIVETYPE:
        ior->io_Actual = DRIVE3_5;
        break;

    case TD_GETNUMTRACKS:
        ior->io_Actual = ds->cylinders * ds->heads;
        break;

    case TD_GETGEOMETRY:
        get_geometry(ds, (struct DriveGeometry *)ior->io_Data);
        break;

    case TD_ADDCHANGEINT:
        if (ds->change_int)
            ior->io_Error = IOERR_ABORTED;
        else
        {
            ds->change_int = ior;
            ior->io_Flags &= ~IOF_QUICK;
            ior = NULL;
        }
        break;

    case TD_REMCHANGEINT:
        if (ds->change_int == ior)
            ds->change_int = NULL;
        break;

    case CMD_READ:
        dbg("CMD_READ, unit=$b, offset=$l, length=$l, data=$l", ds->unit, ior->io_Offset, ior->io_Length, ior->io_Data);
        if (!ds->present)
            ior->io_Error = TDERR_DiskChanged;
        else
        {
            PutMsg(&dev->op_req_mp, &ior->io_Message);
            ior->io_Flags &= ~IOF_QUICK;
            ior = NULL;
        }
        break;

    case TD_FORMAT:
    case CMD_WRITE:
        dbg("CMD_WRITE, unit=$b, offset=$l, length=$l, data=$l", ds->unit, ior->io_Offset, ior->io_Length, ior->io_Data);
        if (!ds->present)
            ior->io_Error = TDERR_DiskChanged;
        else if (ds->write_protected)
            ior->io_Error = TDERR_WriteProt;
        else
        {
            PutMsg(&dev->op_req_mp, &ior->io_Message);
            ior->io_Flags &= ~IOF_QUICK;
            ior = NULL;
        }
        break;

    default:
        ior->io_Error = IOERR_NOCMD;
        break;
    }

    if (ior && !(ior->io_Flags & IOF_QUICK))
        ReplyMsg(&ior->io_Message);
}

static ULONG abort_io(__reg("a6") struct DiskDevice *dev, __reg("a1") struct IOStdReq *ior)
{
    return IOERR_NOCMD;
}

static ULONG get_socket_id()
{
    struct timerequest tr;
    memset(&tr, 0, sizeof(tr));
    tr.tr_node.io_Message.mn_Length = sizeof(tr);
    tr.tr_node.io_Message.mn_Node.ln_Type = NT_REPLYMSG;

    OpenDevice(TIMERNAME, UNIT_VBLANK, &tr.tr_node, 0);
    tr.tr_node.io_Command = TR_GETSYSTIME;
    DoIO(&tr.tr_node);
    CloseDevice(&tr.tr_node);

    return tr.tr_time.tv_secs * TICKS_PER_SECOND;
}

struct MountListEntry
{
    struct DeviceNode dev_node;
    struct FileSysStartupMsg fssm;
    ULONG env_vec[20];
    char drive_name[8];
    char device_name[20];
    struct BootNode boot_node;
};

static ULONG env_vec_template[20] = {
    19,             // DE_TABLESIZE
    128,            // DE_SIZEBLOCK
    0,              // DE_SECORG
    2,              // DE_NUMHEADS
    1,              // DE_SECSPERBLK
    11,             // DE_BLKSPERTRACK
    2,              // DE_RESERVEDBLKS
    0,              // DE_PREFAC
    0,              // DE_INTERLEAVE
    0,              // DE_LOWCYL
    79,             // DE_UPPERCYL
    2,              // DE_NUMBUFFERS
    0,              // DE_BUFMEMTYPE
    TRACK_SIZE,     // DE_MAXTRANSFER
    0x7fffffff,     // DE_MASK
    BOOT_PRIORITY,  // DE_BOOTPRI
    ID_DOS_DISK,    // DE_DOSTYPE
    0,              // DE_BAUD
    0,              // DE_CONTROL
    2               // DE_BOOTBLOCKS
};

static void add_boot_node(struct ExpansionBase *expansion_base, struct DriveState *ds)
{
    struct MountListEntry *mle = AllocMem(sizeof(struct MountListEntry), MEMF_PUBLIC | MEMF_CLEAR);

    //mle->dev_node.dn_Type = DLT_DEVICE;
    mle->dev_node.dn_StackSize = 600;
    mle->dev_node.dn_Priority = 10;
    mle->dev_node.dn_Startup = ((ULONG)(&mle->fssm)) >> 2;
    mle->dev_node.dn_Name = ((ULONG)(&mle->drive_name[0])) >> 2;

    mle->fssm.fssm_Unit = ds->unit;
    mle->fssm.fssm_Device = ((ULONG)(&mle->device_name[0])) >> 2;
    mle->fssm.fssm_Environ = ((ULONG)(&mle->env_vec[0])) >> 2;

    memcpy(mle->env_vec, env_vec_template, sizeof(env_vec_template));
    ds->env_vec = mle->env_vec;

    *(ULONG *)(&mle->drive_name[0]) = (3 << 24) | ('P' << 16) | ('D' << 8) | ('0' + ds->unit);

    mle->device_name[0] = sizeof(device_name);
    memcpy(&mle->device_name[1], device_name, sizeof(device_name));

    mle->boot_node.bn_Node.ln_Type = NT_BOOTNODE;
    mle->boot_node.bn_Node.ln_Pri = BOOT_PRIORITY;
    mle->boot_node.bn_DeviceNode = (APTR)&mle->dev_node;

    Forbid();
    Enqueue(&expansion_base->MountList, &mle->boot_node.bn_Node);
    Permit();
}

static struct Library *init_device(__reg("a6") struct ExecBase *sys_base, __reg("a0") BPTR seg_list, __reg("d0") struct DiskDevice *dev)
{
    dev->saved_seg_list = seg_list;

    dev->lib.lib_Node.ln_Type = NT_DEVICE;
    dev->lib.lib_Node.ln_Name = (char *)device_name;
    dev->lib.lib_Flags = LIBF_SUMUSED | LIBF_CHANGED;
    dev->lib.lib_Version = 1;
    dev->lib.lib_Revision = 0;
    dev->lib.lib_IdString = (APTR)id_string;

    init_message_port(&dev->a314_mp, SIGB_A314, &dev->task);
    init_message_port(&dev->op_req_mp, SIGB_OP_REQ, &dev->task);

    init_a314_ioreq(&dev->read_ior, &dev->a314_mp);

    if (OpenDevice((char *)a314_device_name, 0, &dev->read_ior.a314_Request, 0))
        goto fail1;

    dev->a314_base = &(dev->read_ior.a314_Request.io_Device->dd_Library);

    memcpy(&dev->write_ior, &dev->read_ior, sizeof(struct A314_IORequest));
    memcpy(&dev->reset_ior, &dev->read_ior, sizeof(struct A314_IORequest));

    dev->track_buffer_address = AllocMemA314(TRACK_SIZE);
    if (dev->track_buffer_address == INVALID_A314_ADDRESS)
        goto fail2;

    for (int i = 0; i < UNIT_COUNT; i++)
    {
        struct DriveState *ds = &dev->drive_states[i];
        ds->unit = i;
        ds->heads = 2; // Default to floppy disk geometry.
        ds->sectors_per_track = 11;
        ds->cylinders = 80;
    }

    NewList(&dev->request_queue);

    dev->a314_socket = get_socket_id();

    dev->task_stack = AllocMem(TASK_STACK_SIZE, MEMF_CLEAR);
    if (!dev->task_stack)
        goto fail3;

    if (!seg_list)
    {
        struct ExpansionBase *expansion_base = (struct ExpansionBase *)OpenLibrary(EXPANSIONNAME, 0);

        if (expansion_base)
        {
            for (int i = 0; i < AUTOBOOT_UNIT_COUNT; i++)
                add_boot_node(expansion_base, &dev->drive_states[i]);

            CloseLibrary(&expansion_base->LibNode);
        }
    }

    init_task(dev);

    dbg_init();
    dbg("Started");

    return &dev->lib;

fail3:
    FreeMemA314(dev->track_buffer_address, TRACK_SIZE);

fail2:
    CloseDevice(&dev->read_ior.a314_Request);

fail1:
    FreeMem((char *)dev - dev->lib.lib_NegSize, dev->lib.lib_NegSize + dev->lib.lib_PosSize);
    return NULL;
}

static BPTR expunge(__reg("a6") struct DiskDevice *dev)
{
    // Currently no support for unloading device driver.
    if (dev->lib.lib_OpenCnt != 0)
    {
        dev->lib.lib_Flags |= LIBF_DELEXP;
        return 0;
    }

    return 0;
}

static void open(__reg("a6") struct DiskDevice *dev, __reg("a1") struct IORequest *ior, __reg("d0") ULONG unitnum, __reg("d1") ULONG flags)
{
    dbg("open device, unit=$l, ior=$l, flags=$l", (ULONG)unitnum, (ULONG)ior, (ULONG)flags);

    ior->io_Error = IOERR_OPENFAIL;
    ior->io_Message.mn_Node.ln_Type = NT_REPLYMSG;

    if (unitnum >= UNIT_COUNT)
        return;

    struct DriveState *ds = &dev->drive_states[unitnum];
    ds->opencnt++;
    dev->lib.lib_OpenCnt++;

    ior->io_Unit = (void *)ds;
    ior->io_Error = 0;
}

static BPTR close(__reg("a6") struct DiskDevice *dev, __reg("a1") struct IORequest *ior)
{
    dbg("close device, ior=$l", ior);

    if (!ior || !ior->io_Unit)
        return 0;

    struct DriveState *ds = (void *)ior->io_Unit;
    ds->opencnt--;

    ior->io_Device = NULL;
    ior->io_Unit = NULL;

    dev->lib.lib_OpenCnt--;

    if (dev->lib.lib_OpenCnt == 0 && (dev->lib.lib_Flags & LIBF_DELEXP))
        return expunge(dev);

    return 0;
}

static ULONG device_vectors[] =
{
    (ULONG)open,
    (ULONG)close,
    (ULONG)expunge,
    0,
    (ULONG)begin_io,
    (ULONG)abort_io,
    -1,
};

ULONG auto_init_tables[] =
{
    sizeof(struct DiskDevice),
    (ULONG)device_vectors,
    0,
    (ULONG)init_device,
};


text: vc romtag.asm device.c debug.c -O3 -nostdlib -o a314disk.device


text:
#include <exec/types.h>
#include <libraries/dos.h>

#include <proto/exec.h>
#include <proto/dos.h>

#include "debug.h"

#if !DEBUG
void dbg_init()
{
}

void debug_process_run()
{
}

void dbg(const char* fmt, ...)
{
}
#else

#define LOG_SIZE 16

extern void debug_process_seglist();
static const char debug_process_name[] = "logger-process";
static struct Process *debug_process = NULL;

static char msg_bufs[LOG_SIZE][128];
static volatile msg_lengths[LOG_SIZE];
static volatile UWORD dbg_head = 0;
static volatile UWORD dbg_tail = 0;
static volatile UWORD dbg_reserved_tail = 0;
static volatile BOOL dropped_message = FALSE;

static const char print_dropped[] = "A message was dropped\n";

struct ExecBase *SysBase;
struct DosLibrary *DOSBase;

void dbg_init()
{
    SysBase = *(struct ExecBase **)4;
    DOSBase = (struct DosLibrary *)OpenLibrary(DOSNAME, 0);

    struct MsgPort *process_mp = CreateProc(
        (char *)debug_process_name,
        20,
        ((ULONG)debug_process_seglist) >> 2,
        2048);

    if (process_mp)
        debug_process = (struct Process *)((char *)process_mp - sizeof(struct Task));
}

void debug_process_run()
{
    BPTR dbg_con = Open("CON:200/0/440/256/a314disk", MODE_NEWFILE);

    while (TRUE)
    {
        Wait(SIGBREAKF_CTRL_D);

        while (dbg_head != dbg_tail)
        {
            Write(dbg_con, msg_bufs[dbg_head], msg_lengths[dbg_head]);
            msg_lengths[dbg_head] = 0;
            dbg_head = (dbg_head + 1) & (LOG_SIZE - 1);
        }

        if (dropped_message)
        {
            Write(dbg_con, (char *)print_dropped, sizeof(print_dropped) - 1);
            dropped_message = FALSE;
        }
    }
}

void dbg(const char* fmt, ...)
{
    Disable();
    int my_slot = dbg_reserved_tail;
    int next_slot = (my_slot + 1) & (LOG_SIZE - 1);

    if (next_slot == dbg_head)
    {
        dropped_message = TRUE;
        Enable();
        return;
    }

    dbg_reserved_tail = next_slot;
    Enable();

    char numbuf[16];

    const char *p = fmt;
    char *q = msg_bufs[my_slot];

    ULONG *args = (ULONG *)&fmt;
    args++;

    while (*p != 0)
    {
        char c = *p++;
        if (c == '$')
        {
            c = *p++;
            if (c == 'b')
            {
                ULONG x = *args++;
                *q++ = '$';
                for (int i = 0; i < 2; i++)
                {
                    int ni = (x >> ((1 - i) * 4)) & 0xf;
                    *q++ = (ni >= 10) ? ('a' + (ni - 10)) : ('0' + ni);
                }
            }
            else if (c == 'w')
            {
                ULONG x = *args++;
                *q++ = '$';
                for (int i = 0; i < 4; i++)
                {
                    int ni = (x >> ((3 - i) * 4)) & 0xf;
                    *q++ = (ni >= 10) ? ('a' + (ni - 10)) : ('0' + ni);
                }
            }
            else if (c == 'l')
            {
                ULONG x = *args++;
                *q++ = '$';
                for (int i = 0; i < 8; i++)
                {
                    int ni = (x >> ((7 - i) * 4)) & 0xf;
                    *q++ = (ni >= 10) ? ('a' + (ni - 10)) : ('0' + ni);
                }
            }
            else if (c == 'S')
            {
                unsigned char *s = (unsigned char *)*args++;
                int l = *s++;
                for (int i = 0; i < l; i++)
                    *q++ = *s++;
            }
            else if (c == 's')
            {
                unsigned char *s = (unsigned char *)*args++;
                while (*s)
                    *q++ = *s++;
            }
            else if (c == '$')
            {
                *q++ = '$';
            }
            else
            {
                *q++ = '$';
                *q++ = c;
            }
        }
        else
        {
            *q++ = c;
        }
    }
    *q++ = '\n';

    Disable();
    msg_lengths[my_slot] = q - msg_bufs[my_slot];

    while (dbg_tail != dbg_reserved_tail && msg_lengths[dbg_tail] != 0)
        dbg_tail = (dbg_tail + 1) & (LOG_SIZE - 1);
    Enable();

    if (debug_process)
        Signal(&debug_process->pr_Task, SIGBREAKF_CTRL_D);
}
#endif


text:
#define DEBUG 0

extern void dbg_init();
extern void dbg(const char* fmt, ...);


text:
#!/usr/bin/python3
# -*- coding: utf-8 -*-

# Copyright (c) 2021 Niklas Ekström

import logging
logging.basicConfig(format='%(levelname)s, %(asctime)s, %(name)s, line %(lineno)d: %(message)s', level=logging.INFO)

import io
import json
import logging
import select
import socket
import struct
from typing import Optional, Tuple, List, Dict

from a314d import A314d

logger = logging.getLogger(__name__)

SERVICE_NAME = 'disk'

READ_TRACK_REQ = 1
WRITE_TRACK_REQ = 2
READ_TRACK_RES = 3
WRITE_TRACK_RES = 4
INSERT_NOTIFY = 5
EJECT_NOTIFY = 6
SET_GEOMETRY = 7

OP_RES_OK = 0
OP_RES_NOT_PRESENT = 1
OP_RES_WRITE_PROTECTED = 2

NUM_DRIVES = 4

CONF_FILE = "/etc/opt/a314/disk.conf"

class Drive(object):
    def __init__(self, unit: int):
        self.unit = unit
        self.f: Optional[io.BufferedRandom] = None
        self.writable = False
        self.geometry: Optional[Tuple[int, int, int]] = None
        self.geometry_sent = False

class DiskService(object):
    def __init__(self):
        self.a314d = A314d(SERVICE_NAME)

        self.control_server_socket = socket.socket()
        self.control_server_socket.bind(('localhost', 23890))
        self.control_server_socket.listen(10)

        self.control_sockets: List[socket.socket] = []
        self.drives = [Drive(i) for i in range(NUM_DRIVES)]
        self.auto_insert: Dict[int, Tuple[str, bool]] = {}
        self.current_stream_id: Optional[int] = None
        self.rbuf = b''
        self.mem_read_queue = []
        self.mem_write_queue = []

    def load_config(self):
        with open(CONF_FILE, 'rt') as f:
            j = json.load(f)
            for e in j.get('auto-insert', []):
                unit: int = e['unit']
                filename: str = e['filename']
                rw: bool = e.get('rw', False)
                self.auto_insert[unit] = (filename, rw)

    def eject_adf(self, unit: int):
        drive = self.drives[unit]
        if not drive.f:
            return f"No disk in unit {unit} to eject"

        drive.f.close()
        drive.f = None

        if self.current_stream_id is not None:
            self.a314d.send_data(self.current_stream_id, struct.pack('>BB', EJECT_NOTIFY, unit))

        return f"Ejected disk from unit {unit}"

    def insert_adf(self, unit: int, filename: str, writable=False):
        self.eject_adf(unit)

        try:
            f = open(filename, 'r+b')
        except:
            return f"Failed to open disk file '{filename}'"

        f.seek(0, 2)
        size = f.tell()

        if size == 80*2*11*512:
            geometry = (2, 11, 80)
        elif size < 2**20:
            f.close()
            return "Failed: HDF must be at least 1 MB"
        elif (size & (2**14 - 1)) != 0:
            f.close()
            return "Failed: HDF size must be a multiple of 16 kB"
        else:
            geometry = (1, 32, size // (2**14))

        drive = self.drives[unit]

        if drive.geometry_sent and drive.geometry != geometry:
            f.close()
            return "Failed: A disk with a different geometry was previously inserted"

        drive.f = f
        drive.writable = writable
        drive.geometry = geometry

        if self.current_stream_id is not None:
            if not drive.geometry_sent:
                self.a314d.send_data(self.current_stream_id, struct.pack('>BBBBI', SET_GEOMETRY, unit, *geometry))
                drive.geometry_sent = True
            self.a314d.send_data(self.current_stream_id, struct.pack('>BBB', INSERT_NOTIFY, unit, 1 if writable else 0))

        return f"Inserted disk file '{filename}' in unit {unit}"

    boot_code = bytes([
        0x43, 0xFA, 0x00, 0x18, 0x4E, 0xAE, 0xFF, 0xA0,
        0x4A, 0x80, 0x67, 0x0A, 0x20, 0x40, 0x20, 0x68,
        0x00, 0x16, 0x70, 0x00, 0x4E, 0x75, 0x70, 0xFF,
        0x60, 0xFA, 0x64, 0x6F, 0x73, 0x2E, 0x6C, 0x69,
        0x62, 0x72, 0x61, 0x72, 0x79])

    boot_code_silent = bytes([
        0x43, 0xFA, 0x00, 0x3E, 0x70, 0x25, 0x4E, 0xAE,
        0xFD, 0xD8, 0x4A, 0x80, 0x67, 0x0C, 0x22, 0x40,
        0x08, 0xE9, 0x00, 0x06, 0x00, 0x22, 0x4E, 0xAE,
        0xFE, 0x62, 0x43, 0xFA, 0x00, 0x18, 0x4E, 0xAE,
        0xFF, 0xA0, 0x4A, 0x80, 0x67, 0x0A, 0x20, 0x40,
        0x20, 0x68, 0x00, 0x16, 0x70, 0x00, 0x4E, 0x75,
        0x70, 0xFF, 0x4E, 0x75, 0x64, 0x6F, 0x73, 0x2E,
        0x6C, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x00,
        0x65, 0x78, 0x70, 0x61, 0x6E, 0x73, 0x69, 0x6F,
        0x6E, 0x2E, 0x6C, 0x69, 0x62, 0x72, 0x61, 0x72,
        0x79])

    def fixup_boot_block(self, disk_data: bytes, drive: Drive):
        assert type(drive.f) is io.BufferedRandom
        assert type(drive.geometry) is tuple

        checksum = struct.unpack_from('>I', disk_data, 4)[0]

        if disk_data[:3] == b'DOS' and checksum == 0:
            size = len(disk_data)
            if size < 1024:
                drive.f.seek(0, 0)
                disk_data = drive.f.read(1024)

            dd = bytearray(disk_data)
            dd[12:12 + len(self.boot_code_silent)] = self.boot_code_silent

            geometry = drive.geometry
            sector_count = geometry[0] * geometry[1] * geometry[2]
            dd[8:12] = struct.pack('>I', sector_count // 2)

            arr = struct.unpack_from('>256I', dd)
            checksum = 0
            for x in arr:
                checksum += x
                if checksum >= 2**32:
                    checksum = checksum - 2**32 + 1
            checksum = checksum ^ (2**32-1)
            dd[4:8] = struct.pack('>I', checksum)

            disk_data = bytes(dd[:size])

        return disk_data

    def process_stream_data(self, stream_id: int, data: bytes):
        kind, unit, length, offset, address = struct.unpack('>BBHII', data)
        drive = self.drives[unit]
        if kind == READ_TRACK_REQ:
            if not drive.f:
                self.a314d.send_data(stream_id, struct.pack('>BBB', READ_TRACK_RES, unit, OP_RES_NOT_PRESENT))
                return

            drive.f.seek(offset, 0)
            disk_data = drive.f.read(length)
            if offset == 0:
                disk_data = self.fixup_boot_block(disk_data, drive)

            self.mem_write_queue.append((stream_id, unit))
            self.a314d.send_write_mem_req(address, disk_data)
        elif kind == WRITE_TRACK_REQ:
            if not drive.f:
                self.a314d.send_data(stream_id, struct.pack('>BBB', WRITE_TRACK_RES, unit, OP_RES_NOT_PRESENT))
                return
            if not drive.writable:
                self.a314d.send_data(stream_id, struct.pack('>BBB', WRITE_TRACK_RES, unit, OP_RES_WRITE_PROTECTED))
                return
            self.mem_read_queue.append((stream_id, unit, offset))
            self.a314d.send_read_mem_req(address, length)

    def process_write_mem_res(self):
        stream_id, unit = self.mem_write_queue.pop(0)
        if stream_id == self.current_stream_id:
            self.a314d.send_data(stream_id, struct.pack('>BBB', READ_TRACK_RES, unit, OP_RES_OK))

    def process_read_mem_res(self, disk_data: bytes):
        stream_id, unit, offset = self.mem_read_queue.pop(0)
        drive = self.drives[unit]
        if stream_id == self.current_stream_id:
            if not drive.f:
                self.a314d.send_data(stream_id, struct.pack('>BBB', WRITE_TRACK_RES, unit, OP_RES_NOT_PRESENT))
                return

            drive.f.seek(offset, 0)
            drive.f.write(disk_data)
            self.a314d.send_data(stream_id, struct.pack('>BBB', WRITE_TRACK_RES, unit, OP_RES_OK))

    def process_drv_msg(self, stream_id: int, ptype: int, payload: bytes):
        if ptype == self.a314d.MSG_CONNECT:
            if payload == SERVICE_NAME.encode() and self.current_stream_id is None:
                logger.info('Amiga connected to disk service')
                self.current_stream_id = stream_id
                self.a314d.send_connect_response(stream_id, 0)

                for unit, (filename, writable) in self.auto_insert.items():
                    self.insert_adf(unit, filename, writable)
            else:
                self.a314d.send_connect_response(stream_id, 3)
        elif ptype == self.a314d.MSG_READ_MEM_RES:
            self.process_read_mem_res(payload)
        elif ptype == self.a314d.MSG_WRITE_MEM_RES:
            self.process_write_mem_res()
        elif self.current_stream_id == stream_id:
            if ptype == self.a314d.MSG_DATA:
                self.process_stream_data(stream_id, payload)
            elif ptype == self.a314d.MSG_EOS:
                raise AssertionError('EOS is not supposed to be in use')
            elif ptype == self.a314d.MSG_RESET:
                self.current_stream_id = None
                for i in range(4):
                    self.eject_adf(i)
                    self.drives[i].geometry_sent = False
                logger.info('Amiga disconnected from disk service')

    def handle_a314d_readable(self):
        buf = self.a314d.drv.recv(8192)
        if not buf:
            logger.error('Connection to a314d was closed, shutting down')
            exit(-1)

        self.rbuf += buf
        while True:
            if len(self.rbuf) < 9:
                break

            (plen, stream_id, ptype) = struct.unpack('=IIB', self.rbuf[:9])
            if len(self.rbuf) < 9 + plen:
                break

            payload = self.rbuf[9:9+plen]
            self.rbuf = self.rbuf[9+plen:]

            self.process_drv_msg(stream_id, ptype, payload)

    def handle_control_socket_readable(self, s: socket.socket):
        buf = s.recv(512)
        if not buf:
            s.close()
            self.control_sockets.remove(s)
            return

        try:
            buf = buf.decode('utf-8')
            arr = buf.strip().split(' ')
            cmd = arr[0]
            if cmd == 'eject':
                unit = int(arr[1])
                res = self.eject_adf(unit)
            elif cmd == 'insert':
                unit = int(arr[1])
                rw = arr[2] == '-rw'
                idx = 3 if rw else 2
                fn = ' '.join(arr[idx:])
                res = self.insert_adf(unit, fn, rw)
            else:
                res = f"Unknown command '{cmd}'"
        except:
            res = "Exception raised when processing command"
        s.sendall((res + '\n').encode('utf-8'))
        s.shutdown(socket.SHUT_WR)
        s.close()
        self.control_sockets.remove(s)

    def run(self):
        self.load_config()

        logger.info('Disk service is running')

        while True:
            try:
                rl = [self.a314d, self.control_server_socket] + self.control_sockets
                rl, _, _ = select.select(rl, [], [], 10.0)
            except KeyboardInterrupt:
                break

            for s in rl:
                if s == self.a314d:
                    self.handle_a314d_readable()
                elif s == self.control_server_socket:
                    cs, _ = self.control_server_socket.accept()
                    self.control_sockets.append(cs)
                else:
                    self.handle_control_socket_readable(s)

        for s in self.control_sockets:
            s.close()
        self.control_server_socket.close()
        self.a314d.close()

        logger.info('Disk service stopped')

if __name__ == '__main__':
    service = DiskService()
    service.run()


text:
    XDEF    _debug_process_seglist
    XREF    _debug_process_run

RTC_MATCHWORD:  equ    $4afc
RTF_AUTOINIT:   equ    (1<<7)
RTF_COLDSTART:  equ    (1<<0)
NT_DEVICE:      equ    3
VERSION:        equ    1
PRIORITY:       equ    -5

        section    code,code

        moveq    #-1,d0
        rts

romtag:
        dc.w    RTC_MATCHWORD
        dc.l    romtag
        dc.l    endcode
        dc.b    RTF_AUTOINIT | RTF_COLDSTART
        dc.b    VERSION
        dc.b    NT_DEVICE
        dc.b    PRIORITY
        dc.l    _device_name
        dc.l    _id_string
        dc.l    _auto_init_tables
endcode:

        cnop    0,4

        dc.l    16
_debug_process_seglist:
        dc.l    0
        jmp     _debug_process_run


text:
PD0:	Device = a314disk.device
	Unit   = 0
	Flags  = 1
	Surfaces  = 2
	BlocksPerTrack = 11
	Reserved = 2
	Interleave = 0
	LowCyl = 0
	HighCyl = 79
	Buffers = 2
	BufMemType = 0
	MaxTransfer = 5632
#

PD1:	Device = a314disk.device
	Unit   = 1
	Flags  = 1
	Surfaces  = 2
	BlocksPerTrack = 11
	Reserved = 2
	Interleave = 0
	LowCyl = 0
	HighCyl = 79
	Buffers = 2
	BufMemType = 0
	MaxTransfer = 5632
#

PD2:	Device = a314disk.device
	Unit   = 2
	Flags  = 1
	Surfaces  = 2
	BlocksPerTrack = 11
	Reserved = 2
	Interleave = 0
	LowCyl = 0
	HighCyl = 79
	Buffers = 2
	BufMemType = 0
	MaxTransfer = 5632
#

PD3:	Device = a314disk.device
	Unit   = 3
	Flags  = 1
	Surfaces  = 2
	BlocksPerTrack = 11
	Reserved = 2
	Interleave = 0
	LowCyl = 0
	HighCyl = 79
	Buffers = 2
	BufMemType = 0
	MaxTransfer = 5632
#


text:
#include <exec/types.h>
#include <exec/memory.h>
#include <exec/execbase.h>
#include <exec/lists.h>
#include <stdio.h>
#include <string.h>

#include <proto/exec.h>

void print_agnus_id()
{
	UWORD vposr = *(UWORD *)0xdff004;
	UWORD agnus = (vposr & 0x7f00) >> 8;
	printf("Agnus id: %02x\n", agnus);
}

void print_memory_regions()
{
	Forbid();

	struct List *memlist = &(SysBase->MemList);
	struct Node *node;

	for (node = memlist->lh_Head; node->ln_Succ != NULL; node = node->ln_Succ)
	{
		struct MemHeader *mh = (struct MemHeader *)node;
		printf("Memory region:\n");
		printf("  Attributes: %8x\n", mh->mh_Attributes);
		printf("  Lower:      %8lx\n", (ULONG)mh->mh_Lower);
		printf("  Upper:      %8lx\n", (ULONG)mh->mh_Upper);
	}

	Permit();
}

void dump_mem(ULONG address, int len)
{
	printf("Dumping %d bytes starting at address %08lx:\n", len, address);
	UBYTE *p = (UBYTE *)address;
	int col = 0;
	while (len)
	{
		if (col == 0)
			printf("  ");
		printf("%02x ", *p++);
		col++;
		if (col == 16)
		{
			printf("\n");
			col = 0;
		}
		len--;
	}
}

#define CLOCK_PORT_ADDRESS	0xdc0000

void write_cp_nibble(int index, UBYTE value)
{
	volatile UBYTE *p = (UBYTE *)CLOCK_PORT_ADDRESS;
	p += 4 * index + 3;
	*p = value & 0xf;
}

UBYTE read_cp_nibble(int index)
{
	volatile UBYTE *p = (UBYTE *)CLOCK_PORT_ADDRESS;
	p += 4 * index + 3;
	return *p & 0xf;
}

void print_usage()
{
	printf( "Usage: diag command [args]\n" \
			" Commands:\n" \
			"   a -- print agnus id\n" \
			"   m -- print memory regions\n" \
			"   d <addr> <len> -- dump <len> bytes starting at address <addr>\n" \
			"   w <reg> <val> -- write <val> to cmem register <reg>\n" \
			"   r <reg> [<num>] -- read and print <num> cmem regs. starting at <reg>\n");
}

int main(int argc, char **argv)
{
	printf("Diagnostics for A314-td\n");
	if (argc == 1)
	{
		print_usage();
		return 0;
	}

	if (strcmp(argv[1], "a") == 0)
	{
		print_agnus_id();
	}
	else if (strcmp(argv[1], "m") == 0)
	{
		print_memory_regions();
	}
	else if (strcmp(argv[1], "d") == 0)
	{
		if (argc < 4)
		{
			print_usage();
			return 0;
		}

		ULONG address;
		ULONG length;
		sscanf(argv[2], "%lx", &address);
		sscanf(argv[3], "%lu", &length);
		dump_mem(address, length);
	}
	else if (strcmp(argv[1], "w") == 0)
	{
		if (argc < 4)
		{
			print_usage();
			return 0;
		}

		LONG reg;
		LONG val;
		sscanf(argv[2], "%ld", &reg);
		sscanf(argv[3], "%ld", &val);

		if (reg < 0 || reg > 15)
		{
			printf("reg must be in range 0..15\n");
			return 0;
		}
		if (val < 0 || val > 15)
		{
			printf("val must be in range 0..15\n");
			return 0;
		}

		Disable();
		UBYTE prev_regd = read_cp_nibble(13);
		write_cp_nibble(13, prev_regd | 8);
		write_cp_nibble(reg, val);
		write_cp_nibble(13, prev_regd);
		Enable();

		printf("Wrote value %lx to cmem regiser %ld\n", val, reg);
	}
	else if (strcmp(argv[1], "r") == 0)
	{
		if (argc < 3)
		{
			print_usage();
			return 0;
		}

		LONG reg;
		LONG num = 1;
		sscanf(argv[2], "%ld", &reg);
		if (argc >= 4)
			sscanf(argv[3], "%ld", &num);

		if (reg < 0 || reg > 15)
		{
			printf("reg must be in range 0..15\n");
			return 0;
		}
		if (num < 1 || num > 16)
		{
			printf("num must be in range 1..16\n");
			return 0;
		}

		printf("Dumping %ld cmem registers starting at register %ld:\n", num, reg);

		Disable();
		UBYTE prev_regd = read_cp_nibble(13);
		write_cp_nibble(13, prev_regd | 8);

		char buf[64];
		char *p = buf;

		while (num)
		{
			UBYTE val = read_cp_nibble(reg);
			if (val < 10)
				*p++ = '0' + val;
			else
				*p++ = 'a' + val - 10;
			*p++ = ' ';
			reg++;
			num--;
		}

		*p++ = 0;

		write_cp_nibble(13, prev_regd);
		Enable();

		printf("  %s\n", buf);
	}
	else
		print_usage();

	return 0;
}


text:
import spidev

spi = spidev.SpiDev()
spi.open(0, 0)

spi.max_speed_hz = 70*1000*1000
spi.mode = 0
spi.cshigh = True

READ_SRAM_CMD = 0
WRITE_SRAM_CMD = 1
READ_CMEM_CMD = 2
WRITE_CMEM_CMD = 3

def read_protocol_version():
    return spi.xfer([255, 0])[1]

proto_ver = read_protocol_version()
if proto_ver not in [0, 1]:
    raise RuntimeError("Unknown protocol version: {}".format(proto_ver))

print("Uses protocol version: {}".format(proto_ver))

def read_sram(addr, n):
    if proto_ver == 0:
        return spi.xfer([(READ_SRAM_CMD << 4) | (addr >> 16) & 0xf, (addr >> 8) & 0xff, addr & 0xff, 0] + [0] * n)[4:]
    elif proto_ver == 1:
        return spi.xfer([(READ_SRAM_CMD << 5) | (addr >> 16) & 0x1f, (addr >> 8) & 0xff, addr & 0xff, 0] + [0] * n)[4:]

def write_sram(addr, data):
    if proto_ver == 0:
        spi.xfer([(WRITE_SRAM_CMD << 4) | ((addr >> 16) & 0xf), (addr >> 8) & 0xff, addr & 0xff] + data)
    elif proto_ver == 1:
        spi.xfer([(WRITE_SRAM_CMD << 5) | ((addr >> 16) & 0x1f), (addr >> 8) & 0xff, addr & 0xff] + data)

def g(n):
    w = 0
    for _ in range(n):
        yield (w >> 8)
        yield (w & 0xff)
        w += 1
        if w == 65536:
            w = 0

data = list(g(65536))
data = data * 4

for i in range(32):
    write_sram(i*2048, data[i*2048:(i+1)*2048])

print(read_sram(0, 2048))


text:
/*
 * Copyright (c) 2018 Niklas Ekström
 */

#include <exec/types.h>
#include <exec/ports.h>
#include <exec/nodes.h>
#include <exec/libraries.h>

#include <devices/timer.h>

#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <libraries/filehandler.h>

#include <proto/exec.h>
#include <proto/dos.h>

#include <string.h>
#include <stdarg.h>

#include "../a314device/a314.h"
#include "../a314device/proto_a314.h"

#include "messages.h"

#define ID_314_DISK (('3' << 24) | ('1' << 16) | ('4' << 8))

#define REQ_RES_BUF_SIZE 256
#define BUFFER_SIZE 4096

// Not defined if using NDK13
#ifndef ACTION_IS_FILESYSTEM
#define ACTION_IS_FILESYSTEM 1027
#endif

#ifndef ACTION_EXAMINE_FH
#define ACTION_EXAMINE_FH 1034
#endif

#ifndef ACTION_SAME_LOCK
#define ACTION_SAME_LOCK 40
#endif

// Grab a reserved action type which seems to be unused
#define ACTION_UNSUPPORTED 65535

struct ExecBase *SysBase;
struct DosLibrary *DOSBase;
struct MsgPort *mp;

struct DeviceList *my_volume;
char default_volume_name[] = "\006PiDisk";

char device_name[32]; // "\004PI0:"

struct MsgPort *timer_mp;
struct timerequest *tr;

struct MsgPort *a314_mp;
struct A314_IORequest *a314_ior;

struct Library *A314Base;

long socket;

char *request_buffer = NULL;
BOOL request_buffer_in_a314_memory;

// These are allocated in A314 memory.
ULONG request_buffer_address;
ULONG data_buffer_address;

void MyNewList(struct List *l)
{
    l->lh_Head = (struct Node *)&(l->lh_Tail);
    l->lh_Tail = NULL;
    l->lh_TailPred = (struct Node *)&(l->lh_Head);
}

struct MsgPort *MyCreatePort(char *name, long pri)
{
	int sigbit = AllocSignal(-1);
	if (sigbit == -1)
		return NULL;

	struct MsgPort *port = (struct MsgPort *)AllocMem(sizeof(struct MsgPort), MEMF_PUBLIC | MEMF_CLEAR);
	if (!port)
	{
		FreeSignal(sigbit);
		return NULL;
	}

	port->mp_Node.ln_Name = name;
	port->mp_Node.ln_Pri = pri;
	port->mp_Node.ln_Type = NT_MSGPORT;
	port->mp_Flags = PA_SIGNAL;
	port->mp_SigBit = sigbit;
	port->mp_SigTask = FindTask(NULL);

	if (name)
		AddPort(port);
	else
		MyNewList(&(port->mp_MsgList));

	return port;
}

struct IORequest *MyCreateExtIO(struct MsgPort *ioReplyPort, ULONG size)
{
	if (!ioReplyPort)
		return NULL;

	struct IORequest *ioReq = (struct IORequest *)AllocMem(size, MEMF_PUBLIC | MEMF_CLEAR);
	if (!ioReq)
		return NULL;

	ioReq->io_Message.mn_Node.ln_Type = NT_MESSAGE;
	ioReq->io_Message.mn_Length = size;
	ioReq->io_Message.mn_ReplyPort = ioReplyPort;

	return ioReq;
}

#define DEBUG 0

#if !DEBUG
void dbg_init()
{
}

void dbg(const char* fmt, ...)
{
}
#else
struct FileHandle *dbg_log;
struct MsgPort *dbg_replyport;
struct StandardPacket *dbg_sp;
char dbg_buf[256];

void dbg_init()
{
	dbg_log = (struct FileHandle *)BADDR(Open("CON:200/0/440/256/a314fs", MODE_NEWFILE));
	dbg_replyport = MyCreatePort(NULL, 0);
	dbg_sp = (struct StandardPacket *)AllocMem(sizeof(struct StandardPacket), MEMF_PUBLIC | MEMF_CLEAR);
}

void dbg_out(int l)
{
	dbg_sp->sp_Msg.mn_Node.ln_Name = (char *)&(dbg_sp->sp_Pkt);
	dbg_sp->sp_Pkt.dp_Link = &(dbg_sp->sp_Msg);
	dbg_sp->sp_Pkt.dp_Port = dbg_replyport;
	dbg_sp->sp_Pkt.dp_Type = ACTION_WRITE;
	dbg_sp->sp_Pkt.dp_Arg1 = (long)dbg_log->fh_Arg1;
	dbg_sp->sp_Pkt.dp_Arg2 = (long)dbg_buf;
	dbg_sp->sp_Pkt.dp_Arg3 = (long)l - 1;

	PutMsg((struct MsgPort *)dbg_log->fh_Type, (struct Message *)dbg_sp);
	WaitPort(dbg_replyport);
	GetMsg(dbg_replyport);
}

void dbg(const char* fmt, ...)
{
	char numbuf[16];

	const char *p = fmt;
	char *q = dbg_buf;

	va_list args;
	va_start(args, fmt);
	while (*p != 0)
	{
		char c = *p++;
		if (c == '$')
		{
			c = *p++;
			if (c == 'b')
			{
				UBYTE x = va_arg(args, UBYTE);
				*q++ = '$';
				for (int i = 0; i < 2; i++)
				{
					int ni = (x >> ((1 - i) * 4)) & 0xf;
					*q++ = (ni >= 10) ? ('a' + (ni - 10)) : ('0' + ni);
				}
			}
			else if (c == 'w')
			{
				UWORD x = va_arg(args, UWORD);
				*q++ = '$';
				for (int i = 0; i < 4; i++)
				{
					int ni = (x >> ((3 - i) * 4)) & 0xf;
					*q++ = (ni >= 10) ? ('a' + (ni - 10)) : ('0' + ni);
				}
			}
			else if (c == 'l')
			{
				ULONG x = va_arg(args, ULONG);
				*q++ = '$';
				for (int i = 0; i < 8; i++)
				{
					int ni = (x >> ((7 - i) * 4)) & 0xf;
					*q++ = (ni >= 10) ? ('a' + (ni - 10)) : ('0' + ni);
				}
			}
			else if (c == 'S')
			{
				unsigned char *s = (unsigned char *)va_arg(args, ULONG);
				int l = *s++;
				for (int i = 0; i < l; i++)
					*q++ = *s++;
			}
			else if (c == 's')
			{
				unsigned char *s = (unsigned char *)va_arg(args, ULONG);
				while (*s)
					*q++ = *s++;
				*q++ = 0;
			}
		}
		else
		{
			*q++ = c;
		}
	}
	*q++ = 0;

	va_end(args);

	dbg_out(q - dbg_buf);
}
#endif

char *DosAllocMem(int len)
{
	long *p = (long *)AllocMem(len + 4, MEMF_PUBLIC | MEMF_CLEAR);
	*p++ = len;
	return((char *)p);
}

void DosFreeMem(char *p)
{
	long *lp = (long *)p;
	long len = *--lp;
	FreeMem((char *)lp, len);
}

void reply_packet(struct DosPacket *dp)
{
	struct MsgPort *reply_port = dp->dp_Port;
	dp->dp_Port = mp;
	PutMsg(reply_port, dp->dp_Link);
}

// Routines for talking to the A314 driver.
LONG a314_cmd_wait(UWORD command, char *buffer, int length)
{
	a314_ior->a314_Request.io_Command = command;
	a314_ior->a314_Request.io_Error = 0;
	a314_ior->a314_Socket = socket;
	a314_ior->a314_Buffer = buffer;
	a314_ior->a314_Length = length;
	return DoIO((struct IORequest *)a314_ior);
}

LONG a314_connect(char *name)
{
	struct DateStamp ds;
	DateStamp(&ds);
	socket = ds.ds_Tick;
	return a314_cmd_wait(A314_CONNECT, name, strlen(name));
}

LONG a314_read(char *buf, int length)
{
	return a314_cmd_wait(A314_READ, buf, length);
}

LONG a314_write(char *buf, int length)
{
	return a314_cmd_wait(A314_WRITE, buf, length);
}

LONG a314_eos()
{
	return a314_cmd_wait(A314_EOS, NULL, 0);
}

LONG a314_reset()
{
	return a314_cmd_wait(A314_RESET, NULL, 0);
}

void create_and_add_volume()
{
	my_volume = (struct DeviceList *)DosAllocMem(sizeof(struct DeviceList));
	my_volume->dl_Name = MKBADDR(default_volume_name);
	my_volume->dl_Type = DLT_VOLUME;
	my_volume->dl_Task = mp;
	my_volume->dl_DiskType = ID_314_DISK;
	DateStamp(&my_volume->dl_VolumeDate);

	struct RootNode *root = (struct RootNode *)DOSBase->dl_Root;
	struct DosInfo *info = (struct DosInfo *)BADDR(root->rn_Info);

	Forbid();
	my_volume->dl_Next = info->di_DevInfo;
	info->di_DevInfo = MKBADDR(my_volume);
	Permit();
}

void startup_fs_handler(struct DosPacket *dp)
{
	unsigned char *name = (unsigned char *)BADDR(dp->dp_Arg1);
	struct FileSysStartupMsg *fssm = (struct FileSysStartupMsg *)BADDR(dp->dp_Arg2);
	struct DeviceNode *node = (struct DeviceNode *)BADDR(dp->dp_Arg3);

	memcpy(device_name, name, *name + 1);
	device_name[*name + 1] = 0;

	node->dn_Task = mp;

	dp->dp_Res1 = DOSTRUE;
	dp->dp_Res2 = 0;
	reply_packet(dp);

	dbg_init();

	dbg("ACTION_STARTUP\n");
	dbg("  device_name = $S\n", (ULONG)device_name);
	dbg("  fssm = $l\n", (ULONG)fssm);
	dbg("  node = $l\n", (ULONG)node);

	timer_mp = MyCreatePort(NULL, 0);
	tr = (struct timerequest *)MyCreateExtIO(timer_mp, sizeof(struct timerequest));
	if (OpenDevice(TIMERNAME, UNIT_MICROHZ, (struct IORequest *)tr, 0) != 0)
	{
		// Om detta händer så är det inget mer vi kan göra.
		// F.n. så antar vi att det inte händer.
		dbg("Fatal error: unable to open timer.device\n");
		return;
	}

	a314_mp = MyCreatePort(NULL, 0);
	a314_ior = (struct A314_IORequest *)MyCreateExtIO(a314_mp, sizeof(struct A314_IORequest));
	if (OpenDevice(A314_NAME, 0, (struct IORequest *)a314_ior, 0) != 0)
	{
		// Om detta failar så är det inget mer vi kan göra.
		// F.n. så antar vi att det inte händer.
		dbg("Fatal error: unable to open a314.device\n");
		return;
	}

	A314Base = &(a314_ior->a314_Request.io_Device->dd_Library);

	if (a314_connect("a314fs") != A314_CONNECT_OK)
	{
		dbg("Fatal error: unable to connect to a314fs on rasp\n");
		// Detta skulle kunna hända.
		// Om det händer så måste vi bara vänta och försöka igen om en stund.

		// TODO: Här måste jag använda timer.device för att sätta en timer om 2 sekunder, då jag försöker ansluta igen.
		return;
	}

	request_buffer = AllocMem(REQ_RES_BUF_SIZE, MEMF_A314);
	request_buffer_in_a314_memory = request_buffer != NULL;
	if (request_buffer_in_a314_memory)
		request_buffer_address = TranslateAddressA314(request_buffer);
	else
	{
		request_buffer = AllocMem(REQ_RES_BUF_SIZE, 0);
		request_buffer_address = AllocMemA314(REQ_RES_BUF_SIZE);
	}

	data_buffer_address = AllocMemA314(BUFFER_SIZE);

	// Så vi kan anta att vi kommer hit, och då har vi en ström till rasp-sidan, där vi kan skicka data.
	create_and_add_volume();

	dbg("Startup successful\n");

	// Om vi senare skulle få problem med anslutningen så hantera det som att disken har skjutits ut, och försök ansluta igen inom två sekunder.
	// Men f.n. är det nått vi inte hanterar.
}

void wait_for_response()
{
	for (unsigned long i = 0; 1; i++)
	{
		if (*request_buffer)
		{
			dbg("--Got response after $l sleeps\n", i);
			return;
		}

		tr->tr_node.io_Command = TR_ADDREQUEST;
		tr->tr_node.io_Message.mn_ReplyPort = timer_mp;
		tr->tr_time.tv_secs = 0;
		tr->tr_time.tv_micro = 1000;
		DoIO((struct IORequest *)tr);
	}
}

void write_req_and_wait_for_res(int len)
{
	if (!request_buffer_in_a314_memory)
		WriteMemA314(request_buffer_address, request_buffer, len);
	ULONG buf[2] = {request_buffer_address, len};
	a314_write((char *)&buf[0], 8);
	//wait_for_response();
	a314_read((char *)&buf[1], 4);
	len = buf[1];
	if (!request_buffer_in_a314_memory)
		ReadMemA314(request_buffer, request_buffer_address, len);
}

struct FileLock *create_and_add_file_lock(long key, long mode)
{
	struct FileLock *lock = (struct FileLock *)DosAllocMem(sizeof(struct FileLock));

	lock->fl_Key = key;
	lock->fl_Access = mode;
	lock->fl_Task = mp;
	lock->fl_Volume = MKBADDR(my_volume);

	Forbid();
	lock->fl_Link = my_volume->dl_Lock;
	my_volume->dl_Lock = MKBADDR(lock);
	Permit();

	return lock;
}

void action_locate_object(struct DosPacket *dp)
{
	struct FileLock *parent = (struct FileLock *)BADDR(dp->dp_Arg1);
	unsigned char *name = (unsigned char *)BADDR(dp->dp_Arg2);
	long mode = dp->dp_Arg3;

	dbg("ACTION_LOCATE_OBJECT\n");
	dbg("  parent lock = $l\n", parent);
	dbg("  name = $S\n", name);
	dbg("  mode = $s\n", mode == SHARED_LOCK ? "SHARED_LOCK" : "EXCLUSIVE_LOCK");

	struct LocateObjectRequest *req = (struct LocateObjectRequest *)request_buffer;
	req->has_response = 0;
	req->type = dp->dp_Type;
	req->key = parent == NULL ? 0 : parent->fl_Key;
	req->mode = mode;

	int nlen = *name;
	memcpy(req->name, name, nlen + 1);

	write_req_and_wait_for_res(sizeof(struct LocateObjectRequest) + nlen);

	struct LocateObjectResponse *res = (struct LocateObjectResponse *)request_buffer;
	if (!res->success)
	{
		dbg("  Failed, error code $l\n", (LONG)res->error_code);
		dp->dp_Res1 = DOSFALSE;
		dp->dp_Res2 = res->error_code;
	}
	else
	{
		struct FileLock *lock = create_and_add_file_lock(res->key, mode);

		dbg("  Returning lock $l\n", lock);
		dp->dp_Res1 = MKBADDR(lock);
		dp->dp_Res2 = 0;
	}

	reply_packet(dp);
}

void action_free_lock(struct DosPacket *dp)
{
	ULONG arg1 = dp->dp_Arg1;
	struct FileLock *lock = (struct FileLock *)BADDR(dp->dp_Arg1);

	dbg("ACTION_FREE_LOCK\n");
	dbg("  lock = $l\n", lock);

	if (lock != NULL)
	{
		struct FreeLockRequest *req = (struct FreeLockRequest *)request_buffer;
		req->has_response = 0;
		req->type = dp->dp_Type;
		req->key = lock->fl_Key;

		write_req_and_wait_for_res(sizeof(struct FreeLockRequest));

		// Ignore the response. Must succeed.
		//struct FreeLockResponse *res = (struct FreeLockResponse *)request_buffer;

		Forbid();
		if (my_volume->dl_Lock == arg1)
			my_volume->dl_Lock = lock->fl_Link;
		else
		{
			struct FileLock *prev = (struct FileLock *)BADDR(my_volume->dl_Lock);
			while (prev->fl_Link != arg1)
				prev = (struct FileLock *)BADDR(prev->fl_Link);
			prev->fl_Link = lock->fl_Link;
		}
		Permit();
		DosFreeMem((char *)lock);
	}

	dp->dp_Res1 = DOSTRUE;
	dp->dp_Res2 = 0;
	reply_packet(dp);
}

void action_copy_dir(struct DosPacket *dp)
{
	struct FileLock *parent = (struct FileLock *)BADDR(dp->dp_Arg1);

	dbg("ACTION_COPY_DIR\n");
	dbg("  lock to duplicate = $l\n", parent);

	if (parent == NULL)
	{
		dp->dp_Res1 = 0;
		dp->dp_Res2 = 0;
	}
	else
	{
		struct CopyDirRequest *req = (struct CopyDirRequest *)request_buffer;
		req->has_response = 0;
		req->type = dp->dp_Type;
		req->key = parent->fl_Key;

		write_req_and_wait_for_res(sizeof(struct CopyDirRequest));

		struct CopyDirResponse *res = (struct CopyDirResponse *)request_buffer;
		if (!res->success)
		{
			dbg("  Failed, error code $l\n", (LONG)res->error_code);
			dp->dp_Res1 = DOSFALSE;
			dp->dp_Res2 = res->error_code;
		}
		else
		{
			struct FileLock *lock = create_and_add_file_lock(res->key, parent->fl_Access);

			dbg("  Returning lock $l\n", lock);
			dp->dp_Res1 = MKBADDR(lock);
			dp->dp_Res2 = 0;
		}
	}

	reply_packet(dp);
}

void action_parent(struct DosPacket *dp)
{
	struct FileLock *prev_lock = (struct FileLock *)BADDR(dp->dp_Arg1);

	dbg("ACTION_PARENT\n");
	dbg("  lock = $l\n", prev_lock);

	if (prev_lock == NULL)
	{
		dp->dp_Res1 = DOSFALSE;
		dp->dp_Res2 = ERROR_INVALID_LOCK;
	}
	else
	{
		struct ParentRequest *req = (struct ParentRequest *)request_buffer;
		req->has_response = 0;
		req->type = dp->dp_Type;
		req->key = prev_lock->fl_Key;

		write_req_and_wait_for_res(sizeof(struct ParentRequest));

		struct ParentResponse *res = (struct ParentResponse *)request_buffer;
		if (!res->success)
		{
			dbg("  Failed, error code $l\n", (LONG)res->error_code);
			dp->dp_Res1 = DOSFALSE;
			dp->dp_Res2 = res->error_code;
		}
		else if (res->key == 0)
		{
			dp->dp_Res1 = 0;
			dp->dp_Res2 = 0;
		}
		else
		{
			struct FileLock *lock = create_and_add_file_lock(res->key, SHARED_LOCK);

			dbg("  Returning lock $l\n", lock);
			dp->dp_Res1 = MKBADDR(lock);
			dp->dp_Res2 = 0;
		}
	}

	reply_packet(dp);
}

void action_examine_object(struct DosPacket *dp)
{
	struct FileLock *lock = (struct FileLock *)BADDR(dp->dp_Arg1);
	struct FileInfoBlock *fib = (struct FileInfoBlock *)BADDR(dp->dp_Arg2);

	dbg("ACTION_EXAMINE_OBJECT\n");
	dbg("  lock = $l\n", lock);
	dbg("  fib = $l\n", fib);

	struct ExamineObjectRequest *req = (struct ExamineObjectRequest *)request_buffer;
	req->has_response = 0;
	req->type = dp->dp_Type;
	req->key = lock == NULL ? 0 : lock->fl_Key;

	write_req_and_wait_for_res(sizeof(struct ExamineObjectRequest));

	struct ExamineObjectResponse *res = (struct ExamineObjectResponse *)request_buffer;
	if (!res->success)
	{
		dbg("  Failed, error code $l\n", (LONG)res->error_code);
		dp->dp_Res1 = DOSFALSE;
		dp->dp_Res2 = res->error_code;
	}
	else
	{
		int nlen = (unsigned char)(res->data[0]);
		memcpy(fib->fib_FileName, res->data, nlen + 1);
		fib->fib_FileName[nlen + 1] = 0;

		int clen = (unsigned char)(res->data[nlen + 1]);
		memcpy(fib->fib_Comment, &(res->data[nlen + 1]), clen + 1);
		fib->fib_Comment[clen + 1] = 0;

		fib->fib_DiskKey = res->disk_key;
		fib->fib_DirEntryType = res->entry_type;
		fib->fib_EntryType = res->entry_type;
		fib->fib_Protection = res->protection;
		fib->fib_Size = res->size;
		fib->fib_NumBlocks = (res->size + 511) >> 9;
		fib->fib_Date.ds_Days = res->date[0];
		fib->fib_Date.ds_Minute = res->date[1];
		fib->fib_Date.ds_Tick = res->date[2];

		dp->dp_Res1 = DOSTRUE;
		dp->dp_Res2 = 0;
	}

	reply_packet(dp);
}

void action_examine_next(struct DosPacket *dp)
{
	struct FileLock *lock = (struct FileLock *)BADDR(dp->dp_Arg1);
	struct FileInfoBlock *fib = (struct FileInfoBlock *)BADDR(dp->dp_Arg2);

	dbg("ACTION_EXAMINE_NEXT\n");
	dbg("  lock = $l\n", lock);
	dbg("  fib = $l\n", fib);

	struct ExamineNextRequest *req = (struct ExamineNextRequest *)request_buffer;
	req->has_response = 0;
	req->type = dp->dp_Type;
	req->key = lock == NULL ? 0 : lock->fl_Key;
	req->disk_key = fib->fib_DiskKey;

	write_req_and_wait_for_res(sizeof(struct ExamineNextRequest));

	struct ExamineNextResponse *res = (struct ExamineNextResponse *)request_buffer;
	if (!res->success)
	{
		dbg("  Failed, error code $l\n", (LONG)res->error_code);
		dp->dp_Res1 = DOSFALSE;
		dp->dp_Res2 = res->error_code;
	}
	else
	{
		int nlen = (unsigned char)(res->data[0]);
		memcpy(fib->fib_FileName, res->data, nlen + 1);
		fib->fib_FileName[nlen + 1] = 0;

		int clen = (unsigned char)(res->data[nlen + 1]);
		memcpy(fib->fib_Comment, &(res->data[nlen + 1]), clen + 1);
		fib->fib_Comment[clen + 1] = 0;

		fib->fib_DiskKey = res->disk_key;
		fib->fib_DirEntryType = res->entry_type;
		fib->fib_EntryType = res->entry_type;
		fib->fib_Protection = res->protection;
		fib->fib_Size = res->size;
		fib->fib_NumBlocks = (res->size + 511) >> 9;
		fib->fib_Date.ds_Days = res->date[0];
		fib->fib_Date.ds_Minute = res->date[1];
		fib->fib_Date.ds_Tick = res->date[2];

		dp->dp_Res1 = DOSTRUE;
		dp->dp_Res2 = 0;
	}

	reply_packet(dp);
}

void action_examine_fh(struct DosPacket *dp)
{
	ULONG arg1 = dp->dp_Arg1;
	struct FileInfoBlock *fib = (struct FileInfoBlock *)BADDR(dp->dp_Arg2);

	dbg("ACTION_EXAMINE_FH\n");
	dbg("  arg1 = $l\n", arg1);
	dbg("  fib = $l\n", fib);

	struct ExamineFhRequest *req = (struct ExamineFhRequest *)request_buffer;
	req->has_response = 0;
	req->type = dp->dp_Type;
	req->arg1 = arg1;

	write_req_and_wait_for_res(sizeof(struct ExamineFhRequest));

	struct ExamineFhResponse *res = (struct ExamineFhResponse *)request_buffer;
	if (!res->success)
	{
		dbg("  Failed, error code $l\n", (LONG)res->error_code);
		dp->dp_Res1 = DOSFALSE;
		dp->dp_Res2 = res->error_code;
	}
	else
	{
		int nlen = (unsigned char)(res->data[0]);
		memcpy(fib->fib_FileName, res->data, nlen + 1);
		fib->fib_FileName[nlen + 1] = 0;

		int clen = (unsigned char)(res->data[nlen + 1]);
		memcpy(fib->fib_Comment, &(res->data[nlen + 1]), clen + 1);
		fib->fib_Comment[clen + 1] = 0;

		fib->fib_DiskKey = res->disk_key;
		fib->fib_DirEntryType = res->entry_type;
		fib->fib_EntryType = res->entry_type;
		fib->fib_Protection = res->protection;
		fib->fib_Size = res->size;
		fib->fib_NumBlocks = (res->size + 511) >> 9;
		fib->fib_Date.ds_Days = res->date[0];
		fib->fib_Date.ds_Minute = res->date[1];
		fib->fib_Date.ds_Tick = res->date[2];

		dp->dp_Res1 = DOSTRUE;
		dp->dp_Res2 = 0;
	}

	reply_packet(dp);
}

void action_findxxx(struct DosPacket *dp)
{
	struct FileHandle *fh = (struct FileHandle *)BADDR(dp->dp_Arg1);
	struct FileLock *lock = (struct FileLock *)BADDR(dp->dp_Arg2);
	unsigned char *name = (unsigned char *)BADDR(dp->dp_Arg3);

	if (dp->dp_Type == ACTION_FINDUPDATE)
		dbg("ACTION_FINDUPDATE\n");
	else if (dp->dp_Type == ACTION_FINDINPUT)
		dbg("ACTION_FINDINPUT\n");
	else if (dp->dp_Type == ACTION_FINDOUTPUT)
		dbg("ACTION_FINDOUTPUT\n");

	dbg("  file handle = $l\n", fh);
	dbg("  lock = $l\n", lock);
	dbg("  name = $S\n", name);

	struct FindXxxRequest *req = (struct FindXxxRequest *)request_buffer;
	req->has_response = 0;
	req->type = dp->dp_Type;
	req->key = lock == NULL ? 0 : lock->fl_Key;

	int nlen = *name;
	memcpy(req->name, name, nlen + 1);

	write_req_and_wait_for_res(sizeof(struct FindXxxRequest) + nlen);

	struct FindXxxResponse *res = (struct FindXxxResponse *)request_buffer;
	if (!res->success)
	{
		dbg("  Failed, error code $l\n", (LONG)res->error_code);
		dp->dp_Res1 = DOSFALSE;
		dp->dp_Res2 = res->error_code;
	}
	else
	{
		fh->fh_Arg1 = res->arg1;
		fh->fh_Type = mp;
		fh->fh_Port = DOSFALSE;  // Not an interactive file.

		dp->dp_Res1 = DOSTRUE;
		dp->dp_Res2 = 0;
	}

	reply_packet(dp);
}

void action_read(struct DosPacket *dp)
{
	ULONG arg1 = dp->dp_Arg1;
	UBYTE *dst = (UBYTE *)dp->dp_Arg2;
	int length = dp->dp_Arg3;

	dbg("ACTION_READ\n");
	dbg("  arg1 = $l\n", arg1);
	dbg("  length = $l\n", length);

	if (length == 0)
	{
		dp->dp_Res1 = -1;
		dp->dp_Res2 = ERROR_INVALID_LOCK; // This is not the correct error.
		reply_packet(dp);
		return;
	}

	int total_read = 0;
	while (length)
	{
		int to_read = length;
		if (to_read > BUFFER_SIZE)
			to_read = BUFFER_SIZE;

		struct ReadRequest *req = (struct ReadRequest *)request_buffer;
		req->has_response = 0;
		req->type = dp->dp_Type;
		req->arg1 = arg1;
		req->address = data_buffer_address;
		req->length = to_read;

		write_req_and_wait_for_res(sizeof(struct ReadRequest));

		struct ReadResponse *res = (struct ReadResponse *)request_buffer;
		if (!res->success)
		{
			dbg("  Failed, error code $l\n", (LONG)res->error_code);
			dp->dp_Res1 = -1;
			dp->dp_Res2 = res->error_code;
			reply_packet(dp);
			return;
		}

		if (res->actual)
		{
			ReadMemA314(dst, data_buffer_address, res->actual);
			dst += res->actual;
			total_read += res->actual;
			length -= res->actual;
		}

		if (res->actual < to_read)
			break;
	}

	dp->dp_Res1 = total_read;
	dp->dp_Res2 = 0;
	reply_packet(dp);
}

void action_write(struct DosPacket *dp)
{
	ULONG arg1 = dp->dp_Arg1;
	UBYTE *src = (UBYTE *)dp->dp_Arg2;
	int length = dp->dp_Arg3;

	dbg("ACTION_WRITE\n");
	dbg("  arg1 = $l\n", arg1);
	dbg("  length = $l\n", length);

	int total_written = 0;
	while (length)
	{
		int to_write = length;
		if (to_write > BUFFER_SIZE)
			to_write = BUFFER_SIZE;

		WriteMemA314(data_buffer_address, src, to_write);

		struct WriteRequest *req = (struct WriteRequest *)request_buffer;
		req->has_response = 0;
		req->type = dp->dp_Type;
		req->arg1 = arg1;
		req->address = data_buffer_address;
		req->length = to_write;

		write_req_and_wait_for_res(sizeof(struct WriteRequest));

		struct WriteResponse *res = (struct WriteResponse *)request_buffer;
		if (!res->success)
		{
			dbg("  Failed, error code $l\n", (LONG)res->error_code);
			dp->dp_Res1 = total_written;
			dp->dp_Res2 = res->error_code;
			reply_packet(dp);
			return;
		}

		if (res->actual)
		{
			src += res->actual;
			total_written += res->actual;
			length -= res->actual;
		}

		if (res->actual < to_write)
			break;
	}

	dp->dp_Res1 = total_written;
	dp->dp_Res2 = 0;
	reply_packet(dp);
}

void action_seek(struct DosPacket *dp)
{
	ULONG arg1 = dp->dp_Arg1;
	LONG new_pos = dp->dp_Arg2;
	LONG mode = dp->dp_Arg3;

	dbg("ACTION_SEEK\n");
	dbg("  arg1 = $l\n", arg1);
	dbg("  new_pos = $l\n", new_pos);
	dbg("  mode = $l\n", mode);

	struct SeekRequest *req = (struct SeekRequest *)request_buffer;
	req->has_response = 0;
	req->type = dp->dp_Type;
	req->arg1 = arg1;
	req->new_pos = new_pos;
	req->mode = mode;

	write_req_and_wait_for_res(sizeof(struct SeekRequest));

	struct SeekResponse *res = (struct SeekResponse *)request_buffer;
	if (!res->success)
	{
		dbg("  Failed, error code $l\n", (LONG)res->error_code);
		dp->dp_Res1 = -1;
		dp->dp_Res2 = res->error_code;
	}
	else
	{
		dp->dp_Res1 = res->old_pos;
		dp->dp_Res2 = 0;
	}

	reply_packet(dp);
}

void action_end(struct DosPacket *dp)
{
	ULONG arg1 = dp->dp_Arg1;

	dbg("ACTION_END\n");
	dbg("  arg1 = $l\n", arg1);

	struct EndRequest *req = (struct EndRequest *)request_buffer;
	req->has_response = 0;
	req->type = dp->dp_Type;
	req->arg1 = arg1;

	write_req_and_wait_for_res(sizeof(struct EndRequest));

	//struct EndResponse *res = (struct EndResponse *)request_buffer;

	dp->dp_Res1 = DOSTRUE;
	dp->dp_Res2 = 0;
	reply_packet(dp);
}

void action_delete_object(struct DosPacket *dp)
{
	struct FileLock *lock = (struct FileLock *)BADDR(dp->dp_Arg1);
	unsigned char *name = (unsigned char *)BADDR(dp->dp_Arg2);

	dbg("ACTION_DELETE_OBJECT\n");
	dbg("  lock = $l\n", lock);
	dbg("  name = $S\n", name);

	struct DeleteObjectRequest *req = (struct DeleteObjectRequest *)request_buffer;
	req->has_response = 0;
	req->type = dp->dp_Type;
	req->key = lock == NULL ? 0 : lock->fl_Key;

	int nlen = *name;
	memcpy(req->name, name, nlen + 1);

	write_req_and_wait_for_res(sizeof(struct DeleteObjectRequest) + nlen);

	struct DeleteObjectResponse *res = (struct DeleteObjectResponse *)request_buffer;
	if (!res->success)
	{
		dbg("  Failed, error code $l\n", (LONG)res->error_code);
		dp->dp_Res1 = DOSFALSE;
		dp->dp_Res2 = res->error_code;
	}
	else
	{
		dp->dp_Res1 = DOSTRUE;
		dp->dp_Res2 = 0;
	}

	reply_packet(dp);
}

void action_rename_object(struct DosPacket *dp)
{
	struct FileLock *lock = (struct FileLock *)BADDR(dp->dp_Arg1);
	unsigned char *name = (unsigned char *)BADDR(dp->dp_Arg2);
	struct FileLock *target_dir = (struct FileLock *)BADDR(dp->dp_Arg3);
	unsigned char *new_name = (unsigned char *)BADDR(dp->dp_Arg4);

	dbg("ACTION_RENAME_OBJECT\n");
	dbg("  lock = $l\n", lock);
	dbg("  name = $S\n", name);
	dbg("  target directory = $l\n", lock);
	dbg("  new name = $S\n", new_name);

	struct RenameObjectRequest *req = (struct RenameObjectRequest *)request_buffer;
	req->has_response = 0;
	req->type = dp->dp_Type;
	req->key = lock == NULL ? 0 : lock->fl_Key;
	req->target_dir = target_dir == NULL ? 0 : target_dir->fl_Key;

	int nlen = *name;
	int nnlen = *new_name;

	req->name_len = nlen;
	req->new_name_len = nnlen;

	unsigned char *p = &(req->new_name_len) + 1;
	memcpy(p, name + 1, nlen);
	p += nlen;
	memcpy(p, new_name + 1, nnlen);

	write_req_and_wait_for_res(sizeof(struct RenameObjectRequest) + nlen + nnlen);

	struct RenameObjectResponse *res = (struct RenameObjectResponse *)request_buffer;
	if (!res->success)
	{
		dbg("  Failed, error code $l\n", (LONG)res->error_code);
		dp->dp_Res1 = DOSFALSE;
		dp->dp_Res2 = res->error_code;
	}
	else
	{
		dp->dp_Res1 = DOSTRUE;
		dp->dp_Res2 = 0;
	}

	reply_packet(dp);
}

void action_create_dir(struct DosPacket *dp)
{
	struct FileLock *lock = (struct FileLock *)BADDR(dp->dp_Arg1);
	unsigned char *name = (unsigned char *)BADDR(dp->dp_Arg2);

	dbg("ACTION_CREATE_DIR\n");
	dbg("  lock = $l\n", lock);
	dbg("  name = $S\n", name);

	struct CreateDirRequest *req = (struct CreateDirRequest *)request_buffer;
	req->has_response = 0;
	req->type = dp->dp_Type;
	req->key = lock == NULL ? 0 : lock->fl_Key;

	int nlen = *name;
	memcpy(req->name, name, nlen + 1);

	write_req_and_wait_for_res(sizeof(struct CreateDirRequest) + nlen);

	struct CreateDirResponse *res = (struct CreateDirResponse *)request_buffer;
	if (!res->success)
	{
		dbg("  Failed, error code $l\n", (LONG)res->error_code);
		dp->dp_Res1 = DOSFALSE;
		dp->dp_Res2 = res->error_code;
	}
	else
	{
		struct FileLock *lock = create_and_add_file_lock(res->key, SHARED_LOCK);

		dbg("  Returning lock $l\n", lock);
		dp->dp_Res1 = MKBADDR(lock);
		dp->dp_Res2 = 0;
	}

	reply_packet(dp);
}

void action_set_protect(struct DosPacket *dp)
{
	struct FileLock *lock = (struct FileLock *)BADDR(dp->dp_Arg2);
	unsigned char *name = (unsigned char *)BADDR(dp->dp_Arg3);
	long mask = dp->dp_Arg4;

	dbg("ACTION_SET_PROTECT\n");
	dbg("  lock = $l\n", lock);
	dbg("  name = $S\n", name);
	dbg("  mask = $l\n", mask);

	struct SetProtectRequest *req = (struct SetProtectRequest *)request_buffer;
	req->has_response = 0;
	req->type = dp->dp_Type;
	req->key = lock == NULL ? 0 : lock->fl_Key;
	req->mask = mask;

	int nlen = *name;
	memcpy(req->name, name, nlen + 1);

	write_req_and_wait_for_res(sizeof(struct SetProtectRequest) + nlen);

	struct SetProtectResponse *res = (struct SetProtectResponse *)request_buffer;
	if (!res->success)
	{
		dbg("  Failed, error code $l\n", (LONG)res->error_code);
		dp->dp_Res1 = DOSFALSE;
		dp->dp_Res2 = res->error_code;
	}
	else
	{
		dp->dp_Res1 = DOSTRUE;
		dp->dp_Res2 = 0;
	}

	reply_packet(dp);
}

void action_set_comment(struct DosPacket *dp)
{
	struct FileLock *lock = (struct FileLock *)BADDR(dp->dp_Arg2);
	unsigned char *name = (unsigned char *)BADDR(dp->dp_Arg3);
	unsigned char *comment = (unsigned char *)BADDR(dp->dp_Arg4);

	dbg("ACTION_SET_COMMENT\n");
	dbg("  lock = $l\n", lock);
	dbg("  name = $S\n", name);
	dbg("  comment = $S\n", comment);

	struct SetCommentRequest *req = (struct SetCommentRequest *)request_buffer;
	req->has_response = 0;
	req->type = dp->dp_Type;
	req->key = lock == NULL ? 0 : lock->fl_Key;

	int nlen = *name;
	int clen = *comment;

	req->name_len = nlen;
	req->comment_len = clen;

	unsigned char *p = &(req->comment_len) + 1;
	memcpy(p, name + 1, nlen);
	p += nlen;
	memcpy(p, comment + 1, clen);

	write_req_and_wait_for_res(sizeof(struct SetCommentRequest) + nlen + clen);

	struct SetCommentResponse *res = (struct SetCommentResponse *)request_buffer;
	if (!res->success)
	{
		dbg("  Failed, error code $l\n", (LONG)res->error_code);
		dp->dp_Res1 = DOSFALSE;
		dp->dp_Res2 = res->error_code;
	}
	else
	{
		dp->dp_Res1 = DOSTRUE;
		dp->dp_Res2 = 0;
	}

	reply_packet(dp);
}

void action_same_lock(struct DosPacket *dp)
{
	struct FileLock *lock1 = (struct FileLock *)BADDR(dp->dp_Arg1);
	struct FileLock *lock2 = (struct FileLock *)BADDR(dp->dp_Arg2);

	dbg("ACTION_SAME_LOCK\n");
	dbg("  locks to compare = $l $l\n", lock1, lock2);

	struct SameLockRequest *req = (struct SameLockRequest *)request_buffer;
	req->has_response = 0;
	req->type = dp->dp_Type;
	req->key1 = lock1->fl_Key;
	req->key2 = lock2->fl_Key;

	write_req_and_wait_for_res(sizeof(struct SameLockRequest));

	struct SameLockResponse *res = (struct SameLockResponse *)request_buffer;
	dp->dp_Res1 = res->success ? DOSTRUE : DOSFALSE;
	dp->dp_Res2 = res->error_code;

	reply_packet(dp);
}

void fill_info_data(struct InfoData *id)
{
	memset(id, 0, sizeof(struct InfoData));
	id->id_DiskState = ID_VALIDATED;
	id->id_NumBlocks = 512 * 1024;
	id->id_NumBlocksUsed = 10;
	id->id_BytesPerBlock = 512;
	id->id_DiskType = my_volume->dl_DiskType;
	id->id_VolumeNode = MKBADDR(my_volume);
	id->id_InUse = DOSTRUE;
}

void action_info(struct DosPacket *dp)
{
	struct FileLock *lock = (struct FileLock *)BADDR(dp->dp_Arg1);
	struct InfoData *id = (struct InfoData *)BADDR(dp->dp_Arg2);

	dbg("ACTION_INFO\n");
	dbg("  lock = $l\n", lock);

	fill_info_data(id);

	dp->dp_Res1 = DOSTRUE;
	dp->dp_Res2 = 0;
	reply_packet(dp);
}

void action_disk_info(struct DosPacket *dp)
{
	struct InfoData *id = (struct InfoData *)BADDR(dp->dp_Arg1);

	dbg("ACTION_DISK_INFO\n");

	fill_info_data(id);

	dp->dp_Res1 = DOSTRUE;
	dp->dp_Res2 = 0;
	reply_packet(dp);
}

void action_is_filesystem(struct DosPacket *dp)
{
	dbg("ACTION_IS_FILESYSTEM\n");

	dp->dp_Res1 = DOSTRUE;
	dp->dp_Res2 = 0;
	reply_packet(dp);
}

void action_unsupported(struct DosPacket *dp)
{
	dbg("ACTION_UNSUPPORTED\n");
	dbg("  Unsupported action: $l\n", (ULONG)dp->dp_Type);

	struct UnsupportedRequest *req = (struct UnsupportedRequest *)request_buffer;
	req->has_response = 0;
	req->type = ACTION_UNSUPPORTED;
	req->dp_Type = dp->dp_Type;

	write_req_and_wait_for_res(sizeof(struct UnsupportedRequest));

	struct UnsupportedResponse *res = (struct UnsupportedResponse *)request_buffer;
	dp->dp_Res1 = res->success ? DOSTRUE : DOSFALSE;
	dp->dp_Res2 = res->error_code;
	reply_packet(dp);
}

void start(__reg("a0") struct DosPacket *startup_packet)
{
	SysBase = *(struct ExecBase **)4;
	DOSBase = (struct DosLibrary *)OpenLibrary(DOSNAME, 0);

	mp = MyCreatePort(NULL, 0);

	startup_fs_handler(startup_packet);

	while (1)
	{
		WaitPort(mp);
		struct StandardPacket *sp = (struct StandardPacket *)GetMsg(mp);
		struct DosPacket *dp = (struct DosPacket *)(sp->sp_Msg.mn_Node.ln_Name);

		switch (dp->dp_Type)
		{
		case ACTION_LOCATE_OBJECT: action_locate_object(dp); break;
		case ACTION_FREE_LOCK: action_free_lock(dp); break;
		case ACTION_COPY_DIR: action_copy_dir(dp); break;
		case ACTION_PARENT: action_parent(dp); break;
		case ACTION_EXAMINE_OBJECT: action_examine_object(dp); break;
		case ACTION_EXAMINE_NEXT: action_examine_next(dp); break;
		case ACTION_EXAMINE_FH: action_examine_fh(dp); break;

		case ACTION_FINDUPDATE: action_findxxx(dp); break;
		case ACTION_FINDINPUT: action_findxxx(dp); break;
		case ACTION_FINDOUTPUT: action_findxxx(dp); break;
		case ACTION_READ: action_read(dp); break;
		case ACTION_WRITE: action_write(dp); break;
		case ACTION_SEEK: action_seek(dp); break;
		case ACTION_END: action_end(dp); break;
		//case ACTION_TRUNCATE: action_truncate(dp); break;

		case ACTION_DELETE_OBJECT: action_delete_object(dp); break;
		case ACTION_RENAME_OBJECT: action_rename_object(dp); break;
		case ACTION_CREATE_DIR: action_create_dir(dp); break;

		case ACTION_SET_PROTECT: action_set_protect(dp); break;
		case ACTION_SET_COMMENT: action_set_comment(dp); break;
		//case ACTION_SET_DATE: action_set_date(dp); break;

		case ACTION_SAME_LOCK: action_same_lock(dp); break;
		case ACTION_DISK_INFO: action_disk_info(dp); break;
		case ACTION_INFO: action_info(dp); break;

		case ACTION_IS_FILESYSTEM: action_is_filesystem(dp); break;

		/*
		case ACTION_CURRENT_VOLUME: action_current_volume(dp); break;
		case ACTION_RENAME_DISK: action_rename_disk(dp); break;

		case ACTION_DIE: //action_die(dp); break;
		case ACTION_MORE_CACHE: //action_more_cache(dp); break;
		case ACTION_FLUSH: //action_flush(dp); break;
		case ACTION_INHIBIT: //action_inhibit(dp); break;
		case ACTION_WRITE_PROTECT: //action_write_protect(dp); break;
		*/

		default: action_unsupported(dp); break;
		}
	}

	dbg("Shutting down\n");

	// More cleaning up is necessary if the handler is to exit.
	CloseLibrary((struct Library *)DOSBase);
}


text:
vc a314fs.c -S -o a314fs.asm
python comment_out_sections.py
vc bcpl_start.asm a314fs.asm bcpl_end.asm -nostdlib -o a314fs
python patch_a314fs.py
del a314fs.asm


text:
with open('a314fs', 'r+b') as f:
    f.seek(0x1c)
    b = f.read(4)
    f.seek(0x20)
    f.write(b)


text:
#pragma pack(push, 1)

struct LocateObjectRequest
{
	short has_response;
	short type;
	long key;
	short mode;
	char name[1];
};

struct LocateObjectResponse
{
	short has_response;
	short success;
	short error_code;
	long key;
};

struct FreeLockRequest
{
	short has_response;
	short type;
	long key;
};

struct FreeLockResponse
{
	short has_response;
	short success;
	short error_code;
};

struct CopyDirRequest
{
	short has_response;
	short type;
	long key;
};

struct CopyDirResponse
{
	short has_response;
	short success;
	short error_code;
	long key;
};

struct ParentRequest
{
	short has_response;
	short type;
	long key;
};

struct ParentResponse
{
	short has_response;
	short success;
	short error_code;
	long key;
};

struct ExamineObjectRequest
{
	short has_response;
	short type;
	long key;
};

struct ExamineObjectResponse
{
	short has_response;
	short success;
	short error_code;

	short disk_key;
	short entry_type;
	int size;
	int protection;
	int date[3];
	char data[1];
};

struct ExamineNextRequest
{
	short has_response;
	short type;
	long key;
	short disk_key;
};

struct ExamineNextResponse
{
	short has_response;
	short success;
	short error_code;

	short disk_key;
	short entry_type;
	int size;
	int protection;
	int date[3];
	char data[1];
};

struct FindXxxRequest
{
	short has_response;
	short type;
	long key;
	char name[1];
};

struct FindXxxResponse
{
	short has_response;
	short success;
	short error_code;
	long arg1;
};

struct ReadRequest
{
	short has_response;
	short type;
	long arg1;
	int address;
	int length;
};

struct ReadResponse
{
	short has_response;
	short success;
	short error_code;
	int actual;
};

struct WriteRequest
{
	short has_response;
	short type;
	long arg1;
	int address;
	int length;
};

struct WriteResponse
{
	short has_response;
	short success;
	short error_code;
	int actual;
};

struct SeekRequest
{
	short has_response;
	short type;
	long arg1;
	int new_pos;
	int mode;
};

struct SeekResponse
{
	short has_response;
	short success;
	short error_code;
	int old_pos;
};

struct EndRequest
{
	short has_response;
	short type;
	long arg1;
};

struct EndResponse
{
	short has_response;
	short success;
	short error_code;
};

struct DeleteObjectRequest
{
	short has_response;
	short type;
	long key;
	char name[1];
};

struct DeleteObjectResponse
{
	short has_response;
	short success;
	short error_code;
};

struct RenameObjectRequest
{
	short has_response;
	short type;
	long key;
	long target_dir;
	unsigned char name_len;
	unsigned char new_name_len;
};

struct RenameObjectResponse
{
	short has_response;
	short success;
	short error_code;
};

struct CreateDirRequest
{
	short has_response;
	short type;
	long key;
	char name[1];
};

struct CreateDirResponse
{
	short has_response;
	short success;
	short error_code;
	long key;
};

struct SetProtectRequest
{
	short has_response;
	short type;
	long key;
	long mask;
	char name[1];
};

struct SetProtectResponse
{
	short has_response;
	short success;
	short error_code;
};

struct SetCommentRequest
{
	short has_response;
	short type;
	long key;
	unsigned char name_len;
	unsigned char comment_len;
};

struct SetCommentResponse
{
	short has_response;
	short success;
	short error_code;
};

struct SameLockRequest
{
	short has_response;
	short type;
	long key1;
	long key2;
};

struct SameLockResponse
{
	short has_response;
	short success;
	short error_code;
};

struct ExamineFhRequest
{
	short has_response;
	short type;
	long arg1;
};

struct ExamineFhResponse
{
	short has_response;
	short success;
	short error_code;

	short disk_key;
	short entry_type;
	int size;
	int protection;
	int date[3];
	char data[1];
};

struct UnsupportedRequest
{
	short has_response;
	short type;
	short dp_Type;
};

struct UnsupportedResponse
{
	short has_response;
	short success;
	short error_code;
};

#pragma pack(pop)


text:
with open('a314fs.asm', 'r+b') as f:
    text = f.read().decode('utf-8')
    text = text.replace('section', ';section')
    f.seek(0, 0)
    f.write(text.encode('utf-8'))