From 178968a6219fd1952fddfeac9db6ca9a4654c4b9 Mon Sep 17 00:00:00 2001 From: Richard Cornwell Date: Sun, 3 Nov 2019 22:11:23 -0500 Subject: [PATCH] KA10: Regular update. Code cleanup and minor enhancements. --- PDP10/ka10_auxcpu.c | 8 +- PDP10/ka10_ch10.c | 4 +- PDP10/ka10_dpk.c | 20 +- PDP10/ka10_mty.c | 142 +++-- PDP10/ka10_pd.c | 44 +- PDP10/ka10_ten11.c | 15 +- PDP10/ka10_tk10.c | 32 +- PDP10/kx10_cpu.c | 24 +- PDP10/kx10_cty.c | 11 + PDP10/kx10_dpy.c | 7 +- PDP10/kx10_imp.c | 1208 ++++++++++++++++++++++++++++++++----------- PDP10/kx10_lp.c | 5 +- PDP10/kx10_mt.c | 2 +- display/display.c | 9 + display/display.h | 5 + doc/ka10_doc.doc | Bin 215040 -> 242688 bytes 16 files changed, 1130 insertions(+), 406 deletions(-) diff --git a/PDP10/ka10_auxcpu.c b/PDP10/ka10_auxcpu.c index fcda5e81..f0031002 100644 --- a/PDP10/ka10_auxcpu.c +++ b/PDP10/ka10_auxcpu.c @@ -54,7 +54,7 @@ static int pia = 0; static int status = 0; -t_value auxcpu_base = 03000000; +int auxcpu_base = 03000000; static t_stat auxcpu_devio(uint32 dev, t_uint64 *data); static t_stat auxcpu_svc (UNIT *uptr); @@ -187,7 +187,7 @@ static t_stat auxcpu_svc (UNIT *uptr) auxcpu_ldsc.rcve = 1; uptr->wait = AUXCPU_POLL; } - sim_activate (uptr, uptr->wait); + sim_clock_coschedule (uptr, uptr->wait); return SCPE_OK; } @@ -407,13 +407,13 @@ static t_stat auxcpu_set_base (UNIT *uptr, int32 val, CONST char *cptr, void *de if (r != SCPE_OK) return SCPE_ARG; - auxcpu_base = x; + auxcpu_base = (int)x; return SCPE_OK; } static t_stat auxcpu_show_base (FILE *st, UNIT *uptr, int32 val, CONST void *desc) { - fprintf (st, "Base: %llo", auxcpu_base); + fprintf (st, "Base: %06o", auxcpu_base); return SCPE_OK; } #endif diff --git a/PDP10/ka10_ch10.c b/PDP10/ka10_ch10.c index 09d332ec..0d682d80 100644 --- a/PDP10/ka10_ch10.c +++ b/PDP10/ka10_ch10.c @@ -430,8 +430,8 @@ t_stat ch10_attach (UNIT *uptr, CONST char *cptr) if (peer[0] == '\0') return sim_messagef (SCPE_2FARG, "Must set Chaosnet PEER \"SET CH PEER=host:port\"\n"); - snprintf (linkinfo, sizeof(linkinfo), "Buffer=%d,UDP,%s,PACKET,Connect=%s,Line=0", - (int)sizeof tx_buffer, cptr, peer); + snprintf (linkinfo, sizeof(linkinfo), "Buffer=%d,UDP,%s,PACKET,Connect=%.*s,Line=0", + (int)sizeof tx_buffer, cptr, (int)(sizeof(linkinfo) - (45 + strlen(cptr))), peer); r = tmxr_attach (&ch10_tmxr, uptr, linkinfo); if (r != SCPE_OK) { sim_debug (DBG_ERR, &ch10_dev, "TMXR error opening master\n"); diff --git a/PDP10/ka10_dpk.c b/PDP10/ka10_dpk.c index 5cc9298c..d9e69d98 100644 --- a/PDP10/ka10_dpk.c +++ b/PDP10/ka10_dpk.c @@ -86,7 +86,7 @@ static int dpk_ird = 0; static int dpk_iwr = 0; UNIT dpk_unit[] = { - {UDATA(dpk_svc, TT_MODE_8B|UNIT_ATTABLE|UNIT_DISABLE, 0)}, /* 0 */ + {UDATA(dpk_svc, TT_MODE_8B|UNIT_IDLE|UNIT_ATTABLE, 0)}, /* 0 */ }; DIB dpk_dib = {DPK_DEVNUM, 1, &dpk_devio, NULL}; @@ -249,8 +249,10 @@ static int dpk_output (int port, TMLN *lp) } ch = ildb (&M[dpk_base + 2*port + 1]); - ch = sim_tt_outcvt(ch & 0377, TT_GET_MODE (dpk_unit[0].flags)); - tmxr_putc_ln (lp, ch); + if (lp->conn) { + ch = sim_tt_outcvt(ch & 0377, TT_GET_MODE (dpk_unit[0].flags)); + tmxr_putc_ln (lp, ch); + } count = M[dpk_base + 2*port] - 1; M[dpk_base + 2*port] = count & 0777777777777LL; @@ -264,7 +266,7 @@ static t_stat dpk_svc (UNIT *uptr) int i; /* 16 ports at 4800 baud, rounded up. */ - sim_activate_after (uptr, 200); + sim_clock_coschedule (uptr, 200); i = tmxr_poll_conn (&dpk_desc); if (i >= 0) { @@ -308,6 +310,8 @@ static t_stat dpk_svc (UNIT *uptr) static t_stat dpk_reset (DEVICE *dptr) { + int i; + sim_debug(DEBUG_CMD, &dpk_dev, "Reset\n"); if (dpk_unit->flags & UNIT_ATT) sim_activate (dpk_unit, tmxr_poll); @@ -321,6 +325,11 @@ static t_stat dpk_reset (DEVICE *dptr) memset (dpk_port, 0, sizeof dpk_port); clr_interrupt(DPK_DEVNUM); + for (i = 0; i < DPK_LINES; i++) { + tmxr_set_line_unit (&dpk_desc, i, dpk_unit); + tmxr_set_line_output_unit (&dpk_desc, i, dpk_unit); + } + return SCPE_OK; } @@ -333,9 +342,6 @@ static t_stat dpk_attach (UNIT *uptr, CONST char *cptr) for (i = 0; i < DPK_LINES; i++) { dpk_ldsc[i].rcve = 0; dpk_ldsc[i].xmte = 0; - /* Clear txdone so tmxr_txdone_ln will not return return true - on the first call. */ - dpk_ldsc[i].txdone = 0; } if (stat == SCPE_OK) sim_activate (uptr, tmxr_poll); diff --git a/PDP10/ka10_mty.c b/PDP10/ka10_mty.c index 6a67e4bc..f47e4a9f 100644 --- a/PDP10/ka10_mty.c +++ b/PDP10/ka10_mty.c @@ -1,6 +1,6 @@ /* ka10_tk10.c: MTY, Morton multiplex box: Terminal multiplexor. - Copyright (c) 2018, Lars Brinkhoff + Copyright (c) 2018-2019, Lars Brinkhoff Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -37,6 +37,7 @@ #define MTY_NAME "MTY" #define MTY_DEVNUM 0400 #define MTY_LINES 32 +#define MTY_FIRST C1 /* Frist character in output word. */ #define MTY_PIA 0000007 /* PI channel assignment */ #define MTY_RQINT 0000010 /* Request interrupt. */ @@ -50,7 +51,8 @@ #define MTY_CONO_BITS (MTY_PIA | MTY_LINE) static t_stat mty_devio(uint32 dev, uint64 *data); -static t_stat mty_svc (UNIT *uptr); +static t_stat mty_input_svc (UNIT *uptr); +static t_stat mty_output_svc (UNIT *uptr); static t_stat mty_reset (DEVICE *dptr); static t_stat mty_attach (UNIT *uptr, CONST char *cptr); static t_stat mty_detach (UNIT *uptr); @@ -59,13 +61,18 @@ static t_stat mty_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); extern int32 tmxr_poll; +static uint32 mty_active_bitmask; +static uint64 mty_output_word[MTY_LINES]; +static int32 mty_input_character; + TMLN mty_ldsc[MTY_LINES] = { 0 }; TMXR mty_desc = { MTY_LINES, 0, 0, mty_ldsc }; static uint64 status = 0; UNIT mty_unit[] = { - {UDATA(mty_svc, TT_MODE_7B|UNIT_ATTABLE|UNIT_DISABLE, 0)}, /* 0 */ + {UDATA(mty_input_svc, TT_MODE_7B|UNIT_IDLE|UNIT_ATTABLE, 0)}, /* 0 */ + {UDATA(mty_output_svc, UNIT_DIS|UNIT_IDLE, 0)}, /* 0 */ }; DIB mty_dib = {MTY_DEVNUM, 1, &mty_devio, NULL}; @@ -85,7 +92,7 @@ MTAB mty_mod[] = { DEVICE mty_dev = { MTY_NAME, mty_unit, NULL, mty_mod, - 1, 8, 0, 1, 8, 36, + 2, 8, 0, 1, 8, 36, NULL, NULL, mty_reset, NULL, mty_attach, mty_detach, &mty_dib, DEV_DISABLE | DEV_DIS | DEV_DEBUG, 0, dev_debug, NULL, NULL, mty_help, NULL, NULL, mty_description @@ -97,7 +104,6 @@ static t_stat mty_devio(uint32 dev, uint64 *data) TMLN *lp; int line; uint64 word; - int ch; switch(dev & 07) { case CONO: @@ -107,9 +113,6 @@ static t_stat mty_devio(uint32 dev, uint64 *data) line = (status & MTY_LINE) >> 12; if (*data & MTY_STOP) { status &= ~MTY_ODONE; - /* Set txdone so future calls to tmxr_txdone_ln will - return -1 rather than 1. */ - mty_ldsc[line].txdone = 1; sim_debug(DEBUG_CMD, &mty_dev, "Clear output done line %d\n", line); } @@ -130,30 +133,21 @@ static t_stat mty_devio(uint32 dev, uint64 *data) line = (status & MTY_LINE) >> 12; word = *data; sim_debug(DEBUG_DATAIO, &mty_dev, "DATAO line %d -> %012llo\n", - line, *data); + line, word); lp = &mty_ldsc[line]; - if (!mty_ldsc[line].conn) - /* If the line isn't connected, clear txdone to force - tmxr_txdone_ln to return 1 rather than -1. */ - mty_ldsc[line].txdone = 0; - /* Write up to five characters extracted from a word. NUL can - only be in the first character. */ - ch = (word >> 29) & 0177; - tmxr_putc_ln (lp, sim_tt_outcvt(ch, TT_GET_MODE (mty_unit[0].flags))); - while ((ch = (word >> 22) & 0177) != 0) { - tmxr_putc_ln (lp, sim_tt_outcvt(ch, TT_GET_MODE (mty_unit[0].flags))); - - word <<= 7; - } + mty_output_word[line] = word | MTY_FIRST; + mty_active_bitmask |= 1 << line; + sim_activate_abs (&mty_unit[1], 0); status &= ~MTY_ODONE; break; case DATAI: line = (status & MTY_LINE) >> 12; lp = &mty_ldsc[line]; - *data = tmxr_getc_ln (lp) & 0177; + *data = mty_input_character; sim_debug(DEBUG_DATAIO, &mty_dev, "DATAI line %d -> %012llo\n", line, *data); status &= ~MTY_IDONE; + sim_activate_abs (&mty_unit[0], 0); break; } @@ -165,52 +159,38 @@ static t_stat mty_devio(uint32 dev, uint64 *data) return SCPE_OK; } -static t_stat mty_svc (UNIT *uptr) +static t_stat mty_input_svc (UNIT *uptr) { static int scan = 0; + int32 ch; int i; - /* High speed device, poll every 0.1 ms. */ - sim_activate_after (uptr, 100); + sim_clock_coschedule (uptr, 1000); i = tmxr_poll_conn (&mty_desc); if (i >= 0) { - mty_ldsc[i].conn = 1; mty_ldsc[i].rcve = 1; mty_ldsc[i].xmte = 1; - /* Set txdone so tmxr_txdone_ln will not return return 1 on - the first call after a new connection. */ - mty_ldsc[i].txdone = 1; sim_debug(DEBUG_CMD, &mty_dev, "Connect %d\n", i); } tmxr_poll_rx (&mty_desc); - tmxr_poll_tx (&mty_desc); for (i = 0; i < MTY_LINES; i++) { /* Round robin scan 32 lines. */ scan = (scan + 1) & 037; - /* 1 means the line became ready since the last check. Ignore - -1 which means "still ready". */ - if (tmxr_txdone_ln (&mty_ldsc[scan]) == 1) { - sim_debug(DEBUG_DETAIL, &mty_dev, "Output ready line %d\n", scan); - status &= ~MTY_LINE; - status |= scan << 12; - status |= MTY_ODONE; - set_interrupt(MTY_DEVNUM, status & MTY_PIA); - break; - } - - if (!mty_ldsc[scan].conn) - continue; - - if (tmxr_input_pending_ln (&mty_ldsc[scan])) { + ch = tmxr_getc_ln (&mty_ldsc[scan]); + if (ch & TMXR_VALID) { + mty_input_character = ch & 0177; sim_debug(DEBUG_DETAIL, &mty_dev, "Input ready line %d\n", scan); status &= ~MTY_LINE; status |= scan << 12; status |= MTY_IDONE; set_interrupt(MTY_DEVNUM, status & MTY_PIA); + + /* No more scanning until DATAI has read this character. */ + sim_cancel (&mty_unit[0]); break; } } @@ -218,17 +198,74 @@ static t_stat mty_svc (UNIT *uptr) return SCPE_OK; } +static t_stat mty_output_svc (UNIT *uptr) +{ + static int scan = 0; + uint64 word; + int i, ch; + int32 txdone; + + for (i = 0; i < MTY_LINES; i++) { + /* Round robin scan 32 lines. */ + scan = (scan + 1) & 037; + + if ((mty_active_bitmask & (1 << scan)) == 0 || + (txdone = tmxr_txdone_ln (&mty_ldsc[scan])) == 0) + continue; + + /* Write up to five characters extracted from a word. NUL + can only be in the first character. */ + word = mty_output_word[scan]; + if (word != 0) { + ch = (word >> 29) & 0177; + ch = sim_tt_outcvt(ch, TT_GET_MODE (mty_unit[0].flags)); + if (tmxr_putc_ln (&mty_ldsc[scan], ch) != SCPE_STALL) + mty_output_word[scan] = (word << 7) & FMASK; + } else if (txdone == 1) { + sim_debug(DEBUG_DETAIL, &mty_dev, "Output ready line %d\n", scan); + status &= ~MTY_LINE; + status |= scan << 12; + status |= MTY_ODONE; + set_interrupt(MTY_DEVNUM, status & MTY_PIA); + mty_active_bitmask &= ~(1 << scan); + + /* Stop scanning; can only signal output done for one line + at a time. */ + break; + } + } + + tmxr_poll_tx (&mty_desc); + + /* SIMH will actually schedule this UNIT when output is due + according to the line speed. */ + sim_activate_after (uptr, 1000000); + + return SCPE_OK; +} + static t_stat mty_reset (DEVICE *dptr) { + int i; + sim_debug(DEBUG_CMD, &mty_dev, "Reset\n"); - if (mty_unit->flags & UNIT_ATT) + if (mty_unit->flags & UNIT_ATT) { sim_activate (mty_unit, tmxr_poll); - else - sim_cancel (mty_unit); + sim_activate_after (&mty_unit[1], 100); + } else { + sim_cancel (&mty_unit[0]); + sim_cancel (&mty_unit[1]); + } status = 0; clr_interrupt(MTY_DEVNUM); + for (i = 0; i < MTY_LINES; i++) { + tmxr_set_line_unit (&mty_desc, i, &mty_unit[0]); + tmxr_set_line_output_unit (&mty_desc, i, &mty_unit[1]); + tmxr_set_line_speed(&mty_ldsc[i], "80000"); + } + return SCPE_OK; } @@ -241,14 +278,12 @@ static t_stat mty_attach (UNIT *uptr, CONST char *cptr) for (i = 0; i < MTY_LINES; i++) { mty_ldsc[i].rcve = 0; mty_ldsc[i].xmte = 0; - /* Set txdone so tmxr_txdone_ln will not return return 1 on - the first call. */ - mty_ldsc[i].txdone = 1; } if (stat == SCPE_OK) { status = 0; sim_activate (uptr, tmxr_poll); } + mty_active_bitmask = 0; return stat; } @@ -262,7 +297,8 @@ static t_stat mty_detach (UNIT *uptr) mty_ldsc[i].xmte = 0; } status = 0; - sim_cancel (uptr); + sim_cancel (&mty_unit[0]); + sim_cancel (&mty_unit[1]); return stat; } diff --git a/PDP10/ka10_pd.c b/PDP10/ka10_pd.c index 2aeaeed9..d428cec9 100644 --- a/PDP10/ka10_pd.c +++ b/PDP10/ka10_pd.c @@ -42,14 +42,24 @@ #define PD_DEVNUM 0500 #define PD_OFF (1 << DEV_V_UF) +#define PIA_CH u3 + +#define PIA_FLG 07 +#define CLK_IRQ 010 + +#define TMR_PD 3 + +int pd_tps = 60; + t_stat pd_devio(uint32 dev, uint64 *data); const char *pd_description (DEVICE *dptr); +t_stat pd_srv(UNIT *uptr); t_stat pd_set_on(UNIT *uptr, int32 val, CONST char *cptr, void *desc); t_stat pd_set_off(UNIT *uptr, int32 val, CONST char *cptr, void *desc); t_stat pd_show_on(FILE *st, UNIT *uptr, int32 val, CONST void *desc); UNIT pd_unit[] = { - {UDATA(NULL, UNIT_DISABLE, 0)}, /* 0 */ + {UDATA(pd_srv, UNIT_IDLE|UNIT_DISABLE, 0)}, /* 0 */ }; DIB pd_dib = {PD_DEVNUM, 1, &pd_devio, NULL}; @@ -91,6 +101,21 @@ t_stat pd_devio(uint32 dev, uint64 *data) else *data = pd_ticks(); break; + case CONI: + *data = (uint64)(pd_unit[0].PIA_CH & (CLK_IRQ|PIA_FLG)); + break; + case CONO: + pd_unit[0].PIA_CH &= ~(PIA_FLG); + pd_unit[0].PIA_CH |= (int32)(*data & PIA_FLG); + if (pd_unit[0].PIA_CH & PIA_FLG) { + if (!sim_is_active(pd_unit)) + sim_activate(pd_unit, 10000); + } + if (*data & CLK_IRQ) { + pd_unit[0].PIA_CH &= ~(CLK_IRQ); + clr_interrupt(PD_DEVNUM); + } + break; default: break; } @@ -98,6 +123,23 @@ t_stat pd_devio(uint32 dev, uint64 *data) return SCPE_OK; } +t_stat +pd_srv(UNIT * uptr) +{ + int32 t; + + t = sim_rtcn_calb (pd_tps, TMR_PD); + sim_activate_after(uptr, 1000000/pd_tps); + if (uptr->PIA_CH & PIA_FLG) { + uptr->PIA_CH |= CLK_IRQ; + set_interrupt(PD_DEVNUM, uptr->PIA_CH); + } else + sim_cancel(uptr); + + return SCPE_OK; +} + + const char *pd_description (DEVICE *dptr) { return "Paul DeCoriolis clock"; diff --git a/PDP10/ka10_ten11.c b/PDP10/ka10_ten11.c index b7f817d1..1ff255bb 100644 --- a/PDP10/ka10_ten11.c +++ b/PDP10/ka10_ten11.c @@ -1,6 +1,6 @@ /* ka10_ten11.c: Rubin 10-11 interface. - Copyright (c) 2018, Lars Brinkhoff + Copyright (c) 2018-2019, Lars Brinkhoff Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -32,12 +32,7 @@ #if (NUM_DEVS_TEN11 > 0) #include -//#include #include -//#include -//#include -//#include -//#include /* Rubin 10-11 pager. */ static uint64 ten11_pager[256]; @@ -73,7 +68,7 @@ static t_stat ten11_attach_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, static const char *ten11_description (DEVICE *dptr); UNIT ten11_unit[1] = { - { UDATA (&ten11_svc, UNIT_IDLE|UNIT_ATTABLE, 0), 1000 }, + { UDATA (&ten11_svc, UNIT_IDLE|UNIT_ATTABLE, 0), 1000 }, }; static REG ten11_reg[] = { @@ -128,7 +123,7 @@ static t_stat ten11_reset (DEVICE *dptr) ten11_desc.buffered = 2048; if (ten11_unit[0].flags & UNIT_ATT) - sim_activate (&ten11_unit[0], 1000); + sim_activate_abs (&ten11_unit[0], 0); else sim_cancel (&ten11_unit[0]); @@ -147,7 +142,7 @@ static t_stat ten11_attach (UNIT *uptr, CONST char *cptr) if (r != SCPE_OK) /* error? */ return r; sim_debug(DBG_TRC, &ten11_dev, "activate connection\n"); - sim_activate (uptr, 10); /* start poll */ + sim_activate_abs (uptr, 0); /* start poll */ uptr->flags |= UNIT_ATT; return SCPE_OK; } @@ -184,7 +179,7 @@ static t_stat ten11_svc (UNIT *uptr) ten11_ldsc.rcve = 1; uptr->wait = TEN11_POLL; } - sim_activate (uptr, uptr->wait); + sim_clock_coschedule (uptr, uptr->wait); return SCPE_OK; } diff --git a/PDP10/ka10_tk10.c b/PDP10/ka10_tk10.c index ad6599d2..1ddfcd77 100644 --- a/PDP10/ka10_tk10.c +++ b/PDP10/ka10_tk10.c @@ -69,7 +69,7 @@ TMXR tk10_desc = { TK10_LINES, 0, 0, tk10_ldsc }; static uint64 status = 0; UNIT tk10_unit[] = { - {UDATA(tk10_svc, TT_MODE_7B|UNIT_ATTABLE|UNIT_DISABLE, 0)}, /* 0 */ + {UDATA(tk10_svc, TT_MODE_7B|UNIT_IDLE|UNIT_ATTABLE, 0)}, /* 0 */ }; DIB tk10_dib = {TK10_DEVNUM, 1, &tk10_devio, NULL}; @@ -115,9 +115,6 @@ static t_stat tk10_devio(uint32 dev, uint64 *data) status &= ~TK10_ODONE; if (!(status & TK10_IDONE)) status &= ~TK10_INT; - /* Set txdone so future calls to tmxr_txdone_ln will - return -1 rather than 1. */ - tk10_ldsc[(status & TK10_TYO) >> 12].txdone = 1; sim_debug(DEBUG_CMD, &tk10_dev, "Clear output done port %lld\n", (status & TK10_TYO) >> 12); } @@ -144,13 +141,11 @@ static t_stat tk10_devio(uint32 dev, uint64 *data) port = (status & TK10_TYO) >> 12; sim_debug(DEBUG_DATAIO, &tk10_dev, "DATAO port %d -> %012llo\n", port, *data); - lp = &tk10_ldsc[port]; - ch = sim_tt_outcvt(*data & 0377, TT_GET_MODE (tk10_unit[0].flags)); - if (!tk10_ldsc[port].conn) - /* If the port isn't connected, clear txdone to force - tmxr_txdone_ln to return 1 rather than -1. */ - tk10_ldsc[port].txdone = 0; - tmxr_putc_ln (lp, ch); + if (tk10_ldsc[port].conn) { + lp = &tk10_ldsc[port]; + ch = sim_tt_outcvt(*data & 0377, TT_GET_MODE (tk10_unit[0].flags)); + tmxr_putc_ln (lp, ch); + } status &= ~TK10_ODONE; if (!(status & TK10_IDONE)) { status &= ~TK10_INT; @@ -185,16 +180,13 @@ static t_stat tk10_svc (UNIT *uptr) int i; /* Slow hardware only supported 300 baud teletypes. */ - sim_activate_after (uptr, 2083); + sim_clock_coschedule (uptr, 2083); i = tmxr_poll_conn (&tk10_desc); if (i >= 0) { tk10_ldsc[i].conn = 1; tk10_ldsc[i].rcve = 1; tk10_ldsc[i].xmte = 1; - /* Set txdone so tmxr_txdone_ln will not return return 1 on - the first call after a new connection. */ - tk10_ldsc[i].txdone = 1; sim_debug(DEBUG_CMD, &tk10_dev, "Connect %d\n", i); } @@ -244,6 +236,8 @@ static t_stat tk10_svc (UNIT *uptr) static t_stat tk10_reset (DEVICE *dptr) { + int i; + sim_debug(DEBUG_CMD, &tk10_dev, "Reset\n"); if (tk10_unit->flags & UNIT_ATT) sim_activate (tk10_unit, tmxr_poll); @@ -253,6 +247,11 @@ static t_stat tk10_reset (DEVICE *dptr) status = 0; clr_interrupt(TK10_DEVNUM); + for (i = 0; i < TK10_LINES; i++) { + tmxr_set_line_unit (&tk10_desc, i, tk10_unit); + tmxr_set_line_output_unit (&tk10_desc, i, tk10_unit); + } + return SCPE_OK; } @@ -265,9 +264,6 @@ static t_stat tk10_attach (UNIT *uptr, CONST char *cptr) for (i = 0; i < TK10_LINES; i++) { tk10_ldsc[i].rcve = 0; tk10_ldsc[i].xmte = 0; - /* Set txdone so tmxr_txdone_ln will not return return 1 on - the first call. */ - tk10_ldsc[i].txdone = 1; } if (stat == SCPE_OK) { status = TK10_GO; diff --git a/PDP10/kx10_cpu.c b/PDP10/kx10_cpu.c index b93bda28..11042363 100644 --- a/PDP10/kx10_cpu.c +++ b/PDP10/kx10_cpu.c @@ -2649,7 +2649,8 @@ if ((reason = build_dev_tab ()) != SCPE_OK) /* build, chk dib_tab */ watch_stop = 0; while ( reason == 0) { /* loop until ABORT */ - if (sim_interval <= 0) { /* check clock queue */ + AIO_CHECK_EVENT; /* queue async events */ + if (sim_interval <= 0) { /* check clock queue */ if ((reason = sim_process_event()) != SCPE_OK) {/* error? stop sim */ #if ITS if (QITS) @@ -2748,6 +2749,7 @@ no_fetch: if (Mem_read(pi_cycle | uuo_cycle, 1, 0)) goto last; /* Handle events during a indirect loop */ + AIO_CHECK_EVENT; /* queue async events */ if (sim_interval-- <= 0) { if ((reason = sim_process_event()) != SCPE_OK) { return reason; @@ -2796,8 +2798,9 @@ st_pi: #endif /* Check if possible idle loop */ - if (sim_idle_enab && (FLAGS & USER) != 0 && PC < 020 && AB < 020 && - (IR & 0760) == 0340) { + if (sim_idle_enab && + (((FLAGS & USER) != 0 && PC < 020 && AB < 020 && (IR & 0760) == 0340) || + (uuo_cycle && (IR & 0740) == 0 && IA == 041))) { sim_idle (TMR_RTC, FALSE); } @@ -4765,6 +4768,7 @@ left: case 0251: /* BLT */ BR = AB; do { + AIO_CHECK_EVENT; /* queue async events */ if (sim_interval <= 0) { sim_process_event(); } @@ -6009,6 +6013,19 @@ qua_srv(UNIT * uptr) #endif +/* + * This sequence of instructions is a mix that hopefully + * represents a resonable instruction set that is a close + * estimate to the normal calibrated result. + */ + +static const char *pdp10_clock_precalibrate_commands[] = { + "-m 100 ADDM 0,110", + "-m 101 ADDI 0,1", + "-m 102 JRST 100", + "PC 100", + NULL}; + /* Reset routine */ t_stat cpu_reset (DEVICE *dptr) @@ -6043,6 +6060,7 @@ exec_map = 0; for(i=0; i < 128; dev_irq[i++] = 0); sim_brk_types = SWMASK('E') | SWMASK('W') | SWMASK('R'); sim_brk_dflt = SWMASK ('E'); +sim_clock_precalibrate_commands = pdp10_clock_precalibrate_commands; sim_rtcn_init_unit (&cpu_unit[0], cpu_unit[0].wait, TMR_RTC); sim_activate(&cpu_unit[0], 10000); #if MPX_DEV diff --git a/PDP10/kx10_cty.c b/PDP10/kx10_cty.c index 32315ff3..eb06805b 100644 --- a/PDP10/kx10_cty.c +++ b/PDP10/kx10_cty.c @@ -181,6 +181,11 @@ t_stat cty_reset (DEVICE *dptr) t_stat cty_stop_os (UNIT *uptr, int32 val, CONST char *cptr, void *desc) { +#if ITS + if (cpu_unit[0].flags & UNIT_ITSPAGE) + M[037] = FMASK; + else +#endif M[CTY_SWITCH] = 1; /* tell OS to stop */ return SCPE_OK; } @@ -196,7 +201,13 @@ t_stat cty_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cpt { fprintf (st, "To stop the cpu use the command:\n\n"); fprintf (st, " sim> SET CTY STOP\n\n"); +#if ITS +fprintf (st, "If the CPU is in standard mode, this will write 1 to location\n\n"); +fprintf (st, "%03o, causing TOPS10 to stop. If the CPU is in ITS mode, this\n\n", CTY_SWITCH); +fprintf (st, "will write -1 to location 037, causing ITS to stop.\n\n"); +#else fprintf (st, "This will write a 1 to location %03o, causing TOPS10 to stop\n\n", CTY_SWITCH); +#endif fprintf (st, "The additional terminals can be set to one of four modes: UC, 7P, 7B, or 8B.\n\n"); fprintf (st, " mode input characters output characters\n\n"); fprintf (st, " UC lower case converted lower case converted to upper case,\n"); diff --git a/PDP10/kx10_dpy.c b/PDP10/kx10_dpy.c index e8b38222..54e2fd6f 100644 --- a/PDP10/kx10_dpy.c +++ b/PDP10/kx10_dpy.c @@ -246,6 +246,8 @@ t_stat dpy_devio(uint32 dev, uint64 *data) { dpy_update_status( uptr, ty340_reset(&dpy_dev), 1); sim_debug(DEBUG_CONO, &dpy_dev, "DPY %03o CONO %06o PC=%06o %06o\n", dev, (uint32)*data, PC, uptr->STAT_REG & ~STAT_VALID); + if (!sim_is_active(uptr)) + sim_activate_after(uptr, DPY_CYCLE_US); break; case DATAO: @@ -262,6 +264,8 @@ t_stat dpy_devio(uint32 dev, uint64 *data) { inst = (uint32)RRZ(*data); dpy_update_status(uptr, ty340_instruction(inst), 1); } + if (!sim_is_active(uptr)) + sim_activate_after(uptr, DPY_CYCLE_US); break; case DATAI: @@ -276,7 +280,8 @@ t_stat dpy_devio(uint32 dev, uint64 *data) { /* Timer service - */ t_stat dpy_svc (UNIT *uptr) { - sim_activate_after(uptr, DPY_CYCLE_US); /* requeue! */ + if (!display_is_blank() || uptr->INT_COUNTDOWN > 0) + sim_activate_after(uptr, DPY_CYCLE_US); /* requeue! */ display_age(DPY_CYCLE_US, 0); /* age the display */ diff --git a/PDP10/kx10_imp.c b/PDP10/kx10_imp.c index e4582234..a5f73c01 100644 --- a/PDP10/kx10_imp.c +++ b/PDP10/kx10_imp.c @@ -145,7 +145,8 @@ #endif #endif -#define IMP_ARPTAB_SIZE 8 +#define IMP_ARPTAB_SIZE 64 +#define IMP_ARP_MAX_AGE 100 uint32 mask[] = { 0xFFFFFFFF, 0xFFFFFFFE, 0xFFFFFFFC, 0xFFFFFFF8, @@ -198,6 +199,12 @@ struct tcp { uint32 seq; /* Sequence number */ uint32 ack; /* Ack number */ uint16 flags; /* Flags */ +#define TCP_FL_FIN 0x01 +#define TCP_FL_SYN 0x02 +#define TCP_FL_RST 0x04 +#define TCP_FL_PSH 0x08 +#define TCP_FL_ACK 0x10 +#define TCP_FL_URG 0x20 uint16 window; /* Window size */ uint16 chksum; /* packet checksum */ uint16 urgent; /* Urgent pointer */ @@ -257,7 +264,8 @@ struct arp_hdr { struct arp_entry { in_addr_T ipaddr; ETH_MAC ethaddr; - uint16 time; + int16 age; +#define ARP_DONT_AGE -1 }; /* DHCP client states */ @@ -294,6 +302,11 @@ struct arp_entry { #define DHCP_MAGIC_COOKIE 0x63825363UL +#define DHCP_INFINITE_LEASE 0xFFFFFFFF + +#define DHCP_UDP_PORT_CLIENT 68 +#define DHCP_UDP_PORT_SERVER 67 + /* This is a list of options for BOOTP and DHCP, see RFC 2132 for descriptions */ /* BootP options */ @@ -312,7 +325,7 @@ struct arp_entry { /* DHCP options */ #define DHCP_OPTION_REQUESTED_IP 50 /* RFC 2132 9.1, requested IP address */ #define DHCP_OPTION_LEASE_TIME 51 /* RFC 2132 9.2, time in seconds, in 4 bytes */ -#define DHCP_OPTION_OVERLOAD 52 /* RFC2132 9.3, use file and/or sname field for options */ +#define DHCP_OPTION_OVERLOAD 52 /* RFC 2132 9.3, use file and/or sname field for options */ #define DHCP_OPTION_MESSAGE_TYPE 53 /* RFC 2132 9.6, important for DHCP */ #define DHCP_OPTION_MESSAGE_TYPE_LEN 1 @@ -340,8 +353,6 @@ struct arp_entry { #define DHCP_SNAME_LEN 64 #define DHCP_FILE_LEN 128 -#define XID 0x3903F326 - PACKED_BEGIN struct dhcp { uint8 op; /* Operation */ @@ -396,7 +407,9 @@ struct imp_stats { struct imp_device { ETH_PCALLBACK rcallback; /* read callback routine */ ETH_PCALLBACK wcallback; /* write callback routine */ - ETH_MAC mac; /* Hardware MAC address */ + ETH_MAC macs[2]; /* Hardware MAC addresses */ +#define mac macs[0] +#define bcast macs[1] struct imp_packet *sendq; /* Send queue */ struct imp_packet *freeq; /* Free queue */ in_addr_T ip; /* Local IP address */ @@ -406,12 +419,12 @@ struct imp_device { int maskbits; /* Mask length */ struct imp_map port_map[64]; /* Ports to adjust */ in_addr_T dhcpip; /* DHCP server address */ - int dhcp; /* Use dhcp */ uint8 dhcp_state; /* State of DHCP */ int dhcp_lease; /* DHCP lease time */ int dhcp_renew; /* DHCP renew time */ int dhcp_rebind; /* DHCP rebind time */ - int sec_tim; /* 1 second timer */ + int dhcp_wait_time; /* seconds waiting for response */ + int dhcp_retry_after; /* timeout time */ int init_state; /* Initialization state */ uint32 dhcp_xid; /* Transaction ID */ int padding; /* Type zero padding */ @@ -428,6 +441,7 @@ struct imp_device { int host_error; int rfnm_count; /* Number of pending RFNM packets */ int pia; /* PIA channels */ + struct arp_entry arp_table[IMP_ARPTAB_SIZE]; } imp_data; extern int32 tmxr_poll; @@ -436,11 +450,10 @@ static CONST ETH_MAC broadcast_ethaddr = {0xff,0xff,0xff,0xff,0xff,0xff}; static CONST in_addr_T broadcast_ipaddr = {0xffffffff}; -static struct arp_entry arp_table[IMP_ARPTAB_SIZE]; - t_stat imp_devio(uint32 dev, uint64 *data); t_stat imp_srv(UNIT *); t_stat imp_eth_srv(UNIT *); +t_stat imp_tim_srv(UNIT *); t_stat imp_reset (DEVICE *dptr); t_stat imp_set_mpx (UNIT *uptr, int32 val, CONST char *cptr, void *desc); t_stat imp_show_mpx (FILE *st, UNIT *uptr, int32 val, CONST void *desc); @@ -453,31 +466,42 @@ t_stat imp_set_gwip (UNIT* uptr, int32 val, CONST char* cptr, void* desc t_stat imp_show_hostip (FILE *st, UNIT *uptr, int32 val, CONST void *desc); t_stat imp_set_hostip (UNIT* uptr, int32 val, CONST char* cptr, void* desc); t_stat imp_show_dhcpip (FILE *st, UNIT *uptr, int32 val, CONST void *desc); +t_stat imp_show_arp (FILE *st, UNIT *uptr, int32 val, CONST void *desc); +t_stat imp_set_arp (UNIT *uptr, int32 val, CONST char *cptr, void *desc); void imp_timer_task(struct imp_device *imp); void imp_send_rfmn(struct imp_device *imp); void imp_packet_in(struct imp_device *imp); void imp_send_packet (struct imp_device *imp_data, int len); void imp_free_packet(struct imp_device *imp, struct imp_packet *p); struct imp_packet * imp_get_packet(struct imp_device *imp); -void imp_arp_update(in_addr_T ipaddr, ETH_MAC *ethaddr); +void imp_arp_update(struct imp_device *imp, in_addr_T ipaddr, ETH_MAC *ethaddr, int age); void imp_arp_arpin(struct imp_device *imp, ETH_PACK *packet); +void imp_arp_arpout(struct imp_device *imp, in_addr_T ipaddr); +struct arp_entry * imp_arp_lookup(struct imp_device *imp, in_addr_T ipaddr); void imp_packet_out(struct imp_device *imp, ETH_PACK *packet); +void imp_packet_debug(struct imp_device *imp, const char *action, ETH_PACK *packet); +void imp_write(struct imp_device *imp, ETH_PACK *packet); void imp_do_dhcp_client(struct imp_device *imp, ETH_PACK *packet); void imp_dhcp_timer(struct imp_device *imp); void imp_dhcp_discover(struct imp_device *imp); +void imp_dhcp_request(struct imp_device *imp, in_addr_T dhcpip); void imp_dhcp_release(struct imp_device *imp); +void imp_arp_age(struct imp_device *imp); t_stat imp_attach (UNIT * uptr, CONST char * cptr); t_stat imp_detach (UNIT * uptr); t_stat imp_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, CONST char *cptr); const char *imp_description (DEVICE *dptr); +static char *ipv4_inet_ntoa(struct in_addr ip); +static int ipv4_inet_aton(const char *str, struct in_addr *inp); int imp_mpx_lvl = 0; -int last_coni; +double last_coni; UNIT imp_unit[] = { - {UDATA(imp_srv, UNIT_IDLE+UNIT_ATTABLE+UNIT_DISABLE, 0)}, /* 0 */ - {UDATA(imp_eth_srv, UNIT_IDLE+UNIT_DISABLE, 0)}, /* 0 */ + {UDATA(imp_srv, UNIT_IDLE+UNIT_ATTABLE+UNIT_DHCP, 0)}, /* 0 */ + {UDATA(imp_eth_srv, UNIT_IDLE+UNIT_DIS, 0)}, /* 0 */ + {UDATA(imp_tim_srv, UNIT_IDLE+UNIT_DIS, 0)}, /* 0 */ }; DIB imp_dib = {IMP_DEVNUM, 1, &imp_devio, NULL}; @@ -485,35 +509,66 @@ MTAB imp_mod[] = { { MTAB_XTD|MTAB_VDV|MTAB_VALR|MTAB_NC, 0, "MAC", "MAC=xx:xx:xx:xx:xx:xx", &imp_set_mac, &imp_show_mac, NULL, "MAC address" }, { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "MPX", "MPX", - &imp_set_mpx, &imp_show_mpx, NULL}, + &imp_set_mpx, &imp_show_mpx, NULL, "ITS Interrupt Channel #"}, { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "IP", "IP=ddd.ddd.ddd.ddd/dd", &imp_set_ip, &imp_show_ip, NULL, "IP address" }, { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "GW", "GW=ddd.ddd.ddd.ddd", - &imp_set_gwip, &imp_show_gwip, NULL, "GW address" }, + &imp_set_gwip, &imp_show_gwip, NULL, "Gateway address" }, { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "HOST", "HOST=ddd.ddd.ddd.ddd", &imp_set_hostip, &imp_show_hostip, NULL, "HOST IP address" }, { MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "ETH", NULL, NULL, ð_show, NULL, "Display attachedable devices" }, - { UNIT_DHCP, 0, "DHCP disabled", "NODHCP", NULL, NULL, NULL, + { UNIT_DHCP, 0, NULL, "NODHCP", NULL, NULL, NULL, "Don't aquire address from DHCP"}, { UNIT_DHCP, UNIT_DHCP, "DHCP", "DHCP", NULL, NULL, NULL, "Use DHCP to set IP address"}, - { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "DHCPIP", "DHCPIP=ddd.ddd.ddd.ddd", - NULL, &imp_show_dhcpip, NULL, "DHCP server address" }, + { MTAB_XTD|MTAB_VDV, 0, "DHCPIP", NULL, + NULL, &imp_show_dhcpip, NULL, "DHCP info" }, { UNIT_DTYPE, (TYPE_MIT << UNIT_V_DTYPE), "MIT", "MIT", NULL, NULL, NULL, "ITS/MIT style interface"}, { UNIT_DTYPE, (TYPE_BBN << UNIT_V_DTYPE), "BBN", "BBN", NULL, NULL, NULL, "Tenex/BBN style interface"}, { UNIT_DTYPE, (TYPE_WAITS << UNIT_V_DTYPE), "WAITS", "WAITS", NULL, NULL, NULL, "WAITS style interface"}, + { MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "ARP", NULL, + NULL, &imp_show_arp, NULL, "ARP IP address->MAC address table" }, + { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, NULL, "ARP=ddd.ddd.ddd.ddd=XX:XX:XX:XX:XX:XX", + &imp_set_arp, NULL, NULL, "Create a static ARP Entry" }, { 0 } }; +/* Simulator debug controls */ +DEBTAB imp_debug[] = { + {"CMD", DEBUG_CMD, "Show command execution to devices"}, + {"DATA", DEBUG_DATA, "Show data transfers"}, + {"DETAIL", DEBUG_DETAIL, "Show details about device"}, + {"EXP", DEBUG_EXP, "Show exception information"}, + {"CONI", DEBUG_CONI, "Show coni instructions"}, + {"CONO", DEBUG_CONO, "Show coni instructions"}, + {"DATAIO", DEBUG_DATAIO, "Show datai and datao instructions"}, + {"IRQ", DEBUG_IRQ, "Show IRQ requests"}, +#define DEBUG_DHCP (DEBUG_IRQ<<1) + {"DHCP", DEBUG_DHCP, "Show DHCP activities"}, +#define DEBUG_ARP (DEBUG_DHCP<<1) + {"ARP", DEBUG_ARP, "Show ARP activities"}, +#define DEBUG_TCP (DEBUG_ARP<<1) + {"TCP", DEBUG_TCP, "Show TCP packet activities"}, +#define DEBUG_UDP (DEBUG_TCP<<1) + {"UDP", DEBUG_UDP, "Show UDP packet activities"}, +#define DEBUG_ICMP (DEBUG_UDP<<1) + {"ICMP", DEBUG_ICMP, "Show ICMP packet activities"}, +#define DEBUG_ETHER (DEBUG_ICMP<<1) + {"ETHER", DEBUG_ETHER, "Show ETHER activities"}, + {0, 0} +}; + + + DEVICE imp_dev = { "IMP", imp_unit, NULL, imp_mod, - 1, 8, 0, 1, 8, 36, + 3, 8, 0, 1, 8, 36, NULL, NULL, &imp_reset, NULL, &imp_attach, &imp_detach, - &imp_dib, DEV_DISABLE | DEV_DIS | DEV_DEBUG, 0, dev_debug, + &imp_dib, DEV_DISABLE | DEV_DIS | DEV_DEBUG, 0, imp_debug, NULL, NULL, &imp_help, NULL, NULL, &imp_description }; #define IMP_OCHN 0000007 @@ -573,7 +628,7 @@ t_stat imp_devio(uint32 dev, uint64 *data) } if (*data & IMPHEC) { /* Clear host error. */ /* Only if there has been a CONI lately. */ - if (last_coni - sim_interval < CONI_TIMEOUT) + if (last_coni - sim_gtime() < CONI_TIMEOUT) uptr->STATUS &= ~IMPHER; } if (*data & IMIIHE) /* Inhibit interrupt on host error. */ @@ -630,7 +685,7 @@ t_stat imp_devio(uint32 dev, uint64 *data) case CONI: switch (GET_DTYPE(uptr->flags)) { case TYPE_MIT: - last_coni = sim_interval; + last_coni = sim_gtime(); *data = (uint64)(uptr->STATUS | (imp_data.pia & 07)); break; case TYPE_BBN: @@ -806,10 +861,13 @@ t_stat imp_eth_srv(UNIT * uptr) imp_packet_in(&imp_data); if (imp_data.init_state >= 3 && imp_data.init_state < 6) { - if (imp_unit[0].flags & UNIT_DHCP && imp_data.dhcp_state != DHCP_STATE_BOUND) + if (imp_unit[0].flags & UNIT_DHCP && + imp_data.dhcp_state != DHCP_STATE_BOUND && + imp_data.dhcp_state != DHCP_STATE_REBINDING && + imp_data.dhcp_state != DHCP_STATE_RENEWING) return SCPE_OK; - sim_debug(DEBUG_DETAIL, &imp_dev, "IMP init Nop %d\n", - imp_data.init_state); + sim_debug(DEBUG_DETAIL, &imp_dev, "IMP init Nop %d\n", + imp_data.init_state); if (imp_unit[0].ILEN == 0) { /* Queue up a nop packet */ imp_data.rbuffer[0] = 0x4; @@ -836,12 +894,6 @@ imp_timer_task(struct imp_device *imp) struct imp_packet *nq = NULL; /* New send queue */ int n; - /* If DHCP enabled, send a discover packet */ - if (imp_data.init_state >= 1 && - imp_unit[0].flags & UNIT_DHCP && imp->dhcp_state == DHCP_STATE_OFF) { - imp_dhcp_discover(&imp_data); - } - /* Scan through adjusted ports and remove old ones */ for (n = 0; n < 64; n++) { if (imp->port_map[n].cls_tim > 0) { @@ -869,13 +921,17 @@ imp_timer_task(struct imp_device *imp) } } imp->sendq = nq; - - if (imp->sec_tim-- == 0) { - imp_dhcp_timer(&imp_data); - imp->sec_tim = 1000; - } } +t_stat imp_tim_srv(UNIT * uptr) +{ + sim_activate_after(uptr, 1000000); /* come back once per second */ + + imp_dhcp_timer(&imp_data); + imp_arp_age(&imp_data); + return SCPE_OK; +} + void imp_packet_in(struct imp_device *imp) { @@ -901,6 +957,7 @@ imp_packet_in(struct imp_device *imp) } return; } + imp_packet_debug(imp, "Received", &read_buffer); hdr = (struct imp_eth_hdr *)(&read_buffer.msg[0]); type = ntohs(hdr->type); if (type == ETHTYPE_ARP) { @@ -915,8 +972,9 @@ imp_packet_in(struct imp_device *imp) (ip_hdr->ip_v_hl & 0xf) * 4]); struct udp *udp_hdr = (struct udp *)payload; /* Check for DHCP traffic */ - if (ip_hdr->ip_p == UDP_PROTO && ntohs(udp_hdr->udp_dport) == 68 && - ntohs(udp_hdr->udp_sport) == 67) { + if (ip_hdr->ip_p == UDP_PROTO && + ntohs(udp_hdr->udp_dport) == DHCP_UDP_PORT_CLIENT && + ntohs(udp_hdr->udp_sport) == DHCP_UDP_PORT_SERVER) { imp_do_dhcp_client(imp, &read_buffer); return; } @@ -924,7 +982,7 @@ imp_packet_in(struct imp_device *imp) /* Process as IP if it is for us */ if (ip_hdr->ip_dst == imp_data.ip || ip_hdr->ip_dst == 0) { /* Add mac address since we will probably need it later */ - imp_arp_update(ip_hdr->ip_src, &hdr->src); + imp_arp_update(imp, ip_hdr->ip_src, &hdr->src, 0); /* Clear beginning of message */ memset(&imp->rbuffer[0], 0, 256); imp->rbuffer[0] = 0xf; @@ -1050,8 +1108,8 @@ imp_packet_in(struct imp_device *imp) } else if (ip_hdr->ip_p == UDP_PROTO) { struct udp *udp_hdr = (struct udp *)payload; if (ip_hdr->ip_p == UDP_PROTO && - htons(udp_hdr->udp_dport) == 68 && - htons(udp_hdr->udp_sport) == 67) { + htons(udp_hdr->udp_dport) == DHCP_UDP_PORT_CLIENT && + htons(udp_hdr->udp_sport) == DHCP_UDP_PORT_SERVER) { imp_do_dhcp_client(imp, &read_buffer); return; } @@ -1061,9 +1119,11 @@ imp_packet_in(struct imp_device *imp) /* Lastly check if ICMP */ } else if (ip_hdr->ip_p == ICMP_PROTO) { struct icmp *icmp_hdr = (struct icmp *)payload; - checksumadjust((uint8 *)&icmp_hdr->chksum, - (uint8 *)(&ip_hdr->ip_src), sizeof(in_addr_T), - (uint8 *)(&imp_data.hostip), sizeof(in_addr_T)); + if ((icmp_hdr->type != 0) && /* Not Echo Reply */ + (icmp_hdr->type != 8)) /* and Not Echo */ + checksumadjust((uint8 *)&icmp_hdr->chksum, + (uint8 *)(&ip_hdr->ip_src), sizeof(in_addr_T), + (uint8 *)(&imp_data.hostip), sizeof(in_addr_T)); } checksumadjust((uint8 *)&ip_hdr->ip_sum, (uint8 *)(&ip_hdr->ip_dst), sizeof(in_addr_T), @@ -1175,8 +1235,6 @@ imp_packet_out(struct imp_device *imp, ETH_PACK *packet) { struct ip_hdr *pkt = (struct ip_hdr *)(&packet->msg[0]); struct imp_packet *send; struct arp_entry *tabptr; - struct arp_hdr *arp; - ETH_PACK arp_pkt; in_addr_T ipaddr; int i; @@ -1289,9 +1347,11 @@ imp_packet_out(struct imp_device *imp, ETH_PACK *packet) { /* Lastly check if ICMP */ } else if (pkt->iphdr.ip_p == ICMP_PROTO) { struct icmp *icmp_hdr = (struct icmp *)payload; - checksumadjust((uint8 *)&icmp_hdr->chksum, - (uint8 *)(&pkt->iphdr.ip_src), sizeof(in_addr_T), - (uint8 *)(&imp->ip), sizeof(in_addr_T)); + if ((icmp_hdr->type != 0) && /* Not Echo Reply */ + (icmp_hdr->type != 8)) /* and Not Echo */ + checksumadjust((uint8 *)&icmp_hdr->chksum, + (uint8 *)(&pkt->iphdr.ip_src), sizeof(in_addr_T), + (uint8 *)(&imp->ip), sizeof(in_addr_T)); } /* Lastly update the header and IP address */ checksumadjust((uint8 *)&pkt->iphdr.ip_sum, @@ -1312,12 +1372,12 @@ imp_packet_out(struct imp_device *imp, ETH_PACK *packet) { ipaddr = imp->gwip; for (i = 0; i < IMP_ARPTAB_SIZE; i++) { - tabptr = &arp_table[i]; + tabptr = &imp->arp_table[i]; if (ipaddr == tabptr->ipaddr) { memcpy(&pkt->ethhdr.dest, &tabptr->ethaddr, 6); memcpy(&pkt->ethhdr.src, &imp->mac, 6); pkt->ethhdr.type = htons(ETHTYPE_IP); - eth_write(&imp->etherface, packet, NULL); + imp_write(imp, packet); imp->rfnm_count++; return; } @@ -1332,7 +1392,439 @@ imp_packet_out(struct imp_device *imp, ETH_PACK *packet) { send->dest = pkt->iphdr.ip_dst; memcpy(&send->packet.msg[0], pkt, send->packet.len); - /* We did not find it, so construct and send a ARP packet */ + /* We did not find it, so construct and send an ARP packet */ + imp_arp_arpout(imp, ipaddr); +} + + +void imp_packet_debug(struct imp_device *imp, const char *action, ETH_PACK *packet) { + struct imp_eth_hdr *eth = (struct imp_eth_hdr *)&packet->msg[0]; + struct arp_hdr *arp = (struct arp_hdr *)eth; + struct ip *ip = (struct ip *)&packet->msg[sizeof(struct imp_eth_hdr)]; + struct udp *udp; + struct tcp *tcp; + struct icmp *icmp; + uint8 *payload; + struct in_addr ipaddr; + size_t len; + int flag; + char src_ip[20]; + char dst_ip[20]; + char src_port[8]; + char dst_port[8]; + char mac_buf[20]; + char flags[64]; + static struct tcp_flag_bits { + const char *name; + uint16 bitmask; + } bits[] = { + {"FIN", TCP_FL_FIN}, + {"SYN", TCP_FL_SYN}, + {"RST", TCP_FL_RST}, + {"PSH", TCP_FL_PSH}, + {"ACK", TCP_FL_ACK}, + {"URG", TCP_FL_URG}, + {NULL, 0} + }; + static const char *icmp_types[] = { + "Echo Reply", // Type 0 + "Type 1 - Unassigned", + "Type 2 - Unassigned", + "Destination Unreachable", // Type 3 + "Source Quench (Deprecated)", // Type 4 + "Redirect", // Type 5 + "Type 6 - Alternate Host Address (Deprecated)", + "Type 7 - Unassigned", + "Echo Request", // Type 8 + "Router Advertisement", // Type 9 + "Router Selection", // Type 10 + "Time Exceeded", // Type 11 + "Type 12 - Parameter Problem", + "Type 13 - Timestamp", + "Type 14 - Timestamp Reply", + "Type 15 - Information Request (Deprecated)", + "Type 16 - Information Reply (Deprecated)", + "Type 17 - Address Mask Request (Deprecated)", + "Type 18 - Address Mask Reply (Deprecated)", + "Type 19 - Reserved (for Security)", + "Type 20 - Reserved (for Robustness Experiment)", + "Type 21 - Reserved (for Robustness Experiment)", + "Type 22 - Reserved (for Robustness Experiment)", + "Type 23 - Reserved (for Robustness Experiment)", + "Type 24 - Reserved (for Robustness Experiment)", + "Type 25 - Reserved (for Robustness Experiment)", + "Type 26 - Reserved (for Robustness Experiment)", + "Type 27 - Reserved (for Robustness Experiment)", + "Type 28 - Reserved (for Robustness Experiment)", + "Type 29 - Reserved (for Robustness Experiment)", + "Type 30 - Traceroute (Deprecated)", + "Type 31 - Datagram Conversion Error (Deprecated)", + "Type 32 - Mobile Host Redirect (Deprecated)", + "Type 33 - IPv6 Where-Are-You (Deprecated)", + "Type 34 - IPv6 I-Am-Here (Deprecated)", + "Type 35 - Mobile Registration Request (Deprecated)", + "Type 36 - Mobile Registration Reply (Deprecated)", + "Type 37 - Domain Name Request (Deprecated)", + "Type 38 - Domain Name Reply (Deprecated)", + "Type 39 - SKIP (Deprecated)", + "Type 40 - Photuris", + "Type 41 - ICMP messages utilized by experimental mobility protocols such as Seamoby", + "Type 42 - Extended Echo Request", + "Type 43 - Extended Echo Reply" + }; + + if (ntohs(eth->type) == ETHTYPE_ARP) { + struct in_addr in_addr; + const char *arp_op = (ARP_REQUEST == ntohs(arp->opcode)) ? "REQUEST" : ((ARP_REPLY == ntohs(arp->opcode)) ? "REPLY" : "Unknown"); + char eth_src[20], eth_dst[20]; + char arp_shwaddr[20], arp_dhwaddr[20]; + char arp_sipaddr[20], arp_dipaddr[20]; + + if (!(imp_dev.dctrl & DEBUG_ARP)) + return; + eth_mac_fmt(&arp->ethhdr.src, eth_src); + eth_mac_fmt(&arp->ethhdr.dest, eth_dst); + eth_mac_fmt(&arp->shwaddr, arp_shwaddr); + memcpy(&in_addr, &arp->sipaddr, sizeof(in_addr)); + strlcpy(arp_sipaddr, ipv4_inet_ntoa(in_addr), sizeof(arp_sipaddr)); + eth_mac_fmt(&arp->dhwaddr, arp_dhwaddr); + memcpy(&in_addr, &arp->dipaddr, sizeof(in_addr)); + strlcpy(arp_dipaddr, ipv4_inet_ntoa(in_addr), sizeof(arp_dipaddr)); + sim_debug(DEBUG_ARP, &imp_dev, + "%s %s EthDst=%s EthSrc=%s shwaddr=%s sipaddr=%s dhwaddr=%s dipaddr=%s\n", + action, arp_op, eth_dst, eth_src, arp_shwaddr, arp_sipaddr, arp_dhwaddr, arp_dipaddr); + return; + } + if (ntohs(eth->type) != ETHTYPE_IP) + return; + if (!(imp_dev.dctrl & (DEBUG_TCP|DEBUG_UDP|DEBUG_ICMP))) + return; + memcpy(&ipaddr, &ip->ip_src, sizeof(ipaddr)); + strlcpy(src_ip, ipv4_inet_ntoa(ipaddr), sizeof(src_ip)); + memcpy(&ipaddr, &ip->ip_dst, sizeof(ipaddr)); + strlcpy(dst_ip, ipv4_inet_ntoa(ipaddr), sizeof(dst_ip)); + payload = (uint8 *)&packet->msg[sizeof(struct imp_eth_hdr) + (ip->ip_v_hl & 0xf) * 4]; + switch (ip->ip_p) { + case UDP_PROTO: + udp = (struct udp *)payload; + snprintf(src_port, sizeof(src_port), "%d", ntohs(udp->udp_sport)); + snprintf(dst_port, sizeof(dst_port), "%d", ntohs(udp->udp_dport)); + sim_debug(DEBUG_UDP, &imp_dev, "%s %d byte packet from %s:%s to %s:%s\n", action, + ntohs(udp->len), src_ip, src_port, dst_ip, dst_port); + if ((imp_dev.dctrl & DEBUG_DHCP) && + (((ntohs(udp->udp_sport) == DHCP_UDP_PORT_CLIENT) && + (ntohs(udp->udp_dport) == DHCP_UDP_PORT_SERVER)) || + ((ntohs(udp->udp_dport) == DHCP_UDP_PORT_CLIENT) && + (ntohs(udp->udp_sport) == DHCP_UDP_PORT_SERVER)))) { + struct dhcp *dhcp = (struct dhcp *)(payload + sizeof(struct udp)); + uint8 *opt = &dhcp->options[0]; + + sim_debug(DEBUG_DHCP, &imp_dev, "%s XID=%08X", + (dhcp->op == DHCP_BOOTREQUEST) ? "REQUEST" : + (dhcp->op == DHCP_BOOTREPLY) ? "REPLY" : + "??????", + dhcp->xid); + if (dhcp->ciaddr) { + memcpy(&ipaddr, &dhcp->ciaddr, sizeof(ipaddr)); + sim_debug(DEBUG_DHCP, &imp_dev, ", ciaddr=%s", ipv4_inet_ntoa(ipaddr)); + } + if (dhcp->yiaddr) { + memcpy(&ipaddr, &dhcp->yiaddr, sizeof(ipaddr)); + sim_debug(DEBUG_DHCP, &imp_dev, ", yiaddr=%s", ipv4_inet_ntoa(ipaddr)); + } + if (dhcp->siaddr) { + memcpy(&ipaddr, &dhcp->siaddr, sizeof(ipaddr)); + sim_debug(DEBUG_DHCP, &imp_dev, ", siaddr=%s", ipv4_inet_ntoa(ipaddr)); + } + if (dhcp->giaddr) { + memcpy(&ipaddr, &dhcp->giaddr, sizeof(ipaddr)); + sim_debug(DEBUG_DHCP, &imp_dev, ", giaddr=%s", ipv4_inet_ntoa(ipaddr)); + } + eth_mac_fmt((ETH_MAC*)&dhcp->chaddr, mac_buf); + sim_debug(DEBUG_DHCP, &imp_dev, ", chaddr=%s Options: ", mac_buf); + while (*opt != DHCP_OPTION_END) { + int opt_len; + u_long numeric; + static const char *opr_names[] = { + "", "DISCOVER", "OFFER", "REQUEST", + "DECLINE", "ACK", "NAK", "RELEASE", + "INFORM" + }; + + + switch(*opt++) { + case DHCP_OPTION_PAD: + break; + default: + opt_len = *opt++; + opt += opt_len; + break; + case DHCP_OPTION_SUBNET_MASK: + opt_len = *opt++; + memcpy(&ipaddr, opt, 4); + sim_debug(DEBUG_DHCP, &imp_dev, ", mask=%s", ipv4_inet_ntoa(ipaddr)); + opt += opt_len; + break; + case DHCP_OPTION_ROUTER: + opt_len = *opt++; + memcpy(&ipaddr, opt, 4); + sim_debug(DEBUG_DHCP, &imp_dev, ", router=%s", ipv4_inet_ntoa(ipaddr)); + opt += opt_len; + break; + case DHCP_OPTION_REQUESTED_IP: + opt_len = *opt++; + memcpy(&ipaddr, opt, 4); + sim_debug(DEBUG_DHCP, &imp_dev, ", requested-ip=%s", ipv4_inet_ntoa(ipaddr)); + opt += opt_len; + break; + case DHCP_OPTION_LEASE_TIME: + opt_len = *opt++; + memcpy(&numeric, opt, 4); + sim_debug(DEBUG_DHCP, &imp_dev, ", lease-time=%d", ntohl(numeric)); + opt += opt_len; + break; + case DHCP_OPTION_T1: + opt_len = *opt++; + memcpy(&numeric, opt, 4); + sim_debug(DEBUG_DHCP, &imp_dev, ", renew-time=%d", ntohl(numeric)); + opt += opt_len; + break; + case DHCP_OPTION_T2: + opt_len = *opt++; + memcpy(&numeric, opt, 4); + sim_debug(DEBUG_DHCP, &imp_dev, ", rebind-time=%d", ntohl(numeric)); + opt += opt_len; + break; + case DHCP_OPTION_SERVER_ID: + opt_len = *opt++; + memcpy(&ipaddr, opt, 4); + sim_debug(DEBUG_DHCP, &imp_dev, ", server-ip=%s", ipv4_inet_ntoa(ipaddr)); + opt += opt_len; + break; + case DHCP_OPTION_MESSAGE_TYPE: + opt_len = *opt++; + sim_debug(DEBUG_DHCP, &imp_dev, "MessageType=%s", opr_names[*opt]); + opt += opt_len; + break; + } + } + sim_debug(DEBUG_DHCP, &imp_dev, "\n"); + } else { + if (udp->len && (imp_dev.dctrl & DEBUG_UDP)) + sim_data_trace(&imp_dev, imp_unit, payload + sizeof(struct udp), "", + ntohs(udp->len), "", DEBUG_DATA); + } + break; + case TCP_PROTO: + tcp = (struct tcp *)payload; + snprintf(src_port, sizeof(src_port), "%d", ntohs(tcp->tcp_sport)); + snprintf(dst_port, sizeof(dst_port), "%d", ntohs(tcp->tcp_dport)); + strlcpy(flags, "", sizeof(flags)); + for (flag=0; bits[flag].name; flag++) { + if (ntohs(tcp->flags) & bits[flag].bitmask) { + if (*flags) + strlcat(flags, ",", sizeof(flags)); + strlcat(flags, bits[flag].name, sizeof(flags)); + } + + } + len = ntohs(ip->ip_len) - ((ip->ip_v_hl & 0xf) * 4 + (ntohs(tcp->flags) >> 12) * 4); + sim_debug(DEBUG_TCP, &imp_dev, "%s %s%s %d byte packet from %s:%s to %s:%s\n", action, + flags, *flags ? ":" : "", (int)len, src_ip, src_port, dst_ip, dst_port); + if (len && (imp_dev.dctrl & DEBUG_TCP)) + sim_data_trace(&imp_dev, imp_unit, payload + 4 * (ntohs(tcp->flags) >> 12), "", len, "", DEBUG_DATA); + break; + case ICMP_PROTO: + icmp = (struct icmp *)payload; + len = ntohs(ip->ip_len) - (ip->ip_v_hl & 0xf) * 4; + sim_debug(DEBUG_ICMP, &imp_dev, "%s %s %d byte packet from %s to %s\n", action, + (icmp->type < sizeof(icmp_types)/sizeof(icmp_types[0])) ? icmp_types[icmp->type] : "", (int)len, src_ip, dst_ip); + if (len && (imp_dev.dctrl & DEBUG_ICMP)) + sim_data_trace(&imp_dev, imp_unit, payload + sizeof(struct icmp), "", len, "", DEBUG_DATA); + break; + } +} + +void imp_write(struct imp_device *imp, ETH_PACK *packet) { + imp_packet_debug(imp, "Sending", packet); + eth_write(&imp->etherface, packet, NULL); +} + +/* + * Update the ARP table, first use free entry, else use oldest. + */ +void +imp_arp_update(struct imp_device *imp, in_addr_T ipaddr, ETH_MAC *ethaddr, int age) +{ + struct arp_entry *tabptr; + int i; + char mac_buf[20]; + + /* Check if entry already in the table. */ + for (i = 0; i < IMP_ARPTAB_SIZE; i++) { + tabptr = &imp->arp_table[i]; + + if (tabptr->ipaddr != 0) { + if (tabptr->ipaddr == ipaddr) { + if (0 != memcmp(&tabptr->ethaddr, ethaddr, sizeof(ETH_MAC))) { + memcpy(&tabptr->ethaddr, ethaddr, sizeof(ETH_MAC)); + eth_mac_fmt(ethaddr, mac_buf); + sim_debug(DEBUG_ARP, &imp_dev, + "updating entry for IP %s to %s\n", + ipv4_inet_ntoa(*((struct in_addr *)&ipaddr)), mac_buf); + } + if (tabptr->age != ARP_DONT_AGE) + tabptr->age = age; + return; + } + } + } + + /* See if we can find an unused entry. */ + for (i = 0; i < IMP_ARPTAB_SIZE; i++) { + tabptr = &imp->arp_table[i]; + + if (tabptr->ipaddr == 0) + break; + } + + /* If no empty entry search for oldest one. */ + if (tabptr->ipaddr != 0) { + int fnd = 0; + int16 tmpage = 0; + for (i = 0; i < IMP_ARPTAB_SIZE; i++) { + tabptr = &imp->arp_table[i]; + if (tabptr->age > tmpage) { + tmpage = tabptr->age; + fnd = i; + } + } + tabptr = &imp->arp_table[fnd]; + } + + /* Now save the entry */ + memcpy(&tabptr->ethaddr, ethaddr, sizeof(ETH_MAC)); + tabptr->ipaddr = ipaddr; + tabptr->age = age; + eth_mac_fmt(ethaddr, mac_buf); + sim_debug(DEBUG_ARP, &imp_dev, + "creating entry for IP %s to %s, initial age=%d\n", + ipv4_inet_ntoa(*((struct in_addr *)&ipaddr)), mac_buf, age); +} + +/* + * Age arp entries and free up old ones. + */ +void imp_arp_age(struct imp_device *imp) +{ + struct arp_entry *tabptr; + int i; + + for (i = 0; i < IMP_ARPTAB_SIZE; i++) { + tabptr = &imp->arp_table[i]; + if (tabptr->ipaddr != 0) { /* active entry? */ + if (tabptr->age != ARP_DONT_AGE) + tabptr->age++; /* Age it */ + /* expire too old entries */ + if (tabptr->age > IMP_ARP_MAX_AGE) { + char mac_buf[20]; + + eth_mac_fmt(&tabptr->ethaddr, mac_buf); + sim_debug(DEBUG_ARP, &imp_dev, + "discarding ARP entry for IP %s %s after %d seconds\n", + ipv4_inet_ntoa(*((struct in_addr *)&tabptr->ipaddr)), mac_buf, IMP_ARP_MAX_AGE); + memset(tabptr, 0, sizeof(*tabptr)); + } + } + } +} + + +/* + * Process incomming ARP packet. + */ + +void +imp_arp_arpin(struct imp_device *imp, ETH_PACK *packet) +{ + struct arp_hdr *arp; + struct in_addr in_addr; + int op; + char mac_buf[20]; + + /* Ignore packet if too short */ + if (packet->len < sizeof(struct arp_hdr)) + return; + arp = (struct arp_hdr *)(&packet->msg[0]); + op = ntohs(arp->opcode); + imp_arp_update(imp, arp->sipaddr, &arp->shwaddr, 0); + + switch (op) { + case ARP_REQUEST: + if (arp->dipaddr == imp->ip) { + + arp->opcode = htons(ARP_REPLY); + memcpy(&arp->dhwaddr, &arp->shwaddr, 6); + memcpy(&arp->shwaddr, &imp->mac, 6); + memcpy(&arp->ethhdr.src, &imp->mac, 6); + memcpy(&arp->ethhdr.dest, &arp->dhwaddr, 6); + + arp->dipaddr = arp->sipaddr; + arp->sipaddr = imp->ip; + arp->ethhdr.type = htons(ETHTYPE_ARP); + packet->len = sizeof(struct arp_hdr); + imp_write(imp, packet); + eth_mac_fmt(&arp->dhwaddr, mac_buf); + sim_debug(DEBUG_ARP, &imp_dev, "replied to received request for IP %s from %s\n", + ipv4_inet_ntoa(*((struct in_addr *)&imp->ip)), mac_buf); + } + break; + + case ARP_REPLY: + /* Check if this is our address */ + if (arp->dipaddr == imp->ip) { + struct imp_packet *nq = NULL; /* New send queue */ + + eth_mac_fmt(&arp->shwaddr, mac_buf); + memcpy(&in_addr, &arp->sipaddr, sizeof(in_addr)); + sim_debug(DEBUG_ARP, &imp_dev, "received reply for IP %s as %s\n", + ipv4_inet_ntoa(in_addr), mac_buf); + /* Scan send queue, and send all packets for this host */ + while (imp->sendq != NULL) { + struct imp_packet *temp = imp->sendq; + imp->sendq = temp->next; + + if (temp->dest == arp->sipaddr) { + struct ip_hdr *pkt = (struct ip_hdr *) + (&temp->packet.msg[0]); + memcpy(&pkt->ethhdr.dest, &arp->shwaddr, 6); + memcpy(&pkt->ethhdr.src, &imp->mac, 6); + pkt->ethhdr.type = htons(ETHTYPE_IP); + imp_write(imp, &temp->packet); + imp->rfnm_count++; + imp_free_packet(imp, temp); + } else { + temp->next = nq; + nq = temp; + } + } + imp->sendq = nq; + } + break; + } + return; +} + +/* + * Send a ARP_REQUEST packet. + */ + +void +imp_arp_arpout(struct imp_device *imp, in_addr_T ipaddr) +{ + struct arp_hdr *arp; + ETH_PACK arp_pkt; + memset(&arp_pkt, 0, sizeof(ETH_PACK)); arp = (struct arp_hdr *)(&arp_pkt.msg[0]); memcpy(&arp->ethhdr.dest, &broadcast_ethaddr, 6); @@ -1349,126 +1841,78 @@ imp_packet_out(struct imp_device *imp, ETH_PACK *packet) { arp->protolen = 4; arp_pkt.len = sizeof(struct arp_hdr); - eth_write(&imp->etherface, &arp_pkt, NULL); + imp_write(imp, &arp_pkt); + sim_debug(DEBUG_ARP, &imp_dev, "sending request for IP %s\n", ipv4_inet_ntoa(*((struct in_addr *)&ipaddr))); } - /* - * Update the ARP table, first use free entry, else use oldest. + * Lookup an IP's ARP entry. */ -void -imp_arp_update(in_addr_T ipaddr, ETH_MAC *ethaddr) + +struct arp_entry *imp_arp_lookup(struct imp_device *imp, in_addr_T ipaddr) { struct arp_entry *tabptr; int i; - static int arptime = 0; /* Check if entry already in the table. */ for (i = 0; i < IMP_ARPTAB_SIZE; i++) { - tabptr = &arp_table[i]; + tabptr = &imp->arp_table[i]; if (tabptr->ipaddr != 0) { - if (tabptr->ipaddr == ipaddr) { - memcpy(&tabptr->ethaddr, ethaddr, sizeof(ETH_MAC)); - tabptr->time = ++arptime; - return; - } + if (tabptr->ipaddr == ipaddr) + return tabptr; } - } - - /* See if we can find an unused entry. */ - for (i = 0; i < IMP_ARPTAB_SIZE; i++) { - tabptr = &arp_table[i]; - - if (tabptr->ipaddr == 0) - break; } - - /* If no empty entry search for oldest one. */ - if (tabptr->ipaddr != 0) { - int fnd = 0; - uint16 tmpage = 0; - for (i = 0; i < IMP_ARPTAB_SIZE; i++) { - tabptr = &arp_table[i]; - if (arptime - tabptr->time > tmpage) { - tmpage = arptime - tabptr->time; - fnd = i; - } - } - tabptr = &arp_table[fnd]; - } - - /* Now update the entry */ - memcpy(&tabptr->ethaddr, ethaddr, sizeof(ETH_MAC)); - tabptr->ipaddr = ipaddr; - tabptr->time = ++arptime; + return NULL; } - -/* - * Process incomming ARP packet. - */ - -void -imp_arp_arpin(struct imp_device *imp, ETH_PACK *packet) +t_stat imp_set_arp (UNIT* uptr, int32 val, CONST char* cptr, void* desc) { - struct arp_hdr *arp; - int op; + char abuf[CBUFSIZE]; + in_addr_T ip; + ETH_MAC mac_addr; - /* Ignore packet if too short */ - if (packet->len < sizeof(struct arp_hdr)) - return; - arp = (struct arp_hdr *)(&packet->msg[0]); - op = ntohs(arp->opcode); + if (!cptr) return SCPE_IERR; - switch (op) { - case ARP_REQUEST: - if (arp->dipaddr == imp->ip) { - imp_arp_update(arp->sipaddr, &arp->shwaddr); - - arp->opcode = htons(ARP_REPLY); - memcpy(&arp->dhwaddr, &arp->shwaddr, 6); - memcpy(&arp->shwaddr, &imp->mac, 6); - memcpy(&arp->ethhdr.src, &imp->mac, 6); - memcpy(&arp->ethhdr.dest, &arp->dhwaddr, 6); - - arp->dipaddr = arp->sipaddr; - arp->sipaddr = imp->ip; - arp->ethhdr.type = htons(ETHTYPE_ARP); - packet->len = sizeof(struct arp_hdr); - eth_write(&imp->etherface, packet, NULL); - } - break; - - case ARP_REPLY: - /* Check if this is our address */ - if (arp->dipaddr == imp->ip) { - struct imp_packet *nq = NULL; /* New send queue */ - imp_arp_update(arp->sipaddr, &arp->shwaddr); - /* Scan send queue, and send all packets for this host */ - while (imp->sendq != NULL) { - struct imp_packet *temp = imp->sendq; - imp->sendq = temp->next; - - if (temp->dest == arp->sipaddr) { - struct ip_hdr *pkt = (struct ip_hdr *) - (&temp->packet.msg[0]); - memcpy(&pkt->ethhdr.dest, &arp->shwaddr, 6); - memcpy(&pkt->ethhdr.src, &imp->mac, 6); - pkt->ethhdr.type = htons(ETHTYPE_IP); - eth_write(&imp->etherface, &temp->packet, NULL); - imp->rfnm_count++; - imp_free_packet(imp, temp); - } else { - temp->next = nq; - nq = temp; - } - } - imp->sendq = nq; - } - break; + cptr = get_glyph (cptr, abuf, '='); + if (cptr && *cptr) { + if (SCPE_OK != eth_mac_scan (&mac_addr, cptr)) /* scan string for mac, put in mac */ + return sim_messagef(SCPE_ARG, "Invalid MAC address: %s", cptr); + } else + return sim_messagef(SCPE_ARG, "Invalid MAC address: %s", cptr); + if (ipv4_inet_aton (abuf, (struct in_addr *)&ip)) { + imp_arp_update(&imp_data, ip, &mac_addr, ARP_DONT_AGE); + return SCPE_OK; } - return; + return sim_messagef(SCPE_ARG, "Invalid IP Address: %s", abuf); +} + +t_stat imp_show_arp (FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + struct arp_entry *tabptr; + int i; + + fprintf (st, "%-17s%-19s%s\n", "IP Address:", "MAC:", "Age:"); + + for (i = 0; i < IMP_ARPTAB_SIZE; i++) { + char buf[32]; + + tabptr = &imp_data.arp_table[i]; + + if (tabptr->ipaddr == 0) + continue; + + eth_mac_fmt(&tabptr->ethaddr, buf); /* format ethernet mac address */ + if (tabptr->age == ARP_DONT_AGE) + fprintf (st, "%-17s%-19s%s\n", + ipv4_inet_ntoa(*((struct in_addr *)&tabptr->ipaddr)), + buf, "static"); + else + fprintf (st, "%-17s%-19s%d\n", + ipv4_inet_ntoa(*((struct in_addr *)&tabptr->ipaddr)), + buf, tabptr->age); + } + return SCPE_OK; } @@ -1477,21 +1921,43 @@ void sent(int status) { sent_flag = 1; } +static const char *dhcp_state_names[] = { + "OFF", "REQUESTING", "INIT", "REBOOTING", + "REBINDING", "RENEWING", "SELECTING", "INFORMING", + "CHECKING", "PERMANENT", "BOUND", "RELEASING", + "BACKING_OFF" + }; + +static const char *dhcp_opr_names[16] = { + "", "DISCOVER", "OFFER", "REQUEST", + "DECLINE", "ACK", "NAK", "RELEASE", + "INFORM" + }; + /* Send out a DHCP packet, fill in IP and Ethernet data. */ void -imp_do_send_dhcp(struct imp_device *imp, ETH_PACK *packet, uint8 *last) +imp_do_send_dhcp(struct imp_device *imp, + const char *op, + ETH_PACK *packet, + uint8 *last, + const struct arp_entry *destarp) { struct ip_hdr *pkt; struct udp *udp; struct udp_hdr udp_hdr; + struct in_addr in_addr; int len; + char mac_buf[20]; pkt = (struct ip_hdr *)(&packet->msg[0]); udp = (struct udp *)(&packet->msg[sizeof(struct imp_eth_hdr) + sizeof(struct ip)]); len = last - (uint8 *)udp; /* Fill in ethernet and IP packet */ - memcpy(&pkt->ethhdr.dest, &broadcast_ethaddr, 6); + if (destarp) + memcpy(&pkt->ethhdr.dest, &destarp->ethaddr, 6); + else + memcpy(&pkt->ethhdr.dest, &broadcast_ethaddr, 6); memcpy(&pkt->ethhdr.src, &imp->mac, 6); pkt->ethhdr.type = htons(ETHTYPE_IP); pkt->iphdr.ip_v_hl = 0x45; @@ -1499,8 +1965,8 @@ imp_do_send_dhcp(struct imp_device *imp, ETH_PACK *packet, uint8 *last) pkt->iphdr.ip_ttl = 128; pkt->iphdr.ip_p = UDP_PROTO; pkt->iphdr.ip_dst = broadcast_ipaddr; - udp->udp_sport = htons(68); - udp->udp_dport = htons(67); + udp->udp_sport = htons(DHCP_UDP_PORT_CLIENT); + udp->udp_dport = htons(DHCP_UDP_PORT_SERVER); udp->len = htons(len); pkt->iphdr.ip_len = htons(len + sizeof(struct ip)); ip_checksum((uint8 *)(&pkt->iphdr.ip_sum), (uint8 *)&pkt->iphdr, 20); @@ -1514,8 +1980,13 @@ imp_do_send_dhcp(struct imp_device *imp, ETH_PACK *packet, uint8 *last) checksumadjust((uint8 *)&udp->chksum, (uint8 *)(&udp_hdr), 0, (uint8 *)(&udp_hdr), sizeof(udp_hdr)); packet->len = len + sizeof(struct ip_hdr); - sent_flag = 0; - eth_write(&imp->etherface, packet, &sent); + imp_write(imp, packet); + + eth_mac_fmt (&pkt->ethhdr.dest, mac_buf); + memcpy(&in_addr, &udp_hdr.ip_dst, sizeof(in_addr)); + sim_debug(DEBUG_DHCP, &imp_dev, + "client sent %s packet to: %s:%d(%s)\n", + op, ipv4_inet_ntoa(in_addr), ntohs(udp->udp_dport), mac_buf); } /* Handle incoming DCHP offer and other requests */ @@ -1524,11 +1995,12 @@ imp_do_dhcp_client(struct imp_device *imp, ETH_PACK *read_buffer) { struct ip *ip_hdr = (struct ip *) (&read_buffer->msg[sizeof(struct imp_eth_hdr)]); - ETH_PACK dhcp_pkt; + struct imp_eth_hdr *eth = (struct imp_eth_hdr *)read_buffer->msg; int hl = (ip_hdr->ip_v_hl & 0xf) * 4; struct dhcp *dhcp; struct udp *upkt; struct udp_hdr udp_hdr; + struct in_addr in_addr; uint8 *opt; uint16 sum; int len; @@ -1536,9 +2008,11 @@ imp_do_dhcp_client(struct imp_device *imp, ETH_PACK *read_buffer) in_addr_T my_mask = 0; /* Local IP mask */ in_addr_T my_gw = 0; /* Gateway IP address */ int lease_time = 0; /* Lease time */ + int renew_time = 0; /* Renewal time */ + int rebind_time = 0; /* Rebind time */ in_addr_T dhcpip = 0; /* DHCP server address */ - int opr = -1; /* Dhcp operation */ - + int opr = -1; /* DHCP operation */ + char mac_buf[20]; /* Source MAC address */ upkt = (struct udp *)(&((uint8 *)(ip_hdr))[hl]); dhcp = (struct dhcp *)(&((uint8 *)(upkt))[sizeof(struct udp)]); @@ -1568,6 +2042,8 @@ imp_do_dhcp_client(struct imp_device *imp, ETH_PACK *read_buffer) opt = &dhcp->options[0]; + my_ip = dhcp->yiaddr; + /* Scan and collect the options we care about */ while (*opt != DHCP_OPTION_END) { switch(*opt++) { @@ -1595,6 +2071,19 @@ imp_do_dhcp_client(struct imp_device *imp, ETH_PACK *read_buffer) case DHCP_OPTION_LEASE_TIME: len = *opt++; memcpy(&lease_time, opt, 4); + lease_time = ntohl(lease_time); + opt += len; + break; + case DHCP_OPTION_T1: + len = *opt++; + memcpy(&renew_time, opt, 4); + renew_time = ntohl(renew_time); + opt += len; + break; + case DHCP_OPTION_T2: + len = *opt++; + memcpy(&rebind_time, opt, 4); + rebind_time = ntohl(rebind_time); opt += len; break; case DHCP_OPTION_SERVER_ID: @@ -1610,144 +2099,143 @@ imp_do_dhcp_client(struct imp_device *imp, ETH_PACK *read_buffer) } } + eth_mac_fmt(ð->src, mac_buf); + memcpy(&in_addr, &udp_hdr.ip_src, sizeof(in_addr)); + sim_debug(DEBUG_DHCP, &imp_dev, + "client incoming %s packet: dhcp_state=%s - wait_time=%d from: %s:%d(%s)\n", + (opr == -1) ? "" : dhcp_opr_names[opr], + dhcp_state_names[imp->dhcp_state], imp->dhcp_wait_time, + ipv4_inet_ntoa(in_addr), ntohs(upkt->udp_sport), mac_buf); + /* Process an offer message */ if (opr == DHCP_OFFER && imp->dhcp_state == DHCP_STATE_SELECTING) { /* Create a REQUEST Packet with IP address given */ - struct dhcp *dhcp_rply; - in_addr_T ip_t; - - memset(&dhcp_pkt.msg[0], 0, ETH_FRAME_SIZE); - dhcp_rply = (struct dhcp *)(&dhcp_pkt.msg[sizeof(struct imp_eth_hdr) + - sizeof(struct ip) + sizeof(struct udp)]); - - dhcp_rply->op = DHCP_BOOTREQUEST; - dhcp_rply->htype = DHCP_HTYPE_ETH; - dhcp_rply->hlen = 6; - dhcp_rply->xid = ++imp->dhcp_xid; - dhcp_rply->cookie = htonl(DHCP_MAGIC_COOKIE); - memcpy(&dhcp_rply->chaddr, &imp->mac, 6); - opt = &dhcp_rply->options[0]; - *opt++ = DHCP_OPTION_MESSAGE_TYPE; - *opt++ = 1; - *opt++ = DHCP_REQUEST; - *opt++ = DHCP_OPTION_REQUESTED_IP; - *opt++ = 4; - ip_t = htonl(dhcp->yiaddr); - *opt++ = (ip_t >> 24) & 0xff; - *opt++ = (ip_t >> 16) & 0xff; - *opt++ = (ip_t >> 8) & 0xff; - *opt++ = ip_t & 0xff; - *opt++ = DHCP_OPTION_SERVER_ID; - *opt++ = 4; - ip_t = imp->dhcpip; - *opt++ = (ip_t >> 24) & 0xff; - *opt++ = (ip_t >> 16) & 0xff; - *opt++ = (ip_t >> 8) & 0xff; - *opt++ = ip_t & 0xff; - *opt++ = DHCP_OPTION_CLIENT_ID; - *opt++ = 6; - for (len = 0; len < 6; len++) - *opt++ = imp->mac[len]; - *opt++ = DHCP_OPTION_PARAMETER_REQUEST_LIST; - *opt++ = 2; /* Number */ - *opt++ = DHCP_OPTION_SUBNET_MASK; /* Subnet mask */ - *opt++ = DHCP_OPTION_ROUTER; /* Routers */ - *opt++ = DHCP_OPTION_END; /* Last option */ - imp_do_send_dhcp(imp, &dhcp_pkt, opt); imp->dhcp_state = DHCP_STATE_REQUESTING; + imp->ip = my_ip; + imp->dhcp_wait_time = 0; + imp->dhcp_retry_after = 4; + imp_dhcp_request(imp, dhcpip); } - if (opr == DHCP_ACK && (imp->dhcp_state == DHCP_STATE_REQUESTING || + if (opr == DHCP_ACK && imp->dhcp_xid == dhcp->xid && + (imp->dhcp_state == DHCP_STATE_REQUESTING || imp->dhcp_state == DHCP_STATE_REBINDING || imp->dhcp_state == DHCP_STATE_RENEWING)) { + ETH_PACK arp_pkt; + struct arp_hdr *arp; + /* Set my IP address to one offered */ imp->ip = dhcp->yiaddr; imp->ip_mask = my_mask; imp->gwip = my_gw; imp->dhcpip = dhcpip; imp->dhcp_state = DHCP_STATE_BOUND; - imp->dhcp_lease = ntohl(lease_time); + imp->dhcp_lease = lease_time; imp->dhcp_renew = imp->dhcp_lease / 2; + if (renew_time) + imp->dhcp_renew = renew_time; imp->dhcp_rebind = (7 * imp->dhcp_lease) / 8; + if (rebind_time) + imp->dhcp_rebind = rebind_time; for (len = 0; len < 33; len++) { - if (mask[len] == my_mask) { + if (htonl(mask[len]) == my_mask) { imp->maskbits = 32 - len; break; } + /* Record a static ARP for the DHCP server */ + imp_arp_update(imp, dhcpip, ð->src, ARP_DONT_AGE); + + /* Broadcast an ARP Reply with the assigned IP Address */ + memset(&arp_pkt, 0, sizeof(ETH_PACK)); + arp = (struct arp_hdr *)(&arp_pkt.msg[0]); + memcpy(&arp->ethhdr.dest, &broadcast_ethaddr, 6); + memcpy(&arp->ethhdr.src, &imp->mac, 6); + arp->ethhdr.type = htons(ETHTYPE_ARP); + memcpy(&arp->dhwaddr, &imp->mac, 6); + memcpy(&arp->shwaddr, &imp->mac, 6); + arp->dipaddr = imp->ip; + arp->sipaddr = imp->ip; + arp->opcode = htons(ARP_REPLY); + arp->hwtype = htons(ARP_HWTYPE_ETH); + arp->protocol = htons(ETHTYPE_IP); + arp->hwlen = 6; + arp->protolen = 4; + arp_pkt.len = sizeof(struct arp_hdr); + imp_write(imp, &arp_pkt); } } if (opr == DHCP_NAK && (imp->dhcp_state == DHCP_STATE_REQUESTING || imp->dhcp_state == DHCP_STATE_REBINDING || - imp->dhcp_state == DHCP_STATE_RENEWING)) + imp->dhcp_state == DHCP_STATE_RENEWING)) { + imp->ip = imp->gwip = imp->dhcpip = imp->maskbits = 0; imp->dhcp_state = DHCP_STATE_OFF; + + } } void imp_dhcp_timer(struct imp_device *imp) { - ETH_PACK dhcp_pkt; - uint8 *opt; - int len; - in_addr_T ip_t; - struct dhcp *dhcp_rply; + if (!(imp_unit[0].flags & UNIT_DHCP)) + return; - if (imp->dhcp_lease-- == 0) - imp->dhcp_state = DHCP_STATE_OFF; - else if (imp->dhcp_rebind-- == 0) { - imp->dhcp_state = DHCP_STATE_REBINDING; - imp->dhcpip = 0; - } else if (imp->dhcp_renew-- == 0) { - imp->dhcp_state = DHCP_STATE_RENEWING; + if (imp->dhcp_state == DHCP_STATE_BOUND) + sim_debug(DEBUG_DETAIL, &imp_dev, + "DHCP timer: dhcp_state=BOUND, Lease remaining time=%d\n", imp->dhcp_lease); + else + sim_debug(DEBUG_DETAIL, &imp_dev, + "DHCP timer: dhcp_state=%s, wait_time=%d\n", dhcp_state_names[imp->dhcp_state], + imp->dhcp_wait_time); + + if ((imp->dhcp_state == DHCP_STATE_BOUND) || + (imp->dhcp_state == DHCP_STATE_REBINDING) || + (imp->dhcp_state == DHCP_STATE_RENEWING)) { + if (imp->dhcp_lease != DHCP_INFINITE_LEASE) { + if (imp->dhcp_lease-- == 0) { + imp->dhcp_state = DHCP_STATE_OFF; + } else { + if (imp->dhcp_rebind-- == 0) { + imp->dhcp_state = DHCP_STATE_REBINDING; + imp->dhcpip = 0; + imp->dhcp_wait_time = 0; + imp->dhcp_retry_after = 4; + } else { + if (imp->dhcp_renew-- == 0) { + imp->dhcp_state = DHCP_STATE_RENEWING; + imp->dhcp_wait_time = 0; + imp->dhcp_retry_after = 4; + } + } + } + } } switch (imp->dhcp_state) { case DHCP_STATE_REBINDING: case DHCP_STATE_RENEWING: - /* Create a REQUEST Packet with IP address given */ - - memset(&dhcp_pkt.msg[0], 0, ETH_FRAME_SIZE); - dhcp_rply = (struct dhcp *)(&dhcp_pkt.msg[sizeof(struct imp_eth_hdr) + - sizeof(struct ip) + sizeof(struct udp)]); - - dhcp_rply->op = DHCP_BOOTREQUEST; - dhcp_rply->htype = DHCP_HTYPE_ETH; - dhcp_rply->hlen = 6; - dhcp_rply->xid = ++imp->dhcp_xid; - dhcp_rply->cookie = htonl(DHCP_MAGIC_COOKIE); - memcpy(&dhcp_rply->chaddr, &imp->mac, 6); - opt = &dhcp_rply->options[0]; - *opt++ = DHCP_OPTION_MESSAGE_TYPE; - *opt++ = 1; - *opt++ = DHCP_REQUEST; - *opt++ = DHCP_OPTION_REQUESTED_IP; - *opt++ = 4; - ip_t = htonl(imp->ip); - *opt++ = (ip_t >> 24) & 0xff; - *opt++ = (ip_t >> 16) & 0xff; - *opt++ = (ip_t >> 8) & 0xff; - *opt++ = ip_t & 0xff; - *opt++ = DHCP_OPTION_SERVER_ID; - *opt++ = 4; - ip_t = imp->dhcpip; - *opt++ = (ip_t >> 24) & 0xff; - *opt++ = (ip_t >> 16) & 0xff; - *opt++ = (ip_t >> 8) & 0xff; - *opt++ = ip_t & 0xff; - *opt++ = DHCP_OPTION_CLIENT_ID; - *opt++ = 6; - for (len = 0; len < 6; len++) - *opt++ = imp->mac[len]; - *opt++ = DHCP_OPTION_PARAMETER_REQUEST_LIST; - *opt++ = 2; /* Number */ - *opt++ = DHCP_OPTION_SUBNET_MASK; /* Subnet mask */ - *opt++ = DHCP_OPTION_ROUTER; /* Routers */ - *opt++ = DHCP_OPTION_END; /* Last option */ - imp_do_send_dhcp(imp, &dhcp_pkt, opt); + /* Create a REQUEST Packet with IP address previously given */ + imp_dhcp_request(imp, (imp->dhcp_state == DHCP_STATE_RENEWING) ? imp->dhcpip : 0); break; case DHCP_STATE_OFF: + imp->dhcp_wait_time = 0; + imp->dhcp_retry_after = 4; imp_dhcp_discover(imp); break; + case DHCP_STATE_SELECTING: + case DHCP_STATE_REQUESTING: + imp->dhcp_wait_time++; + if (imp->dhcp_wait_time % imp->dhcp_retry_after == 0) { + if (imp->dhcp_retry_after < 64) + imp->dhcp_retry_after *= 2; + imp->dhcp_wait_time = 0; + if (imp->dhcp_state == DHCP_STATE_SELECTING) + imp_dhcp_discover(imp); + else + imp_dhcp_request(imp, imp->dhcpip); + } + break; + default: break; } @@ -1759,6 +2247,7 @@ imp_dhcp_discover(struct imp_device *imp) ETH_PACK dhcp_pkt; struct dhcp *dhcp; uint8 *opt; + int len; /* Fill in Discover packet */ memset(&dhcp_pkt.msg[0], 0, ETH_FRAME_SIZE); @@ -1769,20 +2258,23 @@ imp_dhcp_discover(struct imp_device *imp) dhcp->htype = DHCP_HTYPE_ETH; dhcp->hlen = 6; dhcp->xid = ++imp->dhcp_xid; + dhcp->secs = imp->dhcp_wait_time; dhcp->cookie = htonl(DHCP_MAGIC_COOKIE); memcpy(&dhcp->chaddr, &imp->mac, 6); opt = &dhcp->options[0]; *opt++ = DHCP_OPTION_MESSAGE_TYPE; *opt++ = 1; *opt++ = DHCP_DISCOVER; + *opt++ = DHCP_OPTION_CLIENT_ID; + *opt++ = 7; + *opt++ = DHCP_HTYPE_ETH; + for (len = 0; len < 6; len++) + *opt++ = imp->mac[len]; if (imp->ip != 0) { - in_addr_T ip_t = htonl(imp->ip); *opt++ = DHCP_OPTION_REQUESTED_IP; *opt++ = 0x04; - *opt++ = (ip_t >> 24) & 0xff; - *opt++ = (ip_t >> 16) & 0xff; - *opt++ = (ip_t >> 8) & 0xff; - *opt++ = ip_t & 0xff; + memcpy(opt, &imp->ip, 4); + opt += 4; } *opt++= DHCP_OPTION_PARAMETER_REQUEST_LIST; *opt++= 2; /* Number */ @@ -1790,10 +2282,58 @@ imp_dhcp_discover(struct imp_device *imp) *opt++= DHCP_OPTION_ROUTER; /* Routers */ *opt++= DHCP_OPTION_END; /* Last option */ /* Fill in ethernet and IP packet */ - imp_do_send_dhcp(imp, &dhcp_pkt, opt); + imp_do_send_dhcp(imp, "DISCOVER", &dhcp_pkt, opt, NULL); imp->dhcp_state = DHCP_STATE_SELECTING; } +void +imp_dhcp_request(struct imp_device *imp, in_addr_T dhcpip) +{ + ETH_PACK dhcp_pkt; + struct dhcp *dhcp; + uint8 *opt; + int len; + + /* Fill in Request packet */ + memset(&dhcp_pkt.msg[0], 0, ETH_FRAME_SIZE); + dhcp = (struct dhcp *)(&dhcp_pkt.msg[sizeof(struct imp_eth_hdr) + + sizeof(struct ip) + sizeof(struct udp)]); + + dhcp->op = DHCP_BOOTREQUEST; + dhcp->htype = DHCP_HTYPE_ETH; + dhcp->hlen = 6; + dhcp->xid = imp->dhcp_xid; + dhcp->cookie = htonl(DHCP_MAGIC_COOKIE); + memcpy(&dhcp->chaddr, &imp->mac, 6); + opt = &dhcp->options[0]; + *opt++ = DHCP_OPTION_MESSAGE_TYPE; + *opt++ = 1; + *opt++ = DHCP_REQUEST; + *opt++ = DHCP_OPTION_REQUESTED_IP; + *opt++ = 4; + memcpy(opt, &imp->ip, 4); + opt += 4; + if (dhcpip) { + *opt++ = DHCP_OPTION_SERVER_ID; + *opt++ = 4; + memcpy(opt, &dhcpip, 4); + opt += 4; + } + *opt++ = DHCP_OPTION_CLIENT_ID; + *opt++ = 7; + *opt++ = DHCP_HTYPE_ETH; + for (len = 0; len < 6; len++) + *opt++ = imp->mac[len]; + *opt++ = DHCP_OPTION_PARAMETER_REQUEST_LIST; + *opt++ = 4; /* Number */ + *opt++ = DHCP_OPTION_SUBNET_MASK; /* Subnet mask */ + *opt++ = DHCP_OPTION_ROUTER; /* Routers */ + *opt++ = DHCP_OPTION_T1; /* Renewal time */ + *opt++ = DHCP_OPTION_T2; /* Rebinding time */ + *opt++ = DHCP_OPTION_END; /* Last option */ + imp_do_send_dhcp(imp, "REQUEST", &dhcp_pkt, opt, imp_arp_lookup(imp, dhcpip)); +} + void imp_dhcp_release(struct imp_device *imp) { @@ -1804,7 +2344,7 @@ imp_dhcp_release(struct imp_device *imp) /* Nothing to send if we are not bound */ - if (imp_data.dhcp_state != DHCP_STATE_OFF) + if (imp->dhcp_state == DHCP_STATE_OFF) return; /* Fill in DHCP RELEASE PACKET */ memset(&dhcp_pkt.msg[0], 0, ETH_FRAME_SIZE); @@ -1815,36 +2355,32 @@ imp_dhcp_release(struct imp_device *imp) dhcp->htype = DHCP_HTYPE_ETH; dhcp->hlen = 6; dhcp->xid = ++imp->dhcp_xid; - dhcp->ciaddr = htonl(imp->ip); + dhcp->ciaddr = imp->ip; dhcp->cookie = htonl(DHCP_MAGIC_COOKIE); memcpy(&dhcp->chaddr, &imp->mac, 6); opt = &dhcp->options[0]; *opt++ = DHCP_OPTION_SERVER_ID; *opt++ = 4; - *opt++ = (imp->dhcpip >> 24) & 0xff; - *opt++ = (imp->dhcpip >> 16) & 0xff; - *opt++ = (imp->dhcpip >> 8) & 0xff; - *opt++ = imp->dhcpip & 0xff; + memcpy(opt, &imp->dhcpip, 4); + opt += 4; *opt++ = DHCP_OPTION_MESSAGE_TYPE; *opt++ = 1; *opt++ = DHCP_RELEASE; if (imp->ip != 0) { - in_addr_T ip_t = htonl(imp->ip); *opt++ = DHCP_OPTION_REQUESTED_IP; *opt++ = 0x04; - *opt++ = (ip_t >> 24) & 0xff; - *opt++ = (ip_t >> 16) & 0xff; - *opt++ = (ip_t >> 8) & 0xff; - *opt++ = ip_t & 0xff; + memcpy(opt, &imp->ip, 4); + opt += 4; } *opt++ = DHCP_OPTION_CLIENT_ID; - *opt++ = 6; + *opt++ = 7; + *opt++ = DHCP_HTYPE_ETH; for (len = 0; len < 6; len++) *opt++ = imp->mac[len]; *opt++= DHCP_OPTION_END; /* Last option */ - imp_do_send_dhcp(imp, &dhcp_pkt, opt); - while(sent_flag == 0); + imp_do_send_dhcp(imp, "RELEASE", &dhcp_pkt, opt, imp_arp_lookup(imp, imp->dhcpip)); imp->dhcp_state = DHCP_STATE_OFF; + imp->ip = imp->gwip = imp->dhcpip = imp->maskbits = 0; } @@ -1854,8 +2390,12 @@ ipv4_inet_ntoa(struct in_addr ip) { static char str[20]; - sprintf (str, "%d.%d.%d.%d", ip.s_addr & 0xFF, (ip.s_addr >> 8) & 0xFF, - (ip.s_addr >> 16) & 0xFF, (ip.s_addr >> 24) & 0xFF); + if (sim_end) + sprintf (str, "%d.%d.%d.%d", ip.s_addr & 0xFF, (ip.s_addr >> 8) & 0xFF, + (ip.s_addr >> 16) & 0xFF, (ip.s_addr >> 24) & 0xFF); + else + sprintf (str, "%d.%d.%d.%d", (ip.s_addr >> 24) & 0xFF, (ip.s_addr >> 16) & 0xFF, + (ip.s_addr >> 8) & 0xFF, ip.s_addr & 0xFF); return str; } @@ -1947,6 +2487,7 @@ t_stat imp_set_mac (UNIT* uptr, int32 val, CONST char* cptr, void* desc) if (status != SCPE_OK) return status; + memcpy(&imp_data.bcast, &broadcast_ethaddr, 6); return SCPE_OK; } @@ -1978,6 +2519,10 @@ t_stat imp_set_ip (UNIT* uptr, int32 val, CONST char* cptr, void* desc) if (ipv4_inet_aton (abuf, &ip)) { imp_data.ip = ip.s_addr; imp_data.ip_mask = htonl(mask[imp_data.maskbits]); + if (uptr->flags & UNIT_DHCP) { + uptr->flags &= ~UNIT_DHCP; + return sim_messagef (SCPE_OK, "IMP DHCP disabled\n"); + } return SCPE_OK; } return SCPE_ARG; @@ -1999,7 +2544,11 @@ t_stat imp_set_gwip (UNIT* uptr, int32 val, CONST char* cptr, void* desc) if (uptr->flags & UNIT_ATT) return SCPE_ALATT; if (ipv4_inet_aton (cptr, &ip)) { - imp_data.gwip = ip.s_addr; + imp_data.gwip = ip.s_addr; + if (uptr->flags & UNIT_DHCP) { + uptr->flags &= ~UNIT_DHCP; + return sim_messagef (SCPE_OK, "IMP DHCP disabled\n"); + } return SCPE_OK; } return SCPE_ARG; @@ -2007,9 +2556,19 @@ t_stat imp_set_gwip (UNIT* uptr, int32 val, CONST char* cptr, void* desc) t_stat imp_show_dhcpip (FILE *st, UNIT *uptr, int32 val, CONST void *desc) { - struct in_addr ip; - ip.s_addr = imp_data.dhcpip; - fprintf (st, "DHCPIP=%s", ipv4_inet_ntoa(ip)); + if (!(uptr->flags & UNIT_DHCP)) { + fprintf (st, "DHCP disabled"); + } else { + struct in_addr ip; + ip.s_addr = imp_data.dhcpip; + fprintf (st, "DHCP Server IP=%s", ipv4_inet_ntoa(ip)); + if (imp_data.dhcp_state == DHCP_STATE_BOUND) { + fprintf (st, ", Lease Expires in %d seconds", imp_data.dhcp_lease); + } else { + fprintf (st, ", State:%s, Waited %d seconds", dhcp_state_names[imp_data.dhcp_state], + imp_data.dhcp_wait_time); + } + } return SCPE_OK; } @@ -2057,9 +2616,16 @@ t_stat imp_reset (DEVICE *dptr) int i; struct imp_packet *p; - /* Clear ARP table. */ - for (i = 0; i < IMP_ARPTAB_SIZE; i++) { - arp_table[i].ipaddr = 0; + for (i = 0; i < 6; i++) { + if (imp_data.mac[i] != 0) + break; + } + if (i == 6) { /* First call to reset? */ + /* Set a default MAC address in a BBN assigned OID range no longer in use */ + imp_set_mac (dptr->units, 0, "00:00:02:00:00:00/24", NULL); + /* Clear ARP table. */ + memset(imp_data.arp_table, 0, sizeof(imp_data.arp_table)); + imp_data.dhcp_state = DHCP_STATE_OFF; } /* Clear queues. */ imp_data.sendq = NULL; @@ -2072,8 +2638,9 @@ t_stat imp_reset (DEVICE *dptr) /* Fix last entry */ imp_data.freeq = p; imp_data.init_state = 0; - last_coni = sim_interval; - imp_data.dhcp_state = DHCP_STATE_OFF; + last_coni = sim_gtime(); + if (imp_unit[0].flags & UNIT_ATT) + sim_activate_after(&imp_unit[2], 1000000); /* Start Timer service */ return SCPE_OK; } @@ -2082,6 +2649,7 @@ t_stat imp_attach(UNIT* uptr, CONST char* cptr) { t_stat status; char* tptr; + char buf[32]; /* Set to correct device number */ switch(GET_DTYPE(imp_unit[0].flags)) { @@ -2093,46 +2661,79 @@ t_stat imp_attach(UNIT* uptr, CONST char* cptr) imp_dib.dev_num = WA_IMP_DEVNUM; break; } + if (!(uptr->flags & UNIT_DHCP) && imp_data.ip == 0) + return sim_messagef (SCPE_NOATT, "%s: An IP Address must be specified when DHCP is disabled\n", + imp_dev.name); + tptr = (char *) malloc(strlen(cptr) + 1); if (tptr == NULL) return SCPE_MEM; strcpy(tptr, cptr); - status = eth_open(&imp_data.etherface, cptr, &imp_dev, 0xFFFF); + status = eth_open(&imp_data.etherface, cptr, &imp_dev, DEBUG_ETHER); if (status != SCPE_OK) { free(tptr); return status; } + eth_mac_fmt(&imp_data.mac, buf); /* format ethernet mac address */ if (SCPE_OK != eth_check_address_conflict (&imp_data.etherface, &imp_data.mac)) { - char buf[32]; - - eth_mac_fmt(&imp_data.mac, buf); /* format ethernet mac address */ - sim_printf("%s: MAC Address Conflict on LAN for address %s\n", imp_dev.name, buf); eth_close(&imp_data.etherface); free(tptr); - return SCPE_NOATT; + return sim_messagef (SCPE_NOATT, "%s: MAC Address Conflict on LAN for address %s\n", + imp_dev.name, buf); } - if (SCPE_OK != eth_filter(&imp_data.etherface, 1, &imp_data.mac, 1, 0)) { + if (SCPE_OK != eth_filter(&imp_data.etherface, 2, &imp_data.mac, 0, 0)) { eth_close(&imp_data.etherface); free(tptr); - return SCPE_NOATT; + return sim_messagef (SCPE_NOATT, "%s: Can't set packet filter for MAC Address %s\n", + imp_dev.name, buf); } uptr->filename = tptr; uptr->flags |= UNIT_ATT; - eth_setcrc(&imp_data.etherface, 0); /* Don't need CRC */ + eth_setcrc(&imp_data.etherface, 0); /* Don't need CRC */ /* init read queue (first time only) */ status = ethq_init(&imp_data.ReadQ, 8); if (status != SCPE_OK) { eth_close(&imp_data.etherface); + uptr->filename = NULL; free(tptr); - return status; + return sim_messagef (status, "%s: Can't initialize receive queue\n", imp_dev.name); } - imp_data.sec_tim = 1000; - imp_data.dhcp_xid = XID; + /* Pick a relatively unique starting xid, presuming that the + MAC address has been verified unique on the LAN by eth_open */ + imp_data.dhcp_xid = (imp_data.mac[0] | (imp_data.mac[1] << 8) | + (imp_data.mac[2] << 16) | (imp_data.mac[3] << 24)) + (uint32)time(NULL); imp_data.dhcp_state = DHCP_STATE_OFF; + memset(imp_data.arp_table, 0, sizeof(imp_data.arp_table)); + /* If we're not doing DHCP and a gateway is defined on the network + then define a static APR entry for the gateway to facilitate + outbound routing */ + if (!(uptr->flags & UNIT_DHCP) && imp_data.gwip) { + ETH_PACK read_buffer; + struct imp_eth_hdr *hdr = (struct imp_eth_hdr *)(&read_buffer.msg[0]); + int type; + struct arp_entry *arp; + + imp_arp_arpout(&imp_data, imp_data.gwip); + sim_os_ms_sleep (300); /* wait for an ARP response to arrive */ + + /* empty the read queue and find ARP_REPLYs */ + do { + memset (&read_buffer, 0, sizeof(read_buffer)); + status = eth_read (&imp_data.etherface, &read_buffer, NULL); + type = ntohs(hdr->type); + if (type == ETHTYPE_ARP) + imp_arp_arpin(&imp_data, &read_buffer); + } while (read_buffer.len > 0); + if ((arp = imp_arp_lookup(&imp_data, imp_data.gwip))) + imp_arp_update(&imp_data, imp_data.gwip, &arp->ethaddr, ARP_DONT_AGE); + } + + eth_set_async (&imp_data.etherface, 0); /* Allow Asynchronous inbound packets */ + sim_activate_after(&imp_unit[2], 1000000); /* Start Timer service */ return SCPE_OK; } @@ -2150,7 +2751,8 @@ t_stat imp_detach(UNIT* uptr) free(uptr->filename); uptr->filename = NULL; uptr->flags &= ~UNIT_ATT; - sim_cancel (uptr+1); /* stop the timer services */ + sim_cancel (uptr+1); /* stop the packet timing services */ + sim_cancel (uptr+2); /* stop the clock timer services */ } return SCPE_OK; } diff --git a/PDP10/kx10_lp.c b/PDP10/kx10_lp.c index fc1aafbe..5f8a5201 100644 --- a/PDP10/kx10_lp.c +++ b/PDP10/kx10_lp.c @@ -125,7 +125,7 @@ t_stat lpt_devio(uint32 dev, uint64 *data) { *data = uptr->STATUS & (PI_DONE|PI_ERROR|DONE_FLG|BUSY_FLG|ERR_FLG); if ((uptr->flags & UNIT_UC) == 0) *data |= C96; - if ((uptr->flags & UNIT_UTF8) == 0) + if ((uptr->flags & UNIT_UTF8) != 0) *data |= C128; if ((uptr->flags & UNIT_ATT) == 0) *data |= ERR_FLG; @@ -266,7 +266,7 @@ lpt_output(UNIT *uptr, char c) { lpt_buffer[uptr->POS++] = u & 0x7f; } uptr->COL++; - } else if (c >= 040) { + } else if (c >= 040 && c < 0177) { lpt_buffer[uptr->POS++] = c; uptr->COL++; } @@ -283,7 +283,6 @@ t_stat lpt_svc (UNIT *uptr) set_interrupt(LP_DEVNUM, uptr->STATUS); return SCPE_OK; } - if ((uptr->flags & UNIT_ATT) == 0) { uptr->STATUS |= ERR_FLG; set_interrupt(LP_DEVNUM, (uptr->STATUS >> 3)); diff --git a/PDP10/kx10_mt.c b/PDP10/kx10_mt.c index 9e45039f..4f03da3d 100644 --- a/PDP10/kx10_mt.c +++ b/PDP10/kx10_mt.c @@ -280,7 +280,7 @@ t_stat mt_devio(uint32 dev, uint64 *data) { uptr->CNTRL &= ~MT_BUSY; wr_eor = 0; mt_status |= NEXT_UNIT; - if (cmd & 010) { + if ((cmd & 010) != 0 || (mt_pia & NEXT_UNIT_ENAB) != 0) { mt_status |= JOB_DONE; set_interrupt(MT_DEVNUM+4, mt_pia >> 3); } else { diff --git a/display/display.c b/display/display.c index 94bf86c4..5ad06912 100644 --- a/display/display.c +++ b/display/display.c @@ -439,6 +439,15 @@ queue_point(struct point *p) p->delay = d; } +/* + * Return true if the display is blank, i.e. no active points in list. + */ +int +display_is_blank(void) +{ + return head->next == head; +} + /* * here to to dynamically adjust interval for examination * of elapsed vs. simulated time, and fritter away diff --git a/display/display.h b/display/display.h index 0470f465..391bf4e7 100644 --- a/display/display.h +++ b/display/display.h @@ -83,6 +83,11 @@ extern int display_scale(void); */ extern int display_age(int,int); +/* + * Return true if the display is blank. + */ +extern int display_is_blank(void); + /* * display intensity levels. * always at least 8 (for VT11/VS60) -- may be mapped internally diff --git a/doc/ka10_doc.doc b/doc/ka10_doc.doc index 9bdca481ee7a7932a2ae24aebf77d59d40de30a6..58e8d19bc07a9789ca4d4db73a1f999ebf598d23 100644 GIT binary patch literal 242688 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 Ue-8*#*BWgdY1cMmU(h7*f98?<9RL6T literal 215040 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 z