Annotation of embedaddon/ntp/ntpd/refclock_neoclock4x.c, revision 1.1.1.1

1.1       misho       1: /*
                      2:  *
                      3:  * Refclock_neoclock4x.c
                      4:  * - NeoClock4X driver for DCF77 or FIA Timecode
                      5:  *
                      6:  * Date: 2009-12-04 v1.16
                      7:  *
                      8:  * see http://www.linum.com/redir/jump/id=neoclock4x&action=redir
                      9:  * for details about the NeoClock4X device
                     10:  *
                     11:  */
                     12: 
                     13: #ifdef HAVE_CONFIG_H
                     14: # include "config.h"
                     15: #endif
                     16: 
                     17: #if defined(REFCLOCK) && (defined(CLOCK_NEOCLOCK4X))
                     18: 
                     19: #include <unistd.h>
                     20: #include <sys/time.h>
                     21: #include <sys/types.h>
                     22: #include <termios.h>
                     23: #include <sys/ioctl.h>
                     24: #include <ctype.h>
                     25: 
                     26: #include "ntpd.h"
                     27: #include "ntp_io.h"
                     28: #include "ntp_control.h"
                     29: #include "ntp_refclock.h"
                     30: #include "ntp_unixtime.h"
                     31: #include "ntp_stdlib.h"
                     32: 
                     33: #if defined HAVE_SYS_MODEM_H
                     34: # include <sys/modem.h>
                     35: # ifndef __QNXNTO__
                     36: #  define TIOCMSET MCSETA
                     37: #  define TIOCMGET MCGETA
                     38: #  define TIOCM_RTS MRTS
                     39: # endif
                     40: #endif
                     41: 
                     42: #ifdef HAVE_TERMIOS_H
                     43: # ifdef TERMIOS_NEEDS__SVID3
                     44: #  define _SVID3
                     45: # endif
                     46: # include <termios.h>
                     47: # ifdef TERMIOS_NEEDS__SVID3
                     48: #  undef _SVID3
                     49: # endif
                     50: #endif
                     51: 
                     52: #ifdef HAVE_SYS_IOCTL_H
                     53: # include <sys/ioctl.h>
                     54: #endif
                     55: 
                     56: /*
                     57:  * NTP version 4.20 change the pp->msec field to pp->nsec.
                     58:  * To allow to support older ntp versions with this sourcefile
                     59:  * you can define NTP_PRE_420 to allow this driver to compile
                     60:  * with ntp version back to 4.1.2.
                     61:  *
                     62:  */
                     63: #if 0
                     64: #define NTP_PRE_420
                     65: #endif
                     66: 
                     67: /*
                     68:  * If you want the driver for whatever reason to not use
                     69:  * the TX line to send anything to your NeoClock4X
                     70:  * device you must tell the NTP refclock driver which
                     71:  * firmware you NeoClock4X device uses.
                     72:  *
                     73:  * If you want to enable this feature change the "#if 0"
                     74:  * line to "#if 1" and make sure that the defined firmware
                     75:  * matches the firmware off your NeoClock4X receiver!
                     76:  *
                     77:  */
                     78: 
                     79: #if 0
                     80: #define NEOCLOCK4X_FIRMWARE                NEOCLOCK4X_FIRMWARE_VERSION_A
                     81: #endif
                     82: 
                     83: /* at this time only firmware version A is known */
                     84: #define NEOCLOCK4X_FIRMWARE_VERSION_A      'A'
                     85: 
                     86: #define NEOCLOCK4X_TIMECODELEN 37
                     87: 
                     88: #define NEOCLOCK4X_OFFSET_SERIAL            3
                     89: #define NEOCLOCK4X_OFFSET_RADIOSIGNAL       9
                     90: #define NEOCLOCK4X_OFFSET_DAY              12
                     91: #define NEOCLOCK4X_OFFSET_MONTH            14
                     92: #define NEOCLOCK4X_OFFSET_YEAR             16
                     93: #define NEOCLOCK4X_OFFSET_HOUR             18
                     94: #define NEOCLOCK4X_OFFSET_MINUTE           20
                     95: #define NEOCLOCK4X_OFFSET_SECOND           22
                     96: #define NEOCLOCK4X_OFFSET_HSEC             24
                     97: #define NEOCLOCK4X_OFFSET_DOW              26
                     98: #define NEOCLOCK4X_OFFSET_TIMESOURCE       28
                     99: #define NEOCLOCK4X_OFFSET_DSTSTATUS        29
                    100: #define NEOCLOCK4X_OFFSET_QUARZSTATUS      30
                    101: #define NEOCLOCK4X_OFFSET_ANTENNA1         31
                    102: #define NEOCLOCK4X_OFFSET_ANTENNA2         33
                    103: #define NEOCLOCK4X_OFFSET_CRC              35
                    104: 
                    105: #define NEOCLOCK4X_DRIVER_VERSION          "1.16 (2009-12-04)"
                    106: 
                    107: #define NSEC_TO_MILLI                      1000000
                    108: 
                    109: struct neoclock4x_unit {
                    110:   l_fp laststamp;      /* last receive timestamp */
                    111:   short        unit;           /* NTP refclock unit number */
                    112:   u_long polled;       /* flag to detect noreplies */
                    113:   char leap_status;    /* leap second flag */
                    114:   int  recvnow;
                    115: 
                    116:   char  firmware[80];
                    117:   char  firmwaretag;
                    118:   char  serial[7];
                    119:   char  radiosignal[4];
                    120:   char  timesource;
                    121:   char  dststatus;
                    122:   char  quarzstatus;
                    123:   int   antenna1;
                    124:   int   antenna2;
                    125:   int   utc_year;
                    126:   int   utc_month;
                    127:   int   utc_day;
                    128:   int   utc_hour;
                    129:   int   utc_minute;
                    130:   int   utc_second;
                    131:   int   utc_msec;
                    132: };
                    133: 
                    134: static int     neoclock4x_start        (int, struct peer *);
                    135: static void    neoclock4x_shutdown     (int, struct peer *);
                    136: static void    neoclock4x_receive      (struct recvbuf *);
                    137: static void    neoclock4x_poll         (int, struct peer *);
                    138: static void    neoclock4x_control      (int, struct refclockstat *, struct refclockstat *, struct peer *);
                    139: 
                    140: static int      neol_atoi_len           (const char str[], int *, int);
                    141: static int      neol_hexatoi_len        (const char str[], int *, int);
                    142: static void     neol_jdn_to_ymd         (unsigned long, int *, int *, int *);
                    143: static void     neol_localtime          (unsigned long, int* , int*, int*, int*, int*, int*);
                    144: static unsigned long neol_mktime        (int, int, int, int, int, int);
                    145: #if !defined(NEOCLOCK4X_FIRMWARE)
                    146: static int      neol_query_firmware     (int, int, char *, int);
                    147: static int      neol_check_firmware     (int, const char*, char *);
                    148: #endif
                    149: 
                    150: struct refclock refclock_neoclock4x = {
                    151:   neoclock4x_start,    /* start up driver */
                    152:   neoclock4x_shutdown, /* shut down driver */
                    153:   neoclock4x_poll,     /* transmit poll message */
                    154:   neoclock4x_control,
                    155:   noentry,             /* initialize driver (not used) */
                    156:   noentry,             /* not used */
                    157:   NOFLAGS                      /* not used */
                    158: };
                    159: 
                    160: static int
                    161: neoclock4x_start(int unit,
                    162:                 struct peer *peer)
                    163: {
                    164:   struct neoclock4x_unit *up;
                    165:   struct refclockproc *pp;
                    166:   int fd;
                    167:   char dev[20];
                    168:   int sl232;
                    169: #if defined(HAVE_TERMIOS)
                    170:   struct termios termsettings;
                    171: #endif
                    172: #if !defined(NEOCLOCK4X_FIRMWARE)
                    173:   int tries;
                    174: #endif
                    175: 
                    176:   (void) snprintf(dev, sizeof(dev)-1, "/dev/neoclock4x-%d", unit);
                    177: 
                    178:   /* LDISC_STD, LDISC_RAW
                    179:    * Open serial port. Use CLK line discipline, if available.
                    180:    */
                    181:   fd = refclock_open(dev, B2400, LDISC_STD);
                    182:   if(fd <= 0)
                    183:     {
                    184:       return (0);
                    185:     }
                    186: 
                    187: #if defined(HAVE_TERMIOS)
                    188: 
                    189: #if 1
                    190:   if(tcgetattr(fd, &termsettings) < 0)
                    191:     {
                    192:       msyslog(LOG_CRIT, "NeoClock4X(%d): (tcgetattr) can't query serial port settings: %m", unit);
                    193:       (void) close(fd);
                    194:       return (0);
                    195:     }
                    196: 
                    197:   /* 2400 Baud 8N2 */
                    198:   termsettings.c_iflag = IGNBRK | IGNPAR | ICRNL;
                    199:   termsettings.c_oflag = 0;
                    200:   termsettings.c_cflag = CS8 | CSTOPB | CLOCAL | CREAD;
                    201:   (void)cfsetispeed(&termsettings, (u_int)B2400);
                    202:   (void)cfsetospeed(&termsettings, (u_int)B2400);
                    203: 
                    204:   if(tcsetattr(fd, TCSANOW, &termsettings) < 0)
                    205:     {
                    206:       msyslog(LOG_CRIT, "NeoClock4X(%d): (tcsetattr) can't set serial port 2400 8N2: %m", unit);
                    207:       (void) close(fd);
                    208:       return (0);
                    209:     }
                    210: 
                    211: #else
                    212:   if(tcgetattr(fd, &termsettings) < 0)
                    213:     {
                    214:       msyslog(LOG_CRIT, "NeoClock4X(%d): (tcgetattr) can't query serial port settings: %m", unit);
                    215:       (void) close(fd);
                    216:       return (0);
                    217:     }
                    218: 
                    219:   /* 2400 Baud 8N2 */
                    220:   termsettings.c_cflag &= ~PARENB;
                    221:   termsettings.c_cflag |= CSTOPB;
                    222:   termsettings.c_cflag &= ~CSIZE;
                    223:   termsettings.c_cflag |= CS8;
                    224: 
                    225:   if(tcsetattr(fd, TCSANOW, &termsettings) < 0)
                    226:     {
                    227:       msyslog(LOG_CRIT, "NeoClock4X(%d): (tcsetattr) can't set serial port 2400 8N2: %m", unit);
                    228:       (void) close(fd);
                    229:       return (0);
                    230:     }
                    231: #endif
                    232: 
                    233: #elif defined(HAVE_SYSV_TTYS)
                    234:   if(ioctl(fd, TCGETA, &termsettings) < 0)
                    235:     {
                    236:       msyslog(LOG_CRIT, "NeoClock4X(%d): (TCGETA) can't query serial port settings: %m", unit);
                    237:       (void) close(fd);
                    238:       return (0);
                    239:     }
                    240: 
                    241:   /* 2400 Baud 8N2 */
                    242:   termsettings.c_cflag &= ~PARENB;
                    243:   termsettings.c_cflag |= CSTOPB;
                    244:   termsettings.c_cflag &= ~CSIZE;
                    245:   termsettings.c_cflag |= CS8;
                    246: 
                    247:   if(ioctl(fd, TCSETA, &termsettings) < 0)
                    248:     {
                    249:       msyslog(LOG_CRIT, "NeoClock4X(%d): (TSGETA) can't set serial port 2400 8N2: %m", unit);
                    250:       (void) close(fd);
                    251:       return (0);
                    252:     }
                    253: #else
                    254:   msyslog(LOG_EMERG, "NeoClock4X(%d): don't know how to set port to 2400 8N2 with this OS!", unit);
                    255:   (void) close(fd);
                    256:   return (0);
                    257: #endif
                    258: 
                    259: #if defined(TIOCMSET) && (defined(TIOCM_RTS) || defined(CIOCM_RTS))
                    260:   /* turn on RTS, and DTR for power supply */
                    261:   /* NeoClock4x is powered from serial line */
                    262:   if(ioctl(fd, TIOCMGET, (caddr_t)&sl232) == -1)
                    263:     {
                    264:       msyslog(LOG_CRIT, "NeoClock4X(%d): can't query RTS/DTR state: %m", unit);
                    265:       (void) close(fd);
                    266:       return (0);
                    267:     }
                    268: #ifdef TIOCM_RTS
                    269:   sl232 = sl232 | TIOCM_DTR | TIOCM_RTS;       /* turn on RTS, and DTR for power supply */
                    270: #else
                    271:   sl232 = sl232 | CIOCM_DTR | CIOCM_RTS;       /* turn on RTS, and DTR for power supply */
                    272: #endif
                    273:   if(ioctl(fd, TIOCMSET, (caddr_t)&sl232) == -1)
                    274:     {
                    275:       msyslog(LOG_CRIT, "NeoClock4X(%d): can't set RTS/DTR to power neoclock4x: %m", unit);
                    276:       (void) close(fd);
                    277:       return (0);
                    278:     }
                    279: #else
                    280:   msyslog(LOG_EMERG, "NeoClock4X(%d): don't know how to set DTR/RTS to power NeoClock4X with this OS!",
                    281:          unit);
                    282:   (void) close(fd);
                    283:   return (0);
                    284: #endif
                    285: 
                    286:   up = (struct neoclock4x_unit *) emalloc(sizeof(struct neoclock4x_unit));
                    287:   if(!(up))
                    288:     {
                    289:       msyslog(LOG_ERR, "NeoClock4X(%d): can't allocate memory for: %m",unit);
                    290:       (void) close(fd);
                    291:       return (0);
                    292:     }
                    293: 
                    294:   memset((char *)up, 0, sizeof(struct neoclock4x_unit));
                    295:   pp = peer->procptr;
                    296:   pp->clockdesc = "NeoClock4X";
                    297:   pp->unitptr = (caddr_t)up;
                    298:   pp->io.clock_recv = neoclock4x_receive;
                    299:   pp->io.srcclock = (caddr_t)peer;
                    300:   pp->io.datalen = 0;
                    301:   pp->io.fd = fd;
                    302:   /*
                    303:    * no fudge time is given by user!
                    304:    * use 169.583333 ms to compensate the serial line delay
                    305:    * formula is:
                    306:    * 2400 Baud / 11 bit = 218.18 charaters per second
                    307:    *  (NeoClock4X timecode len)
                    308:    */
                    309:   pp->fudgetime1 = (NEOCLOCK4X_TIMECODELEN * 11) / 2400.0;
                    310: 
                    311:   /*
                    312:    * Initialize miscellaneous variables
                    313:    */
                    314:   peer->precision = -10;
                    315:   peer->burst = NSTAGE;
                    316:   memcpy((char *)&pp->refid, "neol", 4);
                    317: 
                    318:   up->leap_status = 0;
                    319:   up->unit = unit;
                    320:   strcpy(up->firmware, "?");
                    321:   up->firmwaretag = '?';
                    322:   strcpy(up->serial, "?");
                    323:   strcpy(up->radiosignal, "?");
                    324:   up->timesource  = '?';
                    325:   up->dststatus   = '?';
                    326:   up->quarzstatus = '?';
                    327:   up->antenna1    = -1;
                    328:   up->antenna2    = -1;
                    329:   up->utc_year    = 0;
                    330:   up->utc_month   = 0;
                    331:   up->utc_day     = 0;
                    332:   up->utc_hour    = 0;
                    333:   up->utc_minute  = 0;
                    334:   up->utc_second  = 0;
                    335:   up->utc_msec    = 0;
                    336: 
                    337: #if defined(NEOCLOCK4X_FIRMWARE)
                    338: #if NEOCLOCK4X_FIRMWARE == NEOCLOCK4X_FIRMWARE_VERSION_A
                    339:   strcpy(up->firmware, "(c) 2002 NEOL S.A. FRANCE / L0.01 NDF:A:* (compile time)");
                    340:   up->firmwaretag = 'A';
                    341: #else
                    342:   msyslog(LOG_EMERG, "NeoClock4X(%d): unknown firmware defined at compile time for NeoClock4X",
                    343:          unit);
                    344:   (void) close(fd);
                    345:   pp->io.fd = -1;
                    346:   free(pp->unitptr);
                    347:   pp->unitptr = NULL;
                    348:   return (0);
                    349: #endif
                    350: #else
                    351:   for(tries=0; tries < 5; tries++)
                    352:     {
                    353:       NLOG(NLOG_CLOCKINFO)
                    354:        msyslog(LOG_INFO, "NeoClock4X(%d): checking NeoClock4X firmware version (%d/5)", unit, tries);
                    355:       /* wait 3 seconds for receiver to power up */
                    356:       sleep(3);
                    357:       if(neol_query_firmware(pp->io.fd, up->unit, up->firmware, sizeof(up->firmware)))
                    358:        {
                    359:          break;
                    360:        }
                    361:     }
                    362: 
                    363:   /* can I handle this firmware version? */
                    364:   if(!neol_check_firmware(up->unit, up->firmware, &up->firmwaretag))
                    365:     {
                    366:       (void) close(fd);
                    367:       pp->io.fd = -1;
                    368:       free(pp->unitptr);
                    369:       pp->unitptr = NULL;
                    370:       return (0);
                    371:     }
                    372: #endif
                    373: 
                    374:   if(!io_addclock(&pp->io))
                    375:     {
                    376:       msyslog(LOG_ERR, "NeoClock4X(%d): error add peer to ntpd: %m", unit);
                    377:       (void) close(fd);
                    378:       pp->io.fd = -1;
                    379:       free(pp->unitptr);
                    380:       pp->unitptr = NULL;
                    381:       return (0);
                    382:     }
                    383: 
                    384:   NLOG(NLOG_CLOCKINFO)
                    385:     msyslog(LOG_INFO, "NeoClock4X(%d): receiver setup successful done", unit);
                    386: 
                    387:   return (1);
                    388: }
                    389: 
                    390: static void
                    391: neoclock4x_shutdown(int unit,
                    392:                   struct peer *peer)
                    393: {
                    394:   struct neoclock4x_unit *up;
                    395:   struct refclockproc *pp;
                    396:   int sl232;
                    397: 
                    398:   if(NULL != peer)
                    399:     {
                    400:       pp = peer->procptr;
                    401:       if(pp != NULL)
                    402:         {
                    403:           up = (struct neoclock4x_unit *)pp->unitptr;
                    404:           if(up != NULL)
                    405:             {
                    406:               if(-1 !=  pp->io.fd)
                    407:                 {
                    408: #if defined(TIOCMSET) && (defined(TIOCM_RTS) || defined(CIOCM_RTS))
                    409:                   /* turn on RTS, and DTR for power supply */
                    410:                   /* NeoClock4x is powered from serial line */
                    411:                   if(ioctl(pp->io.fd, TIOCMGET, (caddr_t)&sl232) == -1)
                    412:                     {
                    413:                       msyslog(LOG_CRIT, "NeoClock4X(%d): can't query RTS/DTR state: %m",
                    414:                               unit);
                    415:                     }
                    416: #ifdef TIOCM_RTS
                    417:                   /* turn on RTS, and DTR for power supply */
                    418:                   sl232 &= ~(TIOCM_DTR | TIOCM_RTS);
                    419: #else
                    420:                   /* turn on RTS, and DTR for power supply */
                    421:                   sl232 &= ~(CIOCM_DTR | CIOCM_RTS);
                    422: #endif
                    423:                   if(ioctl(pp->io.fd, TIOCMSET, (caddr_t)&sl232) == -1)
                    424:                     {
                    425:                       msyslog(LOG_CRIT, "NeoClock4X(%d): can't set RTS/DTR to power neoclock4x: %m",
                    426:                               unit);
                    427:                     }
                    428: #endif
                    429:                   io_closeclock(&pp->io);
                    430:                 }
                    431:               free(up);
                    432:               pp->unitptr = NULL;
                    433:             }
                    434:         }
                    435:     }
                    436: 
                    437:   msyslog(LOG_ERR, "NeoClock4X(%d): shutdown", unit);
                    438: 
                    439:   NLOG(NLOG_CLOCKINFO)
                    440:     msyslog(LOG_INFO, "NeoClock4X(%d): receiver shutdown done", unit);
                    441: }
                    442: 
                    443: static void
                    444: neoclock4x_receive(struct recvbuf *rbufp)
                    445: {
                    446:   struct neoclock4x_unit *up;
                    447:   struct refclockproc *pp;
                    448:   struct peer *peer;
                    449:   unsigned long calc_utc;
                    450:   int day;
                    451:   int month;   /* ddd conversion */
                    452:   int c;
                    453:   int dsec;
                    454:   unsigned char calc_chksum;
                    455:   int recv_chksum;
                    456: 
                    457:   peer = (struct peer *)rbufp->recv_srcclock;
                    458:   pp = peer->procptr;
                    459:   up = (struct neoclock4x_unit *)pp->unitptr;
                    460: 
                    461:   /* wait till poll interval is reached */
                    462:   if(0 == up->recvnow)
                    463:     return;
                    464: 
                    465:   /* reset poll interval flag */
                    466:   up->recvnow = 0;
                    467: 
                    468:   /* read last received timecode */
                    469:   pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &pp->lastrec);
                    470:   pp->leap = LEAP_NOWARNING;
                    471: 
                    472:   if(NEOCLOCK4X_TIMECODELEN != pp->lencode)
                    473:     {
                    474:       NLOG(NLOG_CLOCKEVENT)
                    475:        msyslog(LOG_WARNING, "NeoClock4X(%d): received data has invalid length, expected %d bytes, received %d bytes: %s",
                    476:                up->unit, NEOCLOCK4X_TIMECODELEN, pp->lencode, pp->a_lastcode);
                    477:       refclock_report(peer, CEVNT_BADREPLY);
                    478:       return;
                    479:     }
                    480: 
                    481:   neol_hexatoi_len(&pp->a_lastcode[NEOCLOCK4X_OFFSET_CRC], &recv_chksum, 2);
                    482: 
                    483:   /* calculate checksum */
                    484:   calc_chksum = 0;
                    485:   for(c=0; c < NEOCLOCK4X_OFFSET_CRC; c++)
                    486:     {
                    487:       calc_chksum += pp->a_lastcode[c];
                    488:     }
                    489:   if(recv_chksum != calc_chksum)
                    490:     {
                    491:       NLOG(NLOG_CLOCKEVENT)
                    492:        msyslog(LOG_WARNING, "NeoClock4X(%d): received data has invalid chksum: %s",
                    493:                up->unit, pp->a_lastcode);
                    494:       refclock_report(peer, CEVNT_BADREPLY);
                    495:       return;
                    496:     }
                    497: 
                    498:   /* Allow synchronization even is quartz clock is
                    499:    * never initialized.
                    500:    * WARNING: This is dangerous!
                    501:    */
                    502:   up->quarzstatus = pp->a_lastcode[NEOCLOCK4X_OFFSET_QUARZSTATUS];
                    503:   if(0==(pp->sloppyclockflag & CLK_FLAG2))
                    504:     {
                    505:       if('I' != up->quarzstatus)
                    506:        {
                    507:          NLOG(NLOG_CLOCKEVENT)
                    508:            msyslog(LOG_NOTICE, "NeoClock4X(%d): quartz clock is not initialized: %s",
                    509:                    up->unit, pp->a_lastcode);
                    510:          pp->leap = LEAP_NOTINSYNC;
                    511:          refclock_report(peer, CEVNT_BADDATE);
                    512:          return;
                    513:        }
                    514:     }
                    515:   if('I' != up->quarzstatus)
                    516:     {
                    517:       NLOG(NLOG_CLOCKEVENT)
                    518:        msyslog(LOG_NOTICE, "NeoClock4X(%d): using uninitialized quartz clock for time synchronization: %s",
                    519:                up->unit, pp->a_lastcode);
                    520:     }
                    521: 
                    522:   /*
                    523:    * If NeoClock4X is not synchronized to a radio clock
                    524:    * check if we're allowed to synchronize with the quartz
                    525:    * clock.
                    526:    */
                    527:   up->timesource = pp->a_lastcode[NEOCLOCK4X_OFFSET_TIMESOURCE];
                    528:   if(0==(pp->sloppyclockflag & CLK_FLAG2))
                    529:     {
                    530:       if('A' != up->timesource)
                    531:        {
                    532:          /* not allowed to sync with quartz clock */
                    533:          if(0==(pp->sloppyclockflag & CLK_FLAG1))
                    534:            {
                    535:              refclock_report(peer, CEVNT_BADTIME);
                    536:              pp->leap = LEAP_NOTINSYNC;
                    537:              return;
                    538:            }
                    539:        }
                    540:     }
                    541: 
                    542:   /* this should only used when first install is done */
                    543:   if(pp->sloppyclockflag & CLK_FLAG4)
                    544:     {
                    545:       msyslog(LOG_DEBUG, "NeoClock4X(%d): received data: %s",
                    546:              up->unit, pp->a_lastcode);
                    547:     }
                    548: 
                    549:   /* 123456789012345678901234567890123456789012345 */
                    550:   /* S/N123456DCF1004021010001202ASX1213CR\r\n */
                    551: 
                    552:   neol_atoi_len(&pp->a_lastcode[NEOCLOCK4X_OFFSET_YEAR], &pp->year, 2);
                    553:   neol_atoi_len(&pp->a_lastcode[NEOCLOCK4X_OFFSET_MONTH], &month, 2);
                    554:   neol_atoi_len(&pp->a_lastcode[NEOCLOCK4X_OFFSET_DAY], &day, 2);
                    555:   neol_atoi_len(&pp->a_lastcode[NEOCLOCK4X_OFFSET_HOUR], &pp->hour, 2);
                    556:   neol_atoi_len(&pp->a_lastcode[NEOCLOCK4X_OFFSET_MINUTE], &pp->minute, 2);
                    557:   neol_atoi_len(&pp->a_lastcode[NEOCLOCK4X_OFFSET_SECOND], &pp->second, 2);
                    558:   neol_atoi_len(&pp->a_lastcode[NEOCLOCK4X_OFFSET_HSEC], &dsec, 2);
                    559: #if defined(NTP_PRE_420)
                    560:   pp->msec = dsec * 10; /* convert 1/100s from neoclock to real miliseconds */
                    561: #else
                    562:   pp->nsec = dsec * 10 * NSEC_TO_MILLI; /* convert 1/100s from neoclock to nanoseconds */
                    563: #endif
                    564: 
                    565:   memcpy(up->radiosignal, &pp->a_lastcode[NEOCLOCK4X_OFFSET_RADIOSIGNAL], 3);
                    566:   up->radiosignal[3] = 0;
                    567:   memcpy(up->serial, &pp->a_lastcode[NEOCLOCK4X_OFFSET_SERIAL], 6);
                    568:   up->serial[6] = 0;
                    569:   up->dststatus = pp->a_lastcode[NEOCLOCK4X_OFFSET_DSTSTATUS];
                    570:   neol_hexatoi_len(&pp->a_lastcode[NEOCLOCK4X_OFFSET_ANTENNA1], &up->antenna1, 2);
                    571:   neol_hexatoi_len(&pp->a_lastcode[NEOCLOCK4X_OFFSET_ANTENNA2], &up->antenna2, 2);
                    572: 
                    573:   /*
                    574:     Validate received values at least enough to prevent internal
                    575:     array-bounds problems, etc.
                    576:   */
                    577:   if((pp->hour < 0) || (pp->hour > 23) ||
                    578:      (pp->minute < 0) || (pp->minute > 59) ||
                    579:      (pp->second < 0) || (pp->second > 60) /*Allow for leap seconds.*/ ||
                    580:      (day < 1) || (day > 31) ||
                    581:      (month < 1) || (month > 12) ||
                    582:      (pp->year < 0) || (pp->year > 99)) {
                    583:     /* Data out of range. */
                    584:     NLOG(NLOG_CLOCKEVENT)
                    585:       msyslog(LOG_WARNING, "NeoClock4X(%d): date/time out of range: %s",
                    586:              up->unit, pp->a_lastcode);
                    587:     refclock_report(peer, CEVNT_BADDATE);
                    588:     return;
                    589:   }
                    590: 
                    591:   /* Year-2000 check not needed anymore. Same problem
                    592:    * will arise at 2099 but what should we do...?
                    593:    *
                    594:    * wrap 2-digit date into 4-digit
                    595:    *
                    596:    * if(pp->year < YEAR_PIVOT)
                    597:    * {
                    598:    *   pp->year += 100;
                    599:    * }
                    600:   */
                    601:   pp->year += 2000;
                    602: 
                    603:   /* adjust NeoClock4X local time to UTC */
                    604:   calc_utc = neol_mktime(pp->year, month, day, pp->hour, pp->minute, pp->second);
                    605:   calc_utc -= 3600;
                    606:   /* adjust NeoClock4X daylight saving time if needed */
                    607:   if('S' == up->dststatus)
                    608:     calc_utc -= 3600;
                    609:   neol_localtime(calc_utc, &pp->year, &month, &day, &pp->hour, &pp->minute, &pp->second);
                    610: 
                    611:   /*
                    612:     some preparations
                    613:   */
                    614:   pp->day = ymd2yd(pp->year, month, day);
                    615:   pp->leap = 0;
                    616: 
                    617:   if(pp->sloppyclockflag & CLK_FLAG4)
                    618:     {
                    619:       msyslog(LOG_DEBUG, "NeoClock4X(%d): calculated UTC date/time: %04d-%02d-%02d %02d:%02d:%02d.%03ld",
                    620:              up->unit,
                    621:              pp->year, month, day,
                    622:              pp->hour, pp->minute, pp->second,
                    623: #if defined(NTP_PRE_420)
                    624:               pp->msec
                    625: #else
                    626:               pp->nsec/NSEC_TO_MILLI
                    627: #endif
                    628:               );
                    629:     }
                    630: 
                    631:   up->utc_year   = pp->year;
                    632:   up->utc_month  = month;
                    633:   up->utc_day    = day;
                    634:   up->utc_hour   = pp->hour;
                    635:   up->utc_minute = pp->minute;
                    636:   up->utc_second = pp->second;
                    637: #if defined(NTP_PRE_420)
                    638:   up->utc_msec   = pp->msec;
                    639: #else
                    640:   up->utc_msec   = pp->nsec/NSEC_TO_MILLI;
                    641: #endif
                    642: 
                    643:   if(!refclock_process(pp))
                    644:     {
                    645:       NLOG(NLOG_CLOCKEVENT)
                    646:        msyslog(LOG_WARNING, "NeoClock4X(%d): refclock_process failed!", up->unit);
                    647:       refclock_report(peer, CEVNT_FAULT);
                    648:       return;
                    649:     }
                    650:   refclock_receive(peer);
                    651: 
                    652:   /* report good status */
                    653:   refclock_report(peer, CEVNT_NOMINAL);
                    654: 
                    655:   record_clock_stats(&peer->srcadr, pp->a_lastcode);
                    656: }
                    657: 
                    658: static void
                    659: neoclock4x_poll(int unit,
                    660:                struct peer *peer)
                    661: {
                    662:   struct neoclock4x_unit *up;
                    663:   struct refclockproc *pp;
                    664: 
                    665:   pp = peer->procptr;
                    666:   up = (struct neoclock4x_unit *)pp->unitptr;
                    667: 
                    668:   pp->polls++;
                    669:   up->recvnow = 1;
                    670: }
                    671: 
                    672: static void
                    673: neoclock4x_control(int unit,
                    674:                   struct refclockstat *in,
                    675:                   struct refclockstat *out,
                    676:                   struct peer *peer)
                    677: {
                    678:   struct neoclock4x_unit *up;
                    679:   struct refclockproc *pp;
                    680: 
                    681:   if(NULL == peer)
                    682:     {
                    683:       msyslog(LOG_ERR, "NeoClock4X(%d): control: unit invalid/inactive", unit);
                    684:       return;
                    685:     }
                    686: 
                    687:   pp = peer->procptr;
                    688:   if(NULL == pp)
                    689:     {
                    690:       msyslog(LOG_ERR, "NeoClock4X(%d): control: unit invalid/inactive", unit);
                    691:       return;
                    692:     }
                    693: 
                    694:   up = (struct neoclock4x_unit *)pp->unitptr;
                    695:   if(NULL == up)
                    696:     {
                    697:       msyslog(LOG_ERR, "NeoClock4X(%d): control: unit invalid/inactive", unit);
                    698:       return;
                    699:     }
                    700: 
                    701:   if(NULL != in)
                    702:     {
                    703:       /* check to see if a user supplied time offset is given */
                    704:       if(in->haveflags & CLK_HAVETIME1)
                    705:        {
                    706:          pp->fudgetime1 = in->fudgetime1;
                    707:          NLOG(NLOG_CLOCKINFO)
                    708:            msyslog(LOG_NOTICE, "NeoClock4X(%d): using fudgetime1 with %0.5fs from ntp.conf.",
                    709:                    unit, pp->fudgetime1);
                    710:        }
                    711: 
                    712:       /* notify */
                    713:       if(pp->sloppyclockflag & CLK_FLAG1)
                    714:        {
                    715:          NLOG(NLOG_CLOCKINFO)
                    716:            msyslog(LOG_NOTICE, "NeoClock4X(%d): quartz clock is used to synchronize time if radio clock has no reception.", unit);
                    717:        }
                    718:       else
                    719:        {
                    720:          NLOG(NLOG_CLOCKINFO)
                    721:            msyslog(LOG_NOTICE, "NeoClock4X(%d): time is only adjusted with radio signal reception.", unit);
                    722:        }
                    723:     }
                    724: 
                    725:   if(NULL != out)
                    726:     {
                    727:       char *tt;
                    728:       char tmpbuf[80];
                    729: 
                    730:       out->kv_list = (struct ctl_var *)0;
                    731:       out->type    = REFCLK_NEOCLOCK4X;
                    732: 
                    733:       snprintf(tmpbuf, sizeof(tmpbuf)-1,
                    734:               "%04d-%02d-%02d %02d:%02d:%02d.%03d",
                    735:               up->utc_year, up->utc_month, up->utc_day,
                    736:               up->utc_hour, up->utc_minute, up->utc_second,
                    737:               up->utc_msec);
                    738:       tt = add_var(&out->kv_list, sizeof(tmpbuf)-1, RO|DEF);
                    739:       snprintf(tt, sizeof(tmpbuf)-1, "calc_utc=\"%s\"", tmpbuf);
                    740: 
                    741:       tt = add_var(&out->kv_list, 40, RO|DEF);
                    742:       snprintf(tt, 39, "radiosignal=\"%s\"", up->radiosignal);
                    743:       tt = add_var(&out->kv_list, 40, RO|DEF);
                    744:       snprintf(tt, 39, "antenna1=\"%d\"", up->antenna1);
                    745:       tt = add_var(&out->kv_list, 40, RO|DEF);
                    746:       snprintf(tt, 39, "antenna2=\"%d\"", up->antenna2);
                    747:       tt = add_var(&out->kv_list, 40, RO|DEF);
                    748:       if('A' == up->timesource)
                    749:        snprintf(tt, 39, "timesource=\"radio\"");
                    750:       else if('C' == up->timesource)
                    751:        snprintf(tt, 39, "timesource=\"quartz\"");
                    752:       else
                    753:        snprintf(tt, 39, "timesource=\"unknown\"");
                    754:       tt = add_var(&out->kv_list, 40, RO|DEF);
                    755:       if('I' == up->quarzstatus)
                    756:        snprintf(tt, 39, "quartzstatus=\"synchronized\"");
                    757:       else if('X' == up->quarzstatus)
                    758:         snprintf(tt, 39, "quartzstatus=\"not synchronized\"");
                    759:       else
                    760:        snprintf(tt, 39, "quartzstatus=\"unknown\"");
                    761:       tt = add_var(&out->kv_list, 40, RO|DEF);
                    762:       if('S' == up->dststatus)
                    763:         snprintf(tt, 39, "dststatus=\"summer\"");
                    764:       else if('W' == up->dststatus)
                    765:         snprintf(tt, 39, "dststatus=\"winter\"");
                    766:       else
                    767:         snprintf(tt, 39, "dststatus=\"unknown\"");
                    768:       tt = add_var(&out->kv_list, 80, RO|DEF);
                    769:       snprintf(tt, 79, "firmware=\"%s\"", up->firmware);
                    770:       tt = add_var(&out->kv_list, 40, RO|DEF);
                    771:       snprintf(tt, 39, "firmwaretag=\"%c\"", up->firmwaretag);
                    772:       tt = add_var(&out->kv_list, 80, RO|DEF);
                    773:       snprintf(tt, 79, "driver version=\"%s\"", NEOCLOCK4X_DRIVER_VERSION);
                    774:       tt = add_var(&out->kv_list, 80, RO|DEF);
                    775:       snprintf(tt, 79, "serialnumber=\"%s\"", up->serial);
                    776:     }
                    777: }
                    778: 
                    779: static int
                    780: neol_hexatoi_len(const char str[],
                    781:                 int *result,
                    782:                 int maxlen)
                    783: {
                    784:   int hexdigit;
                    785:   int i;
                    786:   int n = 0;
                    787: 
                    788:   for(i=0; isxdigit((int)str[i]) && i < maxlen; i++)
                    789:     {
                    790:       hexdigit = isdigit((int)str[i]) ? toupper(str[i]) - '0' : toupper(str[i]) - 'A' + 10;
                    791:       n = 16 * n + hexdigit;
                    792:     }
                    793:   *result = n;
                    794:   return (n);
                    795: }
                    796: 
                    797: static int
                    798: neol_atoi_len(const char str[],
                    799:                  int *result,
                    800:                  int maxlen)
                    801: {
                    802:   int digit;
                    803:   int i;
                    804:   int n = 0;
                    805: 
                    806:   for(i=0; isdigit((int)str[i]) && i < maxlen; i++)
                    807:     {
                    808:       digit = str[i] - '0';
                    809:       n = 10 * n + digit;
                    810:     }
                    811:   *result = n;
                    812:   return (n);
                    813: }
                    814: 
                    815: /* Converts Gregorian date to seconds since 1970-01-01 00:00:00.
                    816:  * Assumes input in normal date format, i.e. 1980-12-31 23:59:59
                    817:  * => year=1980, mon=12, day=31, hour=23, min=59, sec=59.
                    818:  *
                    819:  * [For the Julian calendar (which was used in Russia before 1917,
                    820:  * Britain & colonies before 1752, anywhere else before 1582,
                    821:  * and is still in use by some communities) leave out the
                    822:  * -year/100+year/400 terms, and add 10.]
                    823:  *
                    824:  * This algorithm was first published by Gauss (I think).
                    825:  *
                    826:  * WARNING: this function will overflow on 2106-02-07 06:28:16 on
                    827:  * machines were long is 32-bit! (However, as time_t is signed, we
                    828:  * will already get problems at other places on 2038-01-19 03:14:08)
                    829:  */
                    830: static unsigned long
                    831: neol_mktime(int year,
                    832:            int mon,
                    833:            int day,
                    834:            int hour,
                    835:            int min,
                    836:            int sec)
                    837: {
                    838:   if (0 >= (int) (mon -= 2)) {    /* 1..12 . 11,12,1..10 */
                    839:     mon += 12;      /* Puts Feb last since it has leap day */
                    840:     year -= 1;
                    841:   }
                    842:   return (((
                    843:             (unsigned long)(year/4 - year/100 + year/400 + 367*mon/12 + day) +
                    844:             year*365 - 719499
                    845:             )*24 + hour /* now have hours */
                    846:            )*60 + min /* now have minutes */
                    847:           )*60 + sec; /* finally seconds */
                    848: }
                    849: 
                    850: static void
                    851: neol_localtime(unsigned long utc,
                    852:               int* year,
                    853:               int* month,
                    854:               int* day,
                    855:               int* hour,
                    856:               int* min,
                    857:               int* sec)
                    858: {
                    859:   *sec = utc % 60;
                    860:   utc /= 60;
                    861:   *min = utc % 60;
                    862:   utc /= 60;
                    863:   *hour = utc % 24;
                    864:   utc /= 24;
                    865: 
                    866:   /*             JDN Date 1/1/1970 */
                    867:   neol_jdn_to_ymd(utc + 2440588L, year, month, day);
                    868: }
                    869: 
                    870: static void
                    871: neol_jdn_to_ymd(unsigned long jdn,
                    872:                int *yy,
                    873:                int *mm,
                    874:                int *dd)
                    875: {
                    876:   unsigned long x, z, m, d, y;
                    877:   unsigned long daysPer400Years = 146097UL;
                    878:   unsigned long fudgedDaysPer4000Years = 1460970UL + 31UL;
                    879: 
                    880:   x = jdn + 68569UL;
                    881:   z = 4UL * x / daysPer400Years;
                    882:   x = x - (daysPer400Years * z + 3UL) / 4UL;
                    883:   y = 4000UL * (x + 1) / fudgedDaysPer4000Years;
                    884:   x = x - 1461UL * y / 4UL + 31UL;
                    885:   m = 80UL * x / 2447UL;
                    886:   d = x - 2447UL * m / 80UL;
                    887:   x = m / 11UL;
                    888:   m = m + 2UL - 12UL * x;
                    889:   y = 100UL * (z - 49UL) + y + x;
                    890: 
                    891:   *yy = (int)y;
                    892:   *mm = (int)m;
                    893:   *dd = (int)d;
                    894: }
                    895: 
                    896: #if !defined(NEOCLOCK4X_FIRMWARE)
                    897: static int
                    898: neol_query_firmware(int fd,
                    899:                    int unit,
                    900:                    char *firmware,
                    901:                    int maxlen)
                    902: {
                    903:   char tmpbuf[256];
                    904:   int len;
                    905:   int lastsearch;
                    906:   unsigned char c;
                    907:   int last_c_was_crlf;
                    908:   int last_crlf_conv_len;
                    909:   int init;
                    910:   int read_errors;
                    911:   int flag = 0;
                    912:   int chars_read;
                    913: 
                    914:   /* wait a little bit */
                    915:   sleep(1);
                    916:   if(-1 != write(fd, "V", 1))
                    917:     {
                    918:       /* wait a little bit */
                    919:       sleep(1);
                    920:       memset(tmpbuf, 0x00, sizeof(tmpbuf));
                    921: 
                    922:       len = 0;
                    923:       lastsearch = 0;
                    924:       last_c_was_crlf = 0;
                    925:       last_crlf_conv_len = 0;
                    926:       init = 1;
                    927:       read_errors = 0;
                    928:       chars_read = 0;
                    929:       for(;;)
                    930:        {
                    931:          if(read_errors > 5)
                    932:            {
                    933:              msyslog(LOG_ERR, "NeoClock4X(%d): can't read firmware version (timeout)", unit);
                    934:              strcpy(tmpbuf, "unknown due to timeout");
                    935:              break;
                    936:            }
                    937:           if(chars_read > 500)
                    938:             {
                    939:              msyslog(LOG_ERR, "NeoClock4X(%d): can't read firmware version (garbage)", unit);
                    940:              strcpy(tmpbuf, "unknown due to garbage input");
                    941:              break;
                    942:             }
                    943:          if(-1 == read(fd, &c, 1))
                    944:            {
                    945:               if(EAGAIN != errno)
                    946:                 {
                    947:                   msyslog(LOG_DEBUG, "NeoClock4x(%d): read: %s", unit ,strerror(errno));
                    948:                   read_errors++;
                    949:                 }
                    950:               else
                    951:                 {
                    952:                   sleep(1);
                    953:                 }
                    954:              continue;
                    955:            }
                    956:           else
                    957:             {
                    958:               chars_read++;
                    959:             }
                    960: 
                    961:          if(init)
                    962:            {
                    963:              if(0xA9 != c) /* wait for (c) char in input stream */
                    964:                continue;
                    965: 
                    966:              strcpy(tmpbuf, "(c)");
                    967:              len = 3;
                    968:              init = 0;
                    969:              continue;
                    970:            }
                    971: 
                    972: #if 0
                    973:          msyslog(LOG_NOTICE, "NeoClock4X(%d): firmware %c = %02Xh", unit, c, c);
                    974: #endif
                    975: 
                    976:          if(0x0A == c || 0x0D == c)
                    977:            {
                    978:              if(last_c_was_crlf)
                    979:                {
                    980:                  char *ptr;
                    981:                  ptr = strstr(&tmpbuf[lastsearch], "S/N");
                    982:                  if(NULL != ptr)
                    983:                    {
                    984:                      tmpbuf[last_crlf_conv_len] = 0;
                    985:                      flag = 1;
                    986:                      break;
                    987:                    }
                    988:                  /* convert \n to / */
                    989:                  last_crlf_conv_len = len;
                    990:                  tmpbuf[len++] = ' ';
                    991:                  tmpbuf[len++] = '/';
                    992:                  tmpbuf[len++] = ' ';
                    993:                  lastsearch = len;
                    994:                }
                    995:              last_c_was_crlf = 1;
                    996:            }
                    997:          else
                    998:            {
                    999:              last_c_was_crlf = 0;
                   1000:              if(0x00 != c)
                   1001:                tmpbuf[len++] = (char) c;
                   1002:            }
                   1003:          tmpbuf[len] = '\0';
                   1004:          if(len > sizeof(tmpbuf)-5)
                   1005:            break;
                   1006:        }
                   1007:     }
                   1008:   else
                   1009:     {
                   1010:       msyslog(LOG_ERR, "NeoClock4X(%d): can't query firmware version", unit);
                   1011:       strcpy(tmpbuf, "unknown error");
                   1012:     }
                   1013:   strncpy(firmware, tmpbuf, maxlen);
                   1014:   firmware[maxlen] = '\0';
                   1015: 
                   1016:   if(flag)
                   1017:     {
                   1018:       NLOG(NLOG_CLOCKINFO)
                   1019:        msyslog(LOG_INFO, "NeoClock4X(%d): firmware version: %s", unit, firmware);
                   1020: 
                   1021:       if(strstr(firmware, "/R2"))
                   1022:        {
                   1023:          msyslog(LOG_INFO, "NeoClock4X(%d): Your NeoClock4X uses the new R2 firmware release. Please note the changed LED behaviour.", unit);
                   1024:        }
                   1025: 
                   1026:     }
                   1027: 
                   1028:   return (flag);
                   1029: }
                   1030: 
                   1031: static int
                   1032: neol_check_firmware(int unit,
                   1033:                     const char *firmware,
                   1034:                     char *firmwaretag)
                   1035: {
                   1036:   char *ptr;
                   1037: 
                   1038:   *firmwaretag = '?';
                   1039:   ptr = strstr(firmware, "NDF:");
                   1040:   if(NULL != ptr)
                   1041:     {
                   1042:       if((strlen(firmware) - strlen(ptr)) >= 7)
                   1043:         {
                   1044:           if(':' == *(ptr+5) && '*' == *(ptr+6))
                   1045:             *firmwaretag = *(ptr+4);
                   1046:         }
                   1047:     }
                   1048: 
                   1049:   if('A' != *firmwaretag)
                   1050:     {
                   1051:       msyslog(LOG_CRIT, "NeoClock4X(%d): firmware version \"%c\" not supported with this driver version!", unit, *firmwaretag);
                   1052:       return (0);
                   1053:     }
                   1054: 
                   1055:   return (1);
                   1056: }
                   1057: #endif
                   1058: 
                   1059: #else
                   1060: int refclock_neoclock4x_bs;
                   1061: #endif /* REFCLOCK */
                   1062: 
                   1063: /*
                   1064:  * History:
                   1065:  * refclock_neoclock4x.c
                   1066:  *
                   1067:  * 2002/04/27 cjh
                   1068:  * Revision 1.0  first release
                   1069:  *
                   1070:  * 2002/07/15 cjh
                   1071:  * preparing for bitkeeper reposity
                   1072:  *
                   1073:  * 2002/09/09 cjh
                   1074:  * Revision 1.1
                   1075:  * - don't assume sprintf returns an int anymore
                   1076:  * - change the way the firmware version is read
                   1077:  * - some customers would like to put a device called
                   1078:  *   data diode to the NeoClock4X device to disable
                   1079:  *   the write line. We need to now the firmware
                   1080:  *   version even in this case. We made a compile time
                   1081:  *   definition in this case. The code was previously
                   1082:  *   only available on request.
                   1083:  *
                   1084:  * 2003/01/08 cjh
                   1085:  * Revision 1.11
                   1086:  * - changing xprinf to xnprinf to avoid buffer overflows
                   1087:  * - change some logic
                   1088:  * - fixed memory leaks if drivers can't initialize
                   1089:  *
                   1090:  * 2003/01/10 cjh
                   1091:  * Revision 1.12
                   1092:  * - replaced ldiv
                   1093:  * - add code to support FreeBSD
                   1094:  *
                   1095:  * 2003/07/07 cjh
                   1096:  * Revision 1.13
                   1097:  * - fix reporting of clock status
                   1098:  *   changes. previously a bad clock
                   1099:  *   status was never reset.
                   1100:  *
                   1101:  * 2004/04/07 cjh
                   1102:  * Revision 1.14
                   1103:  * - open serial port in a way
                   1104:  *   AIX and some other OS can
                   1105:  *   handle much better
                   1106:  *
                   1107:  * 2006/01/11 cjh
                   1108:  * Revision 1.15
                   1109:  * - remove some unsued #ifdefs
                   1110:  * - fix nsec calculation, closes #499
                   1111:  *
                   1112:  * 2009/12/04 cjh
                   1113:  * Revision 1.16
                   1114:  * - change license to ntp COPYRIGHT notice. This should allow Debian
                   1115:  *   to add this refclock driver in further releases.
                   1116:  * - detect R2 hardware
                   1117:  *
                   1118:  */
                   1119: 
                   1120: 
                   1121: 
                   1122: 
                   1123: 
                   1124: 

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>