From 3cd6d60e0b10f0b6eacddb89f8f11c162433e115 Mon Sep 17 00:00:00 2001 From: DrDoof <8052722+DrDoof@users.noreply.github.com> Date: Wed, 23 Sep 2026 22:32:04 +0200 Subject: [PATCH] ntp: keep the time from an NTP server The switch has no battery backed clock, only a seconds counter that starts at zero on every boot. An SNTP client keeps UTC as the time of the last answer plus the seconds counted since: "ntp server ", "ntp interval ", "ntp timezone <+hh:mm>", "ntp dst off|eu|us", "ntp on|off", "ntp" and "time". A server given by name goes through the DNS resolver, an answer from an unsynchronised server is ignored, and a failed attempt is repeated after 30 seconds. The local time adds the time zone offset and, while the European or United States rule says so, one hour. The date is split with a shift and subtract division and a walk over the years, so no 32-bit division from the library ends up in the common area. The System page gets the NTP settings and shows the local time. --- Makefile | 2 + cmd_parser.c | 8 +- doc/ntp.md | 33 +++++ html/app.js | 62 ++++++++- html/index.html | 16 +++ ntp.c | 341 ++++++++++++++++++++++++++++++++++++++++++++++++ ntp.h | 41 ++++++ ntp_cli.c | 119 +++++++++++++++++ rtlplayground.c | 2 + udp_apps.c | 1 + udp_apps.h | 1 + 11 files changed, 618 insertions(+), 8 deletions(-) create mode 100644 doc/ntp.md create mode 100644 ntp.c create mode 100644 ntp.h create mode 100644 ntp_cli.c diff --git a/Makefile b/Makefile index d7fbf131..cf8f7a9a 100644 --- a/Makefile +++ b/Makefile @@ -71,6 +71,8 @@ SRCS = \ sfp.c \ dns.c \ dns_cli.c \ + ntp.c \ + ntp_cli.c \ syslog.c \ udp_apps.c diff --git a/cmd_parser.c b/cmd_parser.c index 93a5af7c..8aeba4f2 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -20,6 +20,7 @@ #include "dhcp.h" #include "syslog.h" #include "dns.h" +#include "ntp.h" #include "uip/uip.h" #include "version.h" @@ -1805,6 +1806,8 @@ void cmd_parser(void) __banked parse_syslog(); } else if (cmd_compare(0, "dns")) { dns_parse(); + } else if (cmd_compare(0, "ntp") || cmd_compare(0, "time")) { + ntp_parse(); } else if (cmd_compare(0, "ip")) { if (cmd_compare(1, "dhcp")) { dhcp_start(); @@ -1951,11 +1954,6 @@ void cmd_parser(void) __banked parse_temp(); } else if (cmd_compare(0, "version")) { print_sw_version(); - } else if (cmd_compare(0, "time")) { - print_string(" Tick counter: "); print_long(ticks); print_string(" Sec Counter: "); - reg_read_m(RTL837X_REG_SEC_COUNTER); - print_sfr_data(); - write_char('\n'); } else if (cmd_compare(0, "history")) { __xdata uint16_t p = (cmd_history_ptr + 1) & CMD_HISTORY_MASK; __xdata uint8_t found_begin = 0; diff --git a/doc/ntp.md b/doc/ntp.md new file mode 100644 index 00000000..129a2230 --- /dev/null +++ b/doc/ntp.md @@ -0,0 +1,33 @@ +# Time + +The switch has no battery backed clock. It counts seconds from power on, and +it can take the date and time from an NTP server once the network is up. The +time is lost at every restart and set again at the first synchronisation. + +``` +ntp server # default pool.ntp.org +ntp interval <1-1440> # minutes between two synchronisations, default 60 +ntp timezone <+hh:mm> # offset from UTC, -12:00 to +14:00 +ntp dst off|eu|us # daylight saving rule +ntp on +ntp off +ntp # settings and time +time # local date and time +``` + +The client speaks SNTP version 4 and synchronises at once when turned on, +then after every interval. A server given by name is resolved through the DNS +resolver before every synchronisation. An answer from an unsynchronised +server is ignored, and a failed attempt is repeated after 30 seconds. + +The local time is UTC plus the time zone offset, plus one hour while daylight +saving time applies: + +* `eu`: from 01:00 UTC on the last Sunday of March to 01:00 UTC on the last + Sunday of October; +* `us`: from 02:00 local time on the second Sunday of March to 02:00 local + time on the first Sunday of November. + +The same settings, with the current local time and the last synchronisation, +are in the Time card of the System page. Settings made there or on the +console are kept only once the configuration is saved. diff --git a/html/app.js b/html/app.js index da8374c1..9cb06558 100644 --- a/html/app.js +++ b/html/app.js @@ -83,7 +83,14 @@ sy_ip_err:"Invalid IP / netmask / gateway",sy_host_err:"Hostname: 1-23 printable sy_net_q:"Apply network settings?",sy_net_d:"The management IP changes to {ip}: this page will need to be reopened there.", sy_ip_changed:"IP changed, reconnect at http://{ip}/",sy_dhcp_q:"Switch to DHCP?", sy_dhcp_d:"The switch requests an address via DHCP. You must find its new IP to reconnect.", -sy_sysip_err:"Invalid syslog server IP",sy_dns1:"DNS server",sy_dns2:"Second DNS",sy_dns_err:"Invalid DNS server IP",sy_dns_dhcp:"from DHCP: {ip}",sy_sysport_err:"Syslog port must be 1-65535", +sy_sysip_err:"Invalid syslog server IP",sy_dns1:"DNS server",sy_dns2:"Second DNS",sy_dns_err:"Invalid DNS server IP",sy_dns_dhcp:"from DHCP: {ip}", +sy_time:"Time",sy_ntp:"NTP",sy_ntpsrv:"NTP server",sy_ntpsrv_p:"host name or IPv4 address",sy_ntpint:"Poll interval [min]", +sy_tz:"Time zone",sy_dst:"Daylight saving",sy_dst_eu:"Europe", +sy_dst_us:"United States",sy_now:"Local time", +sy_sync:"Last synchronisation",sy_ntpaddr:"Server address",sy_nosync:"not synchronised",sy_waiting:"waiting for the server", +sy_ago:"{n} min ago",sy_ntp_err:"Enter the host name or IPv4 address of the NTP server",sy_ntpint_err:"Poll interval must be 1-1440", +sy_time_note:"The switch has no battery backed clock: the time is lost at every restart and set again from the NTP server once the network is up. Save to flash to keep the settings.", +sy_sysport_err:"Syslog port must be 1-65535", sy_pw_len:"Password: 1-20 characters",sy_pw_space:"Password cannot contain spaces",sy_pw_match:"Passwords do not match", sy_pw_q:"Change admin password?",sy_pw_d:"Takes effect immediately for new logins. Save to flash to persist.", sy_reboot_q:"Reboot the switch?",sy_reboot_d:"There are UNSAVED changes and they will be lost. Save to flash first if you want to keep them.", @@ -659,6 +666,8 @@ var CONF_CMDS=[ /^syslog\s+(on|off)$/,/^syslog\s+ip\s+(\d{1,3}\.){3}\d{1,3}$/,/^syslog\s+port\s+\d{1,5}$/, /^session\s+\d{1,5}$/, /^dns\s+server\s+(\d{1,3}\.){3}\d{1,3}(\s+(\d{1,3}\.){3}\d{1,3})?$/, + /^ntp\s+(on|off)$/,/^ntp\s+server\s+[A-Za-z0-9.-]{1,47}$/,/^ntp\s+interval\s+\d{1,4}$/, + /^ntp\s+timezone\s+[+-]?\d{1,2}(:\d{2})?$/,/^ntp\s+dst\s+(off|eu|us)$/, /^passwd\s+\S+$/,/^hostname\s+\S{1,23}$/, /^vlan\s+\d{1,4}\s+d$/,/^vlan\s+\d{1,4}\s+mgmt$/, /^vlan\s+\d{1,4}(\s+[a-zA-Z]\w*)?(\s+\d{1,2}t?)+$/, @@ -1871,11 +1880,58 @@ $("cfgreload").addEventListener("click",cfgReload); $("cfgwrite").addEventListener("click",function(){ writeConfig($("cfgedit").value,t("cw_title")); }); -tabHooks.system={enter:sysLoad}; +var ntpBase=null,ntpTimer=null; +function tzText(m){var a=Math.abs(m);return(m<0?"-":"+")+String(Math.floor(a/60)).padStart(2,"0")+":"+String(a%60).padStart(2,"0")} +function ntpTick(){ + if(!ntpBase)return; + var d=new Date(ntpBase.ms+(Date.now()-ntpBase.at)); + function p(n){return String(n).padStart(2,"0")} + $("sy-sync").textContent=t("sy_ago",{n:ntpBase.sync+Math.floor((Date.now()-ntpBase.at)/60000)}); + $("sy-now").textContent=d.getUTCFullYear()+"-"+p(d.getUTCMonth()+1)+"-"+p(d.getUTCDate())+" " + +p(d.getUTCHours())+":"+p(d.getUTCMinutes())+":"+p(d.getUTCSeconds())+" UTC"+ntpBase.tz; +} +function ntpLoad(){ + return api("/cmd",{method:"POST",body:"ntp"}).then(function(r){ + var m=r.body.match(/NTP (on|off), server (\S*), address ([\d.]+), interval (\d+) min, timezone ([+-]\d\d:\d\d), dst (\w+)/); + if(!m)return; + var x=r.body.match(/Time (\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d) UTC([+-]\d\d:\d\d)( DST)?, synchronised (\d+) min ago/); + $("sy-ntpaddr").textContent=m[3]==="0.0.0.0"?"-":m[3]; + if(x){ + ntpBase={at:Date.now(),ms:Date.UTC(+x[1],+x[2]-1,+x[3],+x[4],+x[5],+x[6]),tz:x[7]+(x[8]?" DST":""),sync:+x[9]}; + ntpTick(); + }else{ + ntpBase=null; + $("sy-now").textContent=t("sy_nosync"); + $("sy-sync").textContent=m[1]==="on"?t("sy_waiting"):"-"; + } + if($("sy-ntpcard").dataset.dirty)return; + $("sy-ntp").checked=m[1]==="on";$("sy-ntpsrv").value=m[2];$("sy-ntpint").value=m[4]; + $("sy-tz").value=m[5];$("sy-dst").value=m[6]; + }).catch(function(){}); +} +[-720,-660,-600,-570,-540,-480,-420,-360,-300,-240,-210,-180,-120,-60,0,60,120,180,210,240,270,300,330,345, + 360,390,420,480,525,540,570,600,630,660,720,765,780,840].forEach(function(z){ + $("sy-tz").appendChild(h("option",{value:tzText(z),text:"UTC"+tzText(z)})); +}); +["change","input"].forEach(function(ev){$("sy-ntpcard").addEventListener(ev,function(){this.dataset.dirty="1"})}); +$("sy-ntpapply").addEventListener("click",function(){ + var srv=$("sy-ntpsrv").value.trim(),iv=$("sy-ntpint").value.trim(); + if(!/^[A-Za-z0-9.-]{1,47}$/.test(srv)){toast(t("sy_ntp_err"),"err");return;} + if(!(+iv>=1&&+iv<=1440)){toast(t("sy_ntpint_err"),"err");return;} + delete $("sy-ntpcard").dataset.dirty; + postCmds(["ntp server "+srv,"ntp interval "+iv,"ntp timezone "+$("sy-tz").value,"ntp dst "+$("sy-dst").value, + "ntp "+($("sy-ntp").checked?"on":"off")]).then(ntpLoad).catch(function(){}); +}); +$("sy-ntprefresh").addEventListener("click",ntpLoad); +tabHooks.system={ + enter:function(){sysLoad();ntpLoad();clearInterval(ntpTimer);ntpTimer=setInterval(ntpTick,1000)}, + leave:function(){clearInterval(ntpTimer)}, +}; var CONF_OVERWRITE=[ /^ip\b/,/^gw\b/,/^netmask\b/,/^hostname\b/, /^syslog\s+ip\b/,/^syslog\s+port\b/,/^dns\s+server\b/,/^passwd\b/,/^session\b/, + /^ntp\s+server\b/,/^ntp\s+interval\b/,/^ntp\s+timezone\b/,/^ntp\s+dst\b/, /^vlan\s+\d{1,4}\s+mgmt$/,/^vlan\s+\d{1,4}(?!\s+mgmt\b)/, /^pvid\s+\d{1,2}\b/, /^port\s+\d{1,2}(?!\s+name\b)/,/^port\s+\d{1,2}\s+name\b/, @@ -1885,7 +1941,7 @@ var CONF_OVERWRITE=[ /^stp\s+(port\s+\d{1,2}|lag\s+[1-4])\s+(edge|cost|prio|guard|filter|p2p)\b/, /^igmp\b/,/^mtu\s+\d{1,2}\b/, ]; -var CONF_TOGGLE=[/^(syslog)\s+(on|off)$/,/^(stp)\s+(on|off)$/,/^(stp\s+(port\s+\d{1,2}|lag\s+[1-4]))\s+(on|off)$/]; +var CONF_TOGGLE=[/^(syslog)\s+(on|off)$/,/^(ntp)\s+(on|off)$/,/^(stp)\s+(on|off)$/,/^(stp\s+(port\s+\d{1,2}|lag\s+[1-4]))\s+(on|off)$/]; function mergeConf(base,texts){ var conf=base.slice(); function drop(rx){conf=conf.filter(function(c){return!rx.test(c)})} diff --git a/html/index.html b/html/index.html index 0afb30bc..05a73509 100644 --- a/html/index.html +++ b/html/index.html @@ -382,6 +382,22 @@ button.danger{background:var(--bad);color:#fff;border-color:transparent}

Service state reflects the startup config; runtime state is not readable.

+

Time

+ + + + + +
+ + +
+ + + +

The switch has no battery backed clock.

+

Admin password

diff --git a/ntp.c b/ntp.c new file mode 100644 index 00000000..9a61bb73 --- /dev/null +++ b/ntp.c @@ -0,0 +1,341 @@ +/* + * SNTP client and local time for the RTL837x platform + * This code is in the Public Domain + */ + +#include +#include "rtl837x_common.h" +#include "rtl837x_regs.h" +#include "cmd_parser.h" +#include "uip/uip.h" +#include "dns.h" +#include "ntp.h" + +#pragma codeseg BANK3 +#pragma constseg BANK3 + +#define NTP_PORT 123 +#define NTP_TRIES 3 +#define NTP_TIMEOUT (3 * SYS_TICK_HZ) +#define NTP_RETRY 30 +#define NTP_UNIX_EPOCH 2208988800UL + +#define NTP_IDLE 0 +#define NTP_RESOLVE 1 +#define NTP_SEND 2 +#define NTP_WAIT 3 + +extern __xdata uint8_t sfr_data[4]; +extern volatile __xdata uint32_t ticks; + +__xdata struct ntp_state ntp_state; +__xdata uip_ipaddr_t ntp_ip; +__xdata uint32_t ntp_now; +__xdata uint32_t ntp_t; +__xdata uint16_t ntp_year; +__xdata uint8_t ntp_mon, ntp_mday, ntp_hour, ntp_min, ntp_sec, ntp_wday, ntp_mlen; + +/* days in each month of a common year */ +static __code const uint8_t ntp_mdays[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + +static __code const char ntp_default_server[] = "pool.ntp.org"; + + +static uint32_t ntp_uptime(void) __reentrant +{ + reg_read_m(RTL837X_REG_SEC_COUNTER); + ((__xdata uint8_t *)&ntp_now)[0] = sfr_data[3]; + ((__xdata uint8_t *)&ntp_now)[1] = sfr_data[2]; + ((__xdata uint8_t *)&ntp_now)[2] = sfr_data[1]; + ((__xdata uint8_t *)&ntp_now)[3] = sfr_data[0]; + return ntp_now; +} + + +static uint8_t ntp_divmod(uint8_t d) __reentrant +{ + uint8_t i, r = 0; + + for (i = 0; i < 32; i++) { + r = (r << 1) | (((uint8_t *)&ntp_t)[3] >> 7); + ntp_t <<= 1; + if (r >= d) { + r -= d; + ntp_t |= 1; + } + } + return r; +} + + +static uint8_t ntp_leap(uint16_t y) __reentrant +{ + return !(y % 4) && ((y % 100) || !(y % 400)); +} + + +static void ntp_split(uint32_t t) __reentrant +{ + uint16_t days; + + ntp_t = t; + ntp_sec = ntp_divmod(60); + ntp_min = ntp_divmod(60); + ntp_hour = ntp_divmod(24); + days = ntp_t; + ntp_wday = (days + 4) % 7; + for (ntp_year = 1970; days >= 365 + ntp_leap(ntp_year); ntp_year++) + days -= 365 + ntp_leap(ntp_year); + for (ntp_mon = 1; ; ntp_mon++) { + ntp_mlen = ntp_mdays[ntp_mon - 1] + (ntp_mon == 2 && ntp_leap(ntp_year)); + if (days < ntp_mlen) + break; + days -= ntp_mlen; + } + ntp_mday = days + 1; +} + + +static uint8_t ntp_sunday(uint8_t n) __reentrant +{ + uint8_t first = (uint16_t)(ntp_wday + 36 - ntp_mday) % 7U; + + first = 1 + (uint16_t)(7 - first) % 7U; + if (n) + return first + 7 * (n - 1); + return first + 7 * ((uint16_t)(ntp_mlen - first) / 7U); +} + + +static uint8_t ntp_in_dst(uint8_t sm, uint8_t sn, uint8_t sh, uint8_t em, uint8_t en, uint8_t eh) __reentrant +{ + uint8_t day; + + if (ntp_mon < sm || ntp_mon > em) + return 0; + if (ntp_mon > sm && ntp_mon < em) + return 1; + if (ntp_mon == sm) { + day = ntp_sunday(sn); + return ntp_mday > day || (ntp_mday == day && ntp_hour >= sh); + } + day = ntp_sunday(en); + return ntp_mday < day || (ntp_mday == day && ntp_hour < eh); +} + + +static void ntp_print2(uint8_t v) __reentrant +{ + write_char('0' + v / 10); + write_char('0' + v % 10); +} + + +static void ntp_print_offset(int16_t off) __reentrant +{ + uint16_t a = off < 0 ? -off : off; + + write_char(off < 0 ? '-' : '+'); + ntp_print2(a / 60); + write_char(':'); + ntp_print2(a % 60); +} + + +static void ntp_connect(__xdata uint8_t *a) __reentrant +{ + if (ntp_state.conn) + uip_udp_remove(ntp_state.conn); + uip_ipaddr(ntp_ip, a[0], a[1], a[2], a[3]); + ntp_state.conn = uip_udp_new(&ntp_ip, HTONS(NTP_PORT)); + if (!ntp_state.conn) + print_string("NTP: no free UDP connection\n"); +} + + +static void ntp_retry(void) __reentrant +{ + ntp_state.phase = NTP_IDLE; + ntp_state.next = ntp_uptime() + NTP_RETRY; +} + + +static void ntp_request(void) __reentrant +{ + __xdata uint8_t *p = (__xdata uint8_t *)uip_appdata; + uint8_t i; + + for (i = 1; i < 48; i++) + p[i] = 0; + p[0] = 0x23; + uip_udp_send(48); +} + + +static void ntp_receive(void) __reentrant +{ + __xdata uint8_t *p = (__xdata uint8_t *)uip_appdata; + + if (ntp_state.phase != NTP_WAIT || uip_datalen() < 48) + return; + if ((p[0] & 7) != 4 || (p[0] & 0xc0) == 0xc0 || !p[1] || p[1] > 15) { + ntp_retry(); + return; + } + ((__xdata uint8_t *)&ntp_state.utc)[0] = p[43]; + ((__xdata uint8_t *)&ntp_state.utc)[1] = p[42]; + ((__xdata uint8_t *)&ntp_state.utc)[2] = p[41]; + ((__xdata uint8_t *)&ntp_state.utc)[3] = p[40]; + ntp_state.utc -= NTP_UNIX_EPOCH; + ntp_state.at = ntp_uptime(); + ntp_state.stratum = p[1]; + if (!ntp_state.synced) { + ntp_state.synced = 1; + print_string("NTP: time set from "); + print_ip(ntp_state.addr); + write_char('\n'); + } + ntp_state.phase = NTP_IDLE; + ntp_state.next = ntp_state.at + (uint32_t)ntp_state.interval * 60; +} + + +void ntp_init(void) __banked +{ + for (ntp_mon = 0; ntp_mon < sizeof(ntp_default_server); ntp_mon++) + ntp_state.server[ntp_mon] = ntp_default_server[ntp_mon]; + ntp_state.enabled = 0; + ntp_state.interval = 60; + ntp_state.offset = 0; + ntp_state.dst = NTP_DST_OFF; + ntp_state.phase = NTP_IDLE; + ntp_state.synced = 0; + ntp_state.conn = 0; +} + + +void ntp_start(void) __banked +{ + ntp_state.enabled = 1; + ntp_state.phase = NTP_IDLE; + ntp_state.next = 0; + ntp_state.addr[0] = ntp_state.addr[1] = ntp_state.addr[2] = ntp_state.addr[3] = 0; + ntp_connect(ntp_state.addr); +} + + +void ntp_stop(void) __banked +{ + ntp_state.enabled = 0; + if (ntp_state.conn) { + uip_udp_remove(ntp_state.conn); + ntp_state.conn = 0; + } +} + + +void ntp_show_time(void) __banked __reentrant +{ + int16_t off = ntp_state.offset; + uint8_t dst = 0; + uint32_t t; + + if (!ntp_state.synced) { + print_string("Time not synchronised\n"); + return; + } + t = ntp_state.utc + (ntp_uptime() - ntp_state.at); + if (ntp_state.dst == NTP_DST_EU) { + ntp_split(t); + dst = ntp_in_dst(3, 0, 1, 10, 0, 1); + } else if (ntp_state.dst == NTP_DST_US) { + ntp_split(t + (int32_t)off * 60); + dst = ntp_in_dst(3, 2, 2, 11, 1, 1); + } + if (dst) + off += 60; + ntp_split(t + (int32_t)off * 60); + print_string("Time "); + itoa_short(ntp_year); + write_char('-'); + ntp_print2(ntp_mon); + write_char('-'); + ntp_print2(ntp_mday); + write_char(' '); + ntp_print2(ntp_hour); + write_char(':'); + ntp_print2(ntp_min); + write_char(':'); + ntp_print2(ntp_sec); + print_string(" UTC"); + ntp_print_offset(off); + print_string(dst ? " DST, synchronised " : ", synchronised "); + ntp_t = ntp_uptime() - ntp_state.at; + ntp_divmod(60); + itoa_short(ntp_t); + print_string(" min ago, stratum "); + itoa_short(ntp_state.stratum); + write_char('\n'); +} + + +void ntp_show(void) __banked __reentrant +{ + int16_t off = ntp_state.offset; + + print_string(ntp_state.enabled ? "NTP on, server " : "NTP off, server "); + print_string_x(ntp_state.server); + print_string(", address "); + print_ip(ntp_state.addr); + print_string(", interval "); + itoa_short(ntp_state.interval); + print_string(" min, timezone "); + ntp_print_offset(off); + print_string(ntp_state.dst == NTP_DST_EU ? ", dst eu\n" : ntp_state.dst == NTP_DST_US ? ", dst us\n" : ", dst off\n"); + ntp_show_time(); +} + + +void ntp_callback(uint16_t lport) __banked __reentrant +{ + if (!ntp_state.conn || lport != ntp_state.conn->lport) + return; + if (uip_newdata()) { + ntp_receive(); + return; + } + switch (ntp_state.phase) { + case NTP_IDLE: + if (ntp_uptime() < ntp_state.next || dns_state.status == DNS_PENDING) + return; + memcpy(dns_state.name, ntp_state.server, DNS_NAME_LEN); + dns_state.verbose = 0; + dns_lookup(); + ntp_state.phase = NTP_RESOLVE; + return; + case NTP_RESOLVE: + if (dns_state.status == DNS_PENDING) + return; + if (dns_state.status != DNS_DONE) { + ntp_retry(); + return; + } + memcpy(ntp_state.addr, dns_state.addr, 4); + ntp_state.tries = 0; + ntp_state.phase = NTP_SEND; + ntp_connect(ntp_state.addr); + return; + case NTP_SEND: + ntp_state.sent = ticks; + ntp_state.phase = NTP_WAIT; + ntp_request(); + return; + case NTP_WAIT: + if (ticks - ntp_state.sent < NTP_TIMEOUT) + return; + if (++ntp_state.tries < NTP_TRIES) + ntp_state.phase = NTP_SEND; + else + ntp_retry(); + } +} diff --git a/ntp.h b/ntp.h new file mode 100644 index 00000000..f99af83e --- /dev/null +++ b/ntp.h @@ -0,0 +1,41 @@ +#ifndef _NTP_H_ +#define _NTP_H_ + +#include +#include "dns.h" + +#define NTP_DST_OFF 0 +#define NTP_DST_EU 1 +#define NTP_DST_US 2 + +struct uip_udp_conn; + +struct ntp_state { + uint8_t enabled; + char server[DNS_NAME_LEN]; /* host name or dotted address */ + uint8_t addr[4]; /* address of the server last used */ + uint16_t interval; /* minutes between two synchronisations */ + int16_t offset; /* time zone, minutes east of UTC */ + uint8_t dst; /* NTP_DST_* */ + uint8_t phase; + uint8_t tries; + uint8_t synced; + uint8_t stratum; + uint32_t utc; /* seconds since 1970 UTC at the last synchronisation */ + uint32_t at; /* uptime seconds at the last synchronisation */ + uint32_t next; /* uptime seconds of the next synchronisation */ + uint32_t sent; /* ticks when the request went out */ + struct uip_udp_conn *conn; +}; + +extern __xdata struct ntp_state ntp_state; + +void ntp_init(void) __banked; +void ntp_start(void) __banked; +void ntp_stop(void) __banked; +void ntp_show(void) __banked __reentrant; +void ntp_show_time(void) __banked __reentrant; +void ntp_callback(uint16_t lport) __banked __reentrant; +void ntp_parse(void) __banked __reentrant; + +#endif diff --git a/ntp_cli.c b/ntp_cli.c new file mode 100644 index 00000000..e3840c46 --- /dev/null +++ b/ntp_cli.c @@ -0,0 +1,119 @@ +/* + * Command line interface of the NTP client for the RTL837x platform + * This code is in the Public Domain + */ + +#include +#include "rtl837x_common.h" +#include "cmd_parser.h" +#include "ntp.h" + +#pragma codeseg BANK2 +#pragma constseg BANK2 + +extern __xdata uint8_t cmd_buffer[CMD_BUF_SIZE]; +extern __xdata uint8_t cmd_words_len; +extern __xdata uint8_t cmd_words_b[15]; +extern __xdata uint16_t atoi_results_short; +uint8_t cmd_compare(uint8_t start, __code const uint8_t * cmd); +uint8_t atoi_short(uint8_t idx); + + +static uint8_t ntp_cli_server(void) __reentrant +{ + __xdata uint8_t *s = &cmd_buffer[cmd_words_b[2]]; + uint8_t n; + + for (n = 0; s[n] && s[n] != ' '; n++) + if (n == DNS_NAME_LEN - 1) + return 0; + memcpy(ntp_state.server, s, n); + ntp_state.server[n] = 0; + return n; +} + + +static uint8_t ntp_cli_timezone(void) __reentrant +{ + __xdata uint8_t *s = &cmd_buffer[cmd_words_b[2]]; + uint8_t neg = 0, h = 0, m = 0, n = 0; + uint16_t v; + + if (*s == '+' || *s == '-') + neg = *s++ == '-'; + while (*s >= '0' && *s <= '9') { + h = h * 10 + (*s++ - '0'); + if (++n > 2) + return 0; + } + if (!n) + return 0; + if (*s == ':') { + s++; + if (s[0] < '0' || s[0] > '9' || s[1] < '0' || s[1] > '9') + return 0; + m = (s[0] - '0') * 10 + s[1] - '0'; + s += 2; + } + if ((*s && *s != ' ') || (m && m != 30 && m != 45)) + return 0; + v = (uint16_t)h * 60 + m; + if (v > (neg ? 720 : 840)) + return 0; + ntp_state.offset = neg ? -(int16_t)v : (int16_t)v; + return 1; +} + + +void ntp_parse(void) __banked __reentrant +{ + if (cmd_compare(0, "time")) { + ntp_show_time(); + return; + } + if (cmd_words_len < 2) { + ntp_show(); + return; + } + if (cmd_compare(1, "on")) { + ntp_start(); + return; + } + if (cmd_compare(1, "off")) { + ntp_stop(); + return; + } + if (cmd_words_len < 3) + goto err; + if (cmd_compare(1, "server")) { + if (!ntp_cli_server()) + goto err; + if (ntp_state.enabled) + ntp_start(); + return; + } + if (cmd_compare(1, "interval")) { + if (!atoi_short(cmd_words_b[2]) || !atoi_results_short || atoi_results_short > 1440) + goto err; + ntp_state.interval = atoi_results_short; + return; + } + if (cmd_compare(1, "timezone")) { + if (!ntp_cli_timezone()) + goto err; + return; + } + if (cmd_compare(1, "dst")) { + if (cmd_compare(2, "off")) + ntp_state.dst = NTP_DST_OFF; + else if (cmd_compare(2, "eu")) + ntp_state.dst = NTP_DST_EU; + else if (cmd_compare(2, "us")) + ntp_state.dst = NTP_DST_US; + else + goto err; + return; + } +err: + print_string("Error: ntp [on|off|server |interval <1-1440>|timezone <+hh:mm>|dst off|eu|us]\n"); +} diff --git a/rtlplayground.c b/rtlplayground.c index 604abfae..825175c9 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -26,6 +26,7 @@ #include "phy.h" #include "syslog.h" #include "dns.h" +#include "ntp.h" #include "httpd/page_impl.h" #include "boot.h" #include "sfp.h" @@ -1758,6 +1759,7 @@ void main(void) syslog_init(); dns_init(); + ntp_init(); #ifdef DEBUG // This register seems to work on the RTL8373 only if also the SDS diff --git a/udp_apps.c b/udp_apps.c index 4292a105..a5304c6f 100644 --- a/udp_apps.c +++ b/udp_apps.c @@ -7,4 +7,5 @@ void udp_callbacks(void) dhcp_callback(uip_udp_conn->lport); // let the application decide if this is for it or not syslog_callback(uip_udp_conn->lport); // let the application decide if this is for it or not dns_callback(uip_udp_conn->lport); + ntp_callback(uip_udp_conn->lport); } \ No newline at end of file diff --git a/udp_apps.h b/udp_apps.h index de9d6a18..6908fafa 100644 --- a/udp_apps.h +++ b/udp_apps.h @@ -4,6 +4,7 @@ #include "dhcp.h" #include "syslog.h" #include "dns.h" +#include "ntp.h" void udp_callbacks(void);