1 ---------------------------------
2 --- @file timestamping.lua
3 --- @brief Timestamping ...
5 ---------------------------------
7 -- FIXME:
this file is ugly because it doesn
't abstract anything properly
10 local ffi = require "ffi"
11 local dpdkc = require "dpdkc"
12 local dpdk = require "dpdk"
13 local device = require "device"
14 local eth = require "proto.ethernet"
15 local memory = require "memory"
16 local timer = require "timer"
20 local dev = device.__devicePrototype
21 local rxQueue = device.__rxQueuePrototype
22 local txQueue = device.__txQueuePrototype
24 -- registers, mostly 82599/X540-specific
25 local RXMTRL = 0x00005120
26 local TSYNCRXCTL = 0x00005188
27 local RXSTMPL = 0x000051E8
28 local RXSTMPH = 0x000051A4
29 local RXSATRH = 0x000051A8
30 local ETQF_0 = 0x00005128
31 local ETQS_0 = 0x0000EC00
33 local TSYNCTXCTL = 0x00008C00
34 local TXSTMPL = 0x00008C04
35 local TXSTMPH = 0x00008C08
36 local TIMEINCA = 0x00008C14 -- X540 only
37 local TIMINCA = 0x00008C14 -- 82599 only (yes, the datasheets actually uses two different names)
39 local SYSTIMEL = 0x00008C0C
40 local SYSTIMEH = 0x00008C10
41 local TIMEADJL = 0x00008C18
42 local TIMEADJH = 0x00008C1C
44 -- 82580 (and others gbit cards?) registers
45 local TSAUXC = 0x0000B640
46 local TIMINCA_82580 = 0x0000B608
47 local TSYNCRXCTL_82580 = 0x0000B620
48 local TSYNCTXCTL_82580 = 0x0000B614
49 local TXSTMPL_82580 = 0x0000B618
50 local TXSTMPH_82580 = 0x0000B61C
51 local RXSTMPL_82580 = 0x0000B624
52 local RXSTMPH_82580 = 0x0000B628
53 local RXSATRH_82580 = 0x0000B630
54 local ETQF_82580_0 = 0x00005CB0
56 local SYSTIMEL_82580 = 0x0000B600
57 local SYSTIMEH_82580 = 0x0000B604
58 local TIMEADJL_82580 = 0x0000B60C
59 local TIMEADJH_82580 = 0x0000B610
62 local SRRCTL_82580 = {}
64 SRRCTL_82580[i] = 0x0000C00C + 0x40 * i
67 -- TODO: support for more registers
69 -- bit names in registers
70 local TSYNCRXCTL_RXTT = 1
71 local TSYNCRXCTL_TYPE_OFFS = 1
72 local TSYNCRXCTL_TYPE_MASK = bit.lshift(7, TSYNCRXCTL_TYPE_OFFS)
73 local TSYNCRXCTL_EN = bit.lshift(1, 4)
75 local TSYNCTXCTL_TXTT = 1
76 local TSYNCTXCTL_EN = bit.lshift(1, 4)
78 local ETQF_FILTER_ENABLE = bit.lshift(1, 31)
79 local ETQF_FILTER_ENABLE_82580 = bit.lshift(1, 26)
80 local ETQF_QUEUE_ENABLE_82580 = bit.lshift(1, 31)
81 local ETQF_IEEE_1588_TIME_STAMP = bit.lshift(1, 30)
83 local ETQS_RX_QUEUE_OFFS = 16
84 local ETQS_QUEUE_ENABLE = bit.lshift(1, 31)
86 local TIMINCA_IP_OFFS = 24 -- 82599 only
88 local TSAUXC_DISABLE = bit.lshift(1, 31)
90 local SRRCTL_TIMESTAMP = bit.lshift(1, 30)
93 local PKT_TX_IEEE1588_TMST = 0x8000
94 local PKT_TX_IP_CKSUM = 0x1000
95 local PKT_TX_UDP_CKSUM = 0x6000
100 function mod.fillL2Packet(buf, seq)
101 seq = seq or (((3 * 255) + 2) * 255 + 1) * 255
103 buf.pkt.data_len = 60
104 buf:getPtpPacket():fill{
107 buf.ol_flags = bit.bor(buf.ol_flags, PKT_TX_IEEE1588_TMST)
112 function mod.readSeq(buf)
113 if buf.pkt.pkt_len < 4 then
116 return buf:getPtpPacket().ptp:getSequenceID()
119 -- TODO these functions should also use the upper 32 bit...
121 --- try to read a tx timestamp if one is available, returns -1 if no timestamp is available
122 function mod.tryReadTxTimestamp(port)
123 local isIgb = device.get(port):getPciId() == device.PCI_ID_82580
125 if bit.band(dpdkc.read_reg32(port, TSYNCTXCTL_82580), TSYNCTXCTL_TXTT) == 0 then
128 local low = dpdkc.read_reg32(port, TXSTMPL_82580)
129 local high = dpdkc.read_reg32(port, TXSTMPH_82580)
132 if bit.band(dpdkc.read_reg32(port, TSYNCTXCTL), TSYNCTXCTL_TXTT) == 0 then
135 local low = dpdkc.read_reg32(port, TXSTMPL)
136 local high = dpdkc.read_reg32(port, TXSTMPH)
141 function mod.tryReadRxTimestamp(port)
142 local isIgb = device.get(port):getPciId() == device.PCI_ID_82580
144 if bit.band(dpdkc.read_reg32(port, TSYNCRXCTL_82580), TSYNCRXCTL_RXTT) == 0 then
147 local low = dpdkc.read_reg32(port, RXSTMPL_82580)
148 local high = dpdkc.read_reg32(port, RXSTMPH_82580)
151 if bit.band(dpdkc.read_reg32(port, TSYNCRXCTL), TSYNCRXCTL_RXTT) == 0 then
154 local low = dpdkc.read_reg32(port, RXSTMPL)
155 local high = dpdkc.read_reg32(port, RXSTMPH)
160 local function startTimerIxgbe(port, id)
161 -- start system timer, this differs slightly between the two currently supported ixgbe-chips
162 if id == device.PCI_ID_X540 then
163 dpdkc.write_reg32(port, TIMEINCA, 1)
164 elseif id == device.PCI_ID_82599 or id == device.PCI_ID_X520 then
165 dpdkc.write_reg32(port, TIMINCA, bit.bor(2, bit.lshift(2, TIMINCA_IP_OFFS)))
166 else -- should not happen
167 errorf("unsupported ixgbe device %s", device.getDeviceName(port))
171 local function startTimerIgb(port, id)
172 if id == device.PCI_ID_82580 then
174 dpdkc.write_reg32(port, TIMINCA_82580, 1)
175 dpdkc.write_reg32(port, TSAUXC, bit.band(dpdkc.read_reg32(port, TSAUXC), bit.bnot(TSAUXC_DISABLE)))
178 errorf("unsupported igb device %s", device.getDeviceName(port))
182 local function enableRxTimestampsIxgbe(port, queue, udpPort, id)
183 startTimerIxgbe(port, id)
185 dpdkc.write_reg32(port, ETQF_0, bit.bor(ETQF_FILTER_ENABLE, ETQF_IEEE_1588_TIME_STAMP, eth.TYPE_PTP))
186 dpdkc.write_reg32(port, ETQS_0, bit.bor(ETQS_QUEUE_ENABLE, bit.lshift(queue, ETQS_RX_QUEUE_OFFS)))
189 -- enable rx timestamping
190 local val = dpdkc.read_reg32(port, TSYNCRXCTL)
191 val = bit.bor(val, TSYNCRXCTL_EN)
192 val = bit.band(val, bit.bnot(TSYNCRXCTL_TYPE_MASK))
193 val = bit.bor(val, bit.lshift(2, TSYNCRXCTL_TYPE_OFFS))
194 dpdkc.write_reg32(port, TSYNCRXCTL, val)
195 -- timestamp udp messages
196 local val = bit.lshift(udpPort, 16)
197 dpdkc.write_reg32(port, RXMTRL, val)
200 local function enableTxTimestampsIxgbe(port, queue, udpPort, id)
201 startTimerIxgbe(port, id)
202 local val = dpdkc.read_reg32(port, TSYNCTXCTL)
203 dpdkc.write_reg32(port, TSYNCTXCTL, bit.bor(val, TSYNCTXCTL_EN))
206 local function enableRxTimestampsIgb(port, queue, udpPort, id)
207 startTimerIgb(port, id)
209 dpdkc.write_reg32(port, ETQF_82580_0, bit.bor(
210 ETQF_FILTER_ENABLE_82580,
211 ETQF_QUEUE_ENABLE_82580,
212 ETQF_IEEE_1588_TIME_STAMP,
214 bit.lshift(queue, 16)
216 -- L3 filter not supported :(
217 -- enable rx timestamping
218 local val = dpdkc.read_reg32(port, TSYNCRXCTL_82580)
219 val = bit.bor(val, TSYNCRXCTL_EN)
220 val = bit.band(val, bit.bnot(TSYNCRXCTL_TYPE_MASK))
221 val = bit.bor(val, bit.lshift(2, TSYNCRXCTL_TYPE_OFFS))
222 dpdkc.write_reg32(port, TSYNCRXCTL_82580, val)
225 local function enableTxTimestampsIgb(port, queue, udpPort, id)
226 startTimerIgb(port, id)
227 local val = dpdkc.read_reg32(port, TSYNCTXCTL_82580)
228 dpdkc.write_reg32(port, TSYNCTXCTL_82580, bit.bor(val, TSYNCTXCTL_EN))
231 local function enableRxTimestampsAllIgb(port, queue, id)
232 startTimerIgb(port, id)
233 local val = dpdkc.read_reg32(port, TSYNCRXCTL_82580)
234 val = bit.bor(val, TSYNCRXCTL_EN)
235 val = bit.band(val, bit.bnot(TSYNCRXCTL_TYPE_MASK))
236 val = bit.bor(val, bit.lshift(bit.lshift(1, 2), TSYNCRXCTL_TYPE_OFFS))
237 dpdkc.write_reg32(port, TSYNCRXCTL_82580, val)
238 dpdkc.write_reg32(port, SRRCTL_82580[queue], bit.bor(dpdkc.read_reg32(port, SRRCTL_82580[queue]), SRRCTL_TIMESTAMP))
242 -- TODO: implement support for more hardware
243 local enableFuncs = {
244 [device.PCI_ID_X540] = { enableRxTimestampsIxgbe, enableTxTimestampsIxgbe },
245 [device.PCI_ID_X520] = { enableRxTimestampsIxgbe, enableTxTimestampsIxgbe },
246 [device.PCI_ID_82599] = { enableRxTimestampsIxgbe, enableTxTimestampsIxgbe },
247 [device.PCI_ID_82580] = { enableRxTimestampsIgb, enableTxTimestampsIgb, enableRxTimestampsAllIgb } }
249 function rxQueue:enableTimestamps(udpPort)
250 udpPort = udpPort or 0
251 local id = self.dev:getPciId()
252 local f = enableFuncs[id]
255 errorf("RX time stamping on device type %s is not supported", self.dev:getName())
257 f(self.id, self.qid, udpPort, id)
260 function rxQueue:enableTimestampsAllPackets()
261 local id = self.dev:getPciId()
262 local f = enableFuncs[id]
265 errorf("Time stamping all RX packets on device type %s is not supported", self.dev:getName())
267 f(self.id, self.qid, id)
270 function txQueue:enableTimestamps(udpPort)
271 udpPort = udpPort or 0
272 local id = self.dev:getPciId()
273 local f = enableFuncs[id]
276 errorf("TX time stamping on device type %s is not supported", self.dev:getName())
278 f(self.id, self.qid, udpPort, id)
281 local function getTimestamp(wait, f, ...)
288 dpdk.sleepMicros(math.min(10, wait))
290 if not dpdk.running() then
297 --- Read a TX timestamp from the device.
298 function txQueue:getTimestamp(wait)
299 return getTimestamp(wait, mod.tryReadTxTimestamp, self.id)
302 --- Read a RX timestamp from the device.
303 function rxQueue:getTimestamp(wait)
304 return getTimestamp(wait, mod.tryReadRxTimestamp, self.id)
307 --- Check if the NIC saved a timestamp.
308 --- @return the PTP sequence number of the timestamped packet, nil otherwise
309 function dev:hasTimestamp()
310 local isIgb = device.get(self.id):getPciId() == device.PCI_ID_82580
312 if bit.band(dpdkc.read_reg32(self.id, TSYNCRXCTL_82580), TSYNCRXCTL_RXTT) == 0 then
315 return bswap16(bit.rshift(dpdkc.read_reg32(self.id, RXSATRH_82580), 16))
317 if bit.band(dpdkc.read_reg32(self.id, TSYNCRXCTL), TSYNCRXCTL_RXTT) == 0 then
320 return bswap16(bit.rshift(dpdkc.read_reg32(self.id, RXSATRH), 16))
324 local timestampScales = {
325 [device.PCI_ID_X540] = 6.4,
326 [device.PCI_ID_X520] = 6.4,
327 [device.PCI_ID_82599] = 6.4,
328 [device.PCI_ID_82580] = 1, } -- ???
330 function dev:getTimestampScale()
331 return timestampScales[self:getPciId()] or 1
334 local timeRegisters = {
335 [device.PCI_ID_X540] = { 1, SYSTIMEL, SYSTIMEH, TIMEADJL, TIMEADJH },
336 [device.PCI_ID_X520] = { 1, SYSTIMEL, SYSTIMEH, TIMEADJL, TIMEADJH },
337 [device.PCI_ID_82599] = { 1, SYSTIMEL, SYSTIMEH, TIMEADJL, TIMEADJH },
338 [device.PCI_ID_82580] = { 2, SYSTIMEL_82580, SYSTIMEH_82580, TIMEADJL_82580, TIMEADJH_82580 }, }
340 function mod.syncClocks(dev1, dev2)
341 local regs1 = timeRegisters[dev1:getPciId()]
342 local regs2 = timeRegisters[dev2:getPciId()]
343 if regs1[1] ~= regs2[1] then
344 error("NICs incompatible, cannot sync clocks")
346 if regs1[2] ~= regs2[2]
347 or regs1[3] ~= regs2[3]
348 or regs1[4] ~= regs2[4]
349 or regs1[5] ~= regs2[5] then
350 error("NYI: NICs use different timestamp registers")
352 dpdkc.sync_clocks(dev1.id, dev2.id, select(2, unpack(regs1)))
355 function mod.getClockDiff(dev1, dev2)
356 return dpdkc.get_clock_difference(dev1.id, dev2.id) * 6.4
359 function mod.readTimestampsSoftware(queue, memory)
360 -- TODO: do not allocate this in the luajit heap (limited size)
361 -- also: use huge pages
362 local numElements = 4096--memory * 1024 * 1024 / 4
363 local arr = ffi.new("uint32_t[?]", numElements)
364 dpdkc.read_timestamps_software(queue.id, queue.qid, arr, numElements)
369 local timestamper = {}
370 timestamper.__index = timestamper
372 --- Create a new timestamper.
373 function mod:newTimestamper(txQueue, rxQueue, mem, udp)
374 mem = mem or memory.createMemPool(function(buf)
375 -- defaults are good enough for us here
377 buf:getUdpPtpPacket():fill{
381 buf:getPtpPacket():fill{
386 txQueue:enableTimestamps()
387 rxQueue:enableTimestamps()
388 return setmetatable({
390 txBufs = mem:bufArray(1),
391 rxBufs = mem:bufArray(128),
401 function mod:newUdpTimestamper(txQueue, rxQueue, mem)
402 return self:newTimestamper(txQueue, rxQueue, mem, true)
405 --- Try to measure the latency of a single packet.
406 --- @param pktSize optional, the size of the generated packet, optional, defaults to the smallest possible size
407 --- @param packetModifier optional, a function that is called with the generated packet, e.g. to modified addresses
408 --- @param maxWait optional (cannot be the only argument) the time in ms to wait before the packet is assumed to be lost (default = 15)
409 function timestamper:measureLatency(pktSize, packetModifier, maxWait)
410 if type(pktSize) == "function" then -- optional first argument was skipped
411 return self:measureLatency(nil, pktSize, packetModifier)
413 pktSize = pktSize or self.udp and 76 or 60
414 maxWait = (maxWait or 15) / 1000
415 self.txBufs:alloc(pktSize)
416 local buf = self.txBufs[1]
417 buf:enableTimestamps()
418 local expectedSeq = self.seq
419 self.seq = (self.seq + 1) % 2^16
421 buf:getUdpPtpPacket().ptp:setSequenceID(expectedSeq)
423 buf:getPtpPacket().ptp:setSequenceID(expectedSeq)
425 if packetModifier then
429 -- change timestamped UDP port as each packet may be on a different port
430 self.rxQueue:enableTimestamps(buf:getUdpPacket().udp:getDstPort())
431 self.txBufs:offloadUdpChecksums()
433 mod.syncClocks(self.txDev, self.rxDev)
434 -- clear any "leftover" timestamps
435 if self.rxDev:hasTimestamp() then
436 self.rxQueue:getTimestamp()
438 self.txQueue:send(self.txBufs)
439 local tx = self.txQueue:getTimestamp(500)
441 -- sent was successful, try to get the packet back (assume that it is lost after a given delay)
442 local timer = timer:new(maxWait)
443 while timer:running() do
444 local rx = self.rxQueue:tryRecv(self.rxBufs, 1000)
445 local timestampedPkt = self.rxDev:hasTimestamp()
446 if not timestampedPkt then
447 -- NIC didn't save a timestamp yet, just
throw away the packets
450 -- received a timestamped packet (not necessarily in this batch)
451 -- FIXME: this loop may run into an ugly edge-case where we somehow
452 -- lose the timestamped packet during reception (e.g. when this is
453 --
running on a shared core and no filters are
set), this case isn't handled here
455 local buf = self.rxBufs[i]
458 -- the flag seems to be quite pointless
459 if buf:
hasTimestamp() and seq == expectedSeq and seq == timestampedPkt then
463 -- can happen if you hotplug cables
466 local delay = (rxTs - tx) * self.rxDev:getTimestampScale()
469 elseif buf:
hasTimestamp() and seq == timestampedPkt then
470 -- we got a timestamp but the wrong sequence number. meh.
472 -- continue, we may still get our packet :)
473 elseif seq == expectedSeq and seq ~= timestampedPkt then
474 -- we got our packet back but it wasn't timestamped
475 -- we likely ran into the previous case earlier and cleared the ts register too late
482 -- looks like our packet got lost :(
485 -- uhm, how did this happen? an unsupported NIC should throw an error earlier
486 print("Warning: failed to timestamp packet on transmission")
487 timer:
new(maxWait):wait()
function ptpHeader getSequenceID()
Retrieve the sequence ID.
pkt getPtpPacket
Cast the packet to a layer 2 Ptp packet.
function ipsecICV set(icv)
Set the IPsec ICV.
local udp
Udp protocol constants.
local mod
high-level dpdk wrapper
local ptp
Ptp protocol constants.
pkt getUdpPtpPacket
Cast the packet to a Ptp over Udp (IP4) packet.
function pkt hasTimestamp()
Check if the PKT_RX_IEEE1588_TMST flag is set.
function pkt getTimestamp()
Retrieve the time stamp information.
function bufArray freeAll()
Free all buffers in the array. Stops when it encounters the first one that is null.
function mod running(extraTime)
Returns false once the app receives SIGTERM or SIGINT, the time set via setRuntime expires...