1
0
mirror of https://github.com/pkoning2/decstuff.git synced 2026-02-08 17:22:05 +00:00
Files
pkoning2.decstuff/ntp/ntp.c
2023-11-20 17:08:26 -05:00

423 lines
12 KiB
C

/* NTP for RSTS/E */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "rstsys.h"
#include "tzutil.h"
#define NOSUCH 5 /* Ethernet driver error code for "no packets pending" */
#define UU_DAT -14 /* Date/Time changer */
#define UU_DET 7 /* Detach */
#define JFLOCK 040000 /* Lock in memory flag */
#define JFSPRI 000400 /* Special (half step) priority boost */
#define DATE 01000 /* Kernel address of date/time */
#define LOWAPR 7 /* APR to use for mapping date/time in kernel lowcore */
#define LOWLEN 1 /* Length in slivers */
#define LOWPAGE (DATE >> 6) /* Sliver PA of lowcore */
#define KDATE (LOWAPR << 13)
struct ethhdr
{
uint8_t dest[6];
uint8_t src[6];
uint16_t proto;
};
#define ETH_IP 0x0008 /* 08-00, as a little endian integer */
struct iphdr /* without options */
{
uint8_t verlen;
uint8_t tos;
uint16_t tl;
uint16_t id;
uint16_t fragoff;
uint8_t ttl;
uint8_t proto;
uint16_t hcs;
uint8_t src[4];
uint8_t dst[4];
};
#define IP_UDP 17 /* IP proto for UDP */
struct udphdr
{
uint16_t src;
uint16_t dest;
uint16_t len;
uint16_t cksum;
};
/* Offset from NTP base to Unix base (1-Jan-70) which conveniently is
* also the RSTS base */
#define UNIXBASE 2208988800UL
/* NTP port number */
#define NTPPORT (123 << 8) /* port 123, in network byte order */
#define NTPVERSION 4
struct ntpshort
{
uint32_t seconds;
uint32_t fraction;
};
/* The fixed parts of the NTP packet, not including the extension fields */
struct ntphdr
{
uint8_t mode; /* LI, VN, mode */
uint8_t stratum;
uint8_t poll;
uint8_t precision;
uint32_t root_delay;
uint32_t root_disp;
char refid[4];
struct ntpshort ref_ts;
struct ntpshort origin_ts;
struct ntpshort rec_ts;
struct ntpshort xmit_ts;
};
static void check (const char *msg)
{
int err = RSTS$FIRQB->firqb;
if (err != 0) {
printf ("%s: error %d: ", msg, err);
RSTS$CLRFQB ();
RSTS$FIRQB->fqfun = RSTS$FIP$ERRFQ;
RSTS$FIRQB->fqfil = err;
RSTS$CALFIP ();
printf ("%28s\n", &(RSTS$FIRQB->fqfil));
exit (2);
}
}
static void setxrb (void *buf, int size)
{
register XRB * const xrb = RSTS$XRB;
RSTS$CLRXRB ();
xrb->xrlen = size;
xrb->xrloc = (short int) buf;
}
#define ETH_CH (4 * 2)
/* Number of receive buffers. NTP doesn't need so many (one would do)
* but this allows us to deal with a burst of other IP broadcast
* messages for other services, without incurring receiver overrun. */
#define ETH_BUFS 5
/* Logical name for Ethernet interface to use */
#define NTPIF "NTP$IF:"
/* OPEN the Ethernet device NTP$IF: */
static void openeth (int ch2)
{
register FIRQB * const fqb = RSTS$FIRQB;
/* Scan the name (logical name) to a FIRQB device spec */
RSTS$CLRFQB ();
setxrb (NTPIF, sizeof (NTPIF) - 1);
/* Set byte count also */
RSTS$XRB->xrbc = sizeof (NTPIF) - 1;
RSTS$FSS ();
check ("fss");
fqb->fqfil = ch2;
fqb->fqmode = 0x8000 + 128; /* No DEC style length */
fqb->fqclus = ETH_BUFS; /* Receive buffers for this portal */
fqb->fqnent = ETH_IP; /* Ethertype 08-00 */
fqb->fqfun = RSTS$FIP$OPNFQ;
RSTS$CALFIP ();
check ("ethernet open");
}
/* Close the Ethernet portal */
static void closeeth (int ch2)
{
register FIRQB * const fqb = RSTS$FIRQB;
RSTS$CLRFQB ();
fqb->fqfil = ch2;
fqb->fqfun = RSTS$FIP$CLSFQ;
RSTS$CALFIP ();
check ("ethernet close");
}
static const uint8_t bc[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
#define ethhnd 050
/* Enable broadcast on the Ethernet portal */
static void setbc (int ch2)
{
register XRB * const xrb = RSTS$XRB;
RSTS$CLRXRB ();
xrb->xrci = ch2;
xrb->xrlen = -3; /* function code: set multicast */
xrb->xrbc = sizeof (bc);
xrb->xrloc = (short) &bc;
xrb->xrblkm = ethhnd; /* Device handler index for Ethernet */
RSTS$SPEC ();
check ("enable broadcast");
}
/* Receive an NTP packet, if one is pending. Returns a pointer to the
NTP header if so, NULL if no NTP packet is currently waiting. We
will loop through all pending packets (in case there are broadcasts
that aren't NTP) but return if there are no more packets, or if an
NTP packet is received even if additional ones are pending. */
static void * getntppkt (int ch2, void *buf, int len)
{
register XRB * const xrb = RSTS$XRB;
register struct ethhdr * const eth = buf;
register struct iphdr * const ip = (struct iphdr * const)((char *) buf + sizeof (struct ethhdr));
register struct udphdr * const udp = (struct udphdr * const) ((char *) buf + sizeof (struct ethhdr) + sizeof (struct iphdr));
for (;;) {
setxrb (buf, len);
xrb->xrci = ch2;
RSTS$READ ();
if (RSTS$FIRQB->firqb == NOSUCH) {
/* No packets pending */
return NULL;
}
check ("ethernet receive");
if (eth->proto != ETH_IP) {
continue; /* should not happen, driver should filter */
}
if (ip->proto != IP_UDP) {
continue;
}
if (udp->dest != NTPPORT) {
continue;
}
/* NTP packet, return with its start address in the buffer */
return udp + 1;
}
}
static char buf[600];
#define announcebuf (buf + 100)
static int pktlen;
static struct rstsdt rnow;
/* Copy the current date/time from the kernel into "rnow". The kernel
doesn't offer a race-free way to get it (.DATE doesn't block
interrupts) so instead keep reading until we get the same answer
twice. */
static void updrnow (void)
{
register const struct rstsdt * const kdt = (const struct rstsdt * const) KDATE;
for (;;) {
rnow.ticks = kdt->ticks;
rnow.seconds = kdt->seconds;
rnow.minutes = kdt->minutes;
rnow.date = kdt->date;
if (rnow.ticks == kdt->ticks && rnow.seconds == kdt->seconds) {
return;
}
}
}
/* Set the supplied date/time into the kernel. We don't block
interrupts, so start by setting the ticks to next second to a full
second, then write the date/time. */
static void updkdate (const struct rstsdt *dt)
{
register struct rstsdt * const kdt = (struct rstsdt * const) KDATE;
kdt->ticks = hertz; /* Prevent rollover in the middle of settings things */
kdt->date = dt->date;
kdt->minutes = dt->minutes;
kdt->seconds = dt->seconds;
kdt->ticks = dt->ticks; /* Finish by setting the real tick count */
}
static char omsbuf[255];
/* Send notification to OMS, if active. */
static void sendoms (const char *msg)
{
register FIRQB * const fqb = RSTS$FIRQB;
register XRB * const xrb = RSTS$XRB;
int l = strlen (msg);
char *p = omsbuf;
/* Build the OMS message buffer */
*p++ = 3; /* Reply flag: noreply */
*p++ = 0;
*p++ = 4; /* Facility: NTP */
*p++ = 3;
strcpy (p, "NTP");
p += 4; /* 3 bytes for string length plus one more for word alignment (!) */
*p++ = 1; /* Text */
if (l >= sizeof (omsbuf) - (p - omsbuf)) {
puts ("Message too long");
return;
}
*p++ = l;
strcpy (p, msg);
p += l;
/* Compute the data length in omsbuf */
l = p - omsbuf;
if ((l & 1) != 0) {
l++; /* Round up length to even */
}
RSTS$CLRFQB ();
RSTS$CLRXRB ();
fqb->fqfil = -11; /* "local send with privileges" */
fqb->fqsizm = 0213; /* Local object 11 */
fqb->fqflag = 2; /* OMS function code for "request" */
strcpy ((char *) (&fqb->fqpflg), "NTP"); /* Facility name */
xrb->xrlen = l;
xrb->xrbc = l;
xrb->xrloc = (uint16_t) omsbuf;
RSTS$MESAG ();
/* "No such receiver" is silently ignored */
if (fqb->firqb == NOSUCH) {
return;
}
check ("OMS send");
}
/* One-time initialization */
static void init (void)
{
int32_t nowsec;
register FIRQB * const fqb = RSTS$FIRQB;
uint16_t lowwindowid;
/* First map kernel memory so we can read/write the date/time values */
RSTS$CLRFQB ();
fqb->fqfil = 4; /* Create window */
fqb->fqppn = LOWAPR << 8; /* Base APR of window in upper byte */
((uint16_t *) &(fqb->fqnam1))[1] = LOWLEN;
fqb->fqmode = 2; /* Read/write access */
RSTS$PLAS ();
check ("Create lowcore window");
lowwindowid = fqb->fqppn & 0377;
RSTS$CLRFQB ();
fqb->fqfil = 8; /* Map window */
fqb->fqsiz = LOWPAGE; /* Physical address to map */
fqb->fqppn = lowwindowid;
fqb->fqext = -4; /* Special region ID value for "physical memory" */
fqb->fqbufl = LOWLEN; /* Length to map */
fqb->fqmode = 2; /* Read/write access */
RSTS$PLAS ();
check ("Map lowcore window");
/* Set priority boost and lock in memory */
RSTS$CLRFQB ();
RSTS$CLRXRB ();
RSTS$XRB->xrlen = JFLOCK | JFSPRI;
RSTS$SET ();
check ("Set flags");
/* Go get current date/time */
updrnow ();
/* Convert RSTS date/time to Unix style seconds since epoch, but local */
nowsec = lctime (&rnow);
/* Obtain the timezone information matching the current local time */
getlocaltzinfo (nowsec);
/* Now open the Ethernet */
openeth (ETH_CH);
setbc (ETH_CH);
}
static void mainloop ()
{
int32_t nowsec, cursec;
uint32_t nowfrac16;
struct ntphdr * ntp;
int32_t delay = 20;
struct rstsdt dt;
int ticks;
int announce;
for (;;) {
/* What is the local time as we know it at the moment? */
updrnow ();
/* Get UTC seconds */
cursec = ltou (lctime (&rnow));
delay = nextt - cursec;
if (delay > 32767) {
delay = 32767;
}
/* Conditionally sleep until next offset change or "forever" */
RSTS$CLRXRB ();
RSTS$XRB->xrlen = 01000000 | delay;
RSTS$SLEEP ();
/* Try to receive something */
ntp = getntppkt (ETH_CH, buf, sizeof (buf));
if (ntp) {
/* We received an NTP packet, process it */
nowsec = ntohl (ntp->xmit_ts.seconds) - UNIXBASE;
/* Keep the 16 MSB of the fractional time, that's plenty */
nowfrac16 = ntohs ((uint16_t) (ntp->xmit_ts.fraction));
/* Assume we'll announce if this is a change in offset */
announce = gettzinfo (nowsec);
/* Convert to RSTS format. First compute ticks, rounded. */
ticks = hertz - ((nowfrac16 * hertz + 32768UL) >> 16);
if (ticks == 0) {
/* It rounded up to a whole second, so set it to 0 and
advance the time in seconds by one. */
ticks = hertz;
nowsec++;
}
mkrststime (nowsec, &dt);
dt.ticks = ticks;
/* What is the local time now (before update? */
updrnow ();
/* Get UTC seconds */
cursec = ltou (lctime (&rnow));
/* Now write the updated date/time to the kernel */
updkdate (&dt);
/* See if we should announce this. Compute the delta time */
cursec -= nowsec;
if (cursec < -1 || cursec > 1) {
/* Changing more than one second, announce it */
announce = 1;
}
if (announce) {
/* "announce" means two things:
1. Wake up any sleeping jobs.
2. Send a message to OMS saying the time was updated.
*/
RSTS$CLRFQB ();
RSTS$FIRQB->fqfun = UU_DAT;
/* Do a no-change date/time "change", that still does a wakeup */
RSTS$UUO ();
/* Send to OMS. */
cvtdt (&dt);
sprintf (announcebuf, "Time updated to %s, stratum %d, source %s",
dtstr, ntp->stratum, ntp->refid);
/* This doesn't seem to be working... */
sendoms (announcebuf);
}
} else {
/* TODO check for offset change */
}
}
closeeth (ETH_CH);
}
int main (int argc, char **argv)
{
init ();
cvtdt (&rnow);
printf ("NTP started %s\n", dtstr);
/* Detach */
RSTS$CLRFQB ();
RSTS$FIRQB->fqfun = UU_DET;
RSTS$FIRQB->fqfil = 0200; /* Close terminal, detach self */
RSTS$UUO ();
check ("detach");
mainloop ();
puts ("Exiting NTP");
return 1; /* success */
}