CHERIoT RTOS
A compartmentalised RTOS for CHERIoT hardware
Loading...
Searching...
No Matches
platform-ethernet.hh
1#pragma once
2#include <allocator.h>
3#include <array>
4#include <cheri.hh>
5#include <cstddef>
6#include <cstdint>
7#include <debug.hh>
8#include <futex.h>
9#include <interrupt.h>
10#include <locks.hh>
11#include <optional>
12#include <platform/concepts/ethernet.hh>
13#include <platform/sunburst/platform-spi.hh>
14#include <thread.h>
15#include <type_traits>
16
17DECLARE_AND_DEFINE_INTERRUPT_CAPABILITY(ethernetInterruptCapability,
18 InterruptName::EthernetInterrupt,
19 true,
20 true);
21
22/**
23 * The driver for KSZ8851 SPI Ethernet MAC.
24 */
26{
27 /**
28 * Flag set when we're debugging this driver.
29 */
30 static constexpr bool DebugEthernet = false;
31
32 /**
33 * Flag set to log messages when frames are dropped.
34 */
35 static constexpr bool DebugDroppedFrames = true;
36
37 /**
38 * Maxmium size of a single Ethernet frame.
39 */
40 static constexpr uint16_t MaxFrameSize = 1500;
41
42 /**
43 * Helper for conditional debug logs and assertions.
44 */
45 using Debug = ConditionalDebug<DebugEthernet, "Ethernet driver">;
46
47 /**
48 * Helper for conditional debug logs and assertions for dropped frames.
49 */
50 using DebugFrameDrops =
51 ConditionalDebug<DebugDroppedFrames, "Ethernet driver">;
52
53 /**
54 * Import the Capability helper from the CHERI namespace.
55 */
56 template<typename T>
57 using Capability = CHERI::Capability<T>;
58
59 /**
60 * SPI commands
61 */
62 enum class SpiCommand : uint8_t
63 {
64 ReadRegister = 0b00,
65 WriteRegister = 0b01,
66 // DMA in this context means that the Ethernet MAC is DMA directly
67 // from the SPI interface into its internal buffer, so it takes single
68 // SPI transaction for the entire frame. It is unrelated to whether
69 // SPI driver uses PIO or DMA for the SPI transaction.
70 ReadDma = 0b10,
71 WriteDma = 0b11,
72 };
73
74 /**
75 * The location of registers
76 */
77 enum class RegisterOffset : uint8_t
78 {
79 ChipConfiguration = 0x08,
80 MacAddressLow = 0x10,
81 MacAdressMiddle = 0x12,
82 MacAddressHigh = 0x14,
83 OnChipBusControl = 0x20,
84 EepromControl = 0x22,
85 MemoryBistInfo = 0x24,
86 GlobalReset = 0x26,
87
88 /* Wakeup frame registers omitted */
89
90 TransmitControl = 0x70,
91 TransmitStatus = 0x72,
92 ReceiveControl1 = 0x74,
93 ReceiveControl2 = 0x76,
94 TransmitQueueMemoryInfo = 0x78,
95 ReceiveFrameHeaderStatus = 0x7C,
96 ReceiveFrameHeaderByteCount = 0x7E,
97 TransmitQueueCommand = 0x80,
98 ReceiveQueueCommand = 0x82,
99 TransmitFrameDataPointer = 0x84,
100 ReceiveFrameDataPointer = 0x86,
101 ReceiveDurationTimerThreshold = 0x8C,
102 ReceiveDataByteCountThreshold = 0x8E,
103 InterruptEnable = 0x90,
104 InterruptStatus = 0x92,
105 ReceiveFrameCountThreshold = 0x9c,
106 TransmitNextTotalFrameSize = 0x9E,
107
108 /* MAC address hash table registers omitted */
109
110 FlowControlLowWatermark = 0xB0,
111 FlowControlHighWatermark = 0xB2,
112 FlowControlOverrunWatermark = 0xB4,
113 ChipIdEnable = 0xC0,
114 ChipGlobalControl = 0xC6,
115 IndirectAccessControl = 0xC8,
116 IndirectAccessDataLow = 0xD0,
117 IndirectAccessDataHigh = 0xD2,
118 PowerManagementEventControl = 0xD4,
119 GoSleepWakeUp = 0xD6,
120 PhyReset = 0xD8,
121 Phy1MiiBasicControl = 0xE4,
122 Phy1MiiBasicStatus = 0xE6,
123 Phy1IdLow = 0xE8,
124 Phy1High = 0xEA,
125 Phy1AutoNegotiationAdvertisement = 0xEC,
126 Phy1AutoNegotiationLinkPartnerAbility = 0xEE,
127 Phy1SpecialControlStatus = 0xF4,
128 Port1Control = 0xF6,
129 Port1Status = 0xF8,
130 };
131
132 using MACAddress = std::array<uint8_t, 6>;
133
134 /**
135 * Flag bits of the TransmitControl register.
136 */
137 enum [[clang::flag_enum]] TransmitControl : uint16_t
138 {
139 TransmitEnable = 1 << 0,
140 TransmitCrcEnable = 1 << 1,
141 TransmitPaddingEnable = 1 << 2,
142 TransmitFlowControlEnable = 1 << 3,
143 FlushTransmitQueue = 1 << 4,
144 TransmitChecksumGenerationIp = 1 << 5,
145 TransmitChecksumGenerationTcp = 1 << 6,
146 TransmitChecksumGenerationIcmp = 1 << 8,
147 };
148
149 /**
150 * Flag bits of the ReceiveControl1 register.
151 */
152 enum [[clang::flag_enum]] ReceiveControl1 : uint16_t
153 {
154 ReceiveEnable = 1 << 0,
155 ReceiveInverseFilter = 1 << 1,
156 ReceiveAllEnable = 1 << 4,
157 ReceiveUnicastEnable = 1 << 5,
158 ReceiveMulticastEnable = 1 << 6,
159 ReceiveBroadcastEnable = 1 << 7,
160 ReceiveMulticastAddressFilteringWithMacAddressEnable = 1 << 8,
161 ReceiveErrorFrameEnable = 1 << 9,
162 ReceiveFlowControlEnable = 1 << 10,
163 ReceivePhysicalAddressFilteringWithMacAddressEnable = 1 << 11,
164 ReceiveIpFrameChecksumCheckEnable = 1 << 12,
165 ReceiveTcpFrameChecksumCheckEnable = 1 << 13,
166 ReceiveUdpFrameChecksumCheckEnable = 1 << 14,
167 FlushReceiveQueue = 1 << 15,
168 };
169
170 /**
171 * Flag bits of the ReceiveControl2 register.
172 */
173 enum [[clang::flag_enum]] ReceiveControl2 : uint16_t
174 {
175 ReceiveSourceAddressFiltering = 1 << 0,
176 ReceiveIcmpFrameChecksumEnable = 1 << 1,
177 UdpLiteFrameEnable = 1 << 2,
178 ReceiveIpv4Ipv6UdpFrameChecksumEqualZero = 1 << 3,
179 ReceiveIpv4Ipv6FragmentFramePass = 1 << 4,
180 DataBurst4Bytes = 0b000 << 5,
181 DataBurst8Bytes = 0b001 << 5,
182 DataBurst16Bytes = 0b010 << 5,
183 DataBurst32Bytes = 0b011 << 5,
184 DataBurstSingleFrame = 0b100 << 5,
185 };
186
187 /**
188 * Flag bits of the ReceiveFrameHeaderStatus register.
189 */
190 enum [[clang::flag_enum]] ReceiveFrameHeaderStatus : uint16_t
191 {
192 ReceiveCrcError = 1 << 0,
193 ReceiveRuntFrame = 1 << 1,
194 ReceiveFrameTooLong = 1 << 2,
195 ReceiveFrameType = 1 << 3,
196 ReceiveMiiError = 1 << 4,
197 ReceiveUnicastFrame = 1 << 5,
198 ReceiveMulticastFrame = 1 << 6,
199 ReceiveBroadcastFrame = 1 << 7,
200 ReceiveUdpFrameChecksumStatus = 1 << 10,
201 ReceiveTcpFrameChecksumStatus = 1 << 11,
202 ReceiveIpFrameChecksumStatus = 1 << 12,
203 ReceiveIcmpFrameChecksumStatus = 1 << 13,
204 ReceiveFrameValid = 1 << 15,
205 };
206
207 /**
208 * Flag bits of the ReceiveQueueCommand register.
209 */
210 enum [[clang::flag_enum]] ReceiveQueueCommand : uint16_t
211 {
212 ReleaseReceiveErrorFrame = 1 << 0,
213 StartDmaAccess = 1 << 3,
214 AutoDequeueReceiveQueueFrameEnable = 1 << 4,
215 ReceiveFrameCountThresholdEnable = 1 << 5,
216 ReceiveDataByteCountThresholdEnable = 1 << 6,
217 ReceiveDurationTimerThresholdEnable = 1 << 7,
218 ReceiveIpHeaderTwoByteOffsetEnable = 1 << 9,
219 ReceiveFrameCountThresholdStatus = 1 << 10,
220 ReceiveDataByteCountThresholdstatus = 1 << 11,
221 ReceiveDurationTimerThresholdStatus = 1 << 12,
222 };
223
224 /**
225 * Flag bits of the TransmitQueueCommand register.
226 */
227 enum [[clang::flag_enum]] TransmitQueueCommand : uint16_t
228 {
229 ManualEnqueueTransmitQueueFrameEnable = 1 << 0,
230 TransmitQueueMemoryAvailableMonitor = 1 << 1,
231 AutoEnqueueTransmitQueueFrameEnable = 1 << 2,
232 };
233
234 /**
235 * Flag bits of the TransmitFrameDataPointer and ReceiveFrameDataPointer
236 * register.
237 */
238 enum [[clang::flag_enum]] FrameDataPointer : uint16_t
239 {
240 /**
241 * When this bit is set, the frame data pointer register increments
242 * automatically on accesses to the data register.
243 */
244 FrameDataPointerAutoIncrement = 1 << 14,
245 };
246
247 /**
248 * Flags bits of the InterruptStatus and InterruptEnable registers.
249 */
250 enum [[clang::flag_enum]] Interrupt : uint16_t
251 {
252 EnergyDetectInterrupt = 1 << 2,
253 LinkupDetectInterrupt = 1 << 3,
254 ReceiveMagicPacketDetectInterrupt = 1 << 4,
255 ReceiveWakeupFrameDetectInterrupt = 1 << 5,
256 TransmitSpaceAvailableInterrupt = 1 << 6,
257 ReceiveProcessStoppedInterrupt = 1 << 7,
258 TransmitProcessStoppedInterrupt = 1 << 8,
259 ReceiveOverrunInterrupt = 1 << 11,
260 ReceiveInterrupt = 1 << 13,
261 TransmitInterrupt = 1 << 14,
262 LinkChangeInterruptStatus = 1 << 15,
263 };
264
265 /**
266 * Flags bits of the PowerManagementEventControl register.
267 */
268 enum [[clang::flag_enum]] PowerManagementEventControl : uint16_t
269 {
270 PowerManagementModeNormal = 0b00 << 0,
271 PowerManagementModeEnergyDetect = 0b01 << 0,
272 PowerManagementModeReserved = 0b10 << 0,
273 PowerManagementModePowerSaving = 0b11 << 0,
274 PowerManagementModeMask = 0b11 << 0,
275
276 WakeUpEventEnergy = 0b0001 << 2,
277 WakeUpEventLinkup = 0b0010 << 2,
278 WakeUpEventMagic = 0b0100 << 2,
279 WakeUpEventFrame = 0b1000 << 2,
280 WakeUpEventMask = 0b1111 << 2,
281
282 WakeUpToNormal = 1 << 6,
283 AutoWakeEnable = 1 << 7,
284
285 WakeOnLanEventPinEnergy = 0b0001 << 8,
286 WakeOnLanEventPinLinkup = 0b0010 << 8,
287 WakeOnLanEventPinMagic = 0b0100 << 8,
288 WakeOnLanEventPinFrame = 0b1000 << 8,
289 WakeOnLanEventPinMask = 0b1111 << 8,
290
291 WakeOnLanEventPinPolarity = 1 << 12,
292 WakeOnLanEventPinDelay = 1 << 14
293 };
294
295 /**
296 * Flags bits of the Port1Control register.
297 */
298 enum [[clang::flag_enum]] Port1Control : uint16_t
299 {
300 Advertised10BTHalfDuplexCapability = 1 << 0,
301 Advertised10BTFullDuplexCapability = 1 << 1,
302 Advertised100BTHalfDuplexCapability = 1 << 2,
303 Advertised100BTFullDuplexCapability = 1 << 3,
304 AdvertisedFlowControlCapability = 1 << 4,
305 ForceDuplex = 1 << 5,
306 ForceSpeed = 1 << 6,
307 AutoNegotiationEnable = 1 << 7,
308 ForceMDIX = 1 << 9,
309 DisableAutoMDIMDIX = 1 << 10,
310 RestartAutoNegotiation = 1 << 13,
311 TransmitterDisable = 1 << 14,
312 LedOff = 1 << 15,
313 };
314
315 /**
316 * Flags bits of the Port1Status register.
317 */
318 enum [[clang::flag_enum]] Port1Status : uint16_t
319 {
320 Partner10BTHalfDuplexCapability = 1 << 0,
321 Partner10BTFullDuplexCapability = 1 << 1,
322 Partner100BTHalfDuplexCapability = 1 << 2,
323 Partner100BTFullDuplexCapability = 1 << 3,
324 PartnerFlowControlCapability = 1 << 4,
325 LinkGood = 1 << 5,
326 AutoNegotiationDone = 1 << 6,
327 MDIXStatus = 1 << 7,
328 OperationDuplex = 1 << 9,
329 OperationSpeed = 1 << 10,
330 PolarityReverse = 1 << 13,
331 HPMDIX = 1 << 15,
332 };
333
334 /**
335 * The futex used to wait for interrupts when packets are available to
336 * receive.
337 */
338 const uint32_t *receiveInterruptFutex;
339
340 /**
341 * Read a register from the KSZ8851.
342 */
343 [[nodiscard]] uint16_t register_read(RegisterOffset reg) const
344 {
345 // KSZ8851 command have the following format:
346 //
347 // First byte:
348 // +---------+-------------+-------------------+
349 // | 7 6 | 5 2 | 1 0 |
350 // +---------+-------------+-------------------+
351 // | Command | Byte Enable | Address (bit 7-6) |
352 // +---------+-------------+-------------------+
353 //
354 // Second byte (for register read/write only):
355 // +-------------------+--------+
356 // | 7 4 | 3 0 |
357 // +-------------------+--------+
358 // | Address (bit 5-2) | Unused |
359 // +-------------------+--------+
360 //
361 // Note that the access is 32-bit since bit 1 & 0 of the address is not
362 // included. KSZ8851 have 16-bit registers so byte enable is used to
363 // determine which register to access from the 32 bits specified by the
364 // address.
365 uint8_t addr = static_cast<uint8_t>(reg);
366 uint8_t byteEnable = (addr & 0x2) == 0 ? 0b0011 : 0b1100;
367 uint8_t bytes[2];
368 bytes[0] = (static_cast<uint8_t>(SpiCommand::ReadRegister) << 6) |
369 (byteEnable << 2) | (addr >> 6);
370 bytes[1] = (addr << 2) & 0b11110000;
371
372 spi()->chip_select_assert(true);
373 spi()->blocking_write(bytes, sizeof(bytes));
374 uint16_t val;
375 spi()->blocking_read(reinterpret_cast<uint8_t *>(&val), sizeof(val));
376 spi()->chip_select_assert(false);
377 return val;
378 }
379
380 /**
381 * Write a register to KSZ8851.
382 */
383 void register_write(RegisterOffset reg, uint16_t val) const
384 {
385 // See register_read for command format.
386 uint8_t addr = static_cast<uint8_t>(reg);
387 uint8_t byteEnable = (addr & 0x2) == 0 ? 0b0011 : 0b1100;
388 uint8_t bytes[2];
389 bytes[0] = (static_cast<uint8_t>(SpiCommand::WriteRegister) << 6) |
390 (byteEnable << 2) | (addr >> 6);
391 bytes[1] = (addr << 2) & 0b11110000;
392
393 spi()->chip_select_assert(true);
394 spi()->blocking_write(bytes, sizeof(bytes));
395 spi()->blocking_write(reinterpret_cast<uint8_t *>(&val), sizeof(val));
396 spi()->wait_idle();
397 spi()->chip_select_assert(false);
398 }
399
400 /**
401 * Set bits in a KSZ8851 register.
402 */
403 void register_set(RegisterOffset reg, uint16_t mask) const
404 {
405 uint16_t old = register_read(reg);
406 register_write(reg, old | mask);
407 }
408
409 /**
410 * Clear bits in a KSZ8851 register.
411 */
412 void register_clear(RegisterOffset reg, uint16_t mask) const
413 {
414 uint16_t old = register_read(reg);
415 register_write(reg, old & ~mask);
416 }
417
418 /**
419 * Helper. Returns a pointer to the SPI device.
420 */
421 [[nodiscard,
422 gnu::always_inline]] Capability<volatile SonataSpi::EthernetMac>
423 spi() const
424 {
425 return MMIO_CAPABILITY(SonataSpi::EthernetMac, spi_ethmac);
426 }
427
428 /**
429 * Number of frames yet to be received since last interrupt acknowledgement.
430 */
431 uint16_t framesToProcess = 0;
432
433 /**
434 * Mutex protecting transmitBuffer if send_frame is reentered.
435 */
436 RecursiveMutex transmitBufferMutex;
437
438 /**
439 * Buffer used by send_frame.
440 */
441 std::unique_ptr<uint8_t[]> transmitBuffer;
442
443 /**
444 * Mutex protecting receiveBuffer if receive_frame is called before a
445 * previous returned frame is dropped.
446 */
447 RecursiveMutex receiveBufferMutex;
448
449 /**
450 * Lock to protect reads/writes to the SPI Chip Selects, which use the
451 * same bits of the MMIO region and thus need to be protected.
452 */
453 FlagLockPriorityInherited chipSelectLock;
454
455 /**
456 * Buffer used by receive_frame.
457 */
458 std::unique_ptr<uint8_t[]> receiveBuffer;
459
460 public:
461 /**
462 * Initialise a reference to the Ethernet device.
463 */
465 {
466 transmitBuffer = std::make_unique<uint8_t[]>(MaxFrameSize);
467 receiveBuffer = std::make_unique<uint8_t[]>(MaxFrameSize);
468
469 // Reset chip. It needs to be hold in reset for at least 10ms.
470 spi()->reset_assert(true);
472 spi()->reset_assert(false);
473
474 uint16_t chipId = register_read(RegisterOffset::ChipIdEnable);
475 Debug::log("Chip ID is {}", chipId);
476
477 // Check the chip ID. The last nibble is revision ID and can be ignored.
478 Debug::Assert((chipId & 0xFFF0) == 0x8870, "Unexpected Chip ID");
479
480 // This is the initialisation sequence suggested by the programmer's
481 // guide.
482 register_write(RegisterOffset::TransmitFrameDataPointer,
483 FrameDataPointer::FrameDataPointerAutoIncrement);
484 register_write(RegisterOffset::TransmitControl,
485 TransmitControl::TransmitCrcEnable |
486 TransmitControl::TransmitPaddingEnable |
487 TransmitControl::TransmitFlowControlEnable |
488 TransmitControl::TransmitChecksumGenerationIp |
489 TransmitControl::TransmitChecksumGenerationTcp |
490 TransmitControl::TransmitChecksumGenerationIcmp);
491 register_write(RegisterOffset::ReceiveFrameDataPointer,
492 FrameDataPointer::FrameDataPointerAutoIncrement);
493 // Configure Receive Frame Threshold for one frame.
494 register_write(RegisterOffset::ReceiveFrameCountThreshold, 0x0001);
495 register_write(RegisterOffset::ReceiveControl1,
496 ReceiveControl1::ReceiveUnicastEnable |
497 ReceiveControl1::ReceiveMulticastEnable |
498 ReceiveControl1::ReceiveBroadcastEnable |
499 ReceiveControl1::ReceiveFlowControlEnable |
500 ReceiveControl1::
501 ReceivePhysicalAddressFilteringWithMacAddressEnable |
502 ReceiveControl1::ReceiveIpFrameChecksumCheckEnable |
503 ReceiveControl1::ReceiveTcpFrameChecksumCheckEnable |
504 ReceiveControl1::ReceiveUdpFrameChecksumCheckEnable);
505 // The frame data burst field in this register controls how many data
506 // from a frame is read per DMA operation. The programmer's guide has a
507 // 4 byte burst, but to reduce SPI transactions and improve performance
508 // we choose to use single-frame data burst which reads the entire
509 // Ethernet frame in a single SPI DMA.
510 register_write(
511 RegisterOffset::ReceiveControl2,
512 ReceiveControl2::UdpLiteFrameEnable |
513 ReceiveControl2::ReceiveIpv4Ipv6UdpFrameChecksumEqualZero |
514 ReceiveControl2::ReceiveIpv4Ipv6FragmentFramePass |
515 ReceiveControl2::DataBurstSingleFrame);
516 register_write(
517 RegisterOffset::ReceiveQueueCommand,
518 ReceiveQueueCommand::ReceiveFrameCountThresholdEnable |
519 ReceiveQueueCommand::AutoDequeueReceiveQueueFrameEnable);
520
521 // Programmer's guide have a step to set the chip in half-duplex when
522 // negotiation failed, but we omit the step since non-switching hubs and
523 // half-duplex Ethernet is rarely used these days.
524
525 register_set(RegisterOffset::Port1Control,
526 Port1Control::RestartAutoNegotiation);
527
528 // Configure Low Watermark to 6KByte available buffer space out of
529 // 12KByte (unit is 4 bytes).
530 register_write(RegisterOffset::FlowControlLowWatermark, 0x0600);
531 // Configure High Watermark to 4KByte available buffer space out of
532 // 12KByte (unit is 4 bytes).
533 register_write(RegisterOffset::FlowControlHighWatermark, 0x0400);
534
535 // Clear the interrupt status
536 register_write(RegisterOffset::InterruptStatus, 0xFFFF);
537 receiveInterruptFutex =
538 interrupt_futex_get(STATIC_SEALED_VALUE(ethernetInterruptCapability));
539 // Enable Receive interrupt
540 register_write(RegisterOffset::InterruptEnable,
541 LinkupDetectInterrupt | ReceiveInterrupt);
542
543 // Enable QMU Transmit.
544 register_set(RegisterOffset::TransmitControl,
545 TransmitControl::TransmitEnable);
546 // Enable QMU Receive.
547 register_set(RegisterOffset::ReceiveControl1,
548 ReceiveControl1::ReceiveEnable);
549 }
550
551 Ksz8851Ethernet(const Ksz8851Ethernet &) = delete;
552 Ksz8851Ethernet(Ksz8851Ethernet &&) = delete;
553
554 /**
555 * This device does not have a unique MAC address and so users must provide
556 * a locally administered MAC address if more than one device is present on
557 * the same network.
558 */
559 static constexpr bool has_unique_mac_address()
560 {
561 return false;
562 }
563
564 static constexpr MACAddress mac_address_default()
565 {
566 return {0x3a, 0x30, 0x25, 0x24, 0xfe, 0x7a};
567 }
568
569 void mac_address_set(MACAddress address = mac_address_default())
570 {
571 register_write(RegisterOffset::MacAddressHigh,
572 (address[0] << 8) | address[1]);
573 register_write(RegisterOffset::MacAdressMiddle,
574 (address[2] << 8) | address[3]);
575 register_write(RegisterOffset::MacAddressLow,
576 (address[4] << 8) | address[5]);
577 }
578
579 uint32_t receive_interrupt_value()
580 {
581 return *receiveInterruptFutex;
582 }
583
584 int receive_interrupt_complete(Timeout *timeout,
585 uint32_t lastInterruptValue)
586 {
587 // If there are frames to process, do not enter wait.
588 if (framesToProcess)
589 {
590 return 0;
591 }
592
593 // Our interrupt is level-triggered; if a frame happens to arrive
594 // between `receive_frame` call and we marking interrupt as received,
595 // it will trigger again immediately after we acknowledge it.
596
597 // Acknowledge the interrupt in the scheduler.
598 interrupt_complete(STATIC_SEALED_VALUE(ethernetInterruptCapability));
599 if (*receiveInterruptFutex == lastInterruptValue)
600 {
601 Debug::log("Acknowledged interrupt, sleeping on futex {}",
602 receiveInterruptFutex);
603 return futex_timed_wait(
604 timeout, receiveInterruptFutex, lastInterruptValue);
605 }
606 Debug::log("Scheduler announces interrupt has fired");
607 return 0;
608 }
609
610 /**
611 * Simple class representing a received Ethernet frame.
612 */
613 class Frame
614 {
615 public:
616 uint16_t length;
617 Capability<uint8_t> buffer;
618
619 private:
620 friend class Ksz8851Ethernet;
622
623 Frame(LockGuard<RecursiveMutex> &&guard,
624 Capability<uint8_t> buffer,
625 uint16_t length)
626 : guard(std::move(guard)), buffer(buffer), length(length)
627 {
628 }
629 };
630
631 /**
632 * Check the link status of the PHY.
633 */
635 {
636 uint16_t status = register_read(RegisterOffset::Port1Status);
637 return (status & Port1Status::LinkGood) != 0;
638 }
639
640 std::optional<Frame> receive_frame()
641 {
642 LockGuard g{chipSelectLock};
643 if (framesToProcess == 0)
644 {
645 uint16_t isr = register_read(RegisterOffset::InterruptStatus);
646
647 if (isr & LinkupDetectInterrupt)
648 {
649 /* Acknowledge the power management event */
650 register_set(RegisterOffset::PowerManagementEventControl,
651 PowerManagementEventControl::WakeUpEventLinkup);
652 }
653
654 if (!(isr & ReceiveInterrupt))
655 {
656 return std::nullopt;
657 }
658
659 // Acknowledge the interrupt
660 register_write(RegisterOffset::InterruptStatus, ReceiveInterrupt);
661
662 // Read number of frames pending.
663 // Note that this is only updated when we acknowledge the interrupt.
664 framesToProcess =
665 register_read(RegisterOffset::ReceiveFrameCountThreshold) >> 8;
666 }
667
668 // Get number of frames pending
669 for (; framesToProcess; framesToProcess--)
670 {
671 uint16_t status =
672 register_read(RegisterOffset::ReceiveFrameHeaderStatus);
673 uint16_t length =
674 register_read(RegisterOffset::ReceiveFrameHeaderByteCount) &
675 0xFFF;
676 bool valid =
677 (status & ReceiveFrameValid) &&
678 !(status &
679 (ReceiveCrcError | ReceiveRuntFrame | ReceiveFrameTooLong |
680 ReceiveMiiError | ReceiveUdpFrameChecksumStatus |
681 ReceiveTcpFrameChecksumStatus | ReceiveIpFrameChecksumStatus |
682 ReceiveIcmpFrameChecksumStatus));
683
684 if (!valid)
685 {
686 DebugFrameDrops::log("Dropping frame with status: {}", status);
687
688 drop_error_frame();
689 continue;
690 }
691
692 if (length == 0)
693 {
694 DebugFrameDrops::log("Dropping frame with zero length");
695
696 drop_error_frame();
697 continue;
698 }
699
700 // The DMA transfer to the Ethernet MAC must be a multiple of 4
701 // bytes.
702 uint16_t paddedLength = (length + 3) & ~0x3;
703 if (paddedLength > MaxFrameSize)
704 {
705 DebugFrameDrops::log("Dropping frame that is too large: {}",
706 length);
707
708 drop_error_frame();
709 continue;
710 }
711
712 Debug::log("Receiving frame of length {}", length);
713
714 LockGuard guard{receiveBufferMutex};
715
716 // Reset receive frame pointer to zero and start DMA transfer
717 // operation.
718 register_write(RegisterOffset::ReceiveFrameDataPointer,
719 FrameDataPointer::FrameDataPointerAutoIncrement);
720 register_set(RegisterOffset::ReceiveQueueCommand, StartDmaAccess);
721
722 // Start receiving via SPI.
723 uint8_t cmd = static_cast<uint8_t>(SpiCommand::ReadDma) << 6;
724 spi()->chip_select_assert(true);
725 spi()->blocking_write(&cmd, 1);
726
727 // Initial words are ReceiveFrameHeaderStatus and
728 // ReceiveFrameHeaderByteCount which we have already know the value.
729 uint8_t dummy[8];
730 spi()->blocking_read(dummy, sizeof(dummy));
731
732 spi()->blocking_read(receiveBuffer.get(), paddedLength);
733
734 spi()->chip_select_assert(false);
735
736 register_clear(RegisterOffset::ReceiveQueueCommand, StartDmaAccess);
737 framesToProcess -= 1;
738
739 Capability<uint8_t> boundedBuffer{receiveBuffer.get()};
740 boundedBuffer.bounds().set_inexact(length);
741 // Remove all permissions except load. This also removes global, so
742 // that this cannot be captured.
743 boundedBuffer.permissions() &=
744 CHERI::PermissionSet{CHERI::Permission::Load};
745
746 return Frame{std::move(guard), boundedBuffer, length};
747 }
748
749 return std::nullopt;
750 }
751
752 /**
753 * Send a packet. This will block if no buffer space is available on
754 * device.
755 *
756 * The third argument is a callback that allows the caller to check the
757 * frame before it's sent but after it's copied into memory that isn't
758 * shared with other compartments.
759 */
760 bool send_frame(const uint8_t *buffer, uint16_t length, auto &&check)
761 {
762 // The DMA transfer to the Ethernet MAC must be a multiple of 4 bytes.
763 uint16_t paddedLength = (length + 3) & ~0x3;
764 if (paddedLength > MaxFrameSize)
765 {
766 Debug::log("Frame size {} is larger than the maximum size", length);
767 return false;
768 }
769
770 LockGuard guard{transmitBufferMutex};
771
772 // We must check the frame pointer and its length. Although it
773 // is supplied by the firewall which is trusted, the firewall
774 // does not check the pointer which is coming from external
775 // untrusted components.
776 Timeout t{10};
777 if ((heap_claim_ephemeral(&t, buffer) < 0) ||
779 CHERI::Permission::Load}>(buffer, length)))
780 {
781 return false;
782 }
783
784 memcpy(transmitBuffer.get(), buffer, length);
785 if (!check(transmitBuffer.get(), length))
786 {
787 return false;
788 }
789
790 LockGuard g{chipSelectLock};
791
792 // Wait for the transmit buffer to be available on the device side.
793 // This needs to include the header.
794 while ((register_read(RegisterOffset::TransmitQueueMemoryInfo) &
795 0xFFF) < length + 4)
796 {
797 }
798
799 Debug::log("Sending frame of length {}", length);
800
801 // Start DMA transfer operation.
802 register_set(RegisterOffset::ReceiveQueueCommand, StartDmaAccess);
803
804 // Start sending via SPI.
805 uint8_t cmd = static_cast<uint8_t>(SpiCommand::WriteDma) << 6;
806 spi()->chip_select_assert(true);
807 spi()->blocking_write(&cmd, 1);
808
809 uint32_t header = static_cast<uint32_t>(length) << 16;
810 spi()->blocking_write(reinterpret_cast<uint8_t *>(&header),
811 sizeof(header));
812
813 spi()->blocking_write(transmitBuffer.get(), paddedLength);
814
815 spi()->wait_idle();
816 spi()->chip_select_assert(false);
817
818 // Stop QMU DMA transfer operation.
819 register_clear(RegisterOffset::ReceiveQueueCommand, StartDmaAccess);
820
821 // Enqueue the frame for transmission.
822 register_set(
823 RegisterOffset::TransmitQueueCommand,
824 TransmitQueueCommand::ManualEnqueueTransmitQueueFrameEnable);
825
826 return true;
827 }
828
829 private:
830 void drop_error_frame()
831 {
832 register_set(RegisterOffset::ReceiveQueueCommand,
833 ReleaseReceiveErrorFrame);
834 // Wait for confirmation of frame release before attempting to process
835 // next frame.
836 while (register_read(RegisterOffset::ReceiveQueueCommand) &
837 ReleaseReceiveErrorFrame)
838 {
839 }
840 }
841};
842
843using EthernetDevice = Ksz8851Ethernet;
844
CHERIoT RTOS heap allocator interface.
int __cheri_libcall heap_claim_ephemeral(TimeoutArgument timeout, const void *ptr, const void *ptr2)
Interface to the ephemeral claims mechanism.
C++ helpers for operating on capabilities.
bool check_pointer(auto &ptr, size_t space=sizeof(std::remove_pointer< decltype(ptr)>))
Checks that ptr is valid, unsealed, has at least Permissions, and has at least space bytes after the ...
Definition cheri.hh:1314
@ Load
This capability can be used to load.
Definition cheri.hh:52
Helper class for accessing capability properties on pointers.
Definition cheri.hh:482
Class encapsulating a set of permissions.
Definition cheri.hh:90
Simple class representing a received Ethernet frame.
The driver for KSZ8851 SPI Ethernet MAC.
bool send_frame(const uint8_t *buffer, uint16_t length, auto &&check)
Send a packet.
Ksz8851Ethernet()
Initialise a reference to the Ethernet device.
static constexpr bool has_unique_mac_address()
This device does not have a unique MAC address and so users must provide a locally administered MAC a...
bool phy_link_status()
Check the link status of the PHY.
A simple RAII type that owns a lock.
Definition locks.hh:298
Priority-inheriting recursive mutex.
Definition locks.hh:156
A specialised driver for the SPI device connected to the Ethernet MAC.
#define STATIC_SEALED_VALUE(name)
Returns a sealed capability to the named object created with DECLARE_STATIC_SEALED_VALUE.
#define MMIO_CAPABILITY(type, name)
Provide a capability of the type volatile type * referring to the MMIO region exported in the linker ...
Concept for an Ethernet adaptor.
Definition ethernet.hh:24
C++ APIs for assertions, invariants, and writing formatted debug messages to a UART.
Futex (compare-and-wait) APIs.
int futex_timed_wait(TimeoutArgument timeout, const volatile uint32_t *address, uint32_t expected, uint32_t flags)
Compare the value at address to expected and, if they match, sleep the thread until a wake event is s...
This file describes the interfaces for compartments to wait for interrupts.
int interrupt_complete(InterruptCapability interruptcapability)
Acknowledge the end of handling an interrupt.
#define DECLARE_AND_DEFINE_INTERRUPT_CAPABILITY( name, number, mayWait, mayComplete)
Helper macro to define an interrupt capability without a separate declaration.
Definition interrupt.h:109
const uint32_t * interrupt_futex_get(InterruptCapability interruptcapability)
Request the futex associated with an interrupt.
C++ wrappers for synchronisation primitives.
Structure representing a timeout.
Definition timeout.h:47
Functions and types used for thread management.
static uint64_t thread_millisecond_wait(uint32_t milliseconds)
Wait for the specified number of milliseconds.
Definition thread.h:152
struct Timeout Timeout
Structure representing a timeout.