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

# Copyright (c) 2018 Niklas Ekström

import asyncio
import json
import struct
import time
import websockets
import socket
import os
import sys

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

protocol = None

class MyProtocol(asyncio.Protocol):
    def connection_made(self, transport):
        global protocol
        protocol = self

        print('Connection to driver made')

        self.transport = transport
        sock = transport.get_extra_info('socket')

        if sock.family == socket.AF_UNIX:
            asyncio.ensure_future(create_websockets_server())
        else:
            sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
            name = 'remotewb'
            msg = struct.pack('=IIB', len(name), 0, MSG_REGISTER_REQ) + name.encode()
            self.transport.write(msg)

        self.rbuf = b''
        self.active_stream_id = None
        self.ptr = None
        self.first_msg = True

    def handle_msg(self, stream_id, mtype, msg):
        if mtype == MSG_REGISTER_RES:
            if msg[0] == 1:
                print('Registered name successfully')
                asyncio.ensure_future(create_websockets_server())
            else:
                print('Unable to register name with driver, shutting down')
                self.transport.close()
        elif mtype == MSG_CONNECT:
            if self.active_stream_id is not None:
                msg = struct.pack('=IIB', 0, self.active_stream_id, MSG_RESET)
                self.transport.write(msg)
            self.active_stream_id = stream_id
            msg = struct.pack('=IIB', 1, self.active_stream_id, MSG_CONNECT_RESPONSE) + b'\x00'
            self.transport.write(msg)
            self.first_msg = True
            print('Amiga connected')
        elif mtype == MSG_DATA:
            if self.active_stream_id == stream_id:
                if self.first_msg:
                    w, h, d, bpr, ptr, count = struct.unpack('>HHHHIH', msg[:14])
                    cmap = struct.unpack('>' + ('H' * count), msg[14:14 + 2*count])
                    self.w = w
                    self.h = h
                    self.d = d
                    self.bpr = bpr
                    self.ptr = ptr
                    self.cmap = cmap
                    self.first_msg = False
                    print('Received data from app', w, h, d, ptr, cmap)
                else:
                    x, y = struct.unpack('>HH', msg)
                    start_read_screenshot()
        elif mtype == MSG_EOS:
            if self.active_stream_id == stream_id:
                msg = struct.pack('=IIB', 0, self.active_stream_id, MSG_EOS)
                self.transport.write(msg)
                self.active_stream_id = None
        elif mtype == MSG_RESET:
            if self.active_stream_id == stream_id:
                self.active_stream_id = None
        elif mtype == MSG_READ_MEM_RES:
            read_screenshot_completed(msg)

    def data_received(self, data):
        self.rbuf += data

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

            mlen, stream_id, mtype = struct.unpack('=IIB', self.rbuf[:9])

            if len(self.rbuf) < 9 + mlen:
                break

            payload = self.rbuf[9:9+mlen]
            self.rbuf = self.rbuf[9+mlen:]
            self.handle_msg(stream_id, mtype, payload)

    def connection_lost(self, exc):
        print('Connection to driver lost', exc)
        if ws_server:
            ws_server.close()
            ws_server.wait_closed()
        loop.stop()

loop = asyncio.get_event_loop()
ws_server = None
active_browser = None

if os.name == 'nt':
    from PIL import Image
    import io

    def bpls2gif(bpls):
        pixels = bytearray(640*256)
        for i in range(3):
            bit = 1 << i
            for y in range(256):
                for b in range(80):
                    by = bpls[(i * 256 + y) * 80 + b]
                    for j in range(8):
                        if by & (1 << (7 - j)):
                            pixels[y*640 + b * 8 + j] |= bit

        pal = []
        for i in range(8):
            c = protocol.cmap[i]
            r = (c & 0x0f00) >> 8
            g = (c & 0x00f0) >> 4
            b = (c & 0x000f)
            pal.append((r << 4) | r)
            pal.append((g << 4) | g)
            pal.append((b << 4) | b)

        im = Image.frombytes('P', (640, 256), bytes(pixels))
        im.putpalette(pal)

        with io.BytesIO() as output:
            im.save(output, format = 'GIF')
            contents = output.getvalue()
            return contents
else:
    import bpls2gif as b2g

    pal_set = False

    def bpls2gif(bpls):
        global pal_set
        if not pal_set:
            pal = []
            for i in range(8):
                c = protocol.cmap[i]
                r = (c & 0x0f00) >> 8
                g = (c & 0x00f0) >> 4
                b = (c & 0x000f)
                pal.append((r << 4) | r)
                pal.append((g << 4) | g)
                pal.append((b << 4) | b)
            b2g.set_palette(bytes(pal))
            pal_set = True
        return b2g.encode(bpls)

async def send_img_to_active_browser(gif):
    jpart = json.dumps({'image':1, 'amiga_present':1}).encode()
    msg = len(jpart).to_bytes(4, 'big') + jpart + gif
    await active_browser.send(msg)

is_reading_screen = False

current_mouse = (0, 0, 0)
last_sent = (0, 0, 0)
event_q = []

def got_browser_event(e):
    global current_mouse

    if 'kc' in e:
        d = e['d']
        kc = e['kc']
        event_q.append(('k', d, kc))
    else:
        x, y, b = e['x'], e['y'], e['b']
        cx, cy, cb = current_mouse

        if (b ^ cb) & 1:
            cb ^= 1
            event_q.append(('m', x, y, cb))
        if (b ^ cb) & 2:
            cb ^= 2
            event_q.append(('m', x, y, cb))

        current_mouse = (x, y, b)

pc_to_ami = {8: 65, 9: 66, 13: 68, 20: 98, 27: 69, 32: 64, 37: 79, 38: 76, 39: 78, 40: 77, 46: 70, 47: 95, 48: 10, 49: 1, 50: 2, 51: 3, 52: 4, 53: 5, 54: 6, 55: 7, 56: 8, 57: 9, 65: 32, 66: 53, 67: 51, 68: 34, 69: 18, 70: 35, 71: 36, 72: 37, 73: 23, 74: 38, 75: 39, 76: 40, 77: 55, 78: 54, 79: 24, 80: 25, 81: 16, 82: 19, 83: 33, 84: 20, 85: 22, 86: 52, 87: 17, 88: 50, 89: 21, 90: 49, 91: 102, 92: 103, 96: 15, 97: 29, 98: 30, 99: 31, 100: 45, 101: 46, 102: 47, 103: 61, 104: 62, 105: 63, 106: 93, 107: 94, 109: 74, 110: 60, 111: 92, 112: 80, 113: 81, 114: 82, 115: 83, 116: 84, 117: 85, 118: 86, 119: 87, 120: 88, 121: 89, 160: 96, 161: 97, 162: 99, 164: 100, 165: 101, 186: 27, 187: 11, 188: 56, 189: 58, 190: 57, 191: 43, 192: 41, 219: 12, 221: 26, 222: 42, 226: 48}

def send_buffered_events():
    global last_sent
    if len(event_q) == 0 and last_sent == current_mouse:
        return

    lst = b''

    while len(event_q) != 0 and len(lst) < 240:
        e = event_q.pop(0)
        if e[0] == 'k':
            _, d, kc = e
            n = 0x4000 | pc_to_ami.get(kc, 0x40)
            if d == 'u':
                n |= 0x2000
            lst += struct.pack('>H', n)
        elif e[0] == 'm':
            _, x, y, b = e
            lst += struct.pack('>HHH', x, y, b)
            last_sent = (x, y, b)

    if len(event_q) == 0 and last_sent != current_mouse:
        x, y, b = current_mouse
        lst += struct.pack('>HHH', x, y, b)
        last_sent = current_mouse

    msg = struct.pack('=IIB', len(lst), protocol.active_stream_id, MSG_DATA) + lst
    protocol.transport.write(msg)

def read_screenshot_completed(bpls):
    global is_reading_screen
    is_reading_screen = False
    if active_browser is not None and protocol is not None and protocol.active_stream_id is not None:
        if protocol.bpr == 240:
            src = bpls
            dst = bytearray(80*256*3)
            for i in range(256):
                for j in range(3):
                    sr = 3*i + j
                    dr = j*256 + i
                    dst[dr*80:(dr+1)*80] = src[sr*80:(sr+1)*80]
            bpls = bytes(dst)
        gif = bpls2gif(bpls)
        asyncio.ensure_future(send_img_to_active_browser(gif))
        send_buffered_events()

def start_read_screenshot():
    global last_read_started, is_reading_screen
    if active_browser is not None and protocol is not None and protocol.active_stream_id is not None and not is_reading_screen:
        is_reading_screen = True
        msg = struct.pack('=IIBII', 8, 0, MSG_READ_MEM_REQ, protocol.ptr, 3*256*80)
        protocol.transport.write(msg)

async def browser_handler(websocket, path):
    global active_browser

    print('Connection to ', path, ' was made')

    is_active = active_browser is None
    if is_active:
        active_browser = websocket

    ami_present = False
    if protocol is not None and protocol.active_stream_id is not None:
        ami_present = True

    jpart = json.dumps({'is_active':(1 if is_active else 0), 'amiga_present':(1 if ami_present else 0)}).encode()
    msg = len(jpart).to_bytes(4, 'big') + jpart
    await websocket.send(msg)

    try:
        while True:
            msg = await websocket.recv()
            if is_active and ami_present:
                e = json.loads(msg)
                got_browser_event(e)
    except websockets.exceptions.ConnectionClosed:
        pass
    finally:
        print('Connection to ', path, ' was closed')

        if is_active:
            active_browser = None

async def create_websockets_server():
    global ws_server
    ws_server = await websockets.serve(browser_handler, '0.0.0.0', 6789, loop = loop)
    print('Websocket server created')

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

if idx != -1:
    fd = int(sys.argv[idx + 1])
    sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM, 0, fd)
    asyncio.ensure_future(loop.create_unix_connection(lambda: MyProtocol(), path = None, sock = sock))
else:
    asyncio.ensure_future(loop.create_connection(lambda: MyProtocol(), 'localhost', 7110))

loop.run_forever()


text:
<!DOCTYPE html>

        <title>RemoteWB -- A314</title>

repo_language: C

mime_type: text/xml


text:
/*
 * Copyright (c) 2018 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 <graphics/gfx.h>
#include <graphics/gfxbase.h>
#include <graphics/view.h>
#include <intuition/intuition.h>
#include <intuition/intuitionbase.h>
#include <intuition/screens.h>
#include <libraries/dos.h>

#include <hardware/intbits.h>

#include <proto/alib.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/intuition.h>
#include <proto/graphics.h>

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

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

struct IntuitionBase *IntuitionBase = NULL;
struct GfxBase *GfxBase = NULL;

struct Library *A314Base = NULL;

struct MsgPort *mp;

ULONG socket;

struct A314_IORequest *cmsg;
struct A314_IORequest *rmsg;
UBYTE arbuf[256];
struct A314_IORequest *wmsg;
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;

struct VBlankData
{
	struct Task *task;
	ULONG signal;
};

struct VBlankData vblank_data;
extern void VBlankServer();
struct Interrupt vblank_interrupt;

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);
}

struct Screen *find_wb_screen()
{
	struct Screen *screen = IntuitionBase->FirstScreen;
	while (screen)
	{
		if ((screen->Flags & SCREENTYPE) == WBENCHSCREEN)
			return screen;
		screen = screen->NextScreen;
	}
	return NULL;
}

int blen = 0;

void append_ulong(ULONG x)
{
	*((ULONG *)&awbuf[blen]) = x;
	blen += 4;
}

void append_uword(UWORD x)
{
	*((UWORD *)&awbuf[blen]) = x;
	blen += 2;
}

WORD last_b = 0;
WORD kbd_qual = 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;

	UWORD qual = IEQUALIFIER_RELATIVEMOUSE | kbd_qual;
	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 send_generated_keyboard_event(UBYTE up, UBYTE kc)
{
	generated_event.ie_NextEvent = NULL;
	generated_event.ie_Class = IECLASS_RAWKEY;
	generated_event.ie_SubClass = 0;
	generated_event.ie_Code = up | kc;

	UWORD qual = kbd_qual;
	if (last_b & 1)
		qual |= IEQUALIFIER_LEFTBUTTON;
	if (last_b & 2)
		qual |= IEQUALIFIER_RBUTTON;
	switch (kc)
	{
	case 0x5a:
	case 0x5b:
	case 0x5c:
	case 0x5d:
	case 0x3d:
	case 0x3e:
	case 0x3f:
	case 0x4a:
	case 0x2d:
	case 0x2e:
	case 0x2f:
	case 0x5e:
	case 0x1d:
	case 0x1e:
	case 0x1f:
	case 0x43:
	case 0x0f:
	case 0x3c:
		qual |= IEQUALIFIER_NUMERICPAD;
		break;
	}
	generated_event.ie_Qualifier = qual;

	generated_event.ie_X = 0;
	generated_event.ie_Y = 0;

	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 *p = (WORD *)&arbuf[0];

		WORD cx = IntuitionBase->MouseX;
		WORD cy = IntuitionBase->MouseY;

		while (length > 0)
		{
			WORD x = *p++;

			if (x & 0x4000)
			{
				UBYTE up = (x & 0x2000) ? 0x80 : 0;
				UBYTE kc = x & 0x7f;

				if (kc >= 0x60 && kc <= 0x67)
				{
					UWORD qual = 1 << (kc - 0x60);
					if (up)
						kbd_qual &= ~qual;
					else
						kbd_qual |= qual;
				}

				send_generated_keyboard_event(up, kc);

				length -= 2;
			}
			else
			{
				WORD y = *p++;
				WORD b = *p++;

				if (b == last_b)
				{
					send_generated_mouse_event(x - cx, y - cy, last_b);
					cx = x;
					cy = y;
				}
				else
				{
					if (cx != x || cy != y)
					{
						send_generated_mouse_event(x - cx, y - cy, last_b);
						cx = x;
						cy = y;
					}
					send_generated_mouse_event(0, 0, b);
					last_b = b;
				}

				length -= 6;
			}
		}

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

void handle_vblank_signal()
{
	blen = 0;
	append_uword(IntuitionBase->MouseX);
	append_uword(IntuitionBase->MouseY);

	if (!pending_a314_write)
		start_a314_write(blen);
}

int main()
{
	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;
	}

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

	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("remotewb") != A314_CONNECT_OK)
	{
		printf("Unable to connect to remotewb service\n");
		goto fail_out2;
	}

	IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library", 0);
	GfxBase = (struct GfxBase *)OpenLibrary("graphics.library", 0);

	Forbid();

	struct Screen *screen = find_wb_screen();
	if (!screen)
	{
		Permit();
		printf("Unable to find workbench screen\n");
		sync_a314_reset();
		goto fail_out3;
	}

	struct BitMap *bm = &(screen->BitMap);

	if (screen->Width != 640 || screen->Height != 256 || bm->Depth != 3 || (bm->BytesPerRow != 80 && bm->BytesPerRow != 240))
	{
		Permit();
		printf("Wrong screen resolution; it is %hdx%hdx%hhu but must be 640x256x3\n", screen->Width, screen->Height, bm->Depth);
		sync_a314_reset();
		goto fail_out3;
	}

	append_uword(screen->Width);
	append_uword(screen->Height);
	append_uword(bm->Depth);
	append_uword(bm->BytesPerRow);

	ULONG ptr = TranslateAddressA314(bm->Planes[0]);

	if (ptr == -1)
	{
		int depth = bm->Depth;
		int size = 80 * 256;

		UBYTE *p = AllocMem(size * depth, MEMF_A314 | MEMF_CHIP);
		if (!p)
		{
			Permit();
			printf("Unable to allocate enough A314 chip memory\n");
			sync_a314_reset();
			goto fail_out3;
		}

		if (bm->BytesPerRow == 80)
		{
			UBYTE *old_planes[8];

			for (int i = 0; i < depth; i++)
			{
				UBYTE *op = bm->Planes[i];
				UBYTE *np = p + (i * size);
				memcpy(np, op, size);
				old_planes[i] = op;
				bm->Planes[i] = np;
			}

			RemakeDisplay();

			for (int i = 0; i < depth; i++)
				FreeMem(old_planes[i], size);

			ptr = TranslateAddressA314(bm->Planes[0]);
		}
		else if (bm->BytesPerRow == 240)
		{
			UBYTE *old_ptr = bm->Planes[0];

			memcpy(p, old_ptr, size * depth);

			for (int i = 0; i < depth; i++)
				bm->Planes[i] = p + (i * 80);

			RemakeDisplay();

			FreeMem(old_ptr, size * depth);

			ptr = TranslateAddressA314(bm->Planes[0]);
		}
	}

	append_ulong(ptr);

	struct ColorMap *cm = screen->ViewPort.ColorMap;
	append_uword(cm->Count);
	for (int i = 0; i < cm->Count; i++)
		append_uword(((UWORD *)cm->ColorTable)[i] & 0xfff);

	Permit();

	sync_a314_write(blen);

	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();

	BYTE vblank_sigbit = AllocSignal(-1);

	ULONG vblanksig = 1 << vblank_sigbit;
	ULONG portsig = 1 << mp->mp_SigBit;

	vblank_data.task = FindTask(NULL);
	vblank_data.signal = vblanksig;

	vblank_interrupt.is_Node.ln_Type = NT_INTERRUPT;
	vblank_interrupt.is_Node.ln_Pri = -60;
	vblank_interrupt.is_Node.ln_Name = NULL;
	vblank_interrupt.is_Data = (APTR)&vblank_data;
	vblank_interrupt.is_Code = VBlankServer;

	AddIntServer(INTB_VERTB, &vblank_interrupt);

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

	while (TRUE)
	{
		ULONG signal = Wait(vblanksig | portsig | SIGBREAKF_CTRL_C);
		if (signal & vblanksig)
		{
			handle_vblank_signal();
		}

		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;
	}

	RemIntServer(INTB_VERTB, &vblank_interrupt);
	FreeSignal(vblank_sigbit);

	CloseDevice((struct IORequest *)id_req);
	DeleteExtIO((struct IORequest *)id_req);
	DeletePort(id_mp);
fail_out3:
	CloseLibrary((struct Library *)GfxBase);
	CloseLibrary((struct Library *)IntuitionBase);;
fail_out2:
	CloseDevice((struct IORequest *)cmsg);
	DeleteExtIO((struct IORequest *)rmsg);
	DeleteExtIO((struct IORequest *)wmsg);
fail_out1:
	DeleteExtIO((struct IORequest *)cmsg);
	DeletePort(mp);
	return 0;
}


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

# Copyright (c) 2018 Niklas Ekström

import select
import sys
import socket
import threading
import time
import os
import struct
import pty
import signal
import termios
import fcntl
import logging
import json

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

FS_CFG_FILE = '/etc/opt/a314/a314fs.conf'
PICMD_CFG_FILE = '/etc/opt/a314/picmd.conf'

volume_paths = {}
search_path = ''
env_vars = {}
sgr_map = {}

def load_cfg():
    with open(FS_CFG_FILE, 'rt') as f:
        cfg = json.load(f)
        devs = cfg['devices']
        for _, dev in devs.items():
            volume_paths[dev['volume']] = dev['path']

    global search_path
    search_path = os.getenv('PATH')

    with open(PICMD_CFG_FILE, 'rt') as f:
        cfg = json.load(f)

        if 'paths' in cfg:
            search_path = ':'.join(cfg['paths']) + ':' + search_path
            os.environ['PATH'] = search_path

        if 'env_vars' in cfg:
            for key, val in cfg['env_vars'].items():
                env_vars[key] = val

        if 'sgr_map' in cfg:
            for key, val in cfg['sgr_map'].items():
                sgr_map[key] = str(val)

load_cfg()

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)

sessions = {}

class PiCmdSession(object):
    def __init__(self, stream_id):
        self.stream_id = stream_id
        self.pid = 0

        self.start_msg = b''
        self.reset_after = None

        self.rasp_was_esc = False
        self.rasp_in_cs = False
        self.rasp_holding = ''

        self.amiga_in_cs = False
        self.amiga_holding = ''

    def process_amiga_ansi(self, data):
        data = data.decode('latin-1')
        out = ''
        for c in data:
            if not self.amiga_in_cs:
                if c == '\x9b':
                    self.amiga_in_cs = True
                    self.amiga_holding = '\x1b['
                else:
                    out += c
            else: # self.amiga_in_cs
                self.amiga_holding += c
                if c >= chr(0x40) and c <= chr(0x7e):
                    if c == 'r':
                        # Window Bounds Report
                        # ESC[1;1;rows;cols r
                        rows, cols = map(int, self.amiga_holding[6:-2].split(';'))
                        winsize = struct.pack('HHHH', rows, cols, 0, 0)
                        fcntl.ioctl(self.fd, termios.TIOCSWINSZ, winsize)
                    elif c == '|':
                        # Input Event Report
                        # ESC[12;0;0;x;x;x;x;x|
                        # Window resized
                        send_data(self.stream_id, b'\x9b' + b'0 q')
                    else:
                        out += self.amiga_holding
                    self.amiga_holding = ''
                    self.amiga_in_cs = False
        if len(out) != 0:
            os.write(self.fd, out.encode('utf-8'))

    def process_msg_data(self, data):
        if self.start_msg is not None:
            self.start_msg += data
            length = struct.unpack_from('>H', self.start_msg, 0)[0]
            if len(self.start_msg) == length:
                buf = self.start_msg

                rows, cols, component_count, arg_count = struct.unpack_from('>HHBB', buf, 2)

                ind = 8

                components = []
                for _ in range(component_count):
                    n = buf[ind]
                    ind += 1
                    components.append(buf[ind:ind+n].decode('latin-1'))
                    ind += n

                args = []
                for _ in range(arg_count):
                    n = buf[ind]
                    ind += 1
                    args.append(buf[ind:ind+n].decode('latin-1'))
                    ind += n

                if arg_count == 0:
                    args.append('bash')

                self.pid, self.fd = pty.fork()
                if self.pid == 0:
                    for key, val in env_vars.items():
                        os.putenv(key, val)
                    os.putenv('PATH', search_path)
                    os.putenv('TERM', 'ansi')
                    winsize = struct.pack('HHHH', rows, cols, 0, 0)
                    fcntl.ioctl(sys.stdin, termios.TIOCSWINSZ, winsize)
                    if component_count != 0 and components[0] in volume_paths:
                        path = volume_paths[components[0]]
                        os.chdir(os.path.join(path, *components[1:]))
                    else:
                        os.chdir(os.getenv('HOME', '/'))
                    os.execvp(args[0], args)

                self.start_msg = None

        elif self.pid:
            self.process_amiga_ansi(data)

    def close(self):
        if self.pid:
            os.kill(self.pid, signal.SIGTERM)
            self.pid = 0
            os.close(self.fd)
        del sessions[self.stream_id]

    def process_rasp_ansi(self, text):
        text = text.decode('utf-8')
        out = ''
        for c in text:
            if not self.rasp_in_cs:
                if not self.rasp_was_esc:
                    if c == '\x1b':
                        self.rasp_was_esc = True
                    else:
                        out += c
                else: # self.rasp_was_esc
                    if c == '[':
                        self.rasp_was_esc = False
                        self.rasp_in_cs = True
                        self.rasp_holding = '\x1b['
                    elif c == '\x1b':
                        out += '\x1b'
                    else:
                        out += '\x1b'
                        out += c
                        self.rasp_was_esc = False
            else: # self.rasp_in_cs
                self.rasp_holding += c
                if c >= chr(0x40) and c <= chr(0x7e):
                    if c == 'm':
                        # Select Graphic Rendition
                        # ESC[30;37m
                        attrs = self.rasp_holding[2:-1].split(';')
                        attrs = [sgr_map[a] if a in sgr_map else a for a in attrs]
                        out += '\x1b[' + (';'.join(attrs)) + 'm'
                    else:
                        out += self.rasp_holding
                    self.rasp_holding = ''
                    self.rasp_in_cs = False
        return out.encode('latin-1', 'replace')

    def handle_text(self):
        try:
            text = os.read(self.fd, 1024)
            text = self.process_rasp_ansi(text)
            while len(text) > 0:
                take = min(len(text), 252)
                send_data(self.stream_id, text[:take])
                text = text[take:]
        except:
            #os.close(self.fd)
            os.kill(self.pid, signal.SIGTERM)
            self.pid = 0
            send_eos(self.stream_id)
            self.reset_after = time.time() + 10

    def handle_timeout(self):
        if self.reset_after and self.reset_after < time.time():
            send_reset(self.stream_id)
            del sessions[self.stream_id]

    def fileno(self):
        return self.fd

def process_drv_msg(stream_id, ptype, payload):
    if ptype == MSG_CONNECT:
        if payload == b'picmd':
            s = PiCmdSession(stream_id)
            sessions[stream_id] = s
            send_connect_response(stream_id, 0)
        else:
            send_connect_response(stream_id, 3)
    elif stream_id in sessions:
        s = sessions[stream_id]

        if ptype == MSG_DATA:
            s.process_msg_data(payload)
        elif ptype == MSG_EOS:
            if s.pid:
                send_eos(s.stream_id)
            s.close()
        elif ptype == MSG_RESET:
            s.close()

done = False

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'picmd')
    _, _, payload = wait_for_msg()
    if payload[0] != 1:
        logger.error('Unable to register picmd with driver, shutting down')
        drv.close()
        done = True

rbuf = b''

if not done:
    logger.info('picmd server is running')

while not done:
    sel_fds = [drv] + [s for s in sessions.values() if s.pid]
    if idx == -1:
        sel_fds.append(sys.stdin)
    rfd, wfd, xfd = select.select(sel_fds, [], [], 5.0)

    for fd in rfd:
        if fd == sys.stdin:
            line = sys.stdin.readline()
            if not line or line.startswith('quit'):
                for s in sessions.values():
                    s.close()
                drv.close()
                done = True
        elif fd == drv:
            buf = drv.recv(1024)
            if not buf:
                for s in sessions.values():
                    s.close()
                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)
        else:
            fd.handle_text()

    for s in sessions.values():
        s.handle_timeout()


text:
vc pi.c -lamiga -o Pi
vc pisetclock.c -lamiga -o PiSetClock


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

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

#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <devices/timer.h>

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

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

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

#define PICMD_SERVICE_NAME "picmd"

struct StartMsgHeader
{
	UWORD length;
	short rows;
	short cols;
	UBYTE component_count;
	UBYTE arg_count;
};

static const char picmd_args[] = "\x04" "date" "\x05" "+%s%z";
#define ARGS_LEN (sizeof(picmd_args) - 1)
#define START_MSG_LEN (sizeof(struct StartMsgHeader) + ARGS_LEN)

struct MsgPort *sync_mp;
struct MsgPort *async_mp;

struct A314_IORequest *read_ior;
struct A314_IORequest *sync_ior;
struct timerequest *tr;

ULONG socket;

UBYTE arbuf[256];

BOOL pending_a314_read = FALSE;
BOOL stream_closed = FALSE;

void start_a314_cmd(struct MsgPort *reply_port, struct A314_IORequest *ior, UWORD cmd, char *buffer, int length)
{
	ior->a314_Request.io_Message.mn_ReplyPort = reply_port;
	ior->a314_Request.io_Command = cmd;
	ior->a314_Request.io_Error = 0;
	ior->a314_Socket = socket;
	ior->a314_Buffer = buffer;
	ior->a314_Length = length;
	SendIO((struct IORequest *)ior);
}

BYTE a314_connect(char *name)
{
	socket = time(NULL);
	start_a314_cmd(sync_mp, sync_ior, A314_CONNECT, name, strlen(name));
	Wait(1L << sync_mp->mp_SigBit);
	GetMsg(sync_mp);
	return sync_ior->a314_Request.io_Error;
}

BYTE a314_write(char *buffer, int length)
{
	start_a314_cmd(sync_mp, sync_ior, A314_WRITE, buffer, length);
	Wait(1L << sync_mp->mp_SigBit);
	GetMsg(sync_mp);
	return sync_ior->a314_Request.io_Error;
}

BYTE a314_eos()
{
	start_a314_cmd(sync_mp, sync_ior, A314_EOS, NULL, 0);
	Wait(1L << sync_mp->mp_SigBit);
	GetMsg(sync_mp);
	return sync_ior->a314_Request.io_Error;
}

BYTE a314_reset()
{
	start_a314_cmd(sync_mp, sync_ior, A314_RESET, NULL, 0);
	Wait(1L << sync_mp->mp_SigBit);
	GetMsg(sync_mp);
	return sync_ior->a314_Request.io_Error;
}

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

void handle_read_data()
{
	static BOOL timestamp_handled = FALSE;

	if (timestamp_handled)
		return;

	UBYTE *p = read_ior->a314_Buffer;
	int len = read_ior->a314_Length;

	unsigned long time_zone = 0;

	for (int i = 0; i < len; i++)
	{
		if (p[i] == '+' || p[i] == '-')
		{
			unsigned long hours = ((p[i+1] - '0') * 10) + (p[i+2] - '0');
			unsigned long minutes = ((p[i+3] - '0') * 10) + (p[i+4] - '0');
			time_zone = (hours * 60 + minutes) * 60;
			if (p[i] == '-')
				time_zone = -time_zone;

			p[i] = 0;
			break;
		}
	}

	unsigned long ts = strtoul(p, NULL, 10);
	ts -= 252460800; // Adjust base from 1970-01-01 to 1978-01-01.
	ts += time_zone;

	tr->tr_time.tv_secs = ts;
	tr->tr_node.io_Command = TR_SETSYSTIME;
	DoIO((struct IORequest *)tr);

	timestamp_handled = TRUE;
}

void handle_a314_read_completed()
{
	pending_a314_read = FALSE;

	if (stream_closed)
		return;

	int res = read_ior->a314_Request.io_Error;
	if (res == A314_READ_OK)
	{
		handle_read_data();
		start_a314_read();
	}
	else if (res == A314_READ_EOS)
	{
		a314_eos();
		stream_closed = TRUE;
	}
	else if (res == A314_READ_RESET)
	{
		stream_closed = TRUE;
	}
}

int main(int argc, char **argv)
{
	sync_mp = CreatePort(NULL, 0);
	async_mp = CreatePort(NULL, 0);

	sync_ior = (struct A314_IORequest *)CreateExtIO(sync_mp, sizeof(struct A314_IORequest));
	read_ior = (struct A314_IORequest *)CreateExtIO(async_mp, sizeof(struct A314_IORequest));

	tr = (struct timerequest *)CreateExtIO(sync_mp, sizeof(struct timerequest));

	if (!sync_mp || !async_mp || !sync_ior || !read_ior || !tr)
	{
		printf("Unable to allocate enough memory\n");
		goto fail1;
	}

	if (OpenDevice(TIMERNAME, 0, (struct IORequest *)tr, 0))
	{
		printf("Unable to open " TIMERNAME "\n");
		goto fail1;
	}

	if (OpenDevice(A314_NAME, 0, (struct IORequest *)sync_ior, 0))
	{
		printf("Unable to open " A314_NAME "\n");
		goto fail2;
	}

	memcpy(read_ior, sync_ior, sizeof(struct A314_IORequest));

	if (a314_connect(PICMD_SERVICE_NAME) != A314_CONNECT_OK)
	{
		printf("Unable to connect to " PICMD_SERVICE_NAME " service\n");
		goto fail3;
	}

	char start_msg[START_MSG_LEN];

	struct StartMsgHeader *hdr = (struct StartMsgHeader *)start_msg;
	hdr->length = START_MSG_LEN;
	hdr->rows = 25;
	hdr->cols = 80;
	hdr->component_count = 0;
	hdr->arg_count = 2;

	memcpy(hdr + 1, picmd_args, ARGS_LEN);

	a314_write(start_msg, START_MSG_LEN);

	start_a314_read();

	ULONG portsig = 1L << async_mp->mp_SigBit;
	BOOL did_reset = FALSE;

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

		if (signal & portsig)
		{
			struct Message *msg;
			while (msg = GetMsg(async_mp))
		                handle_a314_read_completed();
		}

		if ((signal & SIGBREAKF_CTRL_C) && !did_reset)
		{
			a314_reset();
			did_reset = TRUE;
		}

		if (stream_closed && !pending_a314_read)
			break;
	}

fail3:
	CloseDevice((struct IORequest *)sync_ior);

fail2:
	CloseDevice((struct IORequest *)tr);

fail1:
	if (tr)
		DeleteExtIO((struct IORequest *)tr);

	if (read_ior)
		DeleteExtIO((struct IORequest *)read_ior);

	if (sync_ior)
		DeleteExtIO((struct IORequest *)sync_ior);

	if (async_mp)
		DeletePort(async_mp);

	if (sync_mp)
		DeletePort(sync_mp);

	return 0;
}


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

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

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

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

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

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

#define PICMD_SERVICE_NAME "picmd"

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

struct StartMsgHeader
{
	UWORD length;
	short rows;
	short cols;
	UBYTE component_count;
	UBYTE arg_count;
};

static struct MsgPort *sync_mp;
static struct MsgPort *async_mp;

static struct A314_IORequest *read_ior;
static struct A314_IORequest *sync_ior;

static BPTR input_file;
static BPTR output_file;

static struct FileHandle *con;

static ULONG socket;

static UBYTE arbuf[256];

static struct StandardPacket sync_sp;
static struct StandardPacket wait_sp;

static BOOL pending_a314_read = FALSE;
static BOOL pending_con_wait = FALSE;
static BOOL stream_closed = FALSE;

static void put_con_sp(struct MsgPort *reply_port, struct StandardPacket *sp, LONG action, LONG arg1, LONG arg2, LONG arg3)
{
	sp->sp_Msg.mn_Node.ln_Type = NT_MESSAGE;
	sp->sp_Msg.mn_Node.ln_Pri = 0;
	sp->sp_Msg.mn_Node.ln_Name = (char *)&(sp->sp_Pkt);
	sp->sp_Msg.mn_Length = sizeof(struct StandardPacket);
	sp->sp_Msg.mn_ReplyPort = reply_port;
	sp->sp_Pkt.dp_Link = &(sp->sp_Msg);
	sp->sp_Pkt.dp_Port = reply_port;
	sp->sp_Pkt.dp_Type = action;
	sp->sp_Pkt.dp_Arg1 = arg1;
	sp->sp_Pkt.dp_Arg2 = arg2;
	sp->sp_Pkt.dp_Arg3 = arg3;
	PutMsg(con->fh_Type, &(sp->sp_Msg));
}

static LONG set_screen_mode(LONG mode)
{
	put_con_sp(sync_mp, &sync_sp, ACTION_SCREEN_MODE, mode, 0, 0);
	Wait(1L << sync_mp->mp_SigBit);
	GetMsg(sync_mp);
	return sync_sp.sp_Pkt.dp_Res1;
}

static void start_con_wait()
{
	put_con_sp(async_mp, &wait_sp, ACTION_WAIT_CHAR, 100000, 0, 0);
	pending_con_wait = TRUE;
}

static LONG con_read(char *s, int length)
{
	return Read(input_file, s, length);
}

static LONG con_write(char *s, int length)
{
	return Write(output_file, s, length);
}

static void start_a314_cmd(struct MsgPort *reply_port, struct A314_IORequest *ior, UWORD cmd, char *buffer, int length)
{
	ior->a314_Request.io_Message.mn_ReplyPort = reply_port;
	ior->a314_Request.io_Command = cmd;
	ior->a314_Request.io_Error = 0;
	ior->a314_Socket = socket;
	ior->a314_Buffer = buffer;
	ior->a314_Length = length;
	SendIO((struct IORequest *)ior);
}

static BYTE a314_connect(char *name)
{
	socket = time(NULL);
	start_a314_cmd(sync_mp, sync_ior, A314_CONNECT, name, strlen(name));
	Wait(1L << sync_mp->mp_SigBit);
	GetMsg(sync_mp);
	return sync_ior->a314_Request.io_Error;
}

static BYTE a314_write(char *buffer, int length)
{
	start_a314_cmd(sync_mp, sync_ior, A314_WRITE, buffer, length);
	Wait(1L << sync_mp->mp_SigBit);
	GetMsg(sync_mp);
	return sync_ior->a314_Request.io_Error;
}

static BYTE a314_eos()
{
	start_a314_cmd(sync_mp, sync_ior, A314_EOS, NULL, 0);
	Wait(1L << sync_mp->mp_SigBit);
	GetMsg(sync_mp);
	return sync_ior->a314_Request.io_Error;
}

static BYTE a314_reset()
{
	start_a314_cmd(sync_mp, sync_ior, A314_RESET, NULL, 0);
	Wait(1L << sync_mp->mp_SigBit);
	GetMsg(sync_mp);
	return sync_ior->a314_Request.io_Error;
}

static void start_a314_read()
{
	start_a314_cmd(async_mp, read_ior, A314_READ, arbuf, 255);
	pending_a314_read = TRUE;
}

static void handle_con_wait_completed()
{
	pending_con_wait = FALSE;

	if (stream_closed)
		return;

	if (wait_sp.sp_Pkt.dp_Res1 == DOSFALSE)
	{
		start_con_wait();
	}
	else
	{
		unsigned char buf[64];
		int len = con_read(buf, sizeof(buf));

		if (len == 0 || len == -1)
		{
			a314_reset();
			stream_closed = TRUE;
		}
		else
		{
			a314_write(buf, len);
			start_con_wait();
		}
	}
}

static void handle_a314_read_completed()
{
	pending_a314_read = FALSE;

	if (stream_closed)
		return;

	int res = read_ior->a314_Request.io_Error;
	if (res == A314_READ_OK)
	{
		UBYTE *p = read_ior->a314_Buffer;
		int len = read_ior->a314_Length;

		con_write(p, len);
		start_a314_read();
	}
	else if (res == A314_READ_EOS)
	{
		a314_eos();
		stream_closed = TRUE;
	}
	else if (res == A314_READ_RESET)
	{
		stream_closed = TRUE;
	}
}

static void create_and_send_start_msg(BPTR current_dir, int argc, char **argv, short rows, short cols)
{
	int buf_len = sizeof(struct StartMsgHeader);

	int component_count = 0;
	UBYTE *components[20];

	if (current_dir != 0)
	{
		struct FileLock *fl = (struct FileLock *)BADDR(current_dir);
		struct DeviceList *dl = (struct DeviceList *)BADDR(fl->fl_Volume);

		if (dl->dl_DiskType == ID_314_DISK)
		{
			struct FileInfoBlock *fib = AllocMem(sizeof(struct FileInfoBlock), 0);

			BPTR lock = DupLock(current_dir);

			while (lock != 0)
			{
				if (Examine(lock, fib) == 0)
				{
					UnLock(lock);
					break;
				}

				int n = strlen(fib->fib_FileName);
				UBYTE *p = AllocMem(n + 1, 0);
				p[0] = (UBYTE)n;
				memcpy(&p[1], fib->fib_FileName, n);
				components[component_count++] = p;

				buf_len += n + 1;

				BPTR child = lock;
				lock = ParentDir(child);
				UnLock(child);
			}

			FreeMem(fib, sizeof(struct FileInfoBlock));
		}
	}

	for (int i = 1; i < argc; i++)
		buf_len += strlen(argv[i]) + 1;

	UBYTE *buffer = AllocMem(buf_len, 0);

	struct StartMsgHeader *hdr = (struct StartMsgHeader *)buffer;
	hdr->length = buf_len;
	hdr->rows = rows;
	hdr->cols = cols;
	hdr->component_count = component_count;
	hdr->arg_count = argc - 1;

	UBYTE *p = buffer + sizeof(struct StartMsgHeader);

	for (int i = 0; i < component_count; i++)
	{
		UBYTE *q = components[component_count - 1 - i];
		int n = *q;
		memcpy(p, q, n + 1);
		p += n + 1;
		FreeMem(q, n + 1);
	}

	for (int i = 1; i < argc; i++)
	{
		UBYTE *q = (UBYTE *)argv[i];
		int n = strlen(q);
		*p++ = (UBYTE)n;
		memcpy(p, q, n);
		p += n;
	}

	for (int i = 0; i < buf_len; i += 224)
	{
		int to_write = buf_len - i;
		if (to_write > 224)
			to_write = 224;
		a314_write(buffer + i, to_write);
	}

	FreeMem(buffer, buf_len);
}

int main(int argc, char **argv)
{
	struct Process *proc = (struct Process *)FindTask(NULL);

	input_file = proc->pr_CIS;
	output_file = proc->pr_COS;

	if (!IsInteractive(input_file))
	{
		printf("Unable to handle redirected input\n");
		goto fail0;
	}

	sync_mp = CreatePort(NULL, 0);
	async_mp = CreatePort(NULL, 0);

	sync_ior = (struct A314_IORequest *)CreateExtIO(sync_mp, sizeof(struct A314_IORequest));
	read_ior = (struct A314_IORequest *)CreateExtIO(sync_mp, sizeof(struct A314_IORequest));

	if (!sync_mp || !async_mp || !sync_ior || !read_ior)
	{
		printf("Unable to allocate enough memory\n");
		goto fail1;
	}

	if (OpenDevice(A314_NAME, 0, (struct IORequest *)sync_ior, 0))
	{
		printf("Unable to open " A314_NAME "\n");
		goto fail1;
	}

	memcpy(read_ior, sync_ior, sizeof(struct A314_IORequest));

	if (a314_connect(PICMD_SERVICE_NAME) != A314_CONNECT_OK)
	{
		printf("Unable to connect to " PICMD_SERVICE_NAME " service\n");
		goto fail2;
	}

	// The interactions with the console are described here:
	// https://wiki.amigaos.net/wiki/Console_Device

	con = (struct FileHandle *)BADDR(input_file);

	set_screen_mode(DOSTRUE);

	int rows = 25;
	int cols = 80;

	if (IsInteractive(output_file))
	{
		// Window Status Request
		con_write("\x9b" "0 q", 4);

		int len = con_read(arbuf, 32);	// "\x9b" "1;1;33;77 r"
		if (len < 10 || arbuf[len - 1] != 'r')
		{
			printf("Failure to receive window bounds report\n");
			a314_reset();
			goto fail3;
		}

		// Set Raw Events
		con_write("\x9b" "12{", 4); // 12 = Window resized

		int start = 5;
		int ind = start;
		while (arbuf[ind] != ';')
			ind++;
		arbuf[ind] = 0;
		rows = atoi(arbuf + start);
		ind++;
		start = ind;
		while (arbuf[ind] != ' ')
			ind++;
		arbuf[ind] = 0;
		cols = atoi(arbuf + start);
	}

	create_and_send_start_msg(proc->pr_CurrentDir, argc, argv, (short)rows, (short)cols);

	start_con_wait();
	start_a314_read();

	ULONG portsig = 1L << async_mp->mp_SigBit;

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

		if (signal & portsig)
		{
			struct Message *msg;
			while (msg = GetMsg(async_mp))
			{
				if (msg == (struct Message *)&wait_sp)
					handle_con_wait_completed();
				else if (msg == (struct Message *)read_ior)
					handle_a314_read_completed();
			}
		}

		if (stream_closed && !pending_a314_read && !pending_con_wait)
			break;
	}

fail3:
	set_screen_mode(DOSFALSE);

fail2:
	CloseDevice((struct IORequest *)sync_ior);

fail1:
	if (read_ior)
		DeleteExtIO((struct IORequest *)read_ior);

	if (sync_ior)
		DeleteExtIO((struct IORequest *)sync_ior);

	if (async_mp)
		DeletePort(async_mp);

	if (sync_mp)
		DeletePort(sync_mp);

fail0:
	return 0;
}


text:
/*
 * Copyright (c) 2018 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 <libraries/dos.h>
#include <intuition/intuition.h>
#include <graphics/gfxbase.h>

#include <hardware/intbits.h>

#include <proto/alib.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/intuition.h>
#include <proto/graphics.h>

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

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

struct IntuitionBase *IntuitionBase = NULL;
struct GfxBase *GfxBase = NULL;

struct Library *A314Base = NULL;

UBYTE *bpl_ptr1;
UBYTE *bpl_ptr2;

int curr_bpl = 0;

struct Screen *screen = NULL;

ULONG socket;

struct A314_IORequest *cmsg;
struct A314_IORequest *rmsg;
UBYTE arbuf[256];
struct A314_IORequest *wmsg;
UBYTE awbuf[256];

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

BOOL waiting_for_frame_response = FALSE;
BOOL close_after_next_frame_response = FALSE;
BOOL no_more_frames = FALSE;

BOOL stream_closed = FALSE;

ULONG vblank_counter = 0;
ULONG req_next_frame_at = 0;

struct VBlankData
{
	struct Task *task;
	ULONG signal;
};

struct VBlankData vblank_data;
extern void VBlankServer();
struct Interrupt vblank_interrupt;

struct MsgPort *mp;

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);
}

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)
	{
		waiting_for_frame_response = FALSE;

		if (close_after_next_frame_response)
		{
			start_a314_reset();
			stream_closed = TRUE;
			return;
		}

		if (*((UWORD *)&arbuf[0]) == 0)
		{
			printf("No more frames, ending now.\n");
			no_more_frames = TRUE;
			return;
		}

		UWORD *pal = (UWORD *)(&arbuf[2]);
		UWORD *ct = (UWORD *)(screen->ViewPort.ColorMap->ColorTable);
		for (int i = 0; i < 16; i++)
			ct[i] = pal[i];

		UBYTE *bpl_ptr;
		if (curr_bpl == 0)
			bpl_ptr = bpl_ptr1;
		else
			bpl_ptr = bpl_ptr2;

		screen->BitMap.Planes[0] = &bpl_ptr[10240*0];
		screen->BitMap.Planes[1] = &bpl_ptr[10240*1];
		screen->BitMap.Planes[2] = &bpl_ptr[10240*2];
		screen->BitMap.Planes[3] = &bpl_ptr[10240*3];

		MakeScreen(screen);
		RethinkDisplay();

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

void handle_a314_write_completed()
{
	pending_a314_write = FALSE;

	if (stream_closed)
		return;

	int res = wmsg->a314_Request.io_Error;
	if (res == A314_WRITE_RESET)
	{
		stream_closed = TRUE;
	}
}

void handle_a314_reset_completed()
{
	pending_a314_reset = FALSE;
}

void handle_vblank_signal()
{
	if (req_next_frame_at <= vblank_counter && !waiting_for_frame_response && !no_more_frames)
	{
		curr_bpl = (curr_bpl + 1) & 1;

		*((UBYTE *)&awbuf[0]) = (UBYTE)curr_bpl;
		start_a314_write(1);

		waiting_for_frame_response = TRUE;
		req_next_frame_at += 4;
	}
	vblank_counter += 1;
}

int main()
{
	IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library", 0);
	GfxBase = (struct GfxBase *)OpenLibrary("graphics.library", 0);

	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;
	}

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

	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("videoplayer") != A314_CONNECT_OK)
	{
		printf("Unable to connect to videoplayer\n");
		goto fail_out2;
	}

	bpl_ptr1 = AllocMem(10240*4, MEMF_A314 | MEMF_CHIP);
	bpl_ptr2 = AllocMem(10240*4, MEMF_A314 | MEMF_CHIP);
	if (bpl_ptr1 == NULL || bpl_ptr2 == NULL)
	{
		printf("Unable to allocate A314 chip memory\n");
		sync_a314_reset();
		goto fail_out3;
	}

	*((ULONG *)&awbuf[0]) = TranslateAddressA314(bpl_ptr1);
	*((ULONG *)&awbuf[4]) = TranslateAddressA314(bpl_ptr2);

	sync_a314_write(8);

	struct BitMap bmp =
	{
		40, 256, 0, 4, 0,
		{NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}
	};

	bmp.Planes[0] = &bpl_ptr1[10240*0];
	bmp.Planes[1] = &bpl_ptr1[10240*1];
	bmp.Planes[2] = &bpl_ptr1[10240*2];
	bmp.Planes[3] = &bpl_ptr1[10240*3];

	struct NewScreen new_screen =
	{
		0, 0,
		320, 256, 4,
		0, 1,
		0, CUSTOMSCREEN | CUSTOMBITMAP | SCREENQUIET,
		NULL, "Videoplayer", NULL, &bmp
	};

	screen = OpenScreen(&new_screen);
	if (screen == NULL)
	{
		printf("Unable to create screen\n");
		sync_a314_reset();
		goto fail_out3;
	}

	start_a314_read();

	BYTE vblank_sigbit = AllocSignal(-1);

	ULONG vblanksig = 1 << vblank_sigbit;
	ULONG portsig = 1L << mp->mp_SigBit;

	vblank_data.task = FindTask(NULL);
	vblank_data.signal = vblanksig;

	vblank_interrupt.is_Node.ln_Type = NT_INTERRUPT;
	vblank_interrupt.is_Node.ln_Pri = -60;
	vblank_interrupt.is_Node.ln_Name = "videoplayer-vblank-interrupt";
	vblank_interrupt.is_Data = (APTR)&vblank_data;
	vblank_interrupt.is_Code = VBlankServer;

	AddIntServer(INTB_VERTB, &vblank_interrupt);

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

	while (TRUE)
	{
		ULONG signal = Wait(vblanksig | portsig | SIGBREAKF_CTRL_C);
		if (signal & vblanksig)
		{
			handle_vblank_signal();
		}

		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 && wmsg->a314_Request.io_Command == A314_WRITE)
					handle_a314_write_completed();
				else if (msg == (struct Message *)cmsg && cmsg->a314_Request.io_Command == A314_RESET)
					handle_a314_reset_completed();
			}
		}

		if (signal & SIGBREAKF_CTRL_C)
		{
			close_after_next_frame_response = TRUE;

			if (!waiting_for_frame_response)
			{
				start_a314_reset();
				stream_closed = TRUE;
			}
		}

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

	RemIntServer(INTB_VERTB, &vblank_interrupt);
	FreeSignal(vblank_sigbit);
	CloseScreen(screen);

fail_out3:
	if (bpl_ptr2)
		FreeMem(bpl_ptr2, 10240*4);
	if (bpl_ptr1)
		FreeMem(bpl_ptr1, 10240*4);

fail_out2:
	CloseDevice((struct IORequest *)cmsg);
	DeleteExtIO((struct IORequest *)rmsg);
	DeleteExtIO((struct IORequest *)wmsg);
fail_out1:
	DeleteExtIO((struct IORequest *)cmsg);
	DeletePort(mp);
	CloseLibrary((struct Library *)GfxBase);
	CloseLibrary((struct Library *)IntuitionBase);;
	return 0;
}


text: vc videoplayer.c vblank_server.asm -lamiga -o VideoPlayer


text:
	XDEF	_VBlankServer
	CODE

;struct VBlankData
;{
;	struct Task *task;
;	ULONG signal;
;};

	; a1 points to VBlankData structure
_VBlankServer:
	move.l	$4.w,a6
	move.l	4(a1),d0	; signal
	move.l	0(a1),a1	; task
	jsr	-324(a6)
	moveq	#0,d0
	rts


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

# Copyright (c) 2018 Niklas Ekström

import glob
import os
import select
import struct
import socket
import sys
import time

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))
        header += data
    (plen, pstream, ptype) = struct.unpack('=IIB', header)
    payload = b''
    while len(payload) < plen:
        data = drv.recv(plen - len(payload))
        payload += data
    return (header, payload)

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

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 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)

write_mem_q = []
current_write_mem_stream_id = 0

def enqueue_write_mem_req(stream_id, address, data):
    global current_write_mem_stream_id

    if current_write_mem_stream_id == 0:
        current_write_mem_stream_id = stream_id
        send_write_mem_req(address, data)
    else:
        write_mem_q.append((stream_id, address, data))

sessions = {}

class VideoPlayerSession(object):
    def __init__(self, stream_id):
        self.stream_id = stream_id

        self.reset_after = None
        self.received_bpl_ptrs = False

    def start(self):
        self.fns = sorted(glob.glob('/home/pi/player/her_dither3/*.ami'))
        self.fc = 0
        self.read_next_frame()

    def close(self):
        del sessions[self.stream_id]

    def massage_pal(self):
        new_pal = b''
        for i in range(16):
            creg, col = struct.unpack('>HH', self.pal[4*i:4*(i+1)])
            new_pal += struct.pack('>H', col)
        self.pal = new_pal

    def read_next_frame(self):
        if self.fc < len(self.fns):
            fn = self.fns[self.fc]
            with open(fn, 'rb') as f:
                self.pal = f.read(64)
                self.bpl_data = f.read(40960)
            self.massage_pal()
            self.fc += 1
        else:
            self.bpl_data = None
            self.pal = None

    def process_msg_data(self, data):
        if not self.received_bpl_ptrs:
            self.addresses = struct.unpack('>II', data)
            self.received_bpl_ptrs = True
        else:
            self.next_bpl = data[0]
            if self.bpl_data is None:
                send_data(self.stream_id, struct.pack('>H', 0))
            else:
                address = self.addresses[self.next_bpl]
                enqueue_write_mem_req(self.stream_id, address, self.bpl_data)

    def process_write_mem_res(self, data):
        send_data(self.stream_id, struct.pack('>H', 1) + self.pal)
        self.read_next_frame()

    def handle_timeout(self):
        if self.reset_after and self.reset_after < time.time():
            send_reset(self.stream_id)
            self.close()

    def fileno(self):
        return self.fd

def process_drv_write_mem_res(payload):
    global current_write_mem_stream_id

    stream_id = current_write_mem_stream_id

    if len(write_mem_q) != 0:
        current_write_mem_stream_id, address, data = write_mem_q.pop(0)
        send_write_mem_req(address, data)
    else:
        current_write_mem_stream_id = 0

    if stream_id in sessions:
        s = sessions[stream_id]
        s.process_write_mem_res(payload)

def process_drv_msg(stream_id, ptype, payload):
    if ptype == MSG_CONNECT:
        if payload == b'videoplayer':
            s = VideoPlayerSession(stream_id)
            sessions[stream_id] = s
            s.start()
            send_connect_response(stream_id, 0)
        else:
            send_connect_response(stream_id, 3)
    elif ptype == MSG_WRITE_MEM_RES:
        process_drv_write_mem_res(payload)
    elif stream_id in sessions:
        s = sessions[stream_id]

        if ptype == MSG_DATA:
            s.process_msg_data(payload)
        elif ptype == MSG_EOS:
            send_eos(s.stream_id)
            s.close()
        elif ptype == MSG_RESET:
            s.close()

done = False

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(b'videoplayer')
    header, payload = wait_for_msg()
    if payload[0] != 1:
        print('Unable to register videoplayer with driver')
        drv.close()
        done = True

rbuf = b''

if not done:
    print('Video player server is running')

while not done:
    sel_fds = [drv]
    if idx == -1:
        sel_fds.append(sys.stdin)
    rfd, wfd, xfd = select.select(sel_fds, [], [], 5.0)

    for fd in rfd:
        if fd == sys.stdin:
            line = sys.stdin.readline()
            if not line or line.startswith('quit'):
                drv.close()
                done = True
        elif fd == drv:
            buf = drv.recv(1024)
            if not buf:
                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)

    if not done:
        for s in sessions.values():
            s.handle_timeout()


text:
# VideoPlayer

The VideoPlayer service only works on the trapdoor version of A314,
and only if the trapdoor memory is chip memory.

You can download
[this zip file](https://www.dropbox.com/s/g5f5c4zf1x55vx3/her_dither3.zip?dl=0)
and unzip to /home/pi/player/her_dither3/*.ami, and then run VideoPlayer in
order to play a video clip.


text:
	XDEF	_check_a314_mapping
	CODE

CLOCK_PORT	equ	$dc0000

		; Must have executed Disable() and WaitBlit() before gets here.
		; Must be in CMEM bank (RegD or'ed with 8).

		; a0 = address
_check_a314_mapping:
		move.l	d2,-(a7)

		clr.l	d0
		lea.l	CLOCK_PORT,a1

		or.b	#$2,$2f(a1)	; Set autodetect bit in RegB
		or.w	d0,(a0)		; Read and write back word from/to (a0)
		and.b	#$d,$2f(a1)	; Clear autodetect bit in RegB

		lea.l	$2b(a1),a1
		move.b	#1,(a1)		; Copy autodetect address to RegA

		moveq	#6-1,d2
.loop:		ror.l	#4,d0
		moveq	#$f,d1
		and.b	(a1),d1
		or.b	d1,d0
		dbra	d2,.loop

		moveq	#20,d1
		rol.l	d1,d0

		move.l	(a7)+,d2
		rts


text:
#include <exec/types.h>

#include "device.h"

extern int probe_pi_interface(struct A314Device *dev);
extern void setup_pi_interface(struct A314Device *dev);

extern void read_from_r2a(struct A314Device *dev, UBYTE *dst, UBYTE offset, int length);
extern void write_to_a2r(struct A314Device *dev, UBYTE type, UBYTE stream_id, UBYTE length, UBYTE *data);

extern void read_pi_cap(struct A314Device *dev);
extern void write_amiga_cap(struct A314Device *dev);

extern void set_pi_irq(struct A314Device *dev);
extern void clear_amiga_irq(struct A314Device *dev);

extern ULONG a314base_translate_address(__reg("a6") struct A314Device *dev, __reg("a0") void *address);
extern ULONG a314base_alloc_mem(__reg("a6") struct A314Device *dev, __reg("d0") ULONG length);
extern void a314base_free_mem(__reg("a6") struct A314Device *dev, __reg("d0") ULONG address, __reg("d1") ULONG length);
extern void a314base_write_mem(__reg("a6") struct A314Device *dev, __reg("d0") ULONG address, __reg("a0") UBYTE *src, __reg("d1") ULONG length);
extern void a314base_read_mem(__reg("a6") struct A314Device *dev, __reg("a0") UBYTE *dst, __reg("d0") ULONG address, __reg("d1") ULONG length);


text:
#include <proto/exec.h>

#include "cmem.h"

#define SysBase (*(struct ExecBase **)4)

#define CLOCK_PORT_ADDRESS	0xdc0000
#define BASE_ADDRESS_LEN	6

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 write_cmem_safe(int index, UBYTE value)
{
	Disable();
	UBYTE prev_regd = read_cp_nibble(13);
	write_cp_nibble(13, prev_regd | 8);
	write_cp_nibble(index, value);
	write_cp_nibble(13, prev_regd);
	Enable();
}

UBYTE read_cmem_safe(int index)
{
	Disable();
	UBYTE prev_regd = read_cp_nibble(13);
	write_cp_nibble(13, prev_regd | 8);
	UBYTE value = read_cp_nibble(index);
	write_cp_nibble(13, prev_regd);
	Enable();
	return value;
}

void write_base_address(ULONG ba)
{
	ba |= 1;

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

	write_cp_nibble(0, 0);

	for (int i = BASE_ADDRESS_LEN - 1; i >= 0; i--)
	{
		ULONG v = (ba >> (i * 4)) & 0xf;
		write_cp_nibble(i, (UBYTE)v);
	}

	write_cp_nibble(13, prev_regd);
	Enable();
}

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

	write_cp_nibble(10, 0);

	ULONG flags = 0;
	for (int i = 0; i < 4; i++)
		flags |= ((ULONG)read_cp_nibble(10)) << (4 * i);

	write_cp_nibble(13, prev_regd);
	Enable();

	return flags;
}


text:
#ifndef __A314_DEVICE_H
#define __A314_DEVICE_H

#include <exec/types.h>
#include <exec/libraries.h>
#include <exec/tasks.h>
#include <exec/ports.h>
#include <exec/interrupts.h>
#include <libraries/dos.h>

#include "protocol.h"

struct A314Device
{
	struct Library lib; // sizeof(struct Library) == 34

	BPTR saved_seg_list;
	BOOL running;

#if defined(MODEL_TD) || defined(MODEL_FE)
	struct ComArea *ca; // offsetof(ca) == 40
#endif

	struct Task task; // offsetof(task) == 44

#if defined(MODEL_TD)
	ULONG bank_address[4];
	UWORD is_a600;

	ULONG fw_flags;
#endif

#if defined(MODEL_TD) || defined(MODEL_FE)
	struct Interrupt vertb_interrupt;
	struct Interrupt ports_interrupt;
#endif

#if defined(MODEL_CP)
	struct ComAreaPtrs cap;

	void *first_chunk;

	struct Interrupt exter_interrupt;
#endif

	struct MsgPort task_mp;

	struct List active_sockets;

	struct Socket *send_queue_head;
	struct Socket *send_queue_tail;

	UBYTE next_stream_id;
};

#if defined(MODEL_TD) || defined(MODEL_FE)
#define CAP_PTR(dev) (&(dev->ca->cap))
#elif defined(MODEL_CP)
#define CAP_PTR(dev) (&(dev->cap))
#endif

extern char device_name[];
extern char id_string[];

#endif /* __A314_DEVICE_H */


text:
#include <exec/types.h>
#include <exec/execbase.h>
#include <exec/devices.h>
#include <exec/errors.h>
#include <exec/ports.h>
#include <libraries/dos.h>
#include <proto/exec.h>

#include "device.h"
#include "debug.h"
#include "a314.h"
#include "startup.h"
#include "fix_mem_region.h"
#include "memory_allocator.h"
#include "pi_if.h"

#define SysBase (*(struct ExecBase **)4)

char device_name[] = A314_NAME;
char id_string[] = A314_NAME " 1.0 (25 Aug 2018)";

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

	// We are being called from InitResident() in initializers.asm.
	// MakeLibrary() was executed before we got here.

	dev->lib.lib_Node.ln_Type = NT_DEVICE;
	dev->lib.lib_Node.ln_Name = 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;

	dbg_init();
	dbg_info("Started");

	// AddDevice() is executed after we return.
	return &dev->lib;
}

static BPTR expunge(__reg("a6") struct A314Device *dev)
{
	// There is currently no support for unloading a314.device.

	if (TRUE) //dev->lib_OpenCnt != 0)
	{
		dev->lib.lib_Flags |= LIBF_DELEXP;
		return 0;
	}

	/*
	BPTR seg_list = saved_seg_list;
	Remove(&dev->lib_Node);
	FreeMem((char *)dev - dev->lib_NegSize, dev->lib_NegSize + dev->lib_PosSize);
	return seg_list;
	*/
}

static void open(__reg("a6") struct A314Device *dev, __reg("a1") struct A314_IORequest *ior, __reg("d0") ULONG unitnum, __reg("d1") ULONG flags)
{
	dbg_trace("Enter: open device");

	dev->lib.lib_OpenCnt++;

	if (dev->lib.lib_OpenCnt == 1 && !dev->running)
	{
		if (!task_start(dev))
		{
			ior->a314_Request.io_Error = IOERR_OPENFAIL;
			ior->a314_Request.io_Message.mn_Node.ln_Type = NT_REPLYMSG;
			dev->lib.lib_OpenCnt--;
			return;
		}
		dev->running = TRUE;
	}

	ior->a314_Request.io_Error = 0;
	ior->a314_Request.io_Message.mn_Node.ln_Type = NT_REPLYMSG;
}

static BPTR close(__reg("a6") struct A314Device *dev, __reg("a1") struct A314_IORequest *ior)
{
	dbg_trace("Enter: close device");

	ior->a314_Request.io_Device = NULL;
	ior->a314_Request.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 void begin_io(__reg("a6") struct A314Device *dev, __reg("a1") struct A314_IORequest *ior)
{
	dbg_trace("Enter: begin_io");

	PutMsg(&dev->task_mp, (struct Message *)ior);
	ior->a314_Request.io_Flags &= ~IOF_QUICK;
}

static ULONG abort_io(__reg("a6") struct A314Device *dev, __reg("a1") struct A314_IORequest *ior)
{
	// There is currently no support for aborting an IORequest.
	return IOERR_NOCMD;
}

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

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


text:
#include <exec/types.h>
#include <devices/timer.h>
#include <hardware/intbits.h>

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

#include <string.h>

#include "pi_if.h"
#include "debug.h"
#include "device.h"
#include "protocol.h"
#include "memory_allocator.h"

// Warning 367 says that an eight bit shift will ignore a byte value,
// which is correct, but intentional.
#pragma dontwarn 367

#define CLOCK_PORT_ADDRESS	0xd80001

#define REG_SRAM	0
#define REG_IRQ		1
#define REG_ADDR_LO	2
#define REG_ADDR_HI	3

#define REG_IRQ_SET	0x80
#define REG_IRQ_CLR	0x00
#define REG_IRQ_PI	0x02
#define REG_IRQ_CP	0x01

#define CP_REG_PTR(reg) ((volatile UBYTE *)(CLOCK_PORT_ADDRESS + (reg << 2)))

#define SysBase (*(struct ExecBase **)4)

extern void IntServer();

void set_pi_irq(struct A314Device *dev)
{
	*CP_REG_PTR(REG_IRQ) = REG_IRQ_SET | REG_IRQ_PI;
}

void clear_amiga_irq(struct A314Device *dev)
{
	*CP_REG_PTR(REG_IRQ) = REG_IRQ_CLR | REG_IRQ_CP;
}

void a314base_write_mem(__reg("a6") struct A314Device *dev, __reg("d0") ULONG address, __reg("a0") UBYTE *src, __reg("d1") ULONG length)
{
	dbg_trace("Enter a314base_write_mem, address=$l, length=$l", address, length);

	if (!length)
		return;

	Disable();

	*CP_REG_PTR(REG_ADDR_LO) = address & 0xff;
	*CP_REG_PTR(REG_ADDR_HI) = (address >> 8) & 0xff;

	volatile UBYTE *p = CP_REG_PTR(REG_SRAM);

	for (int i = 0; i < length; i++)
		*p = *src++;

	Enable();
}

void a314base_read_mem(__reg("a6") struct A314Device *dev, __reg("a0") UBYTE *dst, __reg("d0") ULONG address, __reg("d1") ULONG length)
{
	dbg_trace("Enter a314base_read_mem, address=$l, length=$l", address, length);

	if (!length)
		return;

	Disable();

	*CP_REG_PTR(REG_ADDR_LO) = address & 0xff;
	*CP_REG_PTR(REG_ADDR_HI) = (address >> 8) & 0xff;

	volatile UBYTE *p = CP_REG_PTR(REG_SRAM);

	for (int i = 0; i < length; i++)
		*dst++ = *p;

	Enable();
}

void read_pi_cap(struct A314Device *dev)
{
	Disable();

	*CP_REG_PTR(REG_ADDR_LO) = (CAP_BASE + 0) & 0xff;
	*CP_REG_PTR(REG_ADDR_HI) = ((CAP_BASE + 0) >> 8) & 0xff;

	dev->cap.r2a_tail = *CP_REG_PTR(REG_SRAM);
	dev->cap.a2r_head = *CP_REG_PTR(REG_SRAM);

	Enable();
}

void write_amiga_cap(struct A314Device *dev)
{
	Disable();

	*CP_REG_PTR(REG_ADDR_LO) = (CAP_BASE + 2) & 0xff;
	*CP_REG_PTR(REG_ADDR_HI) = ((CAP_BASE + 2) >> 8) & 0xff;

	*CP_REG_PTR(REG_SRAM) = dev->cap.a2r_tail;
	*CP_REG_PTR(REG_SRAM) = dev->cap.r2a_head;

	Enable();
}

void read_from_r2a(struct A314Device *dev, UBYTE *dst, UBYTE offset, int length)
{
	if (!length)
		return;

	volatile UBYTE *p = CP_REG_PTR(REG_SRAM);

	Disable();

	*CP_REG_PTR(REG_ADDR_LO) = (R2A_BASE + offset) & 0xff;
	*CP_REG_PTR(REG_ADDR_HI) = ((R2A_BASE + offset) >> 8) & 0xff;

	for (int i = 0; i < length; i++)
	{
		*dst++ = *p;
		offset++;
		if (offset == 0)
		{
			*CP_REG_PTR(REG_ADDR_LO) = (R2A_BASE + 0) & 0xff;
			*CP_REG_PTR(REG_ADDR_HI) = ((R2A_BASE + 0) >> 8) & 0xff;
		}
	}

	Enable();
}

void write_to_a2r(struct A314Device *dev, UBYTE type, UBYTE stream_id, UBYTE length, UBYTE *data)
{
	dbg_trace("Enter: write_to_a2r, type=$b, stream_id=$b, length=$b", type, stream_id, length);

	struct PktHdr hdr = {length, type, stream_id};

	Disable();

	UBYTE offset = dev->cap.a2r_tail;

	*CP_REG_PTR(REG_ADDR_LO) = (A2R_BASE + offset) & 0xff;
	*CP_REG_PTR(REG_ADDR_HI) = ((A2R_BASE + offset) >> 8) & 0xff;

	volatile UBYTE *p = CP_REG_PTR(REG_SRAM);

	for (int i = 0; i < sizeof(hdr); i++)
	{
		*p = ((UBYTE *)&hdr)[i];
		offset++;
		if (offset == 0)
		{
			*CP_REG_PTR(REG_ADDR_LO) = (A2R_BASE + 0) & 0xff;
			*CP_REG_PTR(REG_ADDR_HI) = ((A2R_BASE + 0) >> 8) & 0xff;
		}
	}

	for (int i = 0; i < length; i++)
	{
		*p = *data++;
		offset++;
		if (offset == 0)
		{
			*CP_REG_PTR(REG_ADDR_LO) = (A2R_BASE + 0) & 0xff;
			*CP_REG_PTR(REG_ADDR_HI) = ((A2R_BASE + 0) >> 8) & 0xff;
		}
	}

	dev->cap.a2r_tail = offset;

	Enable();
}

static int probe_pi_interface_once(struct A314Device *dev)
{
	int found = FALSE;

	Disable();
	*CP_REG_PTR(REG_ADDR_HI) = CAP_BASE >> 8;
	*CP_REG_PTR(REG_ADDR_LO) = 6;
	*CP_REG_PTR(REG_SRAM) = 0x99;
	*CP_REG_PTR(REG_SRAM) = 0xbd;
	*CP_REG_PTR(REG_ADDR_LO) = 7;
	if (*CP_REG_PTR(REG_SRAM) == 0xbd)
	{
		*CP_REG_PTR(REG_ADDR_LO) = 6;
		if (*CP_REG_PTR(REG_SRAM) == 0x99)
			found = TRUE;
	}
	Enable();

	return found;
}

static int delay_1s()
{
	int success = FALSE;

	struct timerequest *tr = AllocMem(sizeof(struct timerequest), MEMF_CLEAR);
	if (!tr)
		goto fail1;

	struct MsgPort *mp = AllocMem(sizeof(struct MsgPort), MEMF_CLEAR);
	if (!mp)
		goto fail2;

	mp->mp_Node.ln_Type = NT_MSGPORT;
	mp->mp_Flags = PA_SIGNAL;
	mp->mp_SigTask = FindTask(NULL);
	mp->mp_SigBit = SIGB_SINGLE;
	NewList(&mp->mp_MsgList);

	if (OpenDevice(TIMERNAME, UNIT_VBLANK, (struct IORequest *)tr, 0))
		goto fail3;

	tr->tr_node.io_Message.mn_Node.ln_Type = NT_REPLYMSG;
	tr->tr_node.io_Message.mn_ReplyPort = mp;
	tr->tr_node.io_Message.mn_Length = sizeof(sizeof(struct timerequest));
	tr->tr_node.io_Command = TR_ADDREQUEST;
	tr->tr_time.tv_secs = 1;
	DoIO((struct IORequest *)tr);

	success = TRUE;

	CloseDevice((struct IORequest *)tr);

fail3:
	FreeMem(mp, sizeof(struct MsgPort));

fail2:
	FreeMem(tr, sizeof(struct timerequest));

fail1:
	return success;
}

int probe_pi_interface(struct A314Device *dev)
{
	for (int i = 7; i >= 0; i--)
	{
		if (probe_pi_interface_once(dev))
			return TRUE;

		if (i == 0 || !delay_1s())
			break;
	}
	return FALSE;
}

static void clear_cap(struct A314Device *dev)
{
	Disable();

	*CP_REG_PTR(REG_ADDR_LO) = CAP_BASE & 0xff;
	*CP_REG_PTR(REG_ADDR_HI) = (CAP_BASE >> 8) & 0xff;

	for (int i = 0; i < 4; i++)
		*CP_REG_PTR(REG_SRAM) = 0;

	Enable();
}

static void update_restart_counter(struct A314Device *dev)
{
	Disable();

	*CP_REG_PTR(REG_ADDR_LO) = (CAP_BASE + 4) & 0xff;
	*CP_REG_PTR(REG_ADDR_HI) = ((CAP_BASE + 4) >> 8) & 0xff;

	UBYTE restart_counter = *CP_REG_PTR(REG_SRAM);

	*CP_REG_PTR(REG_ADDR_LO) = (CAP_BASE + 4) & 0xff;
	*CP_REG_PTR(REG_ADDR_HI) = ((CAP_BASE + 4) >> 8) & 0xff;

	*CP_REG_PTR(REG_SRAM) = restart_counter + 1;
	*CP_REG_PTR(REG_SRAM) = 0xa3;
	*CP_REG_PTR(REG_SRAM) = 0x14;

	Enable();
}

static void add_int6_handler(struct A314Device *dev)
{
	memset(&dev->exter_interrupt, 0, sizeof(struct Interrupt));
	dev->exter_interrupt.is_Node.ln_Type = NT_INTERRUPT;
	dev->exter_interrupt.is_Node.ln_Pri = 0;
	dev->exter_interrupt.is_Node.ln_Name = device_name;
	dev->exter_interrupt.is_Data = (APTR)&dev->task;
	dev->exter_interrupt.is_Code = IntServer;

	AddIntServer(INTB_EXTER, &dev->exter_interrupt);
}

void setup_pi_interface(struct A314Device *dev)
{
	init_memory_allocator(dev);

	memset(&dev->cap, 0, sizeof(dev->cap));

	clear_cap(dev);

	clear_amiga_irq(dev);

	add_int6_handler(dev);

	// FIXME: The current scheme is that a314d is notified that a314.device
	// has restarted through a restart counter variable.
	// Another option would be to have an event that specifically signals that
	// a314.device has restarted.
	// A third option is for the interface to monitor the RESET_n signal,
	// and notify a314d that the Amiga has been reset/restarted.

	update_restart_counter(dev);

	set_pi_irq(dev);
}


text:
vc -DMODEL_TD=1 romtag.asm driver_task.c debug.c device.c startup.c sockets.c pi_if_td.c int_server_td.asm fix_mem_region.c cmem.c check_a314_mapping.asm -O3 -nostdlib -o a314-td.device
vc -DMODEL_FE=1 romtag.asm driver_task.c debug.c device.c startup.c sockets.c pi_if_fe.c int_server_fe.asm fix_mem_region.c -O3 -nostdlib -o a314-fe.device
vc -DMODEL_CP=1 romtag.asm driver_task.c debug.c device.c startup.c sockets.c pi_if_cp.c int_server_cp.asm memory_allocator.c -O3 -nostdlib -o a314-cp.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/a314.device", 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, ...);

#if !DEBUG

#define dbg_error(...)
#define dbg_warning(...)
#define dbg_info(...)
#define dbg_trace(...)

#else

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

#define dbg_error(...) dbg(__VA_ARGS__)
#define dbg_warning(...) dbg(__VA_ARGS__)
#define dbg_info(...) dbg(__VA_ARGS__)
#define dbg_trace(...) dbg(__VA_ARGS__)

#endif


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

#include <exec/types.h>
#include <exec/interrupts.h>
#include <exec/lists.h>
#include <exec/memory.h>
#include <exec/nodes.h>
#include <exec/ports.h>
#include <exec/io.h>
#include <exec/errors.h>
#include <exec/libraries.h>
#include <exec/devices.h>
#include <exec/execbase.h>

#include <libraries/dos.h>

#include <proto/exec.h>

#include <string.h>

#include "a314.h"
#include "debug.h"
#include "device.h"
#include "pi_if.h"
#include "protocol.h"
#include "sockets.h"
#include "startup.h"

#if defined(MODEL_TD)
#include "cmem.h"
#elif defined(MODEL_FE)
#include "handshake_fe.h"
#endif

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

#define SysBase (*(struct ExecBase **)4)

static int used_in_r2a(struct ComAreaPtrs *cap)
{
	return (cap->r2a_tail - cap->r2a_head) & 255;
}

static int used_in_a2r(struct ComAreaPtrs *cap)
{
	return (cap->a2r_tail - cap->a2r_head) & 255;
}

static BOOL room_in_a2r(struct ComAreaPtrs *cap, int len)
{
	return used_in_a2r(cap) + 3 + len <= 255;
}

static void close_socket(struct A314Device *dev, struct Socket *s, BOOL should_send_reset)
{
	dbg_trace("Enter: close_socket");

	if (s->pending_connect != NULL)
	{
		struct A314_IORequest *ior = s->pending_connect;
		ior->a314_Request.io_Error = A314_CONNECT_RESET;
		ReplyMsg((struct Message *)ior);

		s->pending_connect = NULL;
	}

	if (s->pending_read != NULL)
	{
		struct A314_IORequest *ior = s->pending_read;
		ior->a314_Length = 0;
		ior->a314_Request.io_Error = A314_READ_RESET;
		ReplyMsg((struct Message *)ior);

		s->pending_read = NULL;
	}

	if (s->pending_write != NULL)
	{
		struct A314_IORequest *ior = s->pending_write;
		ior->a314_Length = 0;
		ior->a314_Request.io_Error = A314_WRITE_RESET; // A314_EOS_RESET == A314_WRITE_RESET
		ReplyMsg((struct Message *)ior);

		s->pending_write = NULL;
	}

	if (s->rq_head != NULL)
	{
		struct QueuedData *qd = s->rq_head;
		while (qd != NULL)
		{
			struct QueuedData *next = qd->next;
			FreeMem(qd, sizeof(struct QueuedData) + qd->length);
			qd = next;
		}
		s->rq_head = NULL;
		s->rq_tail = NULL;
	}

	remove_from_send_queue(dev, s);

	// No operations can be pending when SOCKET_CLOSED is set.
	// However, may not be able to delete socket yet, because is waiting to send PKT_RESET.
	s->flags |= SOCKET_CLOSED;

	BOOL should_delete_socket = TRUE;

	if (should_send_reset)
	{
		if (dev->send_queue_head == NULL && room_in_a2r(CAP_PTR(dev), 0))
		{
			write_to_a2r(dev, PKT_RESET, s->stream_id, 0, NULL);
		}
		else
		{
			s->flags |= SOCKET_SHOULD_SEND_RESET;
			add_to_send_queue(dev, s, 0);
			should_delete_socket = FALSE;
		}
	}

	if (should_delete_socket)
		delete_socket(dev, s);
}

static void handle_pkt_connect_response(struct A314Device *dev, UBYTE offset, UBYTE length, struct Socket *s)
{
	dbg_trace("Enter: handle_pkt_connect_response");

	if (s->pending_connect == NULL)
	{
		dbg_error("SERIOUS ERROR: received a CONNECT RESPONSE even though no connect was pending");
		// Should reset stream?
	}
	else if (length != 1)
	{
		dbg_error("SERIOUS ERROR: received a CONNECT RESPONSE whose length was not 1");
		// Should reset stream?
	}
	else
	{
		UBYTE result;
		read_from_r2a(dev, &result, offset, 1);

		if (result == 0)
		{
			struct A314_IORequest *ior = s->pending_connect;
			ior->a314_Request.io_Error = A314_CONNECT_OK;
			ReplyMsg((struct Message *)ior);

			s->pending_connect = NULL;
		}
		else
		{
			struct A314_IORequest *ior = s->pending_connect;
			ior->a314_Request.io_Error = A314_CONNECT_UNKNOWN_SERVICE;
			ReplyMsg((struct Message *)ior);

			s->pending_connect = NULL;

			close_socket(dev, s, FALSE);
		}
	}
}

static void handle_pkt_data(struct A314Device *dev, UBYTE offset, UBYTE length, struct Socket *s)
{
	dbg_trace("Enter: handle_pkt_data");

	if (s->pending_read != NULL)
	{
		struct A314_IORequest *ior = s->pending_read;

		if (ior->a314_Length < length)
			close_socket(dev, s, TRUE);
		else
		{
			UBYTE *dst = ior->a314_Buffer;
			read_from_r2a(dev, dst, offset, length);
			ior->a314_Length = length;
			ior->a314_Request.io_Error = A314_READ_OK;
			ReplyMsg((struct Message *)ior);

			s->pending_read = NULL;
		}
	}
	else
	{
		struct QueuedData *qd = (struct QueuedData *)AllocMem(sizeof(struct QueuedData) + length, 0);
		qd->next = NULL,
		qd->length = length;

		UBYTE *dst = qd->data;
		read_from_r2a(dev, dst, offset, length);

		if (s->rq_head == NULL)
			s->rq_head = qd;
		else
			s->rq_tail->next = qd;
		s->rq_tail = qd;
	}
}

static void handle_pkt_eos(struct A314Device *dev, struct Socket *s)
{
	dbg_trace("Enter: handle_pkt_eos");

	s->flags |= SOCKET_RCVD_EOS_FROM_RPI;

	if (s->pending_read != NULL)
	{
		struct A314_IORequest *ior = s->pending_read;
		ior->a314_Length = 0;
		ior->a314_Request.io_Error = A314_READ_EOS;
		ReplyMsg((struct Message *)ior);

		s->pending_read = NULL;

		s->flags |= SOCKET_SENT_EOS_TO_APP;

		if (s->flags & SOCKET_SENT_EOS_TO_RPI)
			close_socket(dev, s, FALSE);
	}
}

static void handle_r2a_packet(struct A314Device *dev, UBYTE type, UBYTE stream_id, UBYTE offset, UBYTE length)
{
	dbg_trace("Enter: handle_r2a_packet, type=$b, stream_id=$b, offset=$b, length=$b");

	struct Socket *s = find_socket_by_stream_id(dev, stream_id);

	if (s != NULL && type == PKT_RESET)
	{
		close_socket(dev, s, FALSE);
		return;
	}

	if (s == NULL || (s->flags & SOCKET_CLOSED))
	{
		// Ignore this packet. The only packet that can do anything useful on a closed
		// channel is CONNECT, which is not handled at this time.
		return;
	}

	if (type == PKT_CONNECT_RESPONSE)
	{
		handle_pkt_connect_response(dev, offset, length, s);
	}
	else if (type == PKT_DATA)
	{
		handle_pkt_data(dev, offset, length, s);
	}
	else if (type == PKT_EOS)
	{
		handle_pkt_eos(dev, s);
	}
}

static void handle_packets_received_r2a(struct A314Device *dev)
{
	dbg_trace("Enter: handle_packets_received_r2a");

	while (used_in_r2a(CAP_PTR(dev)) != 0)
	{
		UBYTE index = CAP_PTR(dev)->r2a_head;

		struct PktHdr hdr;
		read_from_r2a(dev, (UBYTE *)&hdr, index, sizeof(hdr));

		index += sizeof(hdr);

		handle_r2a_packet(dev, hdr.type, hdr.stream_id, index, hdr.length);

		index += hdr.length;
		CAP_PTR(dev)->r2a_head = index;
	}
}

static void handle_room_in_a2r(struct A314Device *dev)
{
	dbg_trace("Enter: handle_room_in_a2r");

	struct ComAreaPtrs *cap = CAP_PTR(dev);

	while (dev->send_queue_head != NULL)
	{
		struct Socket *s = dev->send_queue_head;

		if (!room_in_a2r(cap, s->send_queue_required_length))
			break;

		remove_from_send_queue(dev, s);

		if (s->pending_connect != NULL)
		{
			struct A314_IORequest *ior = s->pending_connect;
			int len = ior->a314_Length;
			write_to_a2r(dev, PKT_CONNECT, s->stream_id, (UBYTE)len, ior->a314_Buffer);
		}
		else if (s->pending_write != NULL)
		{
			struct A314_IORequest *ior = s->pending_write;
			int len = ior->a314_Length;

			if (ior->a314_Request.io_Command == A314_WRITE)
			{
				write_to_a2r(dev, PKT_DATA, s->stream_id, (UBYTE)len, ior->a314_Buffer);

				ior->a314_Request.io_Error = A314_WRITE_OK;
				ReplyMsg((struct Message *)ior);

				s->pending_write = NULL;
			}
			else // A314_EOS
			{
				write_to_a2r(dev, PKT_EOS, s->stream_id, 0, NULL);

				ior->a314_Request.io_Error = A314_EOS_OK;
				ReplyMsg((struct Message *)ior);

				s->pending_write = NULL;

				s->flags |= SOCKET_SENT_EOS_TO_RPI;

				if (s->flags & SOCKET_SENT_EOS_TO_APP)
					close_socket(dev, s, FALSE);
			}
		}
		else if (s->flags & SOCKET_SHOULD_SEND_RESET)
		{
			write_to_a2r(dev, PKT_RESET, s->stream_id, 0, NULL);
			delete_socket(dev, s);
		}
		else
		{
			dbg_error("SERIOUS ERROR: Socket was in send queue but has nothing to send");
		}
	}
}

static void handle_app_connect(struct A314Device *dev, struct A314_IORequest *ior, struct Socket *s)
{
	dbg_trace("Enter: handle_app_connect");

	if (s != NULL)
	{
		ior->a314_Request.io_Error = A314_CONNECT_SOCKET_IN_USE;
		ReplyMsg((struct Message *)ior);
	}
	else if (ior->a314_Length + 3 > 255)
	{
		ior->a314_Request.io_Error = A314_CONNECT_RESET;
		ReplyMsg((struct Message *)ior);
	}
	else
	{
		s = create_socket(dev, ior->a314_Request.io_Message.mn_ReplyPort->mp_SigTask, ior->a314_Socket);

		s->pending_connect = ior;
		s->flags = 0;

		int len = ior->a314_Length;
		if (dev->send_queue_head == NULL && room_in_a2r(CAP_PTR(dev), len))
		{
			write_to_a2r(dev, PKT_CONNECT, s->stream_id, (UBYTE)len, ior->a314_Buffer);
		}
		else
		{
			add_to_send_queue(dev, s, len);
		}
	}
}

static void handle_app_read(struct A314Device *dev, struct A314_IORequest *ior, struct Socket *s)
{
	dbg_trace("Received a READ request from application");

	if (s == NULL || (s->flags & SOCKET_CLOSED))
	{
		ior->a314_Length = 0;
		ior->a314_Request.io_Error = A314_READ_RESET;
		ReplyMsg((struct Message *)ior);
	}
	else
	{
		if (s->pending_connect != NULL || s->pending_read != NULL)
		{
			ior->a314_Length = 0;
			ior->a314_Request.io_Error = A314_READ_RESET;
			ReplyMsg((struct Message *)ior);

			close_socket(dev, s, TRUE);
		}
		else if (s->rq_head != NULL)
		{
			struct QueuedData *qd = s->rq_head;
			int len = qd->length;

			if (ior->a314_Length < len)
			{
				ior->a314_Length = 0;
				ior->a314_Request.io_Error = A314_READ_RESET;
				ReplyMsg((struct Message *)ior);

				close_socket(dev, s, TRUE);
			}
			else
			{
				s->rq_head = qd->next;
				if (s->rq_head == NULL)
					s->rq_tail = NULL;

				memcpy(ior->a314_Buffer, qd->data, len);
				FreeMem(qd, sizeof(struct QueuedData) + len);

				ior->a314_Length = len;
				ior->a314_Request.io_Error = A314_READ_OK;
				ReplyMsg((struct Message *)ior);
			}
		}
		else if (s->flags & SOCKET_RCVD_EOS_FROM_RPI)
		{
			ior->a314_Length = 0;
			ior->a314_Request.io_Error = A314_READ_EOS;
			ReplyMsg((struct Message *)ior);

			s->flags |= SOCKET_SENT_EOS_TO_APP;

			if (s->flags & SOCKET_SENT_EOS_TO_RPI)
				close_socket(dev, s, FALSE);
		}
		else
			s->pending_read = ior;
	}
}

static void handle_app_write(struct A314Device *dev, struct A314_IORequest *ior, struct Socket *s)
{
	dbg_trace("Received a WRITE request from application");

	if (s == NULL || (s->flags & SOCKET_CLOSED))
	{
		ior->a314_Length = 0;
		ior->a314_Request.io_Error = A314_WRITE_RESET;
		ReplyMsg((struct Message *)ior);
	}
	else
	{
		int len = ior->a314_Length;
		if (s->pending_connect != NULL || s->pending_write != NULL || (s->flags & SOCKET_RCVD_EOS_FROM_APP) || len + 3 > 255)
		{
			ior->a314_Length = 0;
			ior->a314_Request.io_Error = A314_WRITE_RESET;
			ReplyMsg((struct Message *)ior);

			close_socket(dev, s, TRUE);
		}
		else
		{
			if (dev->send_queue_head == NULL && room_in_a2r(CAP_PTR(dev), len))
			{
				write_to_a2r(dev, PKT_DATA, s->stream_id, (UBYTE)len, ior->a314_Buffer);

				ior->a314_Request.io_Error = A314_WRITE_OK;
				ReplyMsg((struct Message *)ior);
			}
			else
			{
				s->pending_write = ior;
				add_to_send_queue(dev, s, len);
			}
		}
	}
}

static void handle_app_eos(struct A314Device *dev, struct A314_IORequest *ior, struct Socket *s)
{
	dbg_trace("Received an EOS request from application");

	if (s == NULL || (s->flags & SOCKET_CLOSED))
	{
		ior->a314_Request.io_Error = A314_EOS_RESET;
		ReplyMsg((struct Message *)ior);
	}
	else
	{
		if (s->pending_connect != NULL || s->pending_write != NULL || (s->flags & SOCKET_RCVD_EOS_FROM_APP))
		{
			ior->a314_Length = 0;
			ior->a314_Request.io_Error = A314_EOS_RESET;
			ReplyMsg((struct Message *)ior);

			close_socket(dev, s, TRUE);
		}
		else
		{
			s->flags |= SOCKET_RCVD_EOS_FROM_APP;

			if (dev->send_queue_head == NULL && room_in_a2r(CAP_PTR(dev), 0))
			{
				write_to_a2r(dev, PKT_EOS, s->stream_id, 0, NULL);

				ior->a314_Request.io_Error = A314_EOS_OK;
				ReplyMsg((struct Message *)ior);

				s->flags |= SOCKET_SENT_EOS_TO_RPI;

				if (s->flags & SOCKET_SENT_EOS_TO_APP)
					close_socket(dev, s, FALSE);
			}
			else
			{
				s->pending_write = ior;
				add_to_send_queue(dev, s, 0);
			}
		}
	}
}

static void handle_app_reset(struct A314Device *dev, struct A314_IORequest *ior, struct Socket *s)
{
	dbg_trace("Enter: handle_app_reset");

	if (s == NULL || (s->flags & SOCKET_CLOSED))
	{
		ior->a314_Request.io_Error = A314_RESET_OK;
		ReplyMsg((struct Message *)ior);
	}
	else
	{
		ior->a314_Request.io_Error = A314_RESET_OK;
		ReplyMsg((struct Message *)ior);

		close_socket(dev, s, TRUE);
	}
}

static void handle_app_request(struct A314Device *dev, struct A314_IORequest *ior)
{
	dbg_trace("Enter: handle_app_request");

	struct Socket *s = find_socket(dev, ior->a314_Request.io_Message.mn_ReplyPort->mp_SigTask, ior->a314_Socket);

	switch (ior->a314_Request.io_Command)
	{
	case A314_CONNECT:
		handle_app_connect(dev, ior, s);
		break;
	case A314_READ:
		handle_app_read(dev, ior, s);
		break;
	case A314_WRITE:
		handle_app_write(dev, ior, s);
		break;
	case A314_EOS:
		handle_app_eos(dev, ior, s);
		break;
	case A314_RESET:
		handle_app_reset(dev, ior, s);
		break;
	default:
		ior->a314_Request.io_Error = IOERR_NOCMD;
		ReplyMsg((struct Message *)ior);
		break;
	}
}

#if defined(MODEL_TD)

void task_main()
{
	struct A314Device *dev = (struct A314Device *)FindTask(NULL)->tc_UserData;
	struct ComAreaPtrs *cap = CAP_PTR(dev);

	while (TRUE)
	{
		dbg_trace("Invoking Wait()");

		ULONG signal = Wait(SIGF_MSGPORT | SIGF_INT);

		dbg_trace("Returned from Wait() with signal=$l", signal);

		UBYTE prev_a2r_tail = cap->a2r_tail;
		UBYTE prev_r2a_head = cap->r2a_head;

		if (signal & SIGF_MSGPORT)
		{
			write_cmem_safe(A_ENABLE_ADDRESS, 0);

			struct Message *msg;
			while (msg = GetMsg(&dev->task_mp))
				handle_app_request(dev, (struct A314_IORequest *)msg);
		}

		UBYTE a_enable = 0;
		while (a_enable == 0)
		{
			handle_packets_received_r2a(dev);
			handle_room_in_a2r(dev);

			UBYTE r_events = 0;
			if (cap->a2r_tail != prev_a2r_tail)
				r_events |= R_EVENT_A2R_TAIL;
			if (cap->r2a_head != prev_r2a_head)
				r_events |= R_EVENT_R2A_HEAD;

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

			if (cap->r2a_head == cap->r2a_tail)
			{
				if (dev->send_queue_head == NULL)
					a_enable = A_EVENT_R2A_TAIL;
				else if (!room_in_a2r(cap, dev->send_queue_head->send_queue_required_length))
					a_enable = A_EVENT_R2A_TAIL | A_EVENT_A2R_HEAD;

				if (a_enable != 0)
				{
					write_cp_nibble(A_ENABLE_ADDRESS, a_enable);
					if (r_events != 0)
						write_cp_nibble(R_EVENTS_ADDRESS, r_events);
				}
			}

			write_cp_nibble(13, prev_regd);
			Enable();
		}
	}

	// There is currently no way to unload a314.device.
}

#elif defined(MODEL_FE)

void task_main()
{
	struct A314Device *dev = (struct A314Device *)FindTask(NULL)->tc_UserData;
	struct ComAreaPtrs *cap = CAP_PTR(dev);

	while (TRUE)
	{
		dbg_trace("Invoking Wait()");

		ULONG signal = Wait(SIGF_MSGPORT | SIGF_INT);

		dbg_trace("Returned from Wait() with signal=$l", signal);

		// Clear enable and event interrupt bits.
		dev->ca->interrupt = 0x0303;

		UBYTE prev_a2r_tail = cap->a2r_tail;
		UBYTE prev_r2a_head = cap->r2a_head;

		if (signal & SIGF_MSGPORT)
		{
			struct Message *msg;
			while (msg = GetMsg(&dev->task_mp))
				handle_app_request(dev, (struct A314_IORequest *)msg);
		}

		UWORD set_int_bits = 0;
		while (!set_int_bits)
		{
			handle_packets_received_r2a(dev);
			handle_room_in_a2r(dev);

			if (cap->r2a_head == cap->r2a_tail)
			{
				if (dev->send_queue_head == NULL)
					set_int_bits = IRQ_R2A_TAIL << 8;
				else if (!room_in_a2r(cap, dev->send_queue_head->send_queue_required_length))
					set_int_bits = (IRQ_R2A_TAIL | IRQ_A2R_HEAD) << 8;
			}
		}

		if (cap->a2r_tail != prev_a2r_tail)
			set_int_bits |= IRQ_A2R_TAIL;
		if (cap->r2a_head != prev_r2a_head)
			set_int_bits |= IRQ_R2A_HEAD;

		dev->ca->interrupt = 0x8000 | set_int_bits;
	}

	// There is currently no way to unload a314.device.
}

#elif defined(MODEL_CP)

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

	while (TRUE)
	{
		dbg_trace("Invoking Wait()");

		ULONG signal = Wait(SIGF_MSGPORT | SIGF_INT);

		dbg_trace("Returned from Wait() with signal=$l", signal);

		read_pi_cap(dev);

		dbg_trace("Read CAP, r2a_tail=$b, a2r_head=$b", dev->cap.r2a_tail, dev->cap.a2r_head);

		UBYTE prev_a2r_tail = dev->cap.a2r_tail;
		UBYTE prev_r2a_head = dev->cap.r2a_head;

		// TODO: Perhaps have two separate events for r2a_tail and a2r_head.
		//       Perhaps also have enable/disable those events separately.

		if (signal & SIGF_MSGPORT)
		{
			struct Message *msg;
			while (msg = GetMsg(&dev->task_mp))
				handle_app_request(dev, (struct A314_IORequest *)msg);
		}

		// TODO: May want to read r2a_tail and a2r_head from shared memory again,
		// and process anything left, in order to interrupt less.

		handle_packets_received_r2a(dev);
		handle_room_in_a2r(dev);

		if (prev_a2r_tail != dev->cap.a2r_tail || prev_r2a_head != dev->cap.r2a_head)
		{
			dbg_trace("Writing CAP, a2r_tail=$b, r2a_head=$b", dev->cap.a2r_tail, dev->cap.r2a_head);

			write_amiga_cap(dev);

			set_pi_irq(dev);
		}
	}

	// There is currently no way to unload a314.device.
}

#endif