SatCat5
temac.cc
1 // Copyright 2021-2024 The Aerospace Corporation.
3 // This file is a part of SatCat5, licensed under CERN-OHL-W v2 or later.
5 
6 #include <satcat5/utils.h>
7 #include <satcat5/log.h>
8 #include <hal_ublaze/temac.h>
9 
10 using satcat5::ptp::Time;
14 
15 // Define address offsets for specific control registers:
16 // (Refer to Xilinx PG051 "Tri-Mode Ethernet MAC v9.0")
17 static constexpr unsigned REG_RXCONFIG = 0x404 / 4;
18 static constexpr unsigned REG_TXCONFIG = 0x408 / 4;
19 static constexpr unsigned REG_FILTER = 0x708 / 4;
20 static constexpr unsigned REG_AVB_RX_BASE = 0x10000 / 4;
21 static constexpr unsigned REG_AVB_TX_BASE = 0x11000 / 4;
22 static constexpr unsigned REG_AVB_TX_CTRL = 0x12000 / 4;
23 static constexpr unsigned REG_AVB_RX_CTRL = 0x12004 / 4;
24 static constexpr unsigned REG_AVB_OFFS_NSEC = 0x12800 / 4;
25 static constexpr unsigned REG_AVB_OFFS_SECL = 0x12808 / 4;
26 static constexpr unsigned REG_AVB_OFFS_SECH = 0x1280C / 4;
27 static constexpr unsigned REG_AVB_RATE = 0x12810 / 4;
28 static constexpr unsigned REG_AVB_NOW_NSEC = 0x12814 / 4;
29 static constexpr unsigned REG_AVB_NOW_SECL = 0x12818 / 4;
30 static constexpr unsigned REG_AVB_NOW_SECH = 0x1281C / 4;
31 
32 // Max for AVB rate register is 63.99 nanoseconds per clock.
33 static constexpr u32 AVB_RATE_MAX = 0x3FFFFFFu;
34 
35 // Parametes for the AVB receive buffers:
36 static constexpr unsigned AVB_RXBUF_DATA = 0; // Offset to Rx frame data
37 static constexpr unsigned AVB_RXBUF_TIME = 252; // Offset to Rx timestamp
38 static constexpr unsigned AVB_RXBUF_SIZE = 256; // Size of each buffer
39 static constexpr unsigned AVB_RXBUF_DLEN = AVB_RXBUF_TIME - AVB_RXBUF_DATA;
40 static constexpr unsigned AVB_TXBUF_LEN = 0; // Offset to Tx frame length
41 static constexpr unsigned AVB_TXBUF_DATA = 8; // Offset to Tx frame data
42 static constexpr unsigned AVB_TXBUF_TIME = 252; // Offset to Tx timestamp
43 static constexpr unsigned AVB_TXBUF_SIZE = 256; // Size of each buffer
44 static constexpr unsigned AVB_TXBUF_DLEN = AVB_TXBUF_TIME - AVB_TXBUF_DATA;
45 
46 // AVB mapping of PTP types to AVB transmit buffer index
47 static constexpr unsigned AVB_TX_SYNC = 0;
48 static constexpr unsigned AVB_TX_FOLLOW_UP = 1;
49 static constexpr unsigned AVB_TX_DLY_REQ = 2;
50 static constexpr unsigned AVB_TX_DLY_RESP = 3;
51 static constexpr unsigned AVB_TX_ANNOUNCE = 4;
52 
53 // From IEEE1588 spec
54 static constexpr u8 PTP_TYPE_MASK = 0x0F;
55 static constexpr u8 PTP_TYPE_SYNC = 0x0;
56 static constexpr u8 PTP_TYPE_DLY_REQ = 0x1;
57 static constexpr u8 PTP_TYPE_PATH_DLY_REQ = 0x2;
58 static constexpr u8 PTP_TYPE_FOLLOW_UP = 0x8;
59 static constexpr u8 PTP_TYPE_DLY_RESP = 0x9;
60 static constexpr u8 PTP_TYPE_ANNOUNCE = 0xB;
61 
62 Temac::Temac(uintptr_t baseaddr)
63  : m_regs((volatile u32*) baseaddr)
64 {
65  m_regs[REG_RXCONFIG] = 0x90000000u; // Reset + Disable FCS passing (RX)
66  m_regs[REG_TXCONFIG] = 0x90000000u; // Reset + Disable FCS passing (TX)
67  m_regs[REG_FILTER] = 0x80000100u; // Promiscuous mode + AVB filter
68 }
69 
70 TemacAvb::TemacAvb(uintptr_t baseaddr, int irq_idx)
71  : Temac(baseaddr)
72  , satcat5::io::ReadableRedirect(&m_rxbuff)
73  , satcat5::irq::Handler("TemacAVB", irq_idx)
74  , m_txbuff(m_txrawbuff, sizeof(m_txrawbuff), 16)
75  , m_rxbuff(m_rxrawbuff, sizeof(m_rxrawbuff), 16)
76  , m_tx_callback(nullptr)
77  , m_prev_buf_idx(0)
78  , m_frames_waiting(0x00)
79 {
80  // Additional setup for AVB system
81  m_regs[REG_AVB_TX_CTRL] = 0; // Tx reset
82  m_regs[REG_AVB_RX_CTRL] = 1; // Rx reset
83  // Read and discard status register to clear interrupt flag.
84  (void) m_regs[REG_AVB_RX_CTRL];
85 
86  // TEMAC AVB requires a GMII or RGMII interface - both have 125MHz clocks
87  // Format is fixed point 6.20, period is 8ns.
88  avb_set_rate(8 << 20);
89  avb_jump_by({ 0, 0 });
90 }
91 
93  m_tx_callback = tx_callback;
94 }
95 
97 {
98  // A read from the nanoseconds register samples the entire counter.
99  u32 time_n = m_regs[REG_AVB_NOW_NSEC];
100  u64 time_s = m_regs[REG_AVB_NOW_SECL];
101  u64 time_e = m_regs[REG_AVB_NOW_SECH];
102  time_s += (time_e << 32);
103  return TemacTime{(s64)time_s, time_n};
104 }
105 
107 {
108  // Sanity check on rate before updating the register.
109  if (incr > AVB_RATE_MAX) incr = AVB_RATE_MAX;
110  m_regs[REG_AVB_RATE] = incr;
111 }
112 
114 {
115  // New timestamp is committed once the nanoseconds register is written
116  m_regs[REG_AVB_OFFS_SECH] = (u32)(delta.sec >> 32);
117  m_regs[REG_AVB_OFFS_SECL] = (u32)(delta.sec >> 0);
118  m_regs[REG_AVB_OFFS_NSEC] = delta.nsec;
119 }
120 
122 {
123  // Testing indicates that shifts smaller than one second have no effect.
124  // This appears to be a bug in the Xilinx IP, so we need a workaround.
125  if (amount.abs() < satcat5::ptp::ONE_SECOND)
126  return amount; // Skip adjustment if it would have no effect.
127  TemacTime tmp = {amount.round_secs(), amount.round_nsec()};
128  avb_jump_by(tmp); // Apply adjustment
129  return Time(0); // Report success (zero residue)
130 }
131 
132 void TemacAvb::clock_rate(s64 offset)
133 {
134  // Limit maximum offset from nominal rate.
135  constexpr s32 NOMINAL = (8 << 20); // 8.0 nsec per clock
136  constexpr s32 MAX_OFFSET = (1 << 20); // 1.0 nsec per clock
137  if (offset < -MAX_OFFSET) offset = -MAX_OFFSET;
138  if (offset > MAX_OFFSET) offset = MAX_OFFSET;
139  m_offset = offset;
140  s32 rate = NOMINAL + (s32)offset;
141  avb_set_rate((u32)rate);
142 }
143 
145 {
146  TemacTime now = avb_get_time();
147  return Time(now.sec, now.nsec);
148 }
149 
151 {
152  // Read the current buffer pointer to see if there are new packets.
153  // (Reading this register also clears the interrupt flag, if set.)
154  u32 rx_status = m_regs[REG_AVB_RX_CTRL];
155 
156  // Status reg contains index of last element in ringbuffer
157  u32 ringbuf_end = ((rx_status >> 8) + 1) & 0xF;
158 
159  // Copy each received packet for later processing:
160  while (m_prev_buf_idx != ringbuf_end) {
161  // Get pointer to the next buffer:
162  const u8* rx_ptp_buf = (const u8*)(m_regs + REG_AVB_RX_BASE);
163  rx_ptp_buf += AVB_RXBUF_SIZE * m_prev_buf_idx;
164 
165  // Extract useful fields from that buffer:
166  // Note: No length indicator, always copy full contents.
167  const u8* pdata = (const u8*)(rx_ptp_buf + AVB_RXBUF_DATA);
168  const u32* ptime = (const u32*)(rx_ptp_buf + AVB_RXBUF_TIME);
169 
170  // Copy received data and metadata to the working buffer.
171  // (Write timestamp first for easier packet processing.)
172  m_rxbuff.write_u32(*ptime);
173  m_rxbuff.write_bytes(AVB_RXBUF_DLEN, pdata);
174  m_rxbuff.write_finalize();
175  // Increment buffer index with wraparound.
176  m_prev_buf_idx = (m_prev_buf_idx + 1) & 0xF;
177  }
178 }
179 
181 {
182  // Polling loop added due to issues with TEMAC interrupt reliability.
184  irq_event();
185  check_frames_waiting();
186 }
187 
188 void TemacAvb::check_frames_waiting()
189 {
190  // Frame waiting indicators are bits 15:8. Check for updates
191  u8 new_frames_waiting = (u8) (m_regs[REG_AVB_TX_CTRL] >> 8);
192  u8 frame_updates = new_frames_waiting ^ m_frames_waiting;
193  if (frame_updates == 0) {
194  return;
195  }
196 
197  // Some frame updates, capture timestamps and send updates if the frame was sent (1 -> 0)
198  for (u32 i = 0; i < 8; i++) {
199  if (((frame_updates >> i) & 0x1) == 0) { // No updates for this frame
200  continue;
201  }
202  if (((new_frames_waiting >> i) & 0x1) == 0) { // Frame was sent
203 
204  // Log(LOG_DEBUG, "Frame was sent from buffer #").write10(i);
205 
206  // Get TX time nsec field, taken at end of trame TX
207  const u8* tx_ptp_buf = (const u8*)(m_regs + REG_AVB_TX_BASE);
208  tx_ptp_buf += AVB_TXBUF_SIZE * i;
209  const u32* ptime = (const u32*)(tx_ptp_buf + AVB_TXBUF_TIME);
210  // Build full TX time with sec field
211  auto now = avb_get_time(); // Convert to PTPTime?
212  s64 tx_time_sec = now.sec;
213  u32 tx_time_nsec = *ptime;
214  if (now.nsec < tx_time_nsec && now.sec > 0) { // Second rollover since packet was sent
215  tx_time_sec--;
216  }
217  Time tx_time(tx_time_sec, tx_time_nsec);
218 
219  // Save state before callback to avoid recusive loop
220  m_frames_waiting = new_frames_waiting;
221 
222  // Send egress time to higher layer state machines
223  if (m_tx_callback) {
224  switch(i) {
225  case AVB_TX_SYNC:
226  m_tx_callback->tx_sync(tx_time);
227  break;
228  case AVB_TX_DLY_REQ:
229  m_tx_callback->tx_delay_req(tx_time);
230  break;
231  default: {}
232  }
233  }
234 
235  } else { // send_frame() sets m_frames_waiting, so something else queued a packet
236  Log(LOG_DEBUG, "Frame was silently queued to buffer #").write10(i);
237  m_frames_waiting = new_frames_waiting;
238  }
239  }
240 }
241 
242 void TemacAvb::send_frame(const u8* buf, unsigned buf_len)
243 {
244 
245  // Sanity check
246  if (buf_len < 14+34) { // min size for eth+PTP headers
247  Log(LOG_WARNING, "Runt frame passed to TemacAvb::send_frame(), ignoring...");
248  return;
249  }
250 
251  // Look up which PTP frame this is by peeking the buffer
252  // Log(LOG_DEBUG, "Sending PTP eth frame").write(buf, 32);
253  u8 ptp_type = buf[14] & 0x0F; // Skip eth header, second nibble of PTP header
254  u8 avb_tx_buf_idx = 0;
255  switch(ptp_type) {
256  // Below defs from TEMAC docs: PG051 v9.0 Table 2-57
257  case PTP_TYPE_SYNC:
258  avb_tx_buf_idx = AVB_TX_SYNC;
259  break;
260  case PTP_TYPE_FOLLOW_UP:
261  avb_tx_buf_idx = AVB_TX_FOLLOW_UP;
262  break;
263  case PTP_TYPE_DLY_REQ:
264  case PTP_TYPE_PATH_DLY_REQ:
265  // There is no Delay_Req buffer? Reusing Pdelay_Req buffer.
266  avb_tx_buf_idx = AVB_TX_DLY_REQ;
267  break;
268  case PTP_TYPE_DLY_RESP:
269  avb_tx_buf_idx = AVB_TX_DLY_RESP;
270  break;
271  case PTP_TYPE_ANNOUNCE:
272  avb_tx_buf_idx = AVB_TX_ANNOUNCE;
273  break;
274  default:
275  Log(LOG_WARNING, "TemacAvb::send_frame() was passed a PTP ethernet frame "
276  "with unsupported PTP type").write10((u32) ptp_type);
277  return;
278  }
279 
280  // Ensure the frame we want to send is not waiting
281  check_frames_waiting();
282  if (m_frames_waiting & (1 << avb_tx_buf_idx)) {
283  // Already pending a frame for this buffer, don't overwrite
284  Log(LOG_INFO, "A new PTP frame was requested in a pending buffer and "
285  "will not be overwritten.");
286  return;
287  }
288 
289  // Get pointer to destination buffer
290  // Write the packet length in the first byte, 7 reserved bytes, data starts on 8th byte
291  const u8* tx_ptp_buf = (const u8*)(m_regs + REG_AVB_TX_BASE);
292  tx_ptp_buf += AVB_TXBUF_SIZE * avb_tx_buf_idx;
293  u8* plen = (u8*)(tx_ptp_buf + AVB_TXBUF_LEN);
294  *plen = (u8) buf_len;
295  u32* pdata = (u32*)(tx_ptp_buf + AVB_TXBUF_DATA);
296  memcpy(pdata, buf, buf_len);
297 
298  // Notify the AVB core intent to send this packet using 8 LSBs of TX CTRL reg
299  m_regs[REG_AVB_TX_CTRL] = (1 << avb_tx_buf_idx);
300  // The frame could be sent before the call to check_frames_waiting(), so go ahead and
301  // register the queued transmission in the member var
302  m_frames_waiting |= (1 << avb_tx_buf_idx); // See above note
303  check_frames_waiting();
304 }
bool write_finalize() override
Mark end of frame and release temporary working data.
Definition: pkt_buffer.cc:105
void write_bytes(unsigned nbytes, const void *src) override
Write 0 or more bytes from a buffer.
Definition: pkt_buffer.cc:73
Automatic lock or mutex.
const char *const m_label
Human-readable label, for debugging.
The Log class creates and formats one log message.
Definition: log.h:195
Log & write10(s32 val)
Print integer as a decimal value with no leading zeros.
Definition: log.cc:261
High-precision timestamp for use with PTP / IEEE1588.
Definition: ptp_time.h:41
satcat5::ptp::Time abs() const
Standard arithmetic operations.
Definition: ptp_time.cc:112
u32 round_nsec() const
Return "nanoseconds" field after rounding to the nearest nanosecond.
Definition: ptp_time.h:78
s64 round_secs() const
Return "seconds" field after rounding to the nearest nanosecond.
Definition: ptp_time.h:75
Xilinx TEMAC with Audio-Video-Bridge (AVB) functionality.
Definition: temac.h:59
void irq_event() override
Method called whenever an interrupt is triggered.
Definition: temac.cc:150
void avb_set_rate(u32 incr)
Update AVB rate register.
Definition: temac.cc:106
void avb_jump_by(const satcat5::ublaze::TemacTime &delta)
One-time increment of the AVB internal timer.
Definition: temac.cc:113
satcat5::ublaze::TemacTime avb_get_time()
Read current time from the AVB internal timer.
Definition: temac.cc:96
void set_tx_callback(TemacAvbTxCallback *tx_callback)
Register callbacks for transmitted timestamps for PTP packets.
Definition: temac.cc:92
void poll_always() override
Child class MUST override this method.
Definition: temac.cc:180
satcat5::ptp::Time clock_adjust(const satcat5::ptp::Time &amount) override
Clock-adjustment API for ptp::TrackingClock.
Definition: temac.cc:121
void send_frame(const u8 *buf, unsigned buflen)
Send an arbitrary PTP frame with Ethernet header.
Definition: temac.cc:242
void clock_rate(s64 offset) override
Clock-adjustment API for ptp::TrackingClock.
Definition: temac.cc:132
satcat5::ptp::Time clock_now() override
Clock-adjustment API for ptp::TrackingClock.
Definition: temac.cc:144
TemacAvb(uintptr_t baseaddr, int irq_idx)
Initialize the core and link to specified instance.
Definition: temac.cc:70
Defines callback methods for timestamped egress times.
Definition: temac.h:42
virtual void tx_sync(const satcat5::ptp::Time &sync_egress)
Callback for PTP "Sync" messages.
Definition: temac.h:45
virtual void tx_delay_req(const satcat5::ptp::Time &delay_req_egress)
Callback for PTP "Delay request" messages.
Definition: temac.h:47
Basic Xilinx TEMAC functionality.
Definition: temac.h:31
Diagnostic logging to UART and/or Ethernet ports.
Timestamp format used by the AVB core.
Definition: temac.h:18
Miscellaneous mathematical utility functions.