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