Skip to main content

e1000/
lib.rs

1#![no_std]
2
3use core::ptr;
4use intel_ethernet::{
5    ctrl, eerd, int_bits, rctl, regs, rx_errors, rx_status, tctl, tx_status, LegacyRxDesc,
6    LegacyTxDesc,
7};
8use net_core::NetError;
9use nic_buffers::{DmaAllocator, DmaRegion};
10use nic_queues::{RxDescriptor, RxRing, TxDescriptor, TxRing};
11
12pub const NUM_RX: usize = 128;
13pub const NUM_TX: usize = 128;
14pub const RX_BUF_SIZE: usize = 4096;
15
16const RESET_MAX_POLLS: u32 = 200_000;
17const EEPROM_MAX_POLLS: u32 = 50_000;
18
19/// Watchdog timeout in successful TX completions. If no TX completes
20/// within this many transmit attempts the link is considered stalled.
21const WATCHDOG_TX_THRESHOLD: u64 = 10_000;
22
23pub const E1000_DEVICE_IDS: &[u16] = &[0x100E, 0x100F, 0x10D3, 0x153A, 0x1539];
24pub const INTEL_VENDOR: u16 = 0x8086;
25
26/// Hardware statistics maintained by the driver.
27pub struct E1000Stats {
28    pub rx_ok: u64,
29    pub rx_errors: u64,
30    pub rx_crc_errors: u64,
31    pub rx_length_errors: u64,
32    pub tx_ok: u64,
33    pub tx_errors: u64,
34    pub tx_dropped: u64,
35    pub tx_late_collision: u64,
36    pub tx_underrun: u64,
37    pub watchdog_resets: u64,
38}
39
40impl E1000Stats {
41    const fn new() -> Self {
42        Self {
43            rx_ok: 0,
44            rx_errors: 0,
45            rx_crc_errors: 0,
46            rx_length_errors: 0,
47            tx_ok: 0,
48            tx_errors: 0,
49            tx_dropped: 0,
50            tx_late_collision: 0,
51            tx_underrun: 0,
52            watchdog_resets: 0,
53        }
54    }
55}
56
57pub struct E1000Nic {
58    mmio: u64,
59    rx: RxRing<LegacyRxDesc>,
60    rx_bufs: [DmaRegion; NUM_RX],
61    rx_ring_phys: u64,
62    tx: TxRing<LegacyTxDesc>,
63    tx_bufs: [DmaRegion; NUM_TX],
64    tx_ring_phys: u64,
65    mac: [u8; 6],
66    link_up: bool,
67    stats: E1000Stats,
68    tx_since_last_reclaim: u64,
69    /// Last known TDH value for watchdog stall detection.
70    last_tdh: usize,
71}
72
73// SAFETY: E1000Nic owns its MMIO region and DMA buffers. It is safe to send
74// across threads as long as only one thread accesses the hardware at a time.
75unsafe impl Send for E1000Nic {}
76
77#[inline]
78unsafe fn rd(base: u64, reg: usize) -> u32 {
79    ptr::read_volatile((base + reg as u64) as *const u32)
80}
81
82#[inline]
83unsafe fn wr(base: u64, reg: usize, val: u32) {
84    ptr::write_volatile((base + reg as u64) as *mut u32, val)
85}
86
87impl E1000Nic {
88    pub fn init(mmio_base: u64, alloc: &dyn DmaAllocator) -> Result<Self, NetError> {
89        unsafe {
90            // --- Reset ---
91            let c = rd(mmio_base, regs::CTRL);
92            log::trace!("e1000: assert CTRL.RST (ctrl={:#x})", c);
93            wr(mmio_base, regs::CTRL, c | ctrl::RST);
94            let mut reset_done = false;
95            for poll in 0..RESET_MAX_POLLS {
96                let ctrl = rd(mmio_base, regs::CTRL);
97                if ctrl & ctrl::RST == 0 {
98                    log::trace!(
99                        "e1000: reset complete after {} polls (ctrl={:#x})",
100                        poll + 1,
101                        ctrl
102                    );
103                    reset_done = true;
104                    break;
105                }
106                core::hint::spin_loop();
107            }
108            if !reset_done {
109                log::warn!(
110                    "e1000: reset timeout after {} CTRL polls (RST never cleared)",
111                    RESET_MAX_POLLS
112                );
113                return Err(NetError::NotReady);
114            }
115
116            // Disable interrupts during setup
117            wr(mmio_base, regs::IMC, 0xFFFF_FFFF);
118            let _ = rd(mmio_base, regs::ICR);
119
120            // --- MAC address ---
121            log::trace!("e1000: read MAC");
122            let mac = Self::read_mac(mmio_base)?;
123            log::trace!(
124                "e1000: MAC {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
125                mac[0],
126                mac[1],
127                mac[2],
128                mac[3],
129                mac[4],
130                mac[5]
131            );
132
133            // NOTE: VET (0x0008) and VFTA (0x5200+) writes are intentionally
134            // skipped. On the 82540EM, offset 0x0008 is the read-only STATUS
135            // register : writing VET there triggers a VERIFY assertion in
136            // VMware's e1000 emulation. The VFTA loop writes 128 entries to
137            // register space that VMware's 82540EM model does not implement.
138            // These are cosmetic (802.1Q is not used) so they are safe to omit.
139            // TODO: re-enable with per-hypervisor detection when VLAN support
140            // is needed.
141
142            // --- RX ring ---
143            let rx_ring_region = alloc
144                .alloc_dma(NUM_RX * core::mem::size_of::<LegacyRxDesc>())
145                .map_err(|_| NetError::NotReady)?;
146            ptr::write_bytes(rx_ring_region.virt, 0, rx_ring_region.size);
147            let rx_descs = rx_ring_region.virt as *mut LegacyRxDesc;
148
149            let mut rx_bufs = [DmaRegion::ZERO; NUM_RX];
150            for rx_buf in rx_bufs.iter_mut().take(NUM_RX) {
151                let buf = alloc
152                    .alloc_dma(RX_BUF_SIZE)
153                    .map_err(|_| NetError::NotReady)?;
154                ptr::write_bytes(buf.virt, 0, RX_BUF_SIZE);
155                *rx_buf = buf;
156            }
157
158            wr(mmio_base, regs::RDBAL, rx_ring_region.phys as u32);
159            wr(mmio_base, regs::RDBAH, (rx_ring_region.phys >> 32) as u32);
160            wr(mmio_base, regs::RDLEN, rx_ring_region.size as u32);
161            wr(mmio_base, regs::RDH, 0);
162            wr(mmio_base, regs::RDT, (NUM_RX - 1) as u32);
163
164            for (i, buf) in rx_bufs.iter().enumerate().take(NUM_RX) {
165                (*rx_descs.add(i)).addr = buf.phys;
166            }
167
168            // --- TX ring ---
169            let tx_ring_region = alloc
170                .alloc_dma(NUM_TX * core::mem::size_of::<LegacyTxDesc>())
171                .map_err(|_| NetError::NotReady)?;
172            ptr::write_bytes(tx_ring_region.virt, 0, tx_ring_region.size);
173            let tx_descs = tx_ring_region.virt as *mut LegacyTxDesc;
174
175            wr(mmio_base, regs::TDBAL, tx_ring_region.phys as u32);
176            wr(mmio_base, regs::TDBAH, (tx_ring_region.phys >> 32) as u32);
177            wr(mmio_base, regs::TDLEN, tx_ring_region.size as u32);
178            wr(mmio_base, regs::TDH, 0);
179            wr(mmio_base, regs::TDT, 0);
180
181            // Pre-allocate TX buffer pool (avoid per-packet DMA alloc).
182            let mut tx_bufs = [DmaRegion::ZERO; NUM_TX];
183            for tx_buf in tx_bufs.iter_mut().take(NUM_TX) {
184                let buf = alloc
185                    .alloc_dma(net_core::MTU)
186                    .map_err(|_| NetError::NotReady)?;
187                ptr::write_bytes(buf.virt, 0, net_core::MTU);
188                *tx_buf = buf;
189            }
190
191            // Enable TX
192            wr(
193                mmio_base,
194                regs::TCTL,
195                tctl::EN | tctl::PSP | (0x10 << tctl::CT_SHIFT) | (0x40 << tctl::COLD_SHIFT),
196            );
197
198            // --- RX control: BSIZE_4096 (BSEX=1, BSIZE=10 => 4096 bytes) ---
199            // RX_BUF_SIZE is 4096; the hardware must match.
200            wr(
201                mmio_base,
202                regs::RCTL,
203                rctl::EN | rctl::BAM | rctl::BSIZE_4096 | rctl::SECRC,
204            );
205
206            // --- Link up + interrupts ---
207            let c = rd(mmio_base, regs::CTRL);
208            wr(mmio_base, regs::CTRL, c | ctrl::SLU);
209
210            // --- Interrupt coalescing ---
211            // ITR    = 1950 =>  approx. 2000 irq/s max (1950 × 256 ns ≈ 500 µs between IRQs).
212            // RDTR   = 0 => fire interrupt after first packet (baseline)
213            // RADV   = 128 => absolute timer: force an interrupt after 128 µs even
214            //                if no new packets arrive (keeps latency bounded).
215            // TIDV   = 0 => transmit: fire on first descriptor writeback
216            // TADV   = 64 => absolute timer: flush TX interrupts after 64 µs.
217            //
218            // ITR uses 256 ns units on 8254x; on e1000e/I210 the same register
219            // uses 1024 ns units : the value still provides adequate coalescing.
220            //
221            // 488 × 256 ns ≈ 125 µs => approx 8 000 irq/s max.  Low enough to keep the
222            // CPU from being swamped under heavy load, high enough for interactive
223            // responsiveness (sub-ms ping).
224            wr(mmio_base, regs::ITR, 488);
225            wr(mmio_base, regs::RDTR, 0);
226            wr(mmio_base, regs::RADV, 128);
227            wr(mmio_base, regs::TIDV, 0);
228            wr(mmio_base, regs::TADV, 64);
229
230            wr(
231                mmio_base,
232                regs::IMS,
233                int_bits::RXT0
234                    | int_bits::LSC
235                    | int_bits::RXDMT0
236                    | int_bits::RXO
237                    | int_bits::TXDW
238                    | int_bits::TXQE,
239            );
240            let status = rd(mmio_base, regs::STATUS);
241            let link_up = (status & 0x02) != 0;
242
243            Ok(Self {
244                mmio: mmio_base,
245                rx: RxRing::new(rx_descs, NUM_RX),
246                rx_bufs,
247                rx_ring_phys: rx_ring_region.phys,
248                tx: TxRing::new(tx_descs, NUM_TX),
249                tx_bufs,
250                tx_ring_phys: tx_ring_region.phys,
251                mac,
252                link_up,
253                stats: E1000Stats::new(),
254                tx_since_last_reclaim: 0,
255                last_tdh: 0,
256            })
257        }
258    }
259
260    pub fn mac_address(&self) -> [u8; 6] {
261        self.mac
262    }
263
264    pub fn link_up(&self) -> bool {
265        self.link_up
266    }
267
268    pub fn check_link(&mut self) -> bool {
269        unsafe {
270            let status = rd(self.mmio, regs::STATUS);
271            self.link_up = (status & 0x02) != 0;
272        }
273        self.link_up
274    }
275
276    pub fn stats(&self) -> &E1000Stats {
277        &self.stats
278    }
279
280    /// Recycle an RX descriptor: clear status, restore buffer address, bump tail.
281    fn recycle_rx_desc(&mut self, idx: usize) {
282        self.rx.desc_mut(idx).clear_status();
283        self.rx
284            .desc_mut(idx)
285            .set_buffer_addr(self.rx_bufs[idx].phys);
286        let new_tail = self.rx.advance();
287        unsafe {
288            wr(self.mmio, regs::RDT, new_tail as u32);
289        }
290    }
291
292    pub fn receive(&mut self, buf: &mut [u8]) -> Result<usize, NetError> {
293        if !self.check_link() {
294            return Err(NetError::LinkDown);
295        }
296
297        let (idx, pkt_len) = self.rx.poll().ok_or(NetError::NoPacket)?;
298
299        // --- RX error checking ---
300        let err = self.rx.desc(idx).errors;
301        let st = self.rx.desc(idx).status;
302
303        if (err & rx_errors::CE) != 0 {
304            log::warn!("e1000: RX CRC error on descriptor {}", idx);
305            self.recycle_rx_desc(idx);
306            self.stats.rx_crc_errors += 1;
307            self.stats.rx_errors += 1;
308            return Err(NetError::NoPacket);
309        }
310        if (err & rx_errors::SE) != 0 {
311            log::warn!("e1000: RX symbol error on descriptor {}", idx);
312            self.recycle_rx_desc(idx);
313            self.stats.rx_errors += 1;
314            return Err(NetError::NoPacket);
315        }
316        if (err & rx_errors::TCPE) != 0 {
317            log::trace!("e1000: RX TCP/UDP checksum error on descriptor {}", idx);
318        }
319        if (err & rx_errors::IPE) != 0 {
320            log::trace!("e1000: RX IP checksum error on descriptor {}", idx);
321        }
322        if (st & rx_status::EOP) == 0 {
323            log::warn!(
324                "e1000: RX descriptor {} missing EOP : fragment dropped",
325                idx
326            );
327            self.recycle_rx_desc(idx);
328            self.stats.rx_length_errors += 1;
329            self.stats.rx_errors += 1;
330            return Err(NetError::NoPacket);
331        }
332
333        // pkt_len comes from the DMA descriptor, i.e. from the device.
334        // A buggy or hostile device reporting a length larger than the RX
335        // buffer would make the copy below read past the DMA allocation
336        // (heap info-leak). Clamp to what was actually allocated.
337        let len = (pkt_len as usize).min(RX_BUF_SIZE);
338        if buf.len() < len {
339            self.recycle_rx_desc(idx);
340            return Err(NetError::BufferTooSmall);
341        }
342
343        unsafe {
344            ptr::copy_nonoverlapping(self.rx_bufs[idx].virt, buf.as_mut_ptr(), len);
345        }
346
347        self.recycle_rx_desc(idx);
348        self.stats.rx_ok += 1;
349        Ok(len)
350    }
351
352    pub fn transmit(&mut self, buf: &[u8]) -> Result<(), NetError> {
353        if !self.check_link() {
354            return Err(NetError::LinkDown);
355        }
356
357        if buf.len() > net_core::MTU {
358            return Err(NetError::BufferTooSmall);
359        }
360
361        let idx = self.tx.tail();
362
363        // Reclaim completed TX buffers before checking fullness.
364        self.reclaim_completed_tx_buffers();
365
366        // Check if slot is still busy after reclaim.
367        if self.tx.desc(idx).cmd != 0 && !self.tx.is_done(idx) {
368            self.stats.tx_dropped += 1;
369            return Err(NetError::TxQueueFull);
370        }
371
372        // Copy into the pre-allocated TX buffer pool slot.
373        unsafe {
374            ptr::copy_nonoverlapping(buf.as_ptr(), self.tx_bufs[idx].virt, buf.len());
375        }
376
377        let _submitted = self.tx.submit(self.tx_bufs[idx].phys, buf.len() as u16);
378        unsafe {
379            wr(self.mmio, regs::TDT, self.tx.tail() as u32);
380        }
381
382        self.tx_since_last_reclaim += 1;
383        self.stats.tx_ok += 1;
384        Ok(())
385    }
386
387    /// Reclaim TX descriptors that the hardware has completed (DD bit set).
388    /// No per-packet DMA free is needed because the buffer pool is
389    /// pre-allocated; we only reset the descriptor for reuse.
390    fn reclaim_completed_tx_buffers(&mut self) {
391        // Scan all slots that might be in-flight.
392        let head = unsafe { rd(self.mmio, regs::TDH) } as usize % NUM_TX;
393        let tail = self.tx.tail();
394
395        let mut idx = head;
396        while idx != tail {
397            if self.tx.is_done(idx) {
398                let st = self.tx.desc(idx).status;
399                if (st & tx_status::LC) != 0 {
400                    log::warn!("e1000: TX late collision at descriptor {}", idx);
401                    self.stats.tx_late_collision += 1;
402                    self.stats.tx_errors += 1;
403                }
404                if (st & tx_status::TU) != 0 {
405                    log::warn!("e1000: TX underrun at descriptor {}", idx);
406                    self.stats.tx_underrun += 1;
407                    self.stats.tx_errors += 1;
408                }
409                // Reset the descriptor for reuse.
410                self.tx.desc_mut(idx).clear();
411            }
412            idx = (idx + 1) % NUM_TX;
413        }
414        self.tx_since_last_reclaim = 0;
415    }
416
417    /// Non-blocking: check if the last submitted TX has completed.
418    pub fn is_transmit_complete(&self) -> bool {
419        let idx = self.tx.tail();
420        self.tx.is_done(idx)
421    }
422
423    /// Blocking spin until the last TX completes.
424    pub fn wait_for_transmit(&self) {
425        while !self.is_transmit_complete() {
426            core::hint::spin_loop();
427        }
428    }
429
430    pub fn tx_is_done(&self, idx: usize) -> bool {
431        self.tx.is_done(idx % NUM_TX)
432    }
433
434    /// Process a hardware interrupt.  Returns the raw ICR value so the
435    /// kernel adapter can decide what to do (e.g. wake a receive task).
436    pub fn handle_interrupt(&mut self) -> u32 {
437        let icr = unsafe { rd(self.mmio, regs::ICR) };
438
439        if (icr & int_bits::LSC) != 0 {
440            let status = unsafe { rd(self.mmio, regs::STATUS) };
441            let was_up = self.link_up;
442            self.link_up = (status & 0x02) != 0;
443            if was_up != self.link_up {
444                log::info!("e1000: link {}", if self.link_up { "up" } else { "down" });
445            }
446        }
447
448        if (icr & (int_bits::RXT0 | int_bits::RXDMT0 | int_bits::RXO)) != 0 {
449            log::trace!("e1000: RX interrupt (icr={:#x})", icr);
450        }
451
452        if (icr & int_bits::TXDW) != 0 {
453            self.reclaim_completed_tx_buffers();
454            log::trace!("e1000: TX descriptor writeback");
455        }
456
457        if (icr & int_bits::TXQE) != 0 {
458            log::trace!("e1000: TX queue empty");
459        }
460
461        if (icr & int_bits::RXO) != 0 {
462            log::warn!("e1000: RX overflow : descriptor ring full");
463        }
464
465        icr
466    }
467
468    /// Watchdog check: called periodically (e.g. from timer IRQ) to detect
469    /// and recover from a stalled link.  Returns `true` if a reset was
470    /// performed.
471    ///
472    /// Stall detection logic:
473    ///   - If `tx_since_last_reclaim` exceeds `WATCHDOG_TX_THRESHOLD` the
474    ///     software has been submitting without any reclaim happening.
475    ///   - If TDH (hardware head) == `last_tdh` => the hardware has not
476    ///     advanced => stall => reset.
477    ///   - If TDH advanced => update `last_tdh` and reset counter (progress).
478    ///   - If TDH == tail (ring empty) => normal idle => reset counter.
479    pub fn watchdog_tick(&mut self, alloc: &dyn DmaAllocator) -> bool {
480        if self.tx_since_last_reclaim < WATCHDOG_TX_THRESHOLD {
481            return false;
482        }
483
484        let tdh = unsafe { rd(self.mmio, regs::TDH) } as usize;
485        let tail = self.tx.tail();
486
487        if tdh != tail {
488            // Descriptors are in-flight : check if head has moved.
489            if tdh == self.last_tdh {
490                log::warn!(
491                    "e1000: watchdog : TX stalled (TDH={} TDT={} last_tdh={}), resetting",
492                    tdh,
493                    tail,
494                    self.last_tdh
495                );
496                self.reinit_hardware(alloc);
497                self.stats.watchdog_resets += 1;
498                return true;
499            }
500            // Head advanced: record new position, reset counter.
501            self.last_tdh = tdh;
502            self.tx_since_last_reclaim = 0;
503        } else {
504            // Ring empty : normal idle.
505            self.tx_since_last_reclaim = 0;
506        }
507        false
508    }
509
510    /// Re-initialise hardware without tearing down the DMA rings.  Used by
511    /// the watchdog to recover from a stuck state.
512    fn reinit_hardware(&mut self, _alloc: &dyn DmaAllocator) {
513        unsafe {
514            // Mask all interrupts while we re-init.
515            wr(self.mmio, regs::IMC, 0xFFFF_FFFF);
516            let _ = rd(self.mmio, regs::ICR);
517
518            // Soft-reset the controller.
519            let c = rd(self.mmio, regs::CTRL);
520            wr(self.mmio, regs::CTRL, c | ctrl::RST);
521            for _ in 0..RESET_MAX_POLLS {
522                if rd(self.mmio, regs::CTRL) & ctrl::RST == 0 {
523                    break;
524                }
525                core::hint::spin_loop();
526            }
527
528            // Re-point the rings using the stored physical addresses.
529            wr(self.mmio, regs::RDBAL, self.rx_ring_phys as u32);
530            wr(self.mmio, regs::RDBAH, (self.rx_ring_phys >> 32) as u32);
531            wr(
532                self.mmio,
533                regs::RDLEN,
534                (NUM_RX * core::mem::size_of::<LegacyRxDesc>()) as u32,
535            );
536            wr(self.mmio, regs::RDH, 0);
537            wr(self.mmio, regs::RDT, (NUM_RX - 1) as u32);
538
539            wr(self.mmio, regs::TDBAL, self.tx_ring_phys as u32);
540            wr(self.mmio, regs::TDBAH, (self.tx_ring_phys >> 32) as u32);
541            wr(
542                self.mmio,
543                regs::TDLEN,
544                (NUM_TX * core::mem::size_of::<LegacyTxDesc>()) as u32,
545            );
546            wr(self.mmio, regs::TDH, 0);
547            wr(self.mmio, regs::TDT, 0);
548
549            // Re-enable interrupts.
550            wr(
551                self.mmio,
552                regs::IMS,
553                int_bits::RXT0
554                    | int_bits::LSC
555                    | int_bits::RXDMT0
556                    | int_bits::RXO
557                    | int_bits::TXDW
558                    | int_bits::TXQE,
559            );
560
561            // Force link up.
562            let c = rd(self.mmio, regs::CTRL);
563            wr(self.mmio, regs::CTRL, c | ctrl::SLU);
564
565            // Reconfigure RX: BSIZE_4096.
566            wr(
567                self.mmio,
568                regs::RCTL,
569                rctl::EN | rctl::BAM | rctl::BSIZE_4096 | rctl::SECRC,
570            );
571
572            // Reconfigure TX.
573            wr(
574                self.mmio,
575                regs::TCTL,
576                tctl::EN | tctl::PSP | (0x10 << tctl::CT_SHIFT) | (0x40 << tctl::COLD_SHIFT),
577            );
578
579            // Reconfigure interrupt coalescing.
580            wr(self.mmio, regs::ITR, 488);
581            wr(self.mmio, regs::RDTR, 0);
582            wr(self.mmio, regs::RADV, 128);
583            wr(self.mmio, regs::TIDV, 0);
584            wr(self.mmio, regs::TADV, 64);
585        }
586
587        self.tx_since_last_reclaim = 0;
588        self.last_tdh = 0;
589        self.check_link();
590    }
591
592    unsafe fn read_mac(base: u64) -> Result<[u8; 6], NetError> {
593        let ral = rd(base, regs::RAL0);
594        let rah = rd(base, regs::RAH0);
595        log::trace!("e1000: RAL0={:#x} RAH0={:#x}", ral, rah);
596
597        if ral != 0 || rah != 0 {
598            return Ok([
599                (ral) as u8,
600                (ral >> 8) as u8,
601                (ral >> 16) as u8,
602                (ral >> 24) as u8,
603                (rah) as u8,
604                (rah >> 8) as u8,
605            ]);
606        }
607
608        log::trace!("e1000: RAL/RAH empty : reading MAC words from EEPROM");
609        let mut mac = [0u8; 6];
610        for i in 0u32..3 {
611            let w = Self::eeprom_read(base, i as u8)?;
612            mac[(i * 2) as usize] = w as u8;
613            mac[(i * 2 + 1) as usize] = (w >> 8) as u8;
614        }
615        if mac == [0; 6] || mac == [0xFF; 6] {
616            return Err(NetError::NotReady);
617        }
618        Ok(mac)
619    }
620
621    unsafe fn eeprom_read(base: u64, addr: u8) -> Result<u16, NetError> {
622        log::trace!("e1000: EEPROM read addr={}", addr);
623        wr(
624            base,
625            regs::EERD,
626            eerd::START | ((addr as u32) << eerd::ADDR_SHIFT),
627        );
628        for poll in 0..EEPROM_MAX_POLLS {
629            let v = rd(base, regs::EERD);
630            if v & eerd::DONE != 0 {
631                let data = ((v >> eerd::DATA_SHIFT) & 0xFFFF) as u16;
632                log::trace!(
633                    "e1000: EEPROM addr={} ok after {} polls data={:#x}",
634                    addr,
635                    poll + 1,
636                    data
637                );
638                return Ok(data);
639            }
640            core::hint::spin_loop();
641        }
642        log::warn!(
643            "e1000: EEPROM addr={} timeout after {} EERD polls",
644            addr,
645            EEPROM_MAX_POLLS
646        );
647        Err(NetError::NotReady)
648    }
649}