flexPTP-basic/ptp_core.c

611 lines
20 KiB
C

/* (C) András Wiesner, 2020-2022 */
#include <math.h>
#include <flexptp_options.h>
#include "ptp_core.h"
#ifndef SIMULATION
#include "FreeRTOS.h"
#include "timers.h"
#endif
#include "ptp_defs.h"
#include "ptp_types.h"
#include "clock_utils.h"
#include "format_utils.h"
#include "msg_utils.h"
#include "sbmc.h"
#include "stats.h"
#include "timeutils.h"
#include "cli_cmds.h"
#include "logging.h"
#ifndef SIMULATION
#include "ptp_msg_tx.h"
#endif
// --------------
// --------------
void ptp_init();
// global state
PtpCoreState gPtpCoreState;
#define S (gPtpCoreState)
static PtpHeader sDelayReqHeader; // header for sending Delay_Reg messages
static SyncCallback sSyncCallback = NULL; // callback function on synchronization
// --------------------------
#define T1 (0)
#define T2 (1)
#define T3 (2)
#define T4 (3)
// --------------------------
void ptp_reset();
// --------------------------
void ptp_set_sync_callback(SyncCallback syncCB)
{
sSyncCallback = syncCB;
}
static void ptp_sbmc_tmr_tick(TimerHandle_t timer);
static void ptp_delreq_tmr_tick(TimerHandle_t xTimer);
//#define RESP_TIMEOUT (2000) // allowed maximal Delay_Resp response time
#define SBMC_TICKRATE (1000) // 1000ms period for SBMC-ticking
void ptp_create_timers()
{
// create smbc timer
S.timers.sbmc = xTimerCreate("sbmctimer", pdMS_TO_TICKS(SBMC_TICKRATE), // timeout
true, // timer operates in repeat mode
(void *)2, // ID
ptp_sbmc_tmr_tick); // callback-function
// create delreq timer
S.timers.delreq = xTimerCreate("delreq", pdMS_TO_TICKS(ptp_logi2ms(0)), true, (void *)3, ptp_delreq_tmr_tick);
}
void ptp_delete_timers()
{
xTimerDelete(S.timers.sbmc, 0);
xTimerDelete(S.timers.delreq, 0);
}
// initialize Delay_Req header
void ptp_init_delay_req_header()
{
sDelayReqHeader.messageType = PTP_MT_Delay_Req;
sDelayReqHeader.transportSpecific = (uint8_t) S.profile.transportSpecific;
sDelayReqHeader.versionPTP = 2; // PTPv2
sDelayReqHeader.messageLength = PTP_HEADER_LENGTH + PTP_TIMESTAMP_LENGTH;
sDelayReqHeader.domainNumber = S.profile.domainNumber;
ptp_clear_flags(&(sDelayReqHeader.flags)); // no flags
sDelayReqHeader.correction_ns = 0;
sDelayReqHeader.correction_subns = 0;
memcpy(&sDelayReqHeader.clockIdentity, &S.hwoptions.clockIdentity, 8);
sDelayReqHeader.sourcePortID = 1; // TODO? No more ports...
sDelayReqHeader.sequenceID = 0; // will change in every sync cycle
sDelayReqHeader.control = PTP_CON_Delay_Req;
sDelayReqHeader.logMessagePeriod = 0;
}
// --------------------------------------
#define DEFAULT_SERVO_OFFSET (2800)
// initialize PTP module
void ptp_init()
{
// create clock identity
ptp_create_clock_identity();
// seed the randomizer
srand(S.hwoptions.clockIdentity);
// reset options
nsToTsI(&S.hwoptions.offset, DEFAULT_SERVO_OFFSET);
// initialize hardware
PTP_HW_INIT(PTP_INCREMENT_NSEC, PTP_ADDEND_INIT);
// initialize controller
PTP_SERVO_INIT();
// create timers
ptp_create_timers();
xTimerStart(S.timers.sbmc, 0); // TODO!!
// reset PTP subsystem
ptp_reset();
#ifdef CLI_REG_CMD
// register cli commands
ptp_register_cli_commands();
#endif // CLI_REG_CMD
}
// deinit PTP module
void ptp_deinit()
{
#ifdef CLI_REG_CMD
// remove cli commands
ptp_remove_cli_commands();
#endif // CLI_REG_CMD
// deinitialize controller
PTP_SERVO_DEINIT();
// delete timers
ptp_delete_timers();
}
// construct and send Delay_Req message (NON-REENTRANT!)
void ptp_send_delay_req_message()
{
static TimestampI zeroTs = { 0, 0 }; // timestamp appended at the end of packet
// PTP message
static RawPtpMessage delReqMsg = { 0 };
delReqMsg.size = sDelayReqHeader.messageLength;
delReqMsg.pTs = &(S.scd.t[T3]);
delReqMsg.tx_dm = S.profile.delayMechanism;
delReqMsg.tx_mc = PTP_MC_EVENT;
// increment sequenceID
sDelayReqHeader.sequenceID = ++S.messaging.delay_reqSequenceID;
sDelayReqHeader.domainNumber = S.profile.domainNumber;
// fill in header
ptp_construct_binary_header(delReqMsg.data, &sDelayReqHeader);
// fill in timestamp
ptp_write_binary_timestamps(delReqMsg.data, &zeroTs, 1);
// send message
ptp_transmit_enqueue(&delReqMsg);
}
// perform clock correction based on gathered timestamps (NON-REENTRANT!)
void ptp_perform_correction()
{
// don't do any processing if no delay_request data is present
if (!nonZeroI(&S.network.meanPathDelay)) {
return;
}
static TimestampI syncMa_prev = { 0 };
// timestamps and time intervals
TimestampI d, syncMa, syncSl, delReqSl, delReqMa;
// copy timestamps to assign them with meaningful names
syncMa = S.scd.t[T1];
syncSl = S.scd.t[T2];
delReqSl = S.scd.t[T3];
delReqMa = S.scd.t[T4];
// log timestamps (if enabled)
CLILOG(S.logging.timestamps,
"seqID: %u\n"
"T1: %d.%09d <- Sync TX (master)\n"
"T2: %d.%09d <- Sync RX (slave) \n"
"T3: %d.%09d <- Del_Req TX (slave) \n"
"T4: %d.%09d <- Del_Req RX (master)\n\n",
(uint32_t) S.messaging.sequenceID,
(int32_t) syncMa.sec, syncMa.nanosec, (int32_t) syncSl.sec, syncSl.nanosec, (int32_t) delReqSl.sec, delReqSl.nanosec, (int32_t) delReqMa.sec, delReqMa.nanosec);
// compute difference between master and slave clock
/*subTime(&d, &syncSl, &syncMa); // t2 - t1 ...
subTime(&d, &d, &delReqMa); // - t4 ...
addTime(&d, &d, &delReqSl); // + t3
divTime(&d, &d, 2); // division by 2 */
subTime(&d, &syncSl, &syncMa); // t2 - t1 ...
subTime(&d, &d, &S.network.meanPathDelay); // - MPD
// substract offset
subTime(&d, &d, &S.hwoptions.offset);
// normalize time difference (eliminate malformed time value issues)
normTime(&d);
// translate time difference into clock tick unit
int32_t d_ticks = tsToTick(&d, PTP_CLOCK_TICK_FREQ_HZ);
// if time difference is at least one second, then jump the clock
int64_t d_ns = nsI(&d);
if (llabs(d_ns) > 20000000) {
PTP_SET_CLOCK(syncMa.sec, syncMa.nanosec);
CLILOG(S.logging.info, "Time difference is over 20ms [%ldns], performing coarse correction!\n", d_ns);
syncMa_prev = syncMa;
return;
}
// prepare data to pass to the controller
double measSyncPeriod_ns;
TimestampI measSyncPeriod;
subTime(&measSyncPeriod, &syncMa, &syncMa_prev);
measSyncPeriod_ns = nsI(&measSyncPeriod);
PtpServoAuxInput saux = { S.scd, S.messaging.logSyncPeriod, S.messaging.syncPeriodMs, measSyncPeriod_ns };
// run controller
float corr_ppb = PTP_SERVO_RUN(nsI(&d), &saux);
// compute addend value
int64_t compAddend = (int64_t) S.hwclock.addend + (int64_t) (corr_ppb * PTP_ADDEND_CORR_PER_PPB_F); // compute addend value
S.hwclock.addend = MIN(compAddend, 0xFFFFFFFF); // limit to 32-bit range
// write addend into hardware
PTP_SET_ADDEND(S.hwclock.addend);
// collect statistics
ptp_collect_stats(nsI(&d));
// log on cli (if enabled)
CLILOG(S.logging.def, "%d %09d %d %09d %d % 9d %d %u %f %ld %09lu\n",
(int32_t) syncMa.sec, syncMa.nanosec, (int32_t) delReqMa.sec, delReqMa.nanosec,
(int32_t) d.sec, d.nanosec, d_ticks, S.hwclock.addend, corr_ppb, nsI(&S.network.meanPathDelay), (uint64_t) measSyncPeriod_ns);
// call sync callback if defined
//if (sSyncCallback != NULL) {
// sSyncCallback(nsI(&d), &scd, S.addend);
//}
syncMa_prev = syncMa;
}
static void ptp_delreq_tmr_tick(TimerHandle_t xTimer)
{
// check that our last Delay_Req has been responded
CLILOG(S.messaging.delay_reqSequenceID != S.messaging.lastRespondedDelReqId, "(P)Del_Req #%d was unresponded!\n", S.messaging.delay_reqSequenceID);
ptp_send_delay_req_message();
}
static void ptp_start_delreq_timer()
{
// start DelReq timer if not running yet
if (S.profile.logDelayReqPeriod != PTP_LOGPER_SYNCMATCHED) {
xTimerReset(S.timers.delreq, 0);
xTimerChangePeriod(S.timers.delreq, pdMS_TO_TICKS(ptp_logi2ms(S.profile.logDelayReqPeriod)), 0);
xTimerStart(S.timers.delreq, 0);
}
}
static void ptp_stop_delreq_timer()
{
// stop DelReq timer (since there's no master to talk to)
xTimerStop(S.timers.delreq, portMAX_DELAY);
}
static void ptp_sbmc_tmr_tick(TimerHandle_t timer)
{
PtpSBmcState *s = &(S.sbmc);
// main state machine dropout
if (s->mstState == SBMC_MASTER_OK) {
s->masterTOCntr += SBMC_TICKRATE;
if (s->masterTOCntr > 2 * s->masterAnnPer_ms + SBMC_TICKRATE) {
s->mstState = SBMC_NO_MASTER;
s->masterProps.priority1 = 255;
s->masterProps.grandmasterClockIdentity = 0;
CLILOG(S.logging.info, "Master lost!\n");
ptp_stop_delreq_timer();
}
}
// candidate switchover machine dropout
if (s->candState == SBMC_CANDIDATE_COLLECTION) {
s->candTOCntr += SBMC_TICKRATE;
if (s->candTOCntr > 2 * s->candAnnPer_ms + SBMC_TICKRATE) {
s->candState = SBMC_NO_CANDIDATE;
}
}
}
// handle announce messages
void ptp_handle_announce_msg(PtpAnnounceBody * pAnn, PtpHeader * pHeader)
{
PtpSBmcState *s = &(S.sbmc);
bool masterChanged = false;
switch (s->mstState) {
case SBMC_NO_MASTER: // no master, accept the first one announcing itself
s->masterProps = *pAnn; // save master settings
s->mstState = SBMC_MASTER_OK; // master found
s->candState = SBMC_NO_CANDIDATE; // stop candidate processing, since master is selected
s->masterAnnPer_ms = ptp_logi2ms(pHeader->logMessagePeriod);
masterChanged = true; // indicate that master has changed
// no break here!
case SBMC_MASTER_OK: // already bound to master
if (pAnn->grandmasterClockIdentity == s->masterProps.grandmasterClockIdentity) { // only clear when receiving from elected master
s->masterTOCntr = 0; // clear counter
}
}
// run only candidate state evaluation if no main state changed took place
if (!masterChanged) {
switch (s->candState) {
case SBMC_NO_CANDIDATE:{
if (ptp_select_better_master(pAnn, &s->masterProps) == 0) {
s->candProps = *pAnn;
s->candAnnPer_ms = ptp_logi2ms(pHeader->logMessagePeriod);
s->candState = SBMC_CANDIDATE_COLLECTION; // switch to next syncState
s->candCntr = 1;
}
break;
}
case SBMC_CANDIDATE_COLLECTION:{
// determine that the received master clock dataset is not better than the previously found one
if (ptp_select_better_master(pAnn, &s->candProps) == 0) { // if better, reset counter and stay in current syncState
s->candProps = *pAnn;
s->candCntr = 1;
} else {
// verify, that announce message comes from the same source
if (pAnn->grandmasterClockIdentity == s->candProps.grandmasterClockIdentity) {
s->candCntr++; // advance counter
s->candTOCntr = 0; // clear counter
}
// if counter expired...
if (s->candCntr == ANNOUNCE_COLLECTION_WINDOW) {
s->masterProps = s->candProps; // change master
s->candProps.priority1 = 255; // set to worst value, i.e. make other values meaningless
s->candProps.grandmasterClockIdentity = 0; // also clear ID
masterChanged = true;
s->candState = SBMC_NO_CANDIDATE; // switch back to NO_CANDIDATE syncState
}
}
break;
}
}
}
if (masterChanged) {
if (S.logging.info) {
MSG("Switched to new master: ");
ptp_print_clock_identity(s->masterProps.grandmasterClockIdentity);
MSG("\n");
}
ptp_start_delreq_timer();
}
}
void ptp_handle_correction_field(TimestampI * ts, const PtpHeader * pHeader)
{
TimestampI correctionField;
correctionField.sec = 0;
correctionField.nanosec = pHeader->correction_ns;
subTime(ts, ts, &correctionField);
normTime(ts);
}
// TODO TODO TODO ...
void ptp_compute_mean_path_delay(const TimestampI * pTs, TimestampI * pMPD)
{
//static double a = 0.533488091091103; // fc = 10Hz
static double a = 0.00186744273170799; // fc = 1Hz
double mpd_prev_ns = nsI(pMPD);
//MSG("%f\n", mpd_prev_ns);
// compute difference between master and slave clock
subTime(pMPD, &pTs[T2], &pTs[T1]); // t2 - t1 ...
subTime(pMPD, pMPD, &pTs[T3]); // - t3 ...
addTime(pMPD, pMPD, &pTs[T4]); // + t4
divTime(pMPD, pMPD, 2); // division by 2
// performing time error filtering
double mpd_new_ns = nsI(pMPD);
mpd_new_ns = a * mpd_prev_ns + (1 - a) * mpd_new_ns; // filtering equation
nsToTsI(pMPD, (int64_t) mpd_new_ns);
}
// reset PTP subsystem
void ptp_reset()
{
// reset subsystem states...
memset(&S.messaging, 0, sizeof(PtpMessagingState)); // messaging state
S.hwclock.addend = PTP_ADDEND_INIT; // HW clock state
PTP_SET_ADDEND(PTP_ADDEND_INIT);
memset(&S.network, 0, sizeof(PtpNetworkState)); // network state
memset(&S.sbmc, 0, sizeof(PtpSBmcState)); // SBMC state
memset(&S.scd, 0, sizeof(PtpSyncCycleData)); // Sync cycle data
// remove delreq time
ptp_stop_delreq_timer();
// reset controller
PTP_SERVO_RESET();
// (re)init header for sending (P)Delay_Req messages
ptp_init_delay_req_header();
// reset statistics
ptp_clear_stats();
}
// packet processing (NON-REENTRANT!!)
void ptp_process_packet(RawPtpMessage * pRawMsg)
{
static PtpHeader header; // PTP header
static Delay_RespIdentification delay_respID; // identification received in every Delay_Resp packet
//TimestampI correctionField;
// header readout
ptp_extract_header(&header, pRawMsg->data);
// if other than Announce received
//MSG("%d\n", header.messageType);
// consider only messages in our domain
if (header.domainNumber != S.profile.domainNumber || header.transportSpecific != S.profile.transportSpecific) {
return;
}
if (header.messageType == PTP_MT_Announce) {
PtpMasterProperties newMstProp;
ptp_extract_announce_message(&newMstProp, pRawMsg->data);
ptp_handle_announce_msg(&newMstProp, &header);
return;
}
// process non-Announce messages
if (header.messageType == PTP_MT_Sync || header.messageType == PTP_MT_Follow_Up) {
switch (S.messaging.m2sState) {
// wait for Sync message
case SIdle:{
// switch into next state if Sync packet has arrived
if (header.messageType == PTP_MT_Sync) {
// save sync interval
S.messaging.logSyncPeriod = header.logMessagePeriod;
S.messaging.syncPeriodMs = ptp_logi2ms(header.logMessagePeriod);
//MSG("%d\n", header.logMessagePeriod);
// save reception time
S.scd.t[T2] = pRawMsg->ts;
// switch to next syncState
S.messaging.sequenceID = header.sequenceID;
// handle two step/one step messaging
//if (header.flags.PTP_TWO_STEP) {
S.messaging.m2sState = SWaitFollowUp;
//} else {
/*ptp_extract_timestamps(&sSyncData.t1, pRawMsg->data, 1); // extract t1
ptp_handle_correction_field(&sSyncData.t2, &header); // process correction field
ptp_perform_correction(); // run clock correction */
//}
}
break;
}
// wait for Follow_Up message
case SWaitFollowUp:
if (header.messageType == PTP_MT_Follow_Up) {
// check sequence ID if the response is ours
if (header.sequenceID == S.messaging.sequenceID) {
ptp_extract_timestamps(&S.scd.t[T1], pRawMsg->data, 1); // read t1
ptp_handle_correction_field(&S.scd.t[T2], &header); // process correction field
// log correction field (if enabled)
CLILOG(S.logging.corr, "C [Follow_Up]: %09lu\n", header.correction_ns);
// delay Delay_Req transmission with a random amount of time
//vTaskDelay(pdMS_TO_TICKS(rand() % (S.syncIntervalMs / 2)));
// send Delay_Req message
if (S.profile.logDelayReqPeriod == PTP_LOGPER_SYNCMATCHED) {
ptp_send_delay_req_message();
}
// jump clock if error is way too big...
TimestampI d;
subTime(&d, &S.scd.t[T2], &S.scd.t[T1]);
if (d.sec != 0) {
PTP_SET_CLOCK(S.scd.t[T1].sec, S.scd.t[T1].nanosec);
}
// run servo only if not syncmatched
if (S.profile.logDelayReqPeriod != PTP_LOGPER_SYNCMATCHED) {
ptp_perform_correction();
}
// switch to next syncState
S.messaging.m2sState = SIdle;
}
}
break;
}
}
// ------ (P)DELAY_RESPONSE PROCESSING --------
// wait for (P)Delay_Resp message
if ((header.messageType == PTP_MT_Delay_Resp && S.profile.delayMechanism == PTP_DM_E2E)) {
if (header.sequenceID == S.messaging.delay_reqSequenceID) { // read clock ID of requester
ptp_read_delay_resp_id_data(&delay_respID, pRawMsg->data);
// if the response was sent to us as a response to our Delay_Req then continue processing
if (delay_respID.requestingSourceClockIdentity == S.hwoptions.clockIdentity && delay_respID.requestingSourcePortIdentity == sDelayReqHeader.sourcePortID) {
ptp_extract_timestamps(&S.scd.t[T4], pRawMsg->data, 1); // store t4
ptp_handle_correction_field(&S.scd.t[T4], &header); // substract correction field from t4
// compute mean path delay
ptp_compute_mean_path_delay(S.scd.t, &S.network.meanPathDelay);
// store last response ID
S.messaging.lastRespondedDelReqId = header.sequenceID;
// perform correction if operating on syncmatched mode
if (S.profile.logDelayReqPeriod == PTP_LOGPER_SYNCMATCHED) {
ptp_perform_correction();
}
// log correction field (if enabled)
CLILOG(S.logging.corr, "C [Del_Resp]: %09lu\n", header.correction_ns);
}
}
}
}
void ptp_store_config(PtpConfig * pConfig)
{
pConfig->profile = S.profile;
pConfig->offset = S.hwoptions.offset;
pConfig->logging = (int)(S.logging.def) | (int)(S.logging.info) << 1 | (int)(S.logging.corr) << 2 | (int)(S.logging.timestamps) << 3 | (int)(S.logging.locked) << 4;
}
void ptp_load_config(const PtpConfig * pConfig)
{
S.profile = pConfig->profile;
S.hwoptions.offset = pConfig->offset;
S.logging.def = pConfig->logging & 1;
S.logging.info = (pConfig->logging >> 1) & 1;
S.logging.corr = (pConfig->logging >> 2) & 1;
S.logging.timestamps = (pConfig->logging >> 3) & 1;
S.logging.locked = (pConfig->logging >> 4) & 1;
}
void ptp_load_config_from_dump(const void *pDump)
{
PtpConfig config;
memcpy(&config, pDump, sizeof(PtpConfig));
ptp_load_config(&config);
ptp_reset();
}
// -----------------------------------------------