/* GSPLUS - Advanced Apple IIGS Emulator Environment Based on the KEGS emulator written by Kent Dickey See COPYRIGHT.txt for Copyright information See LICENSE.txt for license (GPL v2) */ /** This module implements the LLAP port of the bridge. **/ #include #include "../defc.h" #include "atbridge.h" #include "port.h" #include "llap.h" typedef enum { LLAP_DDP_SHORT = 0x01, LLAP_DDP_LONG = 0x02, LLAP_ENQ = 0x81, LLAP_ACK = 0x82, LLAP_RTS = 0x84, LLAP_CTS = 0x85 } LLAP_TYPES; const unsigned int LLAP_PACKET_MAX = 603 /* bytes */; const unsigned int LLAP_PACKET_MIN = 3 /* bytes */; const double LLAP_IDG = 400 /* microseconds */; const double LLAP_IFG = 200 /* microseconds */; const double GAP_TOLERANCE = 4.0; static struct packet_port_t llap_port; typedef enum { DIALOG_READY, DIALOG_GOT_CTS, DIALOG_WAIT_IDG } DIALOG_STATE; static DIALOG_STATE dialog_state; static double dialog_end_dcycs; static double last_frame_dcycs; void llap_init() { dialog_state = DIALOG_READY; last_frame_dcycs = 0; port_init(&llap_port); } void llap_shutdown() { port_shutdown(&llap_port); } /** Queue one data packet out from the bridge's LLAP port and into the guest. **/ void llap_enqueue_out(struct packet_t* packet) { // Generate the RTS. struct packet_t* rts = (struct packet_t*)malloc(sizeof(struct packet_t)); rts->source.network = packet->source.network; rts->source.node = packet->source.node; rts->dest.network = packet->dest.network; rts->dest.node = packet->dest.node; rts->size = 0; rts->data = 0; rts->type = LLAP_RTS; enqueue_packet(&llap_port.out, rts); // Enqueue the data. enqueue_packet(&llap_port.out, packet); } struct packet_t* llap_dequeue_in() { return dequeue(&llap_port.in); } static void llap_dump_packet(size_t size, byte data[]) { if (size < LLAP_PACKET_MIN) atbridge_printf("LLAP short packet.\n"); else if (size > LLAP_PACKET_MAX) atbridge_printf("LLAP long packet.\n"); else { at_node_t dest = data[0]; at_node_t source = data[1]; LLAP_TYPES type = (LLAP_TYPES)(data[2]); const char* typeName = 0; switch (type) { case LLAP_DDP_SHORT: typeName = "DDP (short)"; break; case LLAP_DDP_LONG: typeName = "DDP (long)"; break; case LLAP_ENQ: typeName = "lapENQ"; break; case LLAP_ACK: typeName = "lapACK"; break; case LLAP_RTS: typeName = "lapRTS"; break; case LLAP_CTS: typeName = "lapCTS"; break; } if (typeName) atbridge_printf("LLAP[%d->%d] %s: %d bytes.\n", source, dest, typeName, size); else atbridge_printf("LLAP[%d->%d] %x: %d bytes.\n", source, dest, type, size); /*for (size_t i = 0; i < size; i++) atbridge_printf("%02x ", data[i]); atbridge_printf("\n");*/ } } /** Reply to a control packet from the GS **/ static void llap_reply_control(at_node_t dest, at_node_t source, LLAP_TYPES type) { struct at_addr_t dest_addr = { 0, dest }; struct at_addr_t source_addr = { 0, source }; // Insert control packets at the head of the queue contrary to normal FIFO queue operation // to ensure that control frames arrive in the intended order. insert(&llap_port.out, dest_addr, source_addr, type, 0, 0); } /** Accept a data packet from the GS. **/ static void llap_handle_data(size_t size, byte data[]) { at_node_t dest = data[0]; at_node_t source = data[1]; LLAP_TYPES type = (LLAP_TYPES)(data[2]); const size_t data_size = size - 3; byte* data_copy = (byte*)malloc(data_size); memcpy(data_copy, data + 3, data_size); struct at_addr_t dest_addr = { 0, dest }; struct at_addr_t source_addr = { 0, source }; enqueue(&llap_port.in, dest_addr, source_addr, type, data_size, data_copy); } /** Accept a control packet from the GS. **/ static void llap_handle_control(size_t size, byte data[]) { at_node_t dest = data[0]; at_node_t source = data[1]; LLAP_TYPES type = (LLAP_TYPES)(data[2]); struct at_addr_t addr = { atbridge_get_net(), dest }; switch (type) { case LLAP_ENQ: // Require the GS to take a valid "client" address not known to be in use. if (dest > 127 || dest == 0 || atbridge_address_used(&addr)) llap_reply_control(source, dest, LLAP_ACK); break; case LLAP_ACK: break; case LLAP_RTS: if (dest != at_broadcast_node) // The GS is trying to make a directed transmission. Provide the required RTS/CTS handshake. // Note that broadcast packets do not require a CTS. llap_reply_control(source, dest, LLAP_CTS); break; case LLAP_CTS: // The GS sent a CTS. If the bridge has pending data, prepare to deliver the packet. dialog_state = DIALOG_GOT_CTS; break; default: break; } } /** Occassionally, we receive an invalid packet from the GS. I'm unsure if this is due to a bug in GS/OS or, more likely, a bug in the SCC emulation. Regardless, when such a thing does occur, discard the current, corrupted dialog. Link errors are routine in real LocalTalk networks, and LocalTalk will recover. **/ static void llap_reset_dialog() { dialog_state = DIALOG_READY; last_frame_dcycs = 0; // Discard packets until the queue is either empty or the next dialog starts (and dialogs begin with an RTS). while (true) { struct packet_t* packet = queue_peek(&llap_port.out); if (packet && (packet->type != LLAP_RTS)) { packet = dequeue(&llap_port.out); if (packet->data) free(packet->data); free(packet); } else break; } } /** Transfer (send) one LLAP packet from the GS. **/ void llap_enqueue_in(double dcycs, size_t size, byte data[]) { atbridge_printf("<%0.0f> TX: ", dcycs); llap_dump_packet(size, data); if (size < LLAP_PACKET_MIN) atbridge_printf("ATBridge: Dropping LLAP short packet.\n"); else if (size > LLAP_PACKET_MAX) atbridge_printf("ATBridge: Dropping LLAP long packet.\n"); else { last_frame_dcycs = dcycs; LLAP_TYPES type = (LLAP_TYPES)(data[2]); switch (type) { case LLAP_DDP_SHORT: case LLAP_DDP_LONG: llap_handle_data(size, data); break; case LLAP_ENQ: case LLAP_ACK: case LLAP_RTS: case LLAP_CTS: llap_handle_control(size, data); break; default: // Intentionally check for valid types and ingore packets with invalid types. // Sometimes, the bridge gets invalid packets from the GS, which tends to break the bridge. atbridge_printf("ATBridge: Dropping LLAP packet with invalid type.\n"); llap_reset_dialog(); } } } /** Transfer (receive) one LLAP packet to the GS. **/ void llap_dequeue_out(double dcycs, size_t* size, byte* data[]) { *size = 0; // The LocalTalk protocol requires a minimum 400us gap between dialogs (called the IDG). // If necessary, wait for the IDG. if (dialog_state == DIALOG_WAIT_IDG) { if ((dcycs - dialog_end_dcycs) >= LLAP_IDG) // The IDG is done. dialog_state = DIALOG_READY; else // Continue waiting for the IDG. return; } // The LocalTalk protocols requires a maximum 200us gap between frames within a dialog (called the IFG). // If we exceed the IFG, the bridge must be stuck in an incomplete or corrupt dialog. In this case, // discard the current dialog and try again. if ((dialog_state != DIALOG_READY) && (last_frame_dcycs != 0) && ((dcycs - last_frame_dcycs) >= (GAP_TOLERANCE*LLAP_IFG))) { llap_reset_dialog(); atbridge_printf("ATBridge: Dialog reset due to IFG violation.\n"); } struct packet_t* packet = queue_peek(&llap_port.out); if ((dialog_state == DIALOG_READY) && (packet) && !(packet->type & 0x80) && (last_frame_dcycs != 0) && ((dcycs - last_frame_dcycs) >= (GAP_TOLERANCE*LLAP_IDG))) { llap_reset_dialog(); packet = queue_peek(&llap_port.out); atbridge_printf("ATBridge: Dialog reset due to IDG violation.\n"); } if (packet && ((packet->type & 0x80) || /* Pass along control frames without waiting for a CTS. */ (!(packet->type & 0x80) && (packet->dest.node == at_broadcast_node) && (dialog_state == DIALOG_READY)) || /* Pass along broadcast frames, which don't wait for CTS frames. */ (!(packet->type & 0x80) && (packet->dest.node != at_broadcast_node) && (dialog_state == DIALOG_GOT_CTS)))) /* Pass along directed frames only after receiving a CTS handshake. */ { dequeue(&llap_port.out); // Prepend the LLAP header. *size = packet->size + 3 + 2; *data = (byte*)malloc(*size); (*data)[0] = packet->dest.node; (*data)[1] = packet->source.node; (*data)[2] = packet->type; // Insert the data into the new LLAP packet. if (*size) memcpy((*data) + 3, packet->data, packet->size); // Fake a frame check sequence (FCS). Since our SCC emulation doesn't actually // check the FCS, the value of the FCS doesn't matter. (*data)[packet->size + 3 + 0] = 0xff; (*data)[packet->size + 3 + 1] = 0xff; atbridge_printf("<%0.0f> RX: ", dcycs); llap_dump_packet(*size, *data); if (packet->type & 0x80) dialog_state = DIALOG_READY; else { // This was the last packet in the dialog. dialog_state = DIALOG_WAIT_IDG; dialog_end_dcycs = dcycs; } last_frame_dcycs = dcycs; free(packet->data); free(packet); } }