From 46b989757e684f174d5216884b63083a182b910f Mon Sep 17 00:00:00 2001 From: beeanyew Date: Tue, 18 May 2021 13:33:06 +0200 Subject: [PATCH] Adapt a314eth to work on PiStorm + A314 emulation Please don't ask me how this works, I have no idea. --- a314/files_pi/eth-config-pi/rc.local | 3 + a314/files_pi/eth-config-pi/tap0 | 9 + a314/software-amiga/a314eth.device | Bin 6468 -> 5896 bytes a314/software-amiga/eth-config-amiga/A314Eth | 3 + .../eth-config-amiga/name_resolution | 1 + a314/software-amiga/eth-config-amiga/routes | 1 + .../software-amiga/ethernet_pistorm/README.md | 34 + .../software-amiga/ethernet_pistorm/build.bat | 1 + a314/software-amiga/ethernet_pistorm/device.c | 766 ++++++++++++++++++ .../ethernet_pistorm/ethernet.py | 264 ++++++ .../ethernet_pistorm/romtag.asm | 33 + a314/software-amiga/ethernet_pistorm/sana2.h | 261 ++++++ .../software-amiga/ethernet_pistorm/tagitem.h | 77 ++ a314/software-amiga/remote-mouse | Bin 6712 -> 0 bytes a314/software-amiga/remotewb | Bin 13340 -> 0 bytes 15 files changed, 1453 insertions(+) create mode 100644 a314/files_pi/eth-config-pi/rc.local create mode 100644 a314/files_pi/eth-config-pi/tap0 create mode 100644 a314/software-amiga/eth-config-amiga/A314Eth create mode 100644 a314/software-amiga/eth-config-amiga/name_resolution create mode 100644 a314/software-amiga/eth-config-amiga/routes create mode 100644 a314/software-amiga/ethernet_pistorm/README.md create mode 100644 a314/software-amiga/ethernet_pistorm/build.bat create mode 100644 a314/software-amiga/ethernet_pistorm/device.c create mode 100644 a314/software-amiga/ethernet_pistorm/ethernet.py create mode 100644 a314/software-amiga/ethernet_pistorm/romtag.asm create mode 100644 a314/software-amiga/ethernet_pistorm/sana2.h create mode 100644 a314/software-amiga/ethernet_pistorm/tagitem.h delete mode 100644 a314/software-amiga/remote-mouse delete mode 100644 a314/software-amiga/remotewb diff --git a/a314/files_pi/eth-config-pi/rc.local b/a314/files_pi/eth-config-pi/rc.local new file mode 100644 index 0000000..f7581f3 --- /dev/null +++ b/a314/files_pi/eth-config-pi/rc.local @@ -0,0 +1,3 @@ +iptables -t nat -A POSTROUTING -o wlan0 -j MASQUERADE +iptables -A FORWARD -i wlan0 -o tap0 -m state --state RELATED,ESTABLISHED -j ACCEPT +iptables -A FORWARD -i tap0 -o wlan0 -j ACCEPT diff --git a/a314/files_pi/eth-config-pi/tap0 b/a314/files_pi/eth-config-pi/tap0 new file mode 100644 index 0000000..372406d --- /dev/null +++ b/a314/files_pi/eth-config-pi/tap0 @@ -0,0 +1,9 @@ +auto tap0 + +iface tap0 inet static + address 192.168.2.1 + netmask 255.255.255.0 + broadcast 192.168.2.255 + pre-up ip tuntap add tap0 mode tap user pi group pi + up ip link set dev tap0 up + post-down ip link del dev tap0 diff --git a/a314/software-amiga/a314eth.device b/a314/software-amiga/a314eth.device index 4781e69bf94b0ce82ce7cf82ab8c1922bc0be6c8..6a8fb8883cb69e4d90a88af8a4c6d4fdff72b095 100644 GIT binary patch literal 5896 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 T;KvaM^uA!dJ>SI63fliKKD{8? literal 6468 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 z +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "../../a314device/a314.h" +#include "../../a314device/proto_a314.h" +#include "sana2.h" + +// Defines. + +#define DEVICE_NAME "a314eth.device" +#define SERVICE_NAME "ethernet" + +#define TASK_PRIO 10 + +#define MACADDR_SIZE 6 +#define NIC_BPS 10000000 + +#define ETH_MTU 1500 +#define RAW_MTU 1518 + +#define READ_FRAME_REQ 1 +#define WRITE_FRAME_REQ 2 +#define READ_FRAME_RES 3 +#define WRITE_FRAME_RES 4 + +#define ET_RBUF_CNT 2 +#define ET_WBUF_CNT 2 +#define ET_BUF_CNT (ET_RBUF_CNT + ET_WBUF_CNT) + +// Typedefs. + +typedef BOOL (*buf_copy_func_t)(__reg("a0") void *dst, __reg("a1") void *src, __reg("d0") LONG size); + +// Structs. + +#pragma pack(push, 1) +struct EthHdr +{ + unsigned char eh_Dst[MACADDR_SIZE]; + unsigned char eh_Src[MACADDR_SIZE]; + unsigned short eh_Type; +}; +#pragma pack(pop) + +#pragma pack(push, 1) +struct ServiceMsg +{ + ULONG sm_Address; + UWORD sm_Length; + UWORD sm_Kind; +}; +#pragma pack(pop) + +struct BufDesc +{ + struct MinNode bd_Node; + void *bd_Buffer; + int bd_Length; +}; + +// Constants. + +const char device_name[] = DEVICE_NAME; +const char id_string[] = DEVICE_NAME " 1.0 (20 July 2020)"; + +static const char service_name[] = SERVICE_NAME; +static const char a314_device_name[] = A314_NAME; + +static const unsigned char macaddr[MACADDR_SIZE] = { 0x40, 0x61, 0x33, 0x31, 0x34, 0x65 }; + +// Global variables. + +BPTR saved_seg_list; +struct ExecBase *SysBase; +struct DosLibrary *DOSBase; +struct Library *A314Base; + +buf_copy_func_t copyfrom; +buf_copy_func_t copyto; + +volatile struct List ut_rbuf_list; +volatile struct List ut_wbuf_list; + +struct BufDesc et_bufs[ET_BUF_CNT]; + +struct List et_rbuf_free_list; +struct List et_rbuf_pending_list; +struct List et_rbuf_has_data_list; + +struct List et_wbuf_free_list; +struct List et_wbuf_pending_list; + +LONG a314_socket; +short last_req_kind; + +struct MsgPort a314_mp; + +struct A314_IORequest read_ior; +struct A314_IORequest write_ior; +struct A314_IORequest reset_ior; + +BOOL pending_a314_read; +BOOL pending_a314_write; +BOOL pending_a314_reset; + +struct ServiceMsg a314_read_buf; +struct ServiceMsg a314_write_buf; + +volatile ULONG sana2_sigmask; +volatile ULONG shutdown_sigmask; + +volatile struct Task *init_task; + +struct Process *device_process; +volatile int device_start_error; + +// External declarations. + +extern void device_process_seglist(); + +// Procedures. + +static struct Library *init_device(__reg("a6") struct ExecBase *sys_base, __reg("a0") BPTR seg_list, __reg("d0") struct Library *dev) +{ + saved_seg_list = seg_list; + + dev->lib_Node.ln_Type = NT_DEVICE; + dev->lib_Node.ln_Name = (char *)device_name; + dev->lib_Flags = LIBF_SUMUSED | LIBF_CHANGED; + dev->lib_Version = 1; + dev->lib_Revision = 0; + dev->lib_IdString = (APTR)id_string; + + SysBase = *(struct ExecBase **)4; + DOSBase = (struct DosLibrary *)OpenLibrary(DOSNAME, 0); + + return dev; +} + +static BPTR expunge(__reg("a6") struct Library *dev) +{ + if (dev->lib_OpenCnt) + { + dev->lib_Flags |= LIBF_DELEXP; + return 0; + } + + // Shady way of waiting for device process to terminate before unloading. + Delay(10); + + CloseLibrary((struct Library *)DOSBase); + + Remove(&dev->lib_Node); + FreeMem((char *)dev - dev->lib_NegSize, dev->lib_NegSize + dev->lib_PosSize); + return saved_seg_list; +} + +static void send_a314_cmd(struct A314_IORequest *ior, UWORD cmd, char *buffer, int length) +{ + ior->a314_Request.io_Command = cmd; + ior->a314_Request.io_Error = 0; + ior->a314_Socket = a314_socket; + ior->a314_Buffer = buffer; + ior->a314_Length = length; + SendIO((struct IORequest *)ior); +} + +static void do_a314_cmd(struct A314_IORequest *ior, UWORD cmd, char *buffer, int length) +{ + ior->a314_Request.io_Command = cmd; + ior->a314_Request.io_Error = 0; + ior->a314_Socket = a314_socket; + ior->a314_Buffer = buffer; + ior->a314_Length = length; + DoIO((struct IORequest *)ior); +} + +static void copy_from_bd_and_reply(struct IOSana2Req *ios2, struct BufDesc *bd) +{ + struct EthHdr *eh = bd->bd_Buffer; + + if (ios2->ios2_Req.io_Flags & SANA2IOF_RAW) + { + ios2->ios2_DataLength = bd->bd_Length; + copyto(ios2->ios2_Data, bd->bd_Buffer, ios2->ios2_DataLength); + ios2->ios2_Req.io_Flags = SANA2IOF_RAW; + } + else + { + ios2->ios2_DataLength = bd->bd_Length - sizeof(struct EthHdr); + copyto(ios2->ios2_Data, &eh[1], ios2->ios2_DataLength); + ios2->ios2_Req.io_Flags = 0; + } + + memcpy(ios2->ios2_SrcAddr, eh->eh_Src, MACADDR_SIZE); + memcpy(ios2->ios2_DstAddr, eh->eh_Dst, MACADDR_SIZE); + + BOOL bcast = TRUE; + for (int i = 0; i < MACADDR_SIZE; i++) + { + if (eh->eh_Dst[i] != 0xff) + { + bcast = FALSE; + break; + } + } + + if (bcast) + ios2->ios2_Req.io_Flags |= SANA2IOF_BCAST; + + ios2->ios2_PacketType = eh->eh_Type; + + ios2->ios2_Req.io_Error = 0; + ReplyMsg(&ios2->ios2_Req.io_Message); +} + +static void copy_to_bd_and_reply(struct BufDesc *bd, struct IOSana2Req *ios2) +{ + struct EthHdr *eh = bd->bd_Buffer; + + if (ios2->ios2_Req.io_Flags & SANA2IOF_RAW) + { + copyfrom(bd->bd_Buffer, ios2->ios2_Data, ios2->ios2_DataLength); + bd->bd_Length = ios2->ios2_DataLength; + } + else + { + eh->eh_Type = ios2->ios2_PacketType; + memcpy(eh->eh_Src, macaddr, sizeof(macaddr)); + memcpy(eh->eh_Dst, ios2->ios2_DstAddr, MACADDR_SIZE); + copyfrom(&eh[1], ios2->ios2_Data, ios2->ios2_DataLength); + bd->bd_Length = ios2->ios2_DataLength + sizeof(struct EthHdr); + } + + ios2->ios2_Req.io_Error = 0; + ReplyMsg(&ios2->ios2_Req.io_Message); +} + +static void handle_a314_reply(struct A314_IORequest *ior) +{ + if (ior == &write_ior) + { + pending_a314_write = FALSE; + + if (ior->a314_Request.io_Error == A314_WRITE_OK) + { + // Start new write later. + } + else // A314_WRITE_RESET + { + // TODO: Handle. What if pi-side is shutting down. + } + } + else if (ior == &read_ior) + { + pending_a314_read = FALSE; + + if (ior->a314_Request.io_Error == A314_READ_OK) + { + if (a314_read_buf.sm_Kind == WRITE_FRAME_RES) + { + struct BufDesc *bd = (struct BufDesc *)RemHead(&et_wbuf_pending_list); + AddTail(&et_wbuf_free_list, (struct Node *)bd); + } + else // READ_FRAME_RES + { + struct BufDesc *bd = (struct BufDesc *)RemHead(&et_rbuf_pending_list); + bd->bd_Length = a314_read_buf.sm_Length; + AddTail(&et_rbuf_has_data_list, (struct Node *)bd); + } + + send_a314_cmd(&read_ior, A314_READ, (void *)&a314_read_buf, sizeof(a314_read_buf)); + pending_a314_read = TRUE; + } + else // A314_READ_RESET + { + // TODO: Handle. What if pi-side is shutting down. + } + } + else if (ior == &reset_ior) + { + pending_a314_reset = FALSE; + } +} + +static struct IOSana2Req *remove_matching_rbuf(ULONG type) +{ + struct Node *node = ut_rbuf_list.lh_Head; + while (node->ln_Succ) + { + struct IOSana2Req *ios2 = (struct IOSana2Req *)node; + if (ios2->ios2_PacketType == type) + { + Remove(node); + return ios2; + } + node = node->ln_Succ; + } + return NULL; +} + +static void complete_read_reqs() +{ + struct Node *node = et_rbuf_has_data_list.lh_Head; + if (!node->ln_Succ) + return; + + Forbid(); + while (node->ln_Succ) + { + struct BufDesc *bd = (struct BufDesc *)node; + struct EthHdr *eh = (struct EthHdr *)bd->bd_Buffer; + + node = node->ln_Succ; + + struct IOSana2Req *ios2 = remove_matching_rbuf(eh->eh_Type); + if (ios2) + { + copy_from_bd_and_reply(ios2, bd); + + Remove((struct Node *)bd); + AddTail(&et_rbuf_free_list, (struct Node *)bd); + } + } + Permit(); +} + +static void maybe_write_req() +{ + if (pending_a314_write) + return; + + BOOL free_et_wbuf = et_wbuf_free_list.lh_Head->ln_Succ != NULL; + BOOL idle_et_rbuf = et_rbuf_free_list.lh_Head->ln_Succ != NULL; + + Forbid(); + + BOOL waiting_ut_wbuf = ut_wbuf_list.lh_Head->ln_Succ != NULL; + + BOOL want_wbuf = free_et_wbuf && waiting_ut_wbuf; + BOOL want_rbuf = idle_et_rbuf; + + if (!want_rbuf && !want_wbuf) + { + Permit(); + return; + } + + short next_req_kind = 0; + + if (last_req_kind == WRITE_FRAME_REQ) + next_req_kind = want_rbuf ? READ_FRAME_REQ : WRITE_FRAME_REQ; + else + next_req_kind = want_wbuf ? WRITE_FRAME_REQ : READ_FRAME_REQ; + + struct IOSana2Req *ios2 = NULL; + if (next_req_kind == WRITE_FRAME_REQ) + ios2 = (struct IOSana2Req*)RemHead((struct List *)&ut_wbuf_list); + + Permit(); + + struct BufDesc *bd; + + if (next_req_kind == READ_FRAME_REQ) + { + bd = (struct BufDesc *)RemHead(&et_rbuf_free_list); + bd->bd_Length = RAW_MTU; + AddTail(&et_rbuf_pending_list, (struct Node *)&bd->bd_Node); + } + else // WRITE_FRAME_REQ + { + bd = (struct BufDesc *)RemHead(&et_wbuf_free_list); + copy_to_bd_and_reply(bd, ios2); + AddTail(&et_wbuf_pending_list, (struct Node *)bd); + } + + a314_write_buf.sm_Address = TranslateAddressA314(bd->bd_Buffer); + a314_write_buf.sm_Length = bd->bd_Length; + a314_write_buf.sm_Kind = next_req_kind; + + send_a314_cmd(&write_ior, A314_WRITE, (void *)&a314_write_buf, sizeof(a314_write_buf)); + pending_a314_write = TRUE; + + last_req_kind = next_req_kind; +} + +void device_process_run() +{ + ULONG sana2_signal = AllocSignal(-1); + sana2_sigmask = 1UL << sana2_signal; + + ULONG shutdown_signal = AllocSignal(-1); + shutdown_sigmask = 1UL << shutdown_signal; + + a314_mp.mp_SigBit = AllocSignal(-1); + a314_mp.mp_SigTask = FindTask(NULL); + + do_a314_cmd(&reset_ior, A314_CONNECT, (char *)service_name, strlen(service_name)); + device_start_error = reset_ior.a314_Request.io_Error == A314_CONNECT_OK ? 0 : -1; + + Signal((struct Task *)init_task, SIGF_SINGLE); + + if (device_start_error) + return; + + ULONG a314_sigmask = 1UL << a314_mp.mp_SigBit; + + send_a314_cmd(&read_ior, A314_READ, (void *)&a314_read_buf, sizeof(a314_read_buf)); + pending_a314_read = TRUE; + + BOOL shutting_down = FALSE; + + while (TRUE) + { + complete_read_reqs(); + maybe_write_req(); + + if (shutting_down && !pending_a314_read && !pending_a314_write && !pending_a314_reset) + break; + + ULONG sigs = Wait(a314_sigmask | sana2_sigmask | shutdown_sigmask); + + if ((sigs & shutdown_sigmask) && !shutting_down) + { + send_a314_cmd(&reset_ior, A314_RESET, NULL, 0); + pending_a314_reset = TRUE; + shutting_down = TRUE; + } + + if (sigs & a314_sigmask) + { + struct A314_IORequest *ior; + while ((ior = (struct A314_IORequest *)GetMsg(&a314_mp))) + handle_a314_reply(ior); + } + } + + Signal((struct Task *)init_task, SIGF_SINGLE); +} + +static struct TagItem *FindTagItem(Tag tagVal, struct TagItem *tagList) +{ + struct TagItem *ti = tagList; + while (ti && ti->ti_Tag != tagVal) + { + switch (ti->ti_Tag) + { + case TAG_DONE: + return NULL; + case TAG_MORE: + ti = (struct TagItem *)ti->ti_Data; + break; + case TAG_SKIP: + ti += ti->ti_Data + 1; + break; + case TAG_IGNORE: + default: + ti++; + break; + } + } + return ti; +} + +static ULONG GetTagData(Tag tagVal, ULONG defaultData, struct TagItem *tagList) +{ + struct TagItem *ti = FindTagItem(tagVal, tagList); + return ti ? ti->ti_Data : defaultData; +} + +static void open(__reg("a6") struct Library *dev, __reg("a1") struct IOSana2Req *ios2, __reg("d0") ULONG unitnum, __reg("d1") ULONG flags) +{ + ios2->ios2_Req.io_Error = IOERR_OPENFAIL; + ios2->ios2_Req.io_Message.mn_Node.ln_Type = NT_REPLYMSG; + + if (unitnum != 0 || dev->lib_OpenCnt) + return; + + dev->lib_OpenCnt++; + + copyfrom = (buf_copy_func_t)GetTagData(S2_CopyFromBuff, 0, (struct TagItem *)ios2->ios2_BufferManagement); + copyto = (buf_copy_func_t)GetTagData(S2_CopyToBuff, 0, (struct TagItem *)ios2->ios2_BufferManagement); + ios2->ios2_BufferManagement = (void *)0xdeadbeefUL; + + memset(&a314_mp, 0, sizeof(a314_mp)); + a314_mp.mp_Node.ln_Pri = 0; + a314_mp.mp_Node.ln_Type = NT_MSGPORT; + a314_mp.mp_Node.ln_Name = (char *)device_name; + a314_mp.mp_Flags = PA_SIGNAL; + NewList(&a314_mp.mp_MsgList); + + memset(&write_ior, 0, sizeof(write_ior)); + write_ior.a314_Request.io_Message.mn_ReplyPort = &a314_mp; + write_ior.a314_Request.io_Message.mn_Length = sizeof(write_ior); + write_ior.a314_Request.io_Message.mn_Node.ln_Type = NT_REPLYMSG; + + A314Base = NULL; + if (OpenDevice((char *)a314_device_name, 0, (struct IORequest *)&write_ior, 0)) + goto error; + + A314Base = &(write_ior.a314_Request.io_Device->dd_Library); + + memcpy(&read_ior, &write_ior, sizeof(read_ior)); + memcpy(&reset_ior, &write_ior, sizeof(reset_ior)); + + struct DateStamp ds; + DateStamp(&ds); + a314_socket = (ds.ds_Minute * 60 * TICKS_PER_SECOND) + ds.ds_Tick; + + last_req_kind = WRITE_FRAME_REQ; + + NewList((struct List *)&ut_rbuf_list); + NewList((struct List *)&ut_wbuf_list); + + NewList(&et_rbuf_free_list); + NewList(&et_rbuf_pending_list); + NewList(&et_rbuf_has_data_list); + + NewList(&et_wbuf_free_list); + NewList(&et_wbuf_pending_list); + + for (int i = 0; i < ET_BUF_CNT; i++) + memset(&et_bufs[i], 0, sizeof(struct BufDesc)); + + for (int i = 0; i < ET_BUF_CNT; i++) + { + struct BufDesc *bd = &et_bufs[i]; + + bd->bd_Buffer = AllocMem(RAW_MTU, MEMF_FAST); + if (!bd->bd_Buffer) + goto error; + + if (i < ET_RBUF_CNT) + AddTail(&et_rbuf_free_list, (struct Node*)&bd->bd_Node); + else + AddTail(&et_wbuf_free_list, (struct Node*)&bd->bd_Node); + } + + init_task = FindTask(NULL); + + struct MsgPort *device_mp = CreateProc((char *)device_name, TASK_PRIO, ((ULONG)device_process_seglist) >> 2, 2048); + if (!device_mp) + goto error; + + device_process = (struct Process *)((char *)device_mp - sizeof(struct Task)); + + Wait(SIGF_SINGLE); + + if (device_start_error) + goto error; + + ios2->ios2_Req.io_Error = 0; + return; + +error: + for (int i = ET_BUF_CNT - 1; i >= 0; i--) + if (et_bufs[i].bd_Buffer) + FreeMem(et_bufs[i].bd_Buffer, RAW_MTU); + + if (A314Base) + { + CloseDevice((struct IORequest *)&write_ior); + A314Base = NULL; + } + + dev->lib_OpenCnt--; +} + +static BPTR close(__reg("a6") struct Library *dev, __reg("a1") struct IOSana2Req *ios2) +{ + init_task = FindTask(NULL); + Signal(&device_process->pr_Task, shutdown_sigmask); + Wait(SIGF_SINGLE); + + for (int i = ET_BUF_CNT - 1; i >= 0; i--) + FreeMem(et_bufs[i].bd_Buffer, RAW_MTU); + + CloseDevice((struct IORequest *)&write_ior); + A314Base = NULL; + + ios2->ios2_Req.io_Device = NULL; + ios2->ios2_Req.io_Unit = NULL; + + dev->lib_OpenCnt--; + + if (dev->lib_OpenCnt == 0 && (dev->lib_Flags & LIBF_DELEXP)) + return expunge(dev); + + return 0; +} + +static void device_query(struct IOSana2Req *req) +{ + struct Sana2DeviceQuery *query; + + query = req->ios2_StatData; + query->DevQueryFormat = 0; + query->DeviceLevel = 0; + + if (query->SizeAvailable >= 18) + query->AddrFieldSize = MACADDR_SIZE * 8; + + if (query->SizeAvailable >= 22) + query->MTU = ETH_MTU; + + if (query->SizeAvailable >= 26) + query->BPS = NIC_BPS; + + if (query->SizeAvailable >= 30) + query->HardwareType = S2WireType_Ethernet; + + query->SizeSupplied = query->SizeAvailable < 30 ? query->SizeAvailable : 30; +} + +static void begin_io(__reg("a6") struct Library *dev, __reg("a1") struct IOSana2Req *ios2) +{ + ios2->ios2_Req.io_Error = S2ERR_NO_ERROR; + ios2->ios2_WireError = S2WERR_GENERIC_ERROR; + + switch (ios2->ios2_Req.io_Command) + { + case CMD_READ: + if (!ios2->ios2_BufferManagement) + { + ios2->ios2_Req.io_Error = S2ERR_BAD_ARGUMENT; + ios2->ios2_WireError = S2WERR_BUFF_ERROR; + break; + } + + Forbid(); + AddTail((struct List *)&ut_rbuf_list, &ios2->ios2_Req.io_Message.mn_Node); + Permit(); + + ios2->ios2_Req.io_Flags &= ~SANA2IOF_QUICK; + ios2 = NULL; + + Signal(&device_process->pr_Task, sana2_sigmask); + break; + + case S2_BROADCAST: + memset(ios2->ios2_DstAddr, 0xff, MACADDR_SIZE); + /* Fall through */ + + case CMD_WRITE: + if (((ios2->ios2_Req.io_Flags & SANA2IOF_RAW) != 0 && ios2->ios2_DataLength > RAW_MTU) || + ((ios2->ios2_Req.io_Flags & SANA2IOF_RAW) == 0 && ios2->ios2_DataLength > ETH_MTU)) + { + ios2->ios2_Req.io_Error = S2ERR_MTU_EXCEEDED; + break; + } + + if (!ios2->ios2_BufferManagement) + { + ios2->ios2_Req.io_Error = S2ERR_BAD_ARGUMENT; + ios2->ios2_WireError = S2WERR_BUFF_ERROR; + break; + } + + Forbid(); + AddTail((struct List *)&ut_wbuf_list, &ios2->ios2_Req.io_Message.mn_Node); + Permit(); + + ios2->ios2_Req.io_Flags &= ~SANA2IOF_QUICK; + ios2 = NULL; + + Signal(&device_process->pr_Task, sana2_sigmask); + break; + + case S2_ONLINE: + case S2_OFFLINE: + case S2_CONFIGINTERFACE: + break; + case S2_GETSTATIONADDRESS: + memcpy(ios2->ios2_SrcAddr, macaddr, sizeof(macaddr)); + memcpy(ios2->ios2_DstAddr, macaddr, sizeof(macaddr)); + break; + case S2_DEVICEQUERY: + device_query(ios2); + break; + + case S2_ONEVENT: + case S2_TRACKTYPE: + case S2_UNTRACKTYPE: + case S2_GETTYPESTATS: + case S2_READORPHAN: + case S2_GETGLOBALSTATS: + case S2_GETSPECIALSTATS: + break; + + default: + ios2->ios2_Req.io_Error = IOERR_NOCMD; + ios2->ios2_WireError = S2WERR_GENERIC_ERROR; + break; + } + + if (ios2) + { + if (ios2->ios2_Req.io_Flags & SANA2IOF_QUICK) + ios2->ios2_Req.io_Message.mn_Node.ln_Type = NT_MESSAGE; + else + ReplyMsg(&ios2->ios2_Req.io_Message); + } +} + +static void remove_from_list(struct List *list, struct Node *node) +{ + for (struct Node *n = list->lh_Head; n->ln_Succ; n = n->ln_Succ) + { + if (n == node) + { + Remove(n); + return; + } + } +} + +static ULONG abort_io(__reg("a6") struct Library *dev, __reg("a1") struct IOSana2Req *ios2) +{ + Forbid(); + remove_from_list((struct List *)&ut_rbuf_list, &ios2->ios2_Req.io_Message.mn_Node); + remove_from_list((struct List *)&ut_wbuf_list, &ios2->ios2_Req.io_Message.mn_Node); + Permit(); + + ios2->ios2_Req.io_Error = IOERR_ABORTED; + ios2->ios2_WireError = 0; + ReplyMsg(&ios2->ios2_Req.io_Message); + + return 0; +} + +static ULONG device_vectors[] = +{ + (ULONG)open, + (ULONG)close, + (ULONG)expunge, + 0, + (ULONG)begin_io, + (ULONG)abort_io, + -1, +}; + +ULONG auto_init_tables[] = +{ + sizeof(struct Library), + (ULONG)device_vectors, + 0, + (ULONG)init_device, +}; diff --git a/a314/software-amiga/ethernet_pistorm/ethernet.py b/a314/software-amiga/ethernet_pistorm/ethernet.py new file mode 100644 index 0000000..028875f --- /dev/null +++ b/a314/software-amiga/ethernet_pistorm/ethernet.py @@ -0,0 +1,264 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Copyright (c) 2020 Niklas Ekström + +import logging +import os +import pytun +import select +import socket +import struct +import sys +import time + +logging.basicConfig(format = '%(levelname)s, %(asctime)s, %(name)s, line %(lineno)d: %(message)s') +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + +MSG_REGISTER_REQ = 1 +MSG_REGISTER_RES = 2 +MSG_DEREGISTER_REQ = 3 +MSG_DEREGISTER_RES = 4 +MSG_READ_MEM_REQ = 5 +MSG_READ_MEM_RES = 6 +MSG_WRITE_MEM_REQ = 7 +MSG_WRITE_MEM_RES = 8 +MSG_CONNECT = 9 +MSG_CONNECT_RESPONSE = 10 +MSG_DATA = 11 +MSG_EOS = 12 +MSG_RESET = 13 + +def wait_for_msg(): + header = b'' + while len(header) < 9: + data = drv.recv(9 - len(header)) + if not data: + logger.error('Connection to a314d was closed, terminating.') + exit(-1) + header += data + (plen, stream_id, ptype) = struct.unpack('=IIB', header) + payload = b'' + while len(payload) < plen: + data = drv.recv(plen - len(payload)) + if not data: + logger.error('Connection to a314d was closed, terminating.') + exit(-1) + payload += data + return (stream_id, ptype, payload) + +def send_register_req(name): + m = struct.pack('=IIB', len(name), 0, MSG_REGISTER_REQ) + name + drv.sendall(m) + +def send_read_mem_req(address, length): + m = struct.pack('=IIBII', 8, 0, MSG_READ_MEM_REQ, address, length) + drv.sendall(m) + +def read_mem(address, length): + send_read_mem_req(address, length) + _, 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) + _, ptype, _ = 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) + +### A314 communication routines above. Actual driver below. + +current_stream_id = None +done = False +rbuf = b'' + +DEV_NAME = 'tap0' +SERVICE_NAME = b'ethernet' + +READ_FRAME_REQ = 1 +WRITE_FRAME_REQ = 2 +READ_FRAME_RES = 3 +WRITE_FRAME_RES = 4 + +mem_read_queue = [] +mem_write_queue = [] + +# Can buffer as many frames as fit in memory. +# Maybe should have a limit on the number of buffers? +waiting_read_reqs = [] +buffered_frames = [] + +DROP_START_SECS = 15.0 + +def process_tap_frame(frame): + if current_stream_id is None: + return + + global drop_start + if drop_start: + if time.time() < start_time + DROP_START_SECS: + return + drop_start = False + + if waiting_read_reqs: + stream_id, address, length = waiting_read_reqs.pop(0) + + if length < len(frame): + logger.error('Fatal error, read frame from TAP larger than buffer') + + mem_write_queue.append((stream_id, address, len(frame))) + send_write_mem_req(address, frame) + else: + buffered_frames.append(frame) + +def process_stream_data(stream_id, data): + address, length, kind = struct.unpack('>IHH', data) + if kind == WRITE_FRAME_REQ: + mem_read_queue.append((stream_id, address, length)) + send_read_mem_req(address, length) + elif kind == READ_FRAME_REQ: + if buffered_frames: + frame = buffered_frames.pop(0) + + if length < len(frame): + logger.error('Fatal error, read frame from TAP larger than buffer') + + mem_write_queue.append((stream_id, address, len(frame))) + send_write_mem_req(address, frame) + else: + waiting_read_reqs.append((stream_id, address, length)) + +def process_read_mem_res(frame): + tap.write(frame) + + stream_id, address, length = mem_read_queue.pop(0) + if stream_id == current_stream_id: + send_data(stream_id, struct.pack('>IHH', address, length, WRITE_FRAME_RES)) + +def process_write_mem_res(): + stream_id, address, length = mem_write_queue.pop(0) + if stream_id == current_stream_id: + send_data(stream_id, struct.pack('>IHH', address, length, READ_FRAME_RES)) + +def process_drv_msg(stream_id, ptype, payload): + global current_stream_id + + if ptype == MSG_CONNECT: + if payload == SERVICE_NAME and current_stream_id is None: + logger.info('Amiga connected') + current_stream_id = stream_id + send_connect_response(stream_id, 0) + else: + send_connect_response(stream_id, 3) + elif ptype == MSG_READ_MEM_RES: + process_read_mem_res(payload) + elif ptype == MSG_WRITE_MEM_RES: + process_write_mem_res() + elif current_stream_id == stream_id: + if ptype == MSG_DATA: + process_stream_data(stream_id, payload) + elif ptype == MSG_EOS: + # EOS is not used. + pass + elif ptype == MSG_RESET: + current_stream_id = None + waiting_read_reqs.clear() + buffered_frames.clear() + logger.info('Amiga disconnected') + +try: + idx = sys.argv.index('-ondemand') +except ValueError: + idx = -1 + +if idx != -1: + fd = int(sys.argv[idx + 1]) + drv = socket.socket(fileno=fd) +else: + drv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + drv.connect(('localhost', 7110)) + drv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + + send_register_req(SERVICE_NAME) + _, _, payload = wait_for_msg() + if payload[0] != 1: + logger.error('Unable to register ethernet with driver, shutting down') + drv.close() + done = True + +if not done: + try: + tap = pytun.TunTapDevice(name=DEV_NAME, flags=pytun.IFF_TAP | pytun.IFF_NO_PI) + except: + logger.error('Unable to open tap device at ' + DEV_NAME) + done = True + + start_time = time.time() + drop_start = True + +if not done: + logger.info('Ethernet service is running') + +while not done: + try: + rl, _, _ = select.select([drv, tap], [], [], 10.0) + except KeyboardInterrupt: + rl = [] + if current_stream_id is not None: + send_reset(current_stream_id) + drv.close() + done = True + + if drv in rl: + buf = drv.recv(1600) + if not buf: + if current_stream_id is not None: + send_reset(current_stream_id) + drv.close() + done = True + else: + rbuf += buf + while True: + if len(rbuf) < 9: + break + + (plen, stream_id, ptype) = struct.unpack('=IIB', rbuf[:9]) + if len(rbuf) < 9 + plen: + break + + payload = rbuf[9:9+plen] + rbuf = rbuf[9+plen:] + + process_drv_msg(stream_id, ptype, payload) + + if tap in rl: + frame = tap.read(1600) + process_tap_frame(frame) + +tap.close() +logger.info('Ethernet service stopped') diff --git a/a314/software-amiga/ethernet_pistorm/romtag.asm b/a314/software-amiga/ethernet_pistorm/romtag.asm new file mode 100644 index 0000000..0b8b52b --- /dev/null +++ b/a314/software-amiga/ethernet_pistorm/romtag.asm @@ -0,0 +1,33 @@ + XDEF _device_process_seglist + XREF _device_process_run + +RTC_MATCHWORD: equ $4afc +RTF_AUTOINIT: equ (1<<7) +NT_DEVICE: equ 3 +VERSION: equ 1 +PRIORITY: equ 0 + + section code,code + + moveq #-1,d0 + rts + +romtag: + dc.w RTC_MATCHWORD + dc.l romtag + dc.l endcode + dc.b RTF_AUTOINIT + dc.b VERSION + dc.b NT_DEVICE + dc.b PRIORITY + dc.l _device_name + dc.l _id_string + dc.l _auto_init_tables +endcode: + + cnop 0,4 + + dc.l 16 +_device_process_seglist: + dc.l 0 + jmp _device_process_run diff --git a/a314/software-amiga/ethernet_pistorm/sana2.h b/a314/software-amiga/ethernet_pistorm/sana2.h new file mode 100644 index 0000000..645a0cd --- /dev/null +++ b/a314/software-amiga/ethernet_pistorm/sana2.h @@ -0,0 +1,261 @@ +#ifndef SANA2_SANA2DEVICE_H +#define SANA2_SANA2DEVICE_H 1 +/* +** $Filename: devices/sana2.h $ +** $Revision: 4.1 $ +** $Date: 1994/10/03 20:55:10 $ +** +** Structure definitions for SANA-II devices. +** +** (C) Copyright 1991 Commodore-Amiga Inc. +** All Rights Reserved +*/ + + +#ifndef EXEC_TYPES_H +#include +#endif + +#ifndef EXEC_PORTS_H +#include +#endif + +#ifndef EXEC_IO_H +#include +#endif + +#ifndef EXEC_ERRORS_H +#include +#endif + +#ifndef DEVICES_TIMER_H +#include +#endif + +#ifndef UTILITY_TAGITEM_H +#include "tagitem.h" +#endif + + +#define SANA2_MAX_ADDR_BITS (128) +#define SANA2_MAX_ADDR_BYTES ((SANA2_MAX_ADDR_BITS+7)/8) + +struct IOSana2Req +{ + struct IORequest ios2_Req; + ULONG ios2_WireError; /* wire type specific error */ + ULONG ios2_PacketType; /* packet type */ + UBYTE ios2_SrcAddr[SANA2_MAX_ADDR_BYTES]; /* source addr */ + UBYTE ios2_DstAddr[SANA2_MAX_ADDR_BYTES]; /* dest address */ + ULONG ios2_DataLength; /* length of packet data */ + VOID *ios2_Data; /* packet data */ + VOID *ios2_StatData; /* statistics data pointer */ + VOID *ios2_BufferManagement; /* see SANA-II OpenDevice adoc */ +}; + + +/* +** defines for the io_Flags field +*/ +#define SANA2IOB_RAW (7) /* raw packet IO requested */ +#define SANA2IOF_RAW (1<) +*/ +#define S2ERR_NO_ERROR 0 /* peachy-keen */ +#define S2ERR_NO_RESOURCES 1 /* resource allocation failure */ +#define S2ERR_BAD_ARGUMENT 3 /* garbage somewhere */ +#define S2ERR_BAD_STATE 4 /* inappropriate state */ +#define S2ERR_BAD_ADDRESS 5 /* who? */ +#define S2ERR_MTU_EXCEEDED 6 /* too much to chew */ +#define S2ERR_NOT_SUPPORTED 8 /* hardware can't support cmd */ +#define S2ERR_SOFTWARE 9 /* software error detected */ +#define S2ERR_OUTOFSERVICE 10 /* driver is OFFLINE */ +#define S2ERR_TX_FAILURE 11 /* Transmission attempt failed */ +/* +** From +** +** IOERR_OPENFAIL (-1) * device/unit failed to open * +** IOERR_ABORTED (-2) * request terminated early [after AbortIO()] * +** IOERR_NOCMD (-3) * command not supported by device * +** IOERR_BADLENGTH (-4) * not a valid length (usually IO_LENGTH) * +** IOERR_BADADDRESS (-5) * invalid address (misaligned or bad range) * +** IOERR_UNITBUSY (-6) * device opens ok, but requested unit is busy * +** IOERR_SELFTEST (-7) * hardware failed self-test * +*/ + +/* +** defined errors for ios2_WireError +*/ +#define S2WERR_GENERIC_ERROR 0 /* no specific info available */ +#define S2WERR_NOT_CONFIGURED 1 /* unit not configured */ +#define S2WERR_UNIT_ONLINE 2 /* unit is currently online */ +#define S2WERR_UNIT_OFFLINE 3 /* unit is currently offline */ +#define S2WERR_ALREADY_TRACKED 4 /* protocol already tracked */ +#define S2WERR_NOT_TRACKED 5 /* protocol not tracked */ +#define S2WERR_BUFF_ERROR 6 /* buff mgt func returned error */ +#define S2WERR_SRC_ADDRESS 7 /* source address problem */ +#define S2WERR_DST_ADDRESS 8 /* destination address problem */ +#define S2WERR_BAD_BROADCAST 9 /* broadcast address problem */ +#define S2WERR_BAD_MULTICAST 10 /* multicast address problem */ +#define S2WERR_MULTICAST_FULL 11 /* multicast address list full */ +#define S2WERR_BAD_EVENT 12 /* unsupported event class */ +#define S2WERR_BAD_STATDATA 13 /* statdata failed sanity check */ +#define S2WERR_IS_CONFIGURED 15 /* attempt to config twice */ +#define S2WERR_NULL_POINTER 16 /* null pointer detected */ +#define S2WERR_TOO_MANY_RETIRES 17 /* tx failed - too many retries */ +#define S2WERR_RCVREL_HDW_ERR 18 /* Driver fixable HW error */ + + +/* +** defined events +*/ +#define S2EVENT_ERROR (1L<<0) /* error catch all */ +#define S2EVENT_TX (1L<<1) /* transmitter error catch all */ +#define S2EVENT_RX (1L<<2) /* receiver error catch all */ +#define S2EVENT_ONLINE (1L<<3) /* unit is in service */ +#define S2EVENT_OFFLINE (1L<<4) /* unit is not in service */ +#define S2EVENT_BUFF (1L<<5) /* buff mgt function error */ +#define S2EVENT_HARDWARE (1L<<6) /* hardware error catch all */ +#define S2EVENT_SOFTWARE (1L<<7) /* software error catch all */ + + +#endif /* SANA2_SANA2DEVICE_H */ diff --git a/a314/software-amiga/ethernet_pistorm/tagitem.h b/a314/software-amiga/ethernet_pistorm/tagitem.h new file mode 100644 index 0000000..15e05cc --- /dev/null +++ b/a314/software-amiga/ethernet_pistorm/tagitem.h @@ -0,0 +1,77 @@ +#ifndef UTILITY_TAGITEM_H +#define UTILITY_TAGITEM_H +/* +** $VER: tagitem.h 40.1 (19.7.1993) +** Includes Release 45.1 +** +** Extended specification mechanism +** +** (C) Copyright 1989-2001 Amiga, Inc. +** All Rights Reserved +*/ + +/*****************************************************************************/ + + +#ifndef EXEC_TYPES_H +#include +#endif + + +/*****************************************************************************/ + + +/* Tags are a general mechanism of extensible data arrays for parameter + * specification and property inquiry. In practice, tags are used in arrays, + * or chain of arrays. + * + */ + +typedef ULONG Tag; + +struct TagItem +{ + Tag ti_Tag; /* identifies the type of data */ + ULONG ti_Data; /* type-specific data */ +}; + +/* constants for Tag.ti_Tag, control tag values */ +#define TAG_DONE (0L) /* terminates array of TagItems. ti_Data unused */ +#define TAG_END (0L) /* synonym for TAG_DONE */ +#define TAG_IGNORE (1L) /* ignore this item, not end of array */ +#define TAG_MORE (2L) /* ti_Data is pointer to another array of TagItems + * note that this tag terminates the current array + */ +#define TAG_SKIP (3L) /* skip this and the next ti_Data items */ + +/* differentiates user tags from control tags */ +#define TAG_USER ((ULONG)(1L<<31)) + +/* If the TAG_USER bit is set in a tag number, it tells utility.library that + * the tag is not a control tag (like TAG_DONE, TAG_IGNORE, TAG_MORE) and is + * instead an application tag. "USER" means a client of utility.library in + * general, including system code like Intuition or ASL, it has nothing to do + * with user code. + */ + + +/*****************************************************************************/ + + +/* Tag filter logic specifiers for use with FilterTagItems() */ +#define TAGFILTER_AND 0 /* exclude everything but filter hits */ +#define TAGFILTER_NOT 1 /* exclude only filter hits */ + + +/*****************************************************************************/ + + +/* Mapping types for use with MapTags() */ +#define MAP_REMOVE_NOT_FOUND 0 /* remove tags that aren't in mapList */ +#define MAP_KEEP_NOT_FOUND 1 /* keep tags that aren't in mapList */ + + +/*****************************************************************************/ + + +#endif /* UTILITY_TAGITEM_H */ diff --git a/a314/software-amiga/remote-mouse b/a314/software-amiga/remote-mouse deleted file mode 100644 index 773f05ef57af6285856b078a9c02cd43104c8123..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6712 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 diff --git a/a314/software-amiga/remotewb b/a314/software-amiga/remotewb deleted file mode 100644 index 9a12a4ccd7574356b1a9b04aa22fec3c6b4ee13f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13340 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