/* Maximum number of events expected to make up a PTP event */ #define MAX_EVENT_FRAGS 3
/* Maximum delay, ms, to begin synchronisation */ #define MAX_SYNCHRONISE_WAIT_MS 2
/* How long, at most, to spend synchronising */ #define SYNCHRONISE_PERIOD_NS 250000
/* How often to update the shared memory time */ #define SYNCHRONISATION_GRANULARITY_NS 200
/* Minimum permitted length of a (corrected) synchronisation time */ #define DEFAULT_MIN_SYNCHRONISATION_NS 120
/* Maximum permitted length of a (corrected) synchronisation time */ #define MAX_SYNCHRONISATION_NS 1000
/* How many (MC) receive events that can be queued */ #define MAX_RECEIVE_EVENTS 8
/* Length of (modified) moving average. */ #define AVERAGE_LENGTH 16
/* How long an unmatched event or packet can be held */ #define PKT_EVENT_LIFETIME_MS 10
/* How long unused unicast filters can be held */ #define UCAST_FILTER_EXPIRY_JIFFIES msecs_to_jiffies(30000)
/* Offsets into PTP packet for identification. These offsets are from the *startoftheIPheader,nottheMACheader.NotethatneitherPTPV1nor *PTPV2permittheuseofIPV4options.
*/ #define PTP_DPORT_OFFSET 22
/* NIC synchronised with single word of time only comprising *partialsecondsandfullnanoseconds:10^9~2^30so2bitsforseconds.
*/ #define MC_NANOSECOND_BITS 30 #define MC_NANOSECOND_MASK ((1 << MC_NANOSECOND_BITS) - 1) #define MC_SECOND_MASK ((1 << (32 - MC_NANOSECOND_BITS)) - 1)
/* Maximum parts-per-billion adjustment that is acceptable */ #define MAX_PPB 1000000
/* Precalculate scale word to avoid long long division at runtime */ /* This is equivalent to 2^66 / 10^9. */ #define PPB_SCALE_WORD ((1LL << (57)) / 1953125LL)
/* How much to shift down after scaling to convert to FP40 */ #define PPB_SHIFT_FP40 26 /* ... and FP44. */ #define PPB_SHIFT_FP44 22
/* PTP 'extra' channel is still a traffic channel, but we only create TX queues *ifPTPusesMACTXtimestamps,notifPTPusestheMCdirectlytotransmit.
*/ staticbool efx_ptp_want_txqs(struct efx_channel *channel)
{ return efx_ptp_use_mac_tx_timestamps(channel->efx);
}
/* Copy software statistics */ for (i = 0; i < PTP_STAT_COUNT; i++) { if (efx_ptp_stat_desc[i].dma_width) continue;
stats[i] = *(unsignedint *)((char *)efx->ptp_data +
efx_ptp_stat_desc[i].offset);
}
/* Fetch MC statistics. We *must* fill in all statistics or *riskleakingkernelmemorytouserland,soiftheMCDI *requestfailswepretendwegotzeroes.
*/
MCDI_SET_DWORD(inbuf, PTP_IN_OP, MC_CMD_PTP_OP_STATUS);
MCDI_SET_DWORD(inbuf, PTP_IN_PERIPH_ID, 0);
rc = efx_mcdi_rpc(efx, MC_CMD_PTP, inbuf, sizeof(inbuf),
outbuf, sizeof(outbuf), NULL); if (rc)
memset(outbuf, 0, sizeof(outbuf));
efx_nic_update_stats(efx_ptp_stat_desc, PTP_STAT_COUNT,
efx_ptp_stat_mask,
stats, _MCDI_PTR(outbuf, 0), false);
return PTP_STAT_COUNT;
}
/* To convert from s27 format to ns we multiply then divide by a power of 2. *Fortheconversionfromnstos27,theoperationisalsoconvertedtoa *multiplyandshift.
*/ #define S27_TO_NS_SHIFT (27) #define NS_TO_S27_MULT (((1ULL << 63) + NSEC_PER_SEC / 2) / NSEC_PER_SEC) #define NS_TO_S27_SHIFT (63 - S27_TO_NS_SHIFT) #define S27_MINOR_MAX (1 << S27_TO_NS_SHIFT)
/* For Huntington platforms NIC time is in seconds and fractions of a second *wheretheminorregisteronlyuses27bitsinunitsof2^-27s.
*/ staticvoid efx_ptp_ns_to_s27(s64 ns, u32 *nic_major, u32 *nic_minor)
{ struct timespec64 ts = ns_to_timespec64(ns);
u32 maj = (u32)ts.tv_sec;
u32 min = (u32)(((u64)ts.tv_nsec * NS_TO_S27_MULT +
(1ULL << (NS_TO_S27_SHIFT - 1))) >> NS_TO_S27_SHIFT);
/* The conversion can result in the minor value exceeding the maximum. *Inthiscase,rounduptothenextsecond.
*/ if (min >= S27_MINOR_MAX) {
min -= S27_MINOR_MAX;
maj++;
}
/* For Medford2 platforms the time is in seconds and quarter nanoseconds. */ staticvoid efx_ptp_ns_to_s_qns(s64 ns, u32 *nic_major, u32 *nic_minor)
{ struct timespec64 ts = ns_to_timespec64(ns);
if (channel)
major = channel->sync_timestamp_major; return major;
}
/* The 8000 series and later can provide the time from the MAC, which is only *48bitslongandprovidesmeta-informationinthetop2bits.
*/ static ktime_t
efx_ptp_mac_nic_to_ktime_correction(struct efx_nic *efx, struct efx_ptp_data *ptp,
u32 nic_major, u32 nic_minor,
s32 correction)
{
u32 sync_timestamp;
ktime_t kt = { 0 };
s16 delta;
if (!(nic_major & 0x80000000)) {
WARN_ON_ONCE(nic_major >> 16);
/* Medford provides 48 bits of timestamp, so we must get the top *16bitsfromthetimesynceventstate. * *Weonlyhavethelower16bitsofthetimenow,butwedo *haveafullresolutiontimestampatsomepointinpast.As *longasthedifferencebetweenthe(real)nowandthesync *islessthan2^15,thenwecanreconstructthedifference *betweenthosetwonumbersusingonlythelower16bitsof *each. * *Putanotherway * *a-b=((amodk)-b)modk * *when-k/2<(a-b)<k/2.Inourcasekis2^16.Weknow *(amodk)andb,socancalculatethedelta,a-b. *
*/
sync_timestamp = last_sync_timestamp_major(efx);
/* Because delta is s16 this does an implicit mask down to *16bitswhichiswhatweneed,assuming *MEDFORD_TX_SECS_EVENT_BITSis16.deltaissignedsothat *wecandealwiththe(unlikely)caseofsynctimestamps *arrivingfromthefuture.
*/
delta = nic_major - sync_timestamp;
/* Recover the fully specified time now, by applying the offset *tothe(fullyspecified)synctime.
*/
nic_major = sync_timestamp + delta;
/* Get PTP attributes and set up time conversions */ staticint efx_ptp_get_attributes(struct efx_nic *efx)
{
MCDI_DECLARE_BUF(inbuf, MC_CMD_PTP_IN_GET_ATTRIBUTES_LEN);
MCDI_DECLARE_BUF(outbuf, MC_CMD_PTP_OUT_GET_ATTRIBUTES_LEN); struct efx_ptp_data *ptp = efx->ptp_data; int rc;
u32 fmt;
size_t out_len;
/* Get the PTP attributes. If the NIC doesn't support the operation we *usethedefaultformatforcompatibilitywitholderNICsi.e. *secondsandnanoseconds.
*/
MCDI_SET_DWORD(inbuf, PTP_IN_OP, MC_CMD_PTP_OP_GET_ATTRIBUTES);
MCDI_SET_DWORD(inbuf, PTP_IN_PERIPH_ID, 0);
rc = efx_mcdi_rpc_quiet(efx, MC_CMD_PTP, inbuf, sizeof(inbuf),
outbuf, sizeof(outbuf), &out_len); if (rc == 0) {
fmt = MCDI_DWORD(outbuf, PTP_OUT_GET_ATTRIBUTES_TIME_FORMAT);
} elseif (rc == -EINVAL) {
fmt = MC_CMD_PTP_OUT_GET_ATTRIBUTES_SECONDS_NANOSECONDS;
} elseif (rc == -EPERM) {
pci_info(efx->pci_dev, "no PTP support\n"); return rc;
} else {
efx_mcdi_display_error(efx, MC_CMD_PTP, sizeof(inbuf),
outbuf, sizeof(outbuf), rc); return rc;
}
/* Set up the shift for conversion between frequency *adjustmentsinparts-per-billionandthefixed-point *fractionalnsformatthattheadapteruses.
*/ if (ptp->capabilities & (1 << MC_CMD_PTP_OUT_GET_ATTRIBUTES_FP44_FREQ_ADJ_LBN))
ptp->adjfreq_ppb_shift = PPB_SHIFT_FP44; else
ptp->adjfreq_ppb_shift = PPB_SHIFT_FP40;
return0;
}
/* Get PTP timestamp corrections */ staticint efx_ptp_get_timestamp_corrections(struct efx_nic *efx)
{
MCDI_DECLARE_BUF(inbuf, MC_CMD_PTP_IN_GET_TIMESTAMP_CORRECTIONS_LEN);
MCDI_DECLARE_BUF(outbuf, MC_CMD_PTP_OUT_GET_TIMESTAMP_CORRECTIONS_V2_LEN); int rc;
size_t out_len;
/* Get the timestamp corrections from the NIC. If this operation is *notsupported(olderNICs)thennocorrectionisrequired.
*/
MCDI_SET_DWORD(inbuf, PTP_IN_OP,
MC_CMD_PTP_OP_GET_TIMESTAMP_CORRECTIONS);
MCDI_SET_DWORD(inbuf, PTP_IN_PERIPH_ID, 0);
/* Repeatedly send the host time to the MC which will capture the hardware *time.
*/ staticvoid efx_ptp_send_times(struct efx_nic *efx, struct pps_event_time *last_time)
{ struct pps_event_time now; struct timespec64 limit; struct efx_ptp_data *ptp = efx->ptp_data; int *mc_running = ptp->start.addr;
/* Write host time for specified period or until MC is done */ while ((timespec64_compare(&now.ts_real, &limit) < 0) &&
READ_ONCE(*mc_running)) { struct timespec64 update_time; unsignedint host_time;
/* Don't update continuously to avoid saturating the PCIe bus */
update_time = now.ts_real;
timespec64_add_ns(&update_time, SYNCHRONISATION_GRANULARITY_NS); do {
pps_get_ts(&now);
} while ((timespec64_compare(&now.ts_real, &update_time) < 0) &&
READ_ONCE(*mc_running));
/* Synchronise NIC with single word of time only */
host_time = (now.ts_real.tv_sec << MC_NANOSECOND_BITS |
now.ts_real.tv_nsec); /* Update host time in NIC memory */
efx->type->ptp_write_host_time(efx, host_time);
}
*last_time = now;
}
/* Read a timeset from the MC's results and partial process. */ staticvoid efx_ptp_read_timeset(MCDI_DECLARE_STRUCT_PTR(data), struct efx_ptp_timeset *timeset)
{ unsigned start_ns, end_ns;
/* Read the set of results and find the last good host-MC *synchronizationresult.TheMCtimeswhenitfinishesreadingthe *hosttimesothecorrectedwindowtimeshouldbefairlyconstant *foragivenplatform.Incrementstatsforanyresultsthatappear *tobeerroneous.
*/ for (i = 0; i < number_readings; i++) {
s32 window, corrected; struct timespec64 wait;
/* Convert the NIC time at last good sync into kernel time. *Nocorrectionisrequired-thistimeistheoutputofa *firmwareprocess.
*/
mc_time = ptp->nic_to_kernel_time(ptp->timeset[last_good].major,
ptp->timeset[last_good].minor, 0);
/* Calculate delay from NIC top of second to last_time */
delta.tv_nsec += ktime_to_timespec64(mc_time).tv_nsec;
/* Set PPS timestamp to match NIC top of second */
ptp->host_time_pps = *last_time;
pps_sub_ts(&ptp->host_time_pps, delta);
return0;
}
/* Synchronize times between the host and the MC */ staticint efx_ptp_synchronize(struct efx_nic *efx, unsignedint num_readings)
{ struct efx_ptp_data *ptp = efx->ptp_data;
MCDI_DECLARE_BUF(synch_buf, MC_CMD_PTP_OUT_SYNCHRONIZE_LENMAX);
size_t response_length; int rc; unsignedlong timeout; struct pps_event_time last_time = {}; unsignedint loops = 0; int *start = ptp->start.addr;
/* Increment the bad syncs counter if the synchronize fails, whatever *thereason.
*/ if (rc != 0)
++ptp->bad_syncs;
return rc;
}
/* Transmit a PTP packet via the dedicated hardware timestamped queue. */ staticvoid efx_ptp_xmit_skb_queue(struct efx_nic *efx, struct sk_buff *skb)
{ struct efx_ptp_data *ptp_data = efx->ptp_data;
u8 type = efx_tx_csum_type_skb(skb); struct efx_tx_queue *tx_queue;
tx_queue = efx_channel_get_tx_queue(ptp_data->channel, type); if (tx_queue && tx_queue->timestamping) {
skb_get(skb);
/* This code invokes normal driver TX code which is always *protectedfromsoftirqswhencalledfromgenericTXcode, *whichinturndisablespreemption.Lookat__dev_queue_xmit *whichusesrcu_read_lock_bhdisablingpreemptionforRCU *plusdisablingsoftirqs.WedonotneedRCUreader *protectionhere. * *AlthoughitistheoreticallysafeforcurrentPTPTX/RXcode *runningwithoutdisablingsoftirqs,therearethreegood *reasondfordoingso: * *1)Thecodeinvokedismainlyimplementedfornon-PTP *packetsanditisalwaysexecutedwithsoftirqs *disabled. *2)ThisbeingasinglePTPpacket,bettertonot *interruptitsprocessingbysoftirqswhichcanlead *tohighlatencies. *3)netdev_xmit_morecheckspreemptionisdisabledand *triggersaBUG_ONifnot.
*/
local_bh_disable();
efx_enqueue_skb(tx_queue, skb);
local_bh_enable();
/* We need to add the filters after enqueuing the packet. *Otherwise,there'shighlatencyinsendingbackthe *timestamp,causingptp4ltimeouts
*/
efx_ptp_insert_unicast_filter(efx, skb);
dev_consume_skb_any(skb);
} else {
WARN_ONCE(1, "PTP channel has no timestamped tx queue\n");
dev_kfree_skb_any(skb);
}
}
/* Transmit a PTP packet, via the MCDI interface, to the wire. */ staticvoid efx_ptp_xmit_skb_mc(struct efx_nic *efx, struct sk_buff *skb)
{
MCDI_DECLARE_BUF(txtime, MC_CMD_PTP_OUT_TRANSMIT_LEN); struct efx_ptp_data *ptp_data = efx->ptp_data; struct skb_shared_hwtstamps timestamps;
size_t len; int rc;
if (!ptp->channel || !list_empty(&ptp->rxfilters_mcast)) return0;
/* Must filter on both event and general ports to ensure *thatthereisnopacketre-ordering.
*/
rc = efx_ptp_insert_ipv4_filter(efx, &ptp->rxfilters_mcast,
htonl(PTP_ADDR_IPV4), PTP_EVENT_PORT, 0); if (rc < 0) goto fail;
/* if the NIC supports hw timestamps by the MAC, we can support *PTPoverIPv6andEthernet
*/ if (efx_ptp_use_mac_tx_timestamps(efx)) {
rc = efx_ptp_insert_ipv6_filter(efx, &ptp->rxfilters_mcast,
&ptp_addr_ipv6, PTP_EVENT_PORT, 0); if (rc < 0) goto fail;
rc = efx_ptp_probe(efx, channel); /* Failure to probe PTP is not fatal; this channel will just not be *usedforanything. *InthecaseofEPERM,efx_ptp_probewillprintitsownmessage(in *efx_ptp_get_attributes()),sowedon'tneedto.
*/ if (rc && rc != -EPERM)
netif_warn(efx, drv, efx->net_dev, "Failed to probe PTP, rc=%d\n", rc); return0;
}
void efx_ptp_remove(struct efx_nic *efx)
{ if (!efx->ptp_data) return;
(void)efx_ptp_disable(efx);
cancel_work_sync(&efx->ptp_data->work);
cancel_delayed_work_sync(&efx->ptp_data->cleanup_work); if (efx->ptp_data->pps_workwq)
cancel_work_sync(&efx->ptp_data->pps_work);
/* Correct version? */ if (ptp->mode == MC_CMD_PTP_MODE_V1) { if (!pskb_may_pull(skb, PTP_V1_MIN_LENGTH)) { returnfalse;
}
data = skb->data;
version = ntohs(*(__be16 *)&data[PTP_V1_VERSION_OFFSET]); if (version != PTP_VERSION_V1) { returnfalse;
}
} else { if (!pskb_may_pull(skb, PTP_V2_MIN_LENGTH)) { returnfalse;
}
data = skb->data;
version = data[PTP_V2_VERSION_OFFSET]; if ((version & PTP_VERSION_V2_MASK) != PTP_VERSION_V2) { returnfalse;
}
}
/* Does this packet require timestamping? */ if (ntohs(*(__be16 *)&data[PTP_DPORT_OFFSET]) == PTP_EVENT_PORT) {
match->state = PTP_PACKET_STATE_UNMATCHED;
/* We expect the sequence number to be in the same position in *thepacketforPTPV1andV2
*/
BUILD_BUG_ON(PTP_V1_SEQUENCE_OFFSET != PTP_V2_SEQUENCE_OFFSET);
BUILD_BUG_ON(PTP_V1_SEQUENCE_LENGTH != PTP_V2_SEQUENCE_LENGTH);
} else {
match->state = PTP_PACKET_STATE_MATCH_UNWANTED;
}
/* Transmit a PTP packet. This has to be transmitted by the MC *itself,throughanMCDIcall.MCDIcallsaren'tpermitted *inthetransmitpathsodefertheactualtransmissiontoasuitableworker.
*/ int efx_ptp_tx(struct efx_nic *efx, struct sk_buff *skb)
{ struct efx_ptp_data *ptp = efx->ptp_data;
ts_info->so_timestamping |= (SOF_TIMESTAMPING_TX_HARDWARE |
SOF_TIMESTAMPING_RX_HARDWARE |
SOF_TIMESTAMPING_RAW_HARDWARE); /* Check licensed features. If we don't have the license for TX *timestamps,theNICwillnotsupportthem.
*/ if (efx_ptp_use_mac_tx_timestamps(efx)) { struct efx_ef10_nic_data *nic_data = efx->nic_data;
int efx_ptp_set_ts_config(struct efx_nic *efx, struct kernel_hwtstamp_config *config, struct netlink_ext_ack __always_unused *extack)
{ /* Not a PTP enabled port */ if (!efx->ptp_data) return -EOPNOTSUPP;
return efx_ptp_ts_init(efx, config);
}
int efx_ptp_get_ts_config(struct efx_nic *efx, struct kernel_hwtstamp_config *config)
{ /* Not a PTP enabled port */ if (!efx->ptp_data) return -EOPNOTSUPP;
*config = efx->ptp_data->config; return0;
}
/* When extracting the sync timestamp minor value, we should discard *theleastsignificanttwobits.Thesearenotrequiredinorder *toreconstructfull-rangetimestampsandtheyareoptionallyused *toreportstatusdependingontheoptionssuppliedwhensubscribing *forsyncevents.
*/
channel->sync_timestamp_major = MCDI_EVENT_FIELD(*ev, PTP_TIME_MAJOR);
channel->sync_timestamp_minor =
(MCDI_EVENT_FIELD(*ev, PTP_TIME_MINOR_MS_8BITS) & 0xFC)
<< ptp->nic_time.sync_event_minor_shift;
/* if sync events have been disabled then we want to silently ignore *thisevent,sothrowawayresult.
*/
(void) cmpxchg(&channel->sync_events_state, SYNC_EVENTS_REQUESTED,
SYNC_EVENTS_VALID);
}
/* get the difference between the packet and sync timestamps, *moduloonesecond
*/
diff = pkt_timestamp_minor - channel->sync_timestamp_minor; if (pkt_timestamp_minor < channel->sync_timestamp_minor)
diff += ptp->nic_time.minor_max;
/* do we roll over a second boundary and need to carry the one? */
carry = (channel->sync_timestamp_minor >= ptp->nic_time.minor_max - diff) ? 1 : 0;
if (diff <= ptp->nic_time.sync_event_diff_max) { /* packet is ahead of the sync event by a quarter of a second or *less(allowingforfuzz)
*/
pkt_timestamp_major = channel->sync_timestamp_major + carry;
} elseif (diff >= ptp->nic_time.sync_event_diff_min) { /* packet is behind the sync event but within the fuzz factor. *ThismeanstheRXpacketandsynceventcrossedastheywere *placedontheeventqueue,whichcansometimeshappen.
*/
pkt_timestamp_major = channel->sync_timestamp_major - 1 + carry;
} else { /* it's outside tolerance in both directions. this might be *indicativeofusmissingsynceventsforsomereason,so *we'llcallitanerrorratherthanriskgivingabogus *timestamp.
*/
netif_vdbg(efx, drv, efx->net_dev, "packet timestamp %x too far from sync event %x:%x\n",
pkt_timestamp_minor, channel->sync_timestamp_major,
channel->sync_timestamp_minor); return;
}
/* attach the timestamps to the skb */
timestamps = skb_hwtstamps(skb);
timestamps->hwtstamp =
ptp->nic_to_kernel_time(pkt_timestamp_major,
pkt_timestamp_minor,
ptp->ts_corrections.general_rx);
}
staticint efx_phc_settime(struct ptp_clock_info *ptp, conststruct timespec64 *e_ts)
{ /* Get the current NIC time, efx_phc_gettime. *Subtractfromthedesiredtimetogettheoffset *callefx_phc_adjtimewiththeoffset
*/ int rc; struct timespec64 time_now; struct timespec64 delta;
rc = efx_phc_gettime(ptp, &time_now); if (rc != 0) return rc;
delta = timespec64_sub(*e_ts, time_now);
rc = efx_phc_adjtime(ptp, timespec64_to_ns(&delta)); if (rc != 0) return rc;
void efx_ptp_defer_probe_with_channel(struct efx_nic *efx)
{ /* Check whether PTP is implemented on this NIC. The DISABLE *operationwillsucceedifandonlyifitisimplemented.
*/ if (efx_ptp_disable(efx) == 0)
efx->extra_channel_type[EFX_EXTRA_CHANNEL_PTP] =
&efx_ptp_channel_type;
}
void efx_ptp_start_datapath(struct efx_nic *efx)
{ if (efx_ptp_restart(efx))
netif_err(efx, drv, efx->net_dev, "Failed to restart PTP.\n"); /* re-enable timestamping if it was previously enabled */ if (efx->type->ptp_set_ts_sync_events)
efx->type->ptp_set_ts_sync_events(efx, true, true);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.