/* pdp11_ddcmp.h: Digital Data Communications Message Protocol support | |
Copyright (c) 2013, Mark Pizzolato | |
Permission is hereby granted, free of charge, to any person obtaining a | |
copy of this software and associated documentation files (the "Software"), | |
to deal in the Software without restriction, including without limitation | |
the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
and/or sell copies of the Software, and to permit persons to whom the | |
Software is furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in | |
all copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | |
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
Except as contained in this notice, the name of Robert M Supnik shall not be | |
used in advertising or otherwise to promote the sale, use or other dealings | |
in this Software without prior written authorization from Robert M Supnik. | |
Digital Data Communications Message Protocol - DDCMP support routines | |
29-May-13 MP Initial implementation | |
*/ | |
#ifndef PDP11_DDCMP_H_ | |
#define PDP11_DDCMP_H_ 0 | |
#include "sim_tmxr.h" | |
/* DDCMP packet types */ | |
#define DDCMP_SYN 0226u /* Sync character on synchronous links */ | |
#define DDCMP_DEL 0377u /* Sync character on asynchronous links */ | |
#define DDCMP_SOH 0201u /* Numbered Data Message Identifier */ | |
#define DDCMP_ENQ 0005u /* Control Message Identifier */ | |
#define DDCMP_DLE 0220u /* Maintenance Message Identifier */ | |
#define DDCMP_CTL_ACK 1 /* Control Message ACK Type */ | |
#define DDCMP_CTL_NAK 2 /* Control Message NAK Type */ | |
#define DDCMP_CTL_REP 3 /* Control Message REP Type */ | |
#define DDCMP_CTL_STRT 6 /* Control Message STRT Type */ | |
#define DDCMP_CTL_STACK 7 /* Control Message STACK Type */ | |
#define DDCMP_FLAG_SELECT 0x2 /* Link Select */ | |
#define DDCMP_FLAG_QSYNC 0x1 /* Quick Sync (next message won't abut this message) */ | |
#define DDCMP_CRC_SIZE 2 /* Bytes in DDCMP CRC fields */ | |
#define DDCMP_HEADER_SIZE 8 /* Bytes in DDCMP Control and Data Message headers (including header CRC) */ | |
#define DDCMP_RESP_OFFSET 3 /* Byte offset of response (ack) number field */ | |
#define DDCMP_NUM_OFFSET 4 /* Byte offset of packet number field */ | |
#define DDCMP_PACKET_TIMEOUT 4 /* Seconds before sending REP command for unacknowledged packets */ | |
#define DDCMP_DBG_PXMT TMXR_DBG_PXMT /* Debug Transmitted Packet Header Contents */ | |
#define DDCMP_DBG_PRCV TMXR_DBG_PRCV /* Debug Received Packet Header Contents */ | |
#define DDCMP_DBG_PDAT 0x1000000 /* Debug Packet Data */ | |
/* Support routines */ | |
/* crc16 polynomial x^16 + x^15 + x^2 + 1 (0xA001) CCITT LSB */ | |
static uint16 crc16_nibble[16] = { | |
0x0000, 0xCC01, 0xD801, 0x1400, 0xF001, 0x3C00, 0x2800, 0xE401, | |
0xA001, 0x6C00, 0x7800, 0xB401, 0x5000, 0x9C01, 0x8801, 0x4400, | |
}; | |
static uint16 ddcmp_crc16(uint16 crc, const void* vbuf, size_t len) | |
{ | |
const unsigned char* buf = (const unsigned char*)vbuf; | |
while(0 != len--) { | |
crc = (crc>>4) ^ crc16_nibble[(*buf ^ crc) & 0xF]; | |
crc = (crc>>4) ^ crc16_nibble[((*buf++)>>4 ^ crc) & 0xF]; | |
}; | |
return(crc); | |
} | |
/* Debug routines */ | |
#include <ctype.h> | |
static void ddcmp_packet_trace (uint32 reason, DEVICE *dptr, const char *txt, const uint8 *msg, int32 len) | |
{ | |
if (sim_deb && dptr && (reason & dptr->dctrl)) { | |
int i, same, group, sidx, oidx; | |
char outbuf[80], strbuf[18]; | |
static const char hex[] = "0123456789ABCDEF"; | |
static const char *const flags [4] = { "..", ".Q", "S.", "SQ" }; | |
static const char *const nak[18] = { "", " (HCRC)", " (DCRC)", " (REPREPLY)", /* 0-3 */ | |
"", "", "", "", /* 4-7 */ | |
" (NOBUF)", " (RXOVR)", "", "", /* 8-11 */ | |
"", "", "", "", /* 12-15 */ | |
" (TOOLONG)", " (HDRFMT)" }; /* 16-17 */ | |
const char *flag = flags[msg[2]>>6]; | |
int msg2 = msg[2] & 0x3F; | |
sim_debug(reason, dptr, "%s len: %d\n", txt, len); | |
switch (msg[0]) { | |
case DDCMP_SOH: /* Data Message */ | |
sim_debug (reason, dptr, "Data Message, Count: %d, Num: %d, Flags: %s, Resp: %d, HDRCRC: %s, DATACRC: %s\n", (msg2 << 8)|msg[1], msg[4], flag, msg[3], | |
(0 == ddcmp_crc16 (0, msg, 8)) ? "OK" : "BAD", (0 == ddcmp_crc16 (0, msg+8, 2+((msg2 << 8)|msg[1]))) ? "OK" : "BAD"); | |
break; | |
case DDCMP_ENQ: /* Control Message */ | |
sim_debug (reason, dptr, "Control: Type: %d ", msg[1]); | |
switch (msg[1]) { | |
case DDCMP_CTL_ACK: /* ACK */ | |
sim_debug (reason, dptr, "(ACK) ACKSUB: %d, Flags: %s, Resp: %d\n", msg2, flag, msg[3]); | |
break; | |
case DDCMP_CTL_NAK: /* NAK */ | |
sim_debug (reason, dptr, "(NAK) Reason: %d%s, Flags: %s, Resp: %d\n", msg2, ((msg2 > 17)? "": nak[msg2]), flag, msg[3]); | |
break; | |
case DDCMP_CTL_REP: /* REP */ | |
sim_debug (reason, dptr, "(REP) REPSUB: %d, Num: %d, Flags: %s\n", msg2, msg[4], flag); | |
break; | |
case DDCMP_CTL_STRT: /* STRT */ | |
sim_debug (reason, dptr, "(STRT) STRTSUB: %d, Flags: %s\n", msg2, flag); | |
break; | |
case DDCMP_CTL_STACK: /* STACK */ | |
sim_debug (reason, dptr, "(STACK) STCKSUB: %d, Flags: %s\n", msg2, flag); | |
break; | |
default: /* Unknown */ | |
sim_debug (reason, dptr, "(Unknown=0%o)\n", msg[1]); | |
break; | |
} | |
if (len != DDCMP_HEADER_SIZE) { | |
sim_debug (reason, dptr, "Unexpected Control Message Length: %d expected %d\n", len, DDCMP_HEADER_SIZE); | |
} | |
if (0 != ddcmp_crc16 (0, msg, len)) { | |
sim_debug (reason, dptr, "Unexpected Message CRC\n"); | |
} | |
break; | |
case DDCMP_DLE: /* Maintenance Message */ | |
sim_debug (reason, dptr, "Maintenance Message, Count: %d, Flags: %s, HDRCRC: %s, DATACRC: %s\n", (msg2 << 8)| msg[1], flag, | |
(0 == ddcmp_crc16 (0, msg, DDCMP_HEADER_SIZE)) ? "OK" : "BAD", (0 == ddcmp_crc16 (0, msg+DDCMP_HEADER_SIZE, 2+((msg2 << 8)| msg[1]))) ? "OK" : "BAD"); | |
break; | |
} | |
if (DDCMP_DBG_PDAT & dptr->dctrl) { | |
for (i=same=0; i<len; i += 16) { | |
if ((i > 0) && (0 == memcmp(&msg[i], &msg[i-16], 16))) { | |
++same; | |
continue; | |
} | |
if (same > 0) { | |
sim_debug(reason, dptr, "%04X thru %04X same as above\n", i-(16*same), i-1); | |
same = 0; | |
} | |
group = (((len - i) > 16) ? 16 : (len - i)); | |
for (sidx=oidx=0; sidx<group; ++sidx) { | |
outbuf[oidx++] = ' '; | |
outbuf[oidx++] = hex[(msg[i+sidx]>>4)&0xf]; | |
outbuf[oidx++] = hex[msg[i+sidx]&0xf]; | |
if (isprint(msg[i+sidx])) | |
strbuf[sidx] = msg[i+sidx]; | |
else | |
strbuf[sidx] = '.'; | |
} | |
outbuf[oidx] = '\0'; | |
strbuf[sidx] = '\0'; | |
sim_debug(reason, dptr, "%04X%-48s %s\n", i, outbuf, strbuf); | |
} | |
if (same > 0) { | |
sim_debug(reason, dptr, "%04X thru %04X same as above\n", i-(16*same), len-1); | |
} | |
} | |
} | |
} | |
uint16 ddcmp_crc16(uint16 crc, const void* vbuf, size_t len); | |
/* Data corruption troll, which simulates imperfect links. | |
*/ | |
/* Evaluate the corruption troll's appetite. | |
* | |
* A message can be eaten (dropped), nibbled (corrupted) or spared. | |
* | |
* The probability of a message not being spared is trollHungerLevel, | |
* expressed in milli-gulps - 0.1%. The troll selects which action | |
* to taken on selected messages with equal probability. | |
* | |
* Nibbled messages' CRCs are changed when possible to simplify | |
* identifying them when debugging. When it's too much work to | |
* find the CRC, the first byte of data is changed. The change | |
* is an XOR to make it possible to reconstruct the original data. | |
* | |
* A particularly unfortunate message can be nibbled by both | |
* the transmitter and receiver; thus the troll applies a direction- | |
* dependent pattern. | |
* | |
* Return TRUE if the troll ate the message. | |
* Return FALSE if the message was nibbled or spared. | |
*/ | |
static t_bool ddcmp_feedCorruptionTroll (TMLN *lp, uint8 *msg, t_bool rx, int32 trollHungerLevel) | |
{ | |
double r, rmax; | |
char msgbuf[80]; | |
if (trollHungerLevel == 0) | |
return FALSE; | |
#if defined(_POSIX_VERSION) || defined (_XOPEN_VERSION) | |
r = (double)random(); | |
rmax = (double)0x7fffffff; | |
#else | |
r = rand(); | |
rmax = (double)RAND_MAX; | |
#endif | |
if (msg[0] == DDCMP_ENQ) { | |
int eat = 0 + (int) (2000.0 * (r / rmax)); | |
if (eat <= (trollHungerLevel * 2)) { /* Hungry? */ | |
if (eat <= trollHungerLevel) { /* Eat the packet */ | |
sprintf (msgbuf, "troll ate a %s control message", rx ? "RCV" : "XMT"); | |
tmxr_debug_msg (rx ? DDCMP_DBG_PRCV : DDCMP_DBG_PXMT, lp, msgbuf); | |
return TRUE; | |
} | |
sprintf (msgbuf, "troll bit a %s control message", rx ? "RCV" : "XMT"); | |
tmxr_debug_msg (rx ? DDCMP_DBG_PRCV : DDCMP_DBG_PXMT, lp, msgbuf); | |
msg[6] ^= rx? 0114: 0154; /* Eat the CRC */ | |
} | |
} | |
else { | |
int eat = 0 + (int) (3000.0 * (r / rmax)); | |
if (eat <= (trollHungerLevel * 3)) { /* Hungry? */ | |
if (eat <= trollHungerLevel) { /* Eat the packet */ | |
sprintf (msgbuf, "troll ate a %s %s message", rx ? "RCV" : "XMT", (msg[0] == DDCMP_SOH)? "data" : "maintenance"); | |
tmxr_debug_msg (rx ? DDCMP_DBG_PRCV : DDCMP_DBG_PXMT, lp, msgbuf); | |
return TRUE; | |
} | |
if (eat <= (trollHungerLevel * 2)) { /* HCRC */ | |
sprintf (msgbuf, "troll bit a %s %s message", rx ? "RCV" : "XMT", (msg[0] == DDCMP_SOH)? "data" : "maintenance"); | |
tmxr_debug_msg (rx ? DDCMP_DBG_PRCV : DDCMP_DBG_PXMT, lp, msgbuf); | |
msg[6] ^= rx? 0124: 0164; | |
} | |
else { /* DCRC */ | |
sprintf (msgbuf, "troll bit %s %s DCRC\n", (rx? "RCV" : "XMT"), ((msg[0] == DDCMP_SOH)? "data" : "maintenance")); | |
tmxr_debug_msg (rx ? DDCMP_DBG_PRCV : DDCMP_DBG_PXMT, lp, msgbuf); | |
msg[8] ^= rx? 0114: 0154; /* Rather than find the CRC, the first data byte will do */ | |
} | |
} | |
} | |
return FALSE; | |
} | |
/* Get packet from specific line | |
Inputs: | |
*lp = pointer to terminal line descriptor | |
**pbuf = pointer to pointer of packet contents | |
*psize = pointer to packet size | |
Output: | |
SCPE_LOST link state lost | |
SCPE_OK Packet returned OR no packet available | |
Implementation notes: | |
1. If a packet is not yet available, then the pbuf address returned is | |
NULL, but success (SCPE_OK) is returned | |
*/ | |
static t_stat ddcmp_tmxr_get_packet_ln (TMLN *lp, const uint8 **pbuf, uint16 *psize, int32 corruptrate) | |
{ | |
int32 c; | |
size_t payloadsize; | |
char msg[32]; | |
while (TMXR_VALID & (c = tmxr_getc_ln (lp))) { | |
c &= ~TMXR_VALID; | |
if (lp->rxpboffset + 1 > lp->rxpbsize) { | |
lp->rxpbsize += 512; | |
lp->rxpb = (uint8 *)realloc (lp->rxpb, lp->rxpbsize); | |
} | |
lp->rxpb[lp->rxpboffset] = c; | |
if ((lp->rxpboffset == 0) && ((c == DDCMP_SYN) || (c == DDCMP_DEL))) { | |
tmxr_debug (DDCMP_DBG_PRCV, lp, "Ignoring Interframe Sync Character", (char *)&lp->rxpb[0], 1); | |
continue; | |
} | |
lp->rxpboffset += 1; | |
if (lp->rxpboffset == 1) { | |
switch (c) { | |
default: | |
tmxr_debug (DDCMP_DBG_PRCV, lp, "Ignoring unexpected byte in DDCMP mode", (char *)&lp->rxpb[0], 1); | |
lp->rxpboffset = 0; | |
case DDCMP_SOH: | |
case DDCMP_ENQ: | |
case DDCMP_DLE: | |
continue; | |
} | |
} | |
if (lp->rxpboffset >= DDCMP_HEADER_SIZE) { | |
if (lp->rxpb[0] == DDCMP_ENQ) { /* Control Message? */ | |
++lp->rxpcnt; | |
*pbuf = lp->rxpb; | |
*psize = DDCMP_HEADER_SIZE; | |
lp->rxpboffset = 0; | |
if (lp->mp->lines > 1) | |
sprintf (msg, "Line%d: <<< RCV Packet", (int)(lp-lp->mp->ldsc)); | |
else | |
strcpy (msg, "<<< RCV Packet"); | |
ddcmp_packet_trace (DDCMP_DBG_PRCV, lp->mp->dptr, msg, lp->rxpb, *psize); | |
if (ddcmp_feedCorruptionTroll (lp, lp->rxpb, TRUE, corruptrate)) | |
break; | |
return SCPE_OK; | |
} | |
payloadsize = ((lp->rxpb[2] & 0x3F) << 8)| lp->rxpb[1]; | |
if (lp->rxpboffset >= 10 + payloadsize) { | |
++lp->rxpcnt; | |
*pbuf = lp->rxpb; | |
*psize = 10 + payloadsize; | |
if (lp->mp->lines > 1) | |
sprintf (msg, "Line%d: <<< RCV Packet", (int)(lp-lp->mp->ldsc)); | |
else | |
strcpy (msg, "<<< RCV Packet"); | |
ddcmp_packet_trace (DDCMP_DBG_PRCV, lp->mp->dptr, msg, lp->rxpb, *psize); | |
lp->rxpboffset = 0; | |
if (ddcmp_feedCorruptionTroll (lp, lp->rxpb, TRUE, corruptrate)) | |
break; | |
return SCPE_OK; | |
} | |
} | |
} | |
*pbuf = NULL; | |
*psize = 0; | |
if (lp->conn) | |
return SCPE_OK; | |
return SCPE_LOST; | |
} | |
/* Store packet in line buffer (or store packet in line buffer and add needed CRCs) | |
Inputs: | |
*lp = pointer to line descriptor | |
*buf = pointer to packet data | |
size = size of packet | |
Outputs: | |
status = ok, connection lost, or stall | |
Implementation notea: | |
1. If the line is not connected, SCPE_LOST is returned. | |
2. If prior packet transmission still in progress, SCPE_STALL is | |
returned and no packet data is stored. The caller must retry later. | |
*/ | |
static t_stat ddcmp_tmxr_put_packet_ln (TMLN *lp, const uint8 *buf, size_t size, int32 corruptrate) | |
{ | |
t_stat r; | |
char msg[32]; | |
if (!lp->conn) | |
return SCPE_LOST; | |
if (lp->txppoffset < lp->txppsize) { | |
tmxr_debug (DDCMP_DBG_PXMT, lp, "Skipped Sending Packet - Transmit Busy", (char *)&lp->txpb[3], size); | |
return SCPE_STALL; | |
} | |
if (lp->txpbsize < size) { | |
lp->txpbsize = size; | |
lp->txpb = (uint8 *)realloc (lp->txpb, lp->txpbsize); | |
} | |
memcpy (lp->txpb, buf, size); | |
lp->txppsize = size; | |
lp->txppoffset = 0; | |
if (lp->mp->lines > 1) | |
sprintf (msg, "Line%d: >>> XMT Packet", (int)(lp-lp->mp->ldsc)); | |
else | |
strcpy (msg, ">>> XMT Packet"); | |
ddcmp_packet_trace (DDCMP_DBG_PXMT, lp->mp->dptr, msg, lp->txpb, lp->txppsize); | |
if (!ddcmp_feedCorruptionTroll (lp, lp->txpb, FALSE, corruptrate)) { | |
++lp->txpcnt; | |
while ((lp->txppoffset < lp->txppsize) && | |
(SCPE_OK == (r = tmxr_putc_ln (lp, lp->txpb[lp->txppoffset])))) | |
++lp->txppoffset; | |
tmxr_send_buffered_data (lp); | |
} | |
return lp->conn ? SCPE_OK : SCPE_LOST; | |
} | |
static t_stat ddcmp_tmxr_put_packet_crc_ln (TMLN *lp, uint8 *buf, size_t size, int32 corruptrate) | |
{ | |
uint16 hdr_crc16 = ddcmp_crc16(0, buf, DDCMP_HEADER_SIZE-DDCMP_CRC_SIZE); | |
buf[DDCMP_HEADER_SIZE-DDCMP_CRC_SIZE] = hdr_crc16 & 0xFF; | |
buf[DDCMP_HEADER_SIZE-DDCMP_CRC_SIZE+1] = (hdr_crc16>>8) & 0xFF; | |
if (size > DDCMP_HEADER_SIZE) { | |
uint16 data_crc16 = ddcmp_crc16(0, buf+DDCMP_HEADER_SIZE, size-(DDCMP_HEADER_SIZE+DDCMP_CRC_SIZE)); | |
buf[size-DDCMP_CRC_SIZE] = data_crc16 & 0xFF; | |
buf[size-DDCMP_CRC_SIZE+1] = (data_crc16>>8) & 0xFF; | |
} | |
return ddcmp_tmxr_put_packet_ln (lp, buf, size, corruptrate); | |
} | |
static void ddcmp_build_data_packet (uint8 *buf, size_t size, uint8 flags, uint8 sequence, uint8 ack) | |
{ | |
buf[0] = DDCMP_SOH; | |
buf[1] = size & 0xFF; | |
buf[2] = ((size >> 8) & 0x3F) | (flags << 6); | |
buf[3] = ack; | |
buf[4] = sequence; | |
buf[5] = 1; | |
} | |
static void ddcmp_build_maintenance_packet (uint8 *buf, size_t size) | |
{ | |
buf[0] = DDCMP_DLE; | |
buf[1] = size & 0xFF; | |
buf[2] = ((size >> 8) & 0x3F) | (DDCMP_FLAG_SELECT|DDCMP_FLAG_QSYNC << 6); | |
buf[3] = 0; | |
buf[4] = 0; | |
buf[5] = 1; | |
} | |
static t_stat ddcmp_tmxr_put_data_packet_ln (TMLN *lp, uint8 *buf, size_t size, uint8 flags, uint8 sequence, uint8 ack) | |
{ | |
ddcmp_build_data_packet (buf, size, flags, sequence, ack); | |
return ddcmp_tmxr_put_packet_crc_ln (lp, buf, size, 0); | |
} | |
static void ddcmp_build_control_packet (uint8 *buf, uint8 type, uint8 subtype, uint8 flags, uint8 sndr, uint8 rcvr) | |
{ | |
buf[0] = DDCMP_ENQ; /* Control Message */ | |
buf[1] = type; /* STACK type */ | |
buf[2] = (subtype & 0x3f) | (flags << 6); | |
/* STACKSUB type and flags */ | |
buf[3] = rcvr; /* RCVR */ | |
buf[4] = sndr; /* SNDR */ | |
buf[5] = 1; /* ADDR */ | |
} | |
static t_stat ddcmp_tmxr_put_control_packet_ln (TMLN *lp, uint8 *buf, uint8 type, uint8 subtype, uint8 flags, uint8 sndr, uint8 rcvr) | |
{ | |
ddcmp_build_control_packet (buf, type, subtype, flags, sndr, rcvr); | |
return ddcmp_tmxr_put_packet_crc_ln (lp, buf, DDCMP_HEADER_SIZE, 0); | |
} | |
static void ddcmp_build_ack_packet (uint8 *buf, uint8 ack, uint8 flags) | |
{ | |
ddcmp_build_control_packet (buf, DDCMP_CTL_ACK, 0, flags, 0, ack); | |
} | |
static t_stat ddcmp_tmxr_put_ack_packet_ln (TMLN *lp, uint8 *buf, uint8 ack, uint8 flags) | |
{ | |
ddcmp_build_ack_packet (buf, ack, flags); | |
return ddcmp_tmxr_put_packet_crc_ln (lp, buf, DDCMP_HEADER_SIZE, 0); | |
} | |
static void ddcmp_build_nak_packet (uint8 *buf, uint8 reason, uint8 nack, uint8 flags) | |
{ | |
ddcmp_build_control_packet (buf, DDCMP_CTL_NAK, reason, flags, 0, nack); | |
} | |
static t_stat ddcmp_tmxr_put_nak_packet_ln (TMLN *lp, uint8 *buf, uint8 reason, uint8 nack, uint8 flags) | |
{ | |
return ddcmp_tmxr_put_control_packet_ln (lp, buf, DDCMP_CTL_NAK, reason, flags, 0, nack); | |
} | |
static void ddcmp_build_rep_packet (uint8 *buf, uint8 ack, uint8 flags) | |
{ | |
ddcmp_build_control_packet (buf, DDCMP_CTL_REP, 0, flags, ack, 0); | |
} | |
static t_stat ddcmp_tmxr_put_rep_packet_ln (TMLN *lp, uint8 *buf, uint8 ack, uint8 flags) | |
{ | |
return ddcmp_tmxr_put_control_packet_ln (lp, buf, DDCMP_CTL_REP, 0, flags, ack, 0); | |
} | |
static void ddcmp_build_start_packet (uint8 *buf) | |
{ | |
ddcmp_build_control_packet (buf, DDCMP_CTL_STRT, 0, DDCMP_FLAG_SELECT|DDCMP_FLAG_QSYNC, 0, 0); | |
} | |
static t_stat ddcmp_tmxr_put_start_packet_ln (TMLN *lp, uint8 *buf) | |
{ | |
ddcmp_build_start_packet (buf); | |
return ddcmp_tmxr_put_packet_crc_ln (lp, buf, DDCMP_HEADER_SIZE, 0); | |
} | |
static void ddcmp_build_start_ack_packet (uint8 *buf) | |
{ | |
ddcmp_build_control_packet (buf, DDCMP_CTL_STACK, 0, DDCMP_FLAG_SELECT|DDCMP_FLAG_QSYNC, 0, 0); | |
} | |
static t_stat ddcmp_tmxr_put_start_ack_packet_ln (TMLN *lp, uint8 *buf) | |
{ | |
ddcmp_build_start_ack_packet (buf); | |
return ddcmp_tmxr_put_packet_crc_ln (lp, buf, DDCMP_HEADER_SIZE, 0); | |
} | |
#endif /* PDP11_DDCMP_H_ */ |