Step 6.3: PTP hybrid mode (unicast Delay_Req/Resp), step 6 done
- As TimeReceiver in ptp.mode hybrid: Delay_Req unicast to the GM (IP
from its Announce, MAC recorded by the RX hook from its Sync frames),
unicastFlag set.
- As TimeTransmitter: a Delay_Req with the unicastFlag gets a unicast
Delay_Resp; multicast requests get multicast replies.
- EMAC timestamps every received frame (en_ts4all): its PTP filter left
unicast Delay_Req unstamped ("no HW RX timestamp").
- A unicast Delay_Resp's logMessageInterval 0x7F is ignored (use
ptp.log_delay_req); log_us() clamps to -7..6 (the shift was undefined).
- Verified vs ptp4l --hybrid_e2e 1: board hybrid TimeReceiver 18
Delay_Req in 20 s, all answered, locked; board TimeTransmitter
(p1 100): tcpdump shows Sync/Follow_Up/Announce to 224.0.1.129,
Delay_Req 192.168.192.233 -> .244 [unicast] and Delay_Resp .244 ->
.233 [unicast] within 0.4 ms; ptp4l s2.
- Docs: TimeTransmitter/hybrid notes; step 6 ticked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,7 @@
|
||||
#define WINDOW 64 // samples for interval/delay statistics
|
||||
#define SUMMARY_US (60 * 1000000LL)
|
||||
#define FLAG_PTP_TIMESCALE 0x0008 // flagField octet 1 bit 3
|
||||
#define FLAG_UNICAST 0x0400 // flagField octet 0 bit 2
|
||||
#define UTC_OFFSET 37 // TAI - UTC (s), announced as information only
|
||||
#define TIME_SOURCE_OSC 0xA0 // internal oscillator
|
||||
|
||||
@@ -60,6 +61,10 @@ static struct {
|
||||
master_t gm; // foreign TimeTransmitter we follow (valid = TimeReceiver)
|
||||
master_t own; // our own dataset for BMCA
|
||||
bool slave_only; // role "slave": never TimeTransmitter
|
||||
bool hybrid; // mode "hybrid": Delay_Req/Resp unicast as TimeReceiver
|
||||
uint8_t gm_mac[6]; // Ethernet source of the GM's Sync (for unicast Delay_Req)
|
||||
bool have_gm_mac;
|
||||
uint32_t dreq_logged_ip; // last Delay_Req destination logged
|
||||
bool master; // we are the TimeTransmitter
|
||||
int64_t listen_since_us; // start of LISTENING (for the announce receipt timeout)
|
||||
volatile bool reconfig;
|
||||
@@ -138,6 +143,13 @@ static void become_master(void);
|
||||
|
||||
/* ----- helpers ----- */
|
||||
|
||||
// 2^log seconds in us; log clamped to the sane PTP range (-7..6).
|
||||
static int64_t log_us(int8_t log)
|
||||
{
|
||||
log = log < -7 ? -7 : log > 6 ? 6 : log;
|
||||
return log >= 0 ? 1000000LL << log : 1000000LL >> -log;
|
||||
}
|
||||
|
||||
static uint16_t rd16(const uint8_t *p) { return (p[0] << 8) | p[1]; }
|
||||
|
||||
static int64_t rd_ts(const uint8_t *p) // 48-bit seconds + 32-bit ns
|
||||
@@ -252,6 +264,7 @@ static void on_announce(const uint8_t *b, int len, uint32_t src_ip)
|
||||
win_clear(&s.delays);
|
||||
});
|
||||
s.sync_pending = s.dreq_pending = false;
|
||||
s.have_gm_mac = false;
|
||||
LOCKED({
|
||||
s.delay_ns = s.prev_t2 = 0;
|
||||
s.stepped = false;
|
||||
@@ -361,10 +374,11 @@ static void on_sync(const uint8_t *b, int len)
|
||||
}
|
||||
eth_mac_time_t t2;
|
||||
uint16_t seq = rd16(b + 30);
|
||||
if (!ptp_hw_rx_ts(PTP_MSG_SYNC, seq, b + 20, &t2)) {
|
||||
if (!ptp_hw_rx_ts_mac(PTP_MSG_SYNC, seq, b + 20, &t2, s.gm_mac)) {
|
||||
ESP_LOGW(TAG, "Sync %u: no HW RX timestamp", seq);
|
||||
return;
|
||||
}
|
||||
s.have_gm_mac = true;
|
||||
s.sync_seq = seq;
|
||||
s.log_sync = (int8_t)b[33];
|
||||
s.t2 = mac_ns(&t2);
|
||||
@@ -401,9 +415,22 @@ static void send_delay_req(void)
|
||||
m[32] = 1; // controlField: Delay_Req
|
||||
m[33] = 0x7f;
|
||||
uint32_t dst;
|
||||
const uint8_t *dst_mac = PTP_MCAST_MAC;
|
||||
inet_aton(PTP_MCAST, (struct in_addr *)&dst);
|
||||
if (s.hybrid && s.gm.ip && s.have_gm_mac) {
|
||||
// Hybrid: unicast to the GM (address from its Announce, MAC from its Sync frames).
|
||||
dst = s.gm.ip;
|
||||
dst_mac = s.gm_mac;
|
||||
m[6] |= FLAG_UNICAST >> 8;
|
||||
}
|
||||
if (dst != s.dreq_logged_ip) {
|
||||
s.dreq_logged_ip = dst;
|
||||
ESP_LOGI(TAG, "Delay_Req %s to " IPSTR " (%02x:%02x:%02x:%02x:%02x:%02x)",
|
||||
dst_mac == PTP_MCAST_MAC ? "multicast" : "unicast", IP2STR((esp_ip4_addr_t *)&dst),
|
||||
dst_mac[0], dst_mac[1], dst_mac[2], dst_mac[3], dst_mac[4], dst_mac[5]);
|
||||
}
|
||||
eth_mac_time_t t3;
|
||||
esp_err_t err = ptp_hw_send_event(PTP_MCAST_MAC, dst, s.dscp, m, sizeof(m), &t3);
|
||||
esp_err_t err = ptp_hw_send_event(dst_mac, dst, s.dscp, m, sizeof(m), &t3);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Delay_Req %u: %s", s.dreq_seq, esp_err_to_name(err));
|
||||
s.dreq_pending = false;
|
||||
@@ -423,7 +450,10 @@ static void on_delay_resp(const uint8_t *b, int len)
|
||||
s.dreq_pending = false;
|
||||
LOCKED(s.delay_resp++);
|
||||
int64_t t4 = rd_ts(b + 34) - rd_corr_ns(b + 8);
|
||||
s.log_dreq = (int8_t)b[33];
|
||||
// Delay_Req interval from the GM; a unicast Delay_Resp carries 0x7F ("not specified"):
|
||||
// then use our configured interval.
|
||||
int8_t l = (int8_t)b[33];
|
||||
s.log_dreq = l >= -7 && l <= 6 ? l : s.cfg_log_dreq;
|
||||
if (!s.prev_t2) {
|
||||
return;
|
||||
}
|
||||
@@ -437,10 +467,6 @@ static void on_delay_resp(const uint8_t *b, int len)
|
||||
|
||||
/* ----- TimeTransmitter ----- */
|
||||
|
||||
static int64_t log_us(int8_t log)
|
||||
{
|
||||
return log >= 0 ? 1000000LL << log : 1000000LL >> -log;
|
||||
}
|
||||
|
||||
static void wr_ts(uint8_t *p, int64_t ns)
|
||||
{
|
||||
@@ -547,7 +573,12 @@ static void on_delay_req(const uint8_t *b, int len, uint32_t src_ip)
|
||||
memcpy(m + 8, b + 8, 8); // correctionField of the Delay_Req
|
||||
wr_ts(m + 34, mac_ns(&t4));
|
||||
memcpy(m + 44, b + 20, 10); // requestingPortIdentity
|
||||
send_general(m, sizeof(m), mcast_ip());
|
||||
// Unicast Delay_Req (hybrid TimeReceiver) -> unicast Delay_Resp; multicast -> multicast.
|
||||
bool unicast = rd16(b + 6) & FLAG_UNICAST;
|
||||
if (unicast) {
|
||||
m[6] |= FLAG_UNICAST >> 8;
|
||||
}
|
||||
send_general(m, sizeof(m), unicast ? src_ip : mcast_ip());
|
||||
LOCKED({
|
||||
s.delay_req++;
|
||||
s.delay_resp++;
|
||||
@@ -594,6 +625,7 @@ static void load_config(void)
|
||||
// Roles (docs): slave = clockClass 255, never transmits; auto/master = 248 with the configured
|
||||
// priorities (auto 250/250 by default, master e.g. p1 100).
|
||||
s.slave_only = strcmp(cJSON_GetObjectItem(c, "role")->valuestring, "slave") == 0;
|
||||
s.hybrid = strcmp(cJSON_GetObjectItem(c, "mode")->valuestring, "hybrid") == 0;
|
||||
s.own_class = s.slave_only ? 255 : 248;
|
||||
s.own = (master_t){
|
||||
.valid = true, .p1 = cJSON_GetObjectItem(c, "priority1")->valueint, .cls = s.own_class,
|
||||
@@ -678,7 +710,8 @@ static void ptp_task(void *arg)
|
||||
load_config();
|
||||
int tos2 = s.dscp << 2;
|
||||
setsockopt(s.gen, IPPROTO_IP, IP_TOS, &tos2, sizeof(tos2));
|
||||
ESP_LOGI(TAG, "config applied: role %s, p1 %u p2 %u, domain %u", s.slave_only ? "slave" : "auto/master",
|
||||
ESP_LOGI(TAG, "config applied: role %s, mode %s, p1 %u p2 %u, domain %u",
|
||||
s.slave_only ? "slave" : "auto/master", s.hybrid ? "hybrid" : "multicast",
|
||||
s.own.p1, s.own.p2, s.domain);
|
||||
// Re-run the decision: a foreign GM worse than our new dataset is dropped; as
|
||||
// TimeTransmitter with role slave we stop.
|
||||
@@ -716,8 +749,7 @@ static void ptp_task(void *arg)
|
||||
}
|
||||
} else if (s.gm.valid) {
|
||||
// announceReceiptTimeout x the GM's announce interval
|
||||
int64_t window = (int64_t)s.timeout * (s.gm.log_announce >= 0 ? 1000000LL << s.gm.log_announce
|
||||
: 1000000LL >> -s.gm.log_announce);
|
||||
int64_t window = (int64_t)s.timeout * log_us(s.gm.log_announce);
|
||||
if (now - s.gm.last_us > window) {
|
||||
ESP_LOGW(TAG, "TimeTransmitter lost (no Announce for %lld ms), listening", window / 1000);
|
||||
LOCKED({
|
||||
@@ -730,7 +762,7 @@ static void ptp_task(void *arg)
|
||||
} else if (now >= s.next_dreq_us && s.prev_t2) {
|
||||
send_delay_req();
|
||||
// Delay_Req interval from the GM's Delay_Resp; randomised 0.5..1.5x
|
||||
int64_t iv = s.log_dreq >= 0 ? 1000000LL << s.log_dreq : 1000000LL >> -s.log_dreq;
|
||||
int64_t iv = log_us(s.log_dreq);
|
||||
s.next_dreq_us = now + iv / 2 + (esp_random() % (uint32_t)iv);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user