MoonGen
 All Files Functions Variables Pages
timestamping.lua
Go to the documentation of this file.
1 ---------------------------------
2 --- @file timestamping.lua
3 --- @brief Timestamping ...
4 --- @todo TODO docu
5 ---------------------------------
6 
7 -- FIXME: this file is ugly because it doesn't abstract anything properly
8 local mod = {}
9 
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"
17 
18 require "proto.ptp"
19 
20 local dev = device.__devicePrototype
21 local rxQueue = device.__rxQueuePrototype
22 local txQueue = device.__txQueuePrototype
23 
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
32 
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)
38 
39 local SYSTIMEL = 0x00008C0C
40 local SYSTIMEH = 0x00008C10
41 local TIMEADJL = 0x00008C18
42 local TIMEADJH = 0x00008C1C
43 
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
55 
56 local SYSTIMEL_82580 = 0x0000B600
57 local SYSTIMEH_82580 = 0x0000B604
58 local TIMEADJL_82580 = 0x0000B60C
59 local TIMEADJH_82580 = 0x0000B610
60 
61 
62 local SRRCTL_82580 = {}
63 for i = 0, 7 do
64  SRRCTL_82580[i] = 0x0000C00C + 0x40 * i
65 end
66 
67 -- TODO: support for more registers
68 
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)
74 
75 local TSYNCTXCTL_TXTT = 1
76 local TSYNCTXCTL_EN = bit.lshift(1, 4)
77 
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)
82 
83 local ETQS_RX_QUEUE_OFFS = 16
84 local ETQS_QUEUE_ENABLE = bit.lshift(1, 31)
85 
86 local TIMINCA_IP_OFFS = 24 -- 82599 only
87 
88 local TSAUXC_DISABLE = bit.lshift(1, 31)
89 
90 local SRRCTL_TIMESTAMP = bit.lshift(1, 30)
91 
92 -- offloading flags
93 local PKT_TX_IEEE1588_TMST = 0x8000
94 local PKT_TX_IP_CKSUM = 0x1000
95 local PKT_TX_UDP_CKSUM = 0x6000
96 
97 
98 ---
99 --- @deprecated
100 function mod.fillL2Packet(buf, seq)
101  seq = seq or (((3 * 255) + 2) * 255 + 1) * 255
102  buf.pkt.pkt_len = 60
103  buf.pkt.data_len = 60
104  buf:getPtpPacket():fill{
105  ptpSequenceID = seq
106  }
107  buf.ol_flags = bit.bor(buf.ol_flags, PKT_TX_IEEE1588_TMST)
108 end
109 
110 ---
111 --- @deprecated
112 function mod.readSeq(buf)
113  if buf.pkt.pkt_len < 4 then
114  return nil
115  end
116  return buf:getPtpPacket().ptp:getSequenceID()
117 end
118 
119 -- TODO these functions should also use the upper 32 bit...
120 
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
124  if isIgb then
125  if bit.band(dpdkc.read_reg32(port, TSYNCTXCTL_82580), TSYNCTXCTL_TXTT) == 0 then
126  return nil
127  end
128  local low = dpdkc.read_reg32(port, TXSTMPL_82580)
129  local high = dpdkc.read_reg32(port, TXSTMPH_82580)
130  return low
131  else
132  if bit.band(dpdkc.read_reg32(port, TSYNCTXCTL), TSYNCTXCTL_TXTT) == 0 then
133  return nil
134  end
135  local low = dpdkc.read_reg32(port, TXSTMPL)
136  local high = dpdkc.read_reg32(port, TXSTMPH)
137  return low
138  end
139 end
140 
141 function mod.tryReadRxTimestamp(port)
142  local isIgb = device.get(port):getPciId() == device.PCI_ID_82580
143  if isIgb then
144  if bit.band(dpdkc.read_reg32(port, TSYNCRXCTL_82580), TSYNCRXCTL_RXTT) == 0 then
145  return nil
146  end
147  local low = dpdkc.read_reg32(port, RXSTMPL_82580)
148  local high = dpdkc.read_reg32(port, RXSTMPH_82580)
149  return low
150  else
151  if bit.band(dpdkc.read_reg32(port, TSYNCRXCTL), TSYNCRXCTL_RXTT) == 0 then
152  return nil
153  end
154  local low = dpdkc.read_reg32(port, RXSTMPL)
155  local high = dpdkc.read_reg32(port, RXSTMPH)
156  return low
157  end
158 end
159 
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))
168  end
169 end
170 
171 local function startTimerIgb(port, id)
172  if id == device.PCI_ID_82580 then
173  -- start the timer
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)))
176 
177  else
178  errorf("unsupported igb device %s", device.getDeviceName(port))
179  end
180 end
181 
182 local function enableRxTimestampsIxgbe(port, queue, udpPort, id)
183  startTimerIxgbe(port, id)
184  -- l2 rx filter
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)))
187  -- L3 filter
188  -- TODO
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)
198 end
199 
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))
204 end
205 
206 local function enableRxTimestampsIgb(port, queue, udpPort, id)
207  startTimerIgb(port, id)
208  -- l2 rx filter
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,
213  eth.TYPE_PTP,
214  bit.lshift(queue, 16)
215  ))
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)
223 end
224 
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))
229 end
230 
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))
239 end
240 
241 
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 } }
248 
249 function rxQueue:enableTimestamps(udpPort)
250  udpPort = udpPort or 0
251  local id = self.dev:getPciId()
252  local f = enableFuncs[id]
253  f = f and f[1]
254  if not f then
255  errorf("RX time stamping on device type %s is not supported", self.dev:getName())
256  end
257  f(self.id, self.qid, udpPort, id)
258 end
259 
260 function rxQueue:enableTimestampsAllPackets()
261  local id = self.dev:getPciId()
262  local f = enableFuncs[id]
263  f = f and f[3]
264  if not f then
265  errorf("Time stamping all RX packets on device type %s is not supported", self.dev:getName())
266  end
267  f(self.id, self.qid, id)
268 end
269 
270 function txQueue:enableTimestamps(udpPort)
271  udpPort = udpPort or 0
272  local id = self.dev:getPciId()
273  local f = enableFuncs[id]
274  f = f and f[2]
275  if not f then
276  errorf("TX time stamping on device type %s is not supported", self.dev:getName())
277  end
278  f(self.id, self.qid, udpPort, id)
279 end
280 
281 local function getTimestamp(wait, f, ...)
282  wait = wait or 0
283  repeat
284  local ts = f(...)
285  if ts then
286  return ts
287  end
288  dpdk.sleepMicros(math.min(10, wait))
289  wait = wait - 10
290  if not dpdk.running() then
291  break
292  end
293  until wait < 0
294  return nil
295 end
296 
297 --- Read a TX timestamp from the device.
298 function txQueue:getTimestamp(wait)
299  return getTimestamp(wait, mod.tryReadTxTimestamp, self.id)
300 end
301 
302 --- Read a RX timestamp from the device.
303 function rxQueue:getTimestamp(wait)
304  return getTimestamp(wait, mod.tryReadRxTimestamp, self.id)
305 end
306 
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
311  if isIgb then
312  if bit.band(dpdkc.read_reg32(self.id, TSYNCRXCTL_82580), TSYNCRXCTL_RXTT) == 0 then
313  return nil
314  end
315  return bswap16(bit.rshift(dpdkc.read_reg32(self.id, RXSATRH_82580), 16))
316  else
317  if bit.band(dpdkc.read_reg32(self.id, TSYNCRXCTL), TSYNCRXCTL_RXTT) == 0 then
318  return nil
319  end
320  return bswap16(bit.rshift(dpdkc.read_reg32(self.id, RXSATRH), 16))
321  end
322 end
323 
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, } -- ???
329 
330 function dev:getTimestampScale()
331  return timestampScales[self:getPciId()] or 1
332 end
333 
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 }, }
339 
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")
345  end
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")
351  end
352  dpdkc.sync_clocks(dev1.id, dev2.id, select(2, unpack(regs1)))
353 end
354 
355 function mod.getClockDiff(dev1, dev2)
356  return dpdkc.get_clock_difference(dev1.id, dev2.id) * 6.4
357 end
358 
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)
365  return arr
366 end
367 
368 
369 local timestamper = {}
370 timestamper.__index = timestamper
371 
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
376  if udp then
377  buf:getUdpPtpPacket():fill{
378  ethSrc = txQueue,
379  }
380  else
381  buf:getPtpPacket():fill{
382  ethSrc = txQueue,
383  }
384  end
385  end)
386  txQueue:enableTimestamps()
387  rxQueue:enableTimestamps()
388  return setmetatable({
389  mem = mem,
390  txBufs = mem:bufArray(1),
391  rxBufs = mem:bufArray(128),
392  txQueue = txQueue,
393  rxQueue = rxQueue,
394  txDev = txQueue.dev,
395  rxDev = rxQueue.dev,
396  seq = 1,
397  udp = udp,
398  }, timestamper)
399 end
400 
401 function mod:newUdpTimestamper(txQueue, rxQueue, mem)
402  return self:newTimestamper(txQueue, rxQueue, mem, true)
403 end
404 
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)
412  end
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
420  if self.udp then
421  buf:getUdpPtpPacket().ptp:setSequenceID(expectedSeq)
422  else
423  buf:getPtpPacket().ptp:setSequenceID(expectedSeq)
424  end
425  if packetModifier then
426  packetModifier(buf)
427  end
428  if self.udp 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()
432  end
433  mod.syncClocks(self.txDev, self.rxDev)
434  -- clear any "leftover" timestamps
435  if self.rxDev:hasTimestamp() then
436  self.rxQueue:getTimestamp()
437  end
438  self.txQueue:send(self.txBufs)
439  local tx = self.txQueue:getTimestamp(500)
440  if tx then
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
448  self.rxBufs:freeAll()
449  else
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
454  for i = 1, rx do
455  local buf = self.rxBufs[i]
456  local seq = (self.udp and buf:getUdpPtpPacket() or buf:getPtpPacket()).ptp:getSequenceID()
457  -- not sure if checking :hasTimestamp is worth it
458  -- the flag seems to be quite pointless
459  if buf:hasTimestamp() and seq == expectedSeq and seq == timestampedPkt then
460  -- yay!
461  local rxTs = self.rxQueue:getTimestamp()
462  if not rxTs then
463  -- can happen if you hotplug cables
464  return nil
465  end
466  local delay = (rxTs - tx) * self.rxDev:getTimestampScale()
467  self.rxBufs:freeAll()
468  return delay
469  elseif buf:hasTimestamp() and seq == timestampedPkt then
470  -- we got a timestamp but the wrong sequence number. meh.
471  self.rxQueue:getTimestamp() -- clears the register
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
476  self.rxBufs:freeAll()
477  return
478  end
479  end
480  end
481  end
482  -- looks like our packet got lost :(
483  return
484  else
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()
488  end
489 end
490 
491 
492 
493 return mod
494 
function ptpHeader getSequenceID()
Retrieve the sequence ID.
pkt getPtpPacket
Cast the packet to a layer 2 Ptp packet.
Definition: ptp.lua:265
function ipsecICV set(icv)
Set the IPsec ICV.
function mod new(n)
local udp
Udp protocol constants.
Definition: udp.lua:23
local mod
high-level dpdk wrapper
Definition: dpdk.lua:6
local ptp
Ptp protocol constants.
Definition: ptp.lua:23
pkt getUdpPtpPacket
Cast the packet to a Ptp over Udp (IP4) packet.
Definition: ptp.lua:267
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...