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            // --- VLAN Ether Type (cosmetic : the 802.1Q network stack does not
134            //     process VLAN tags, so this only makes the hardware parse the
135            //     tag field without any functional effect.)
136            // TODO here ! ---
137            wr(mmio_base, regs::VET, 0x8100);
138            // Zero-out VLAN filter table (accept no VLANs; functional once the
139            // stack gains 802.1Q support).
140            for i in 0..128u32 {
141                wr(mmio_base, regs::VFTA + (i as usize) * 4, 0);
142            }
143
144            // --- RX ring ---
145            let rx_ring_region = alloc
146                .alloc_dma(NUM_RX * core::mem::size_of::<LegacyRxDesc>())
147                .map_err(|_| NetError::NotReady)?;
148            ptr::write_bytes(rx_ring_region.virt, 0, rx_ring_region.size);
149            let rx_descs = rx_ring_region.virt as *mut LegacyRxDesc;
150
151            let mut rx_bufs = [DmaRegion::ZERO; NUM_RX];
152            for rx_buf in rx_bufs.iter_mut().take(NUM_RX) {
153                let buf = alloc
154                    .alloc_dma(RX_BUF_SIZE)
155                    .map_err(|_| NetError::NotReady)?;
156                ptr::write_bytes(buf.virt, 0, RX_BUF_SIZE);
157                *rx_buf = buf;
158            }
159
160            wr(mmio_base, regs::RDBAL, rx_ring_region.phys as u32);
161            wr(mmio_base, regs::RDBAH, (rx_ring_region.phys >> 32) as u32);
162            wr(mmio_base, regs::RDLEN, rx_ring_region.size as u32);
163            wr(mmio_base, regs::RDH, 0);
164            wr(mmio_base, regs::RDT, (NUM_RX - 1) as u32);
165
166            for (i, buf) in rx_bufs.iter().enumerate().take(NUM_RX) {
167                (*rx_descs.add(i)).addr = buf.phys;
168            }
169
170            // --- TX ring ---
171            let tx_ring_region = alloc
172                .alloc_dma(NUM_TX * core::mem::size_of::<LegacyTxDesc>())
173                .map_err(|_| NetError::NotReady)?;
174            ptr::write_bytes(tx_ring_region.virt, 0, tx_ring_region.size);
175            let tx_descs = tx_ring_region.virt as *mut LegacyTxDesc;
176
177            wr(mmio_base, regs::TDBAL, tx_ring_region.phys as u32);
178            wr(mmio_base, regs::TDBAH, (tx_ring_region.phys >> 32) as u32);
179            wr(mmio_base, regs::TDLEN, tx_ring_region.size as u32);
180            wr(mmio_base, regs::TDH, 0);
181            wr(mmio_base, regs::TDT, 0);
182
183            // Pre-allocate TX buffer pool (avoid per-packet DMA alloc).
184            let mut tx_bufs = [DmaRegion::ZERO; NUM_TX];
185            for tx_buf in tx_bufs.iter_mut().take(NUM_TX) {
186                let buf = alloc
187                    .alloc_dma(net_core::MTU)
188                    .map_err(|_| NetError::NotReady)?;
189                ptr::write_bytes(buf.virt, 0, net_core::MTU);
190                *tx_buf = buf;
191            }
192
193            // Enable TX
194            wr(
195                mmio_base,
196                regs::TCTL,
197                tctl::EN | tctl::PSP | (0x10 << tctl::CT_SHIFT) | (0x40 << tctl::COLD_SHIFT),
198            );
199
200            // --- RX control: BSIZE_4096 (BSIZE=00 | BSEX=1) ---
201            // RX_BUF_SIZE is 4096; the hardware must match.
202            wr(
203                mmio_base,
204                regs::RCTL,
205                rctl::EN | rctl::BAM | rctl::BSIZE_4096 | rctl::SECRC,
206            );
207
208            // --- Link up + interrupts ---
209            let c = rd(mmio_base, regs::CTRL);
210            wr(mmio_base, regs::CTRL, c | ctrl::SLU);
211
212            // --- Interrupt coalescing ---
213            // ITR    = 1950 =>  approx. 2000 irq/s max (1950 × 256 ns ≈ 500 µs between IRQs).
214            // RDTR   = 0 => fire interrupt after first packet (baseline)
215            // RADV   = 128 => absolute timer: force an interrupt after 128 µs even
216            //                if no new packets arrive (keeps latency bounded).
217            // TIDV   = 0 => transmit: fire on first descriptor writeback
218            // TADV   = 64 => absolute timer: flush TX interrupts after 64 µs.
219            //
220            // ITR uses 256 ns units on 8254x; on e1000e/I210 the same register
221            // uses 1024 ns units : the value still provides adequate coalescing.
222            //
223            // 488 × 256 ns ≈ 125 µs => approx 8 000 irq/s max.  Low enough to keep the
224            // CPU from being swamped under heavy load, high enough for interactive
225            // responsiveness (sub-ms ping).
226            wr(mmio_base, regs::ITR, 488);
227            wr(mmio_base, regs::RDTR, 0);
228            wr(mmio_base, regs::RADV, 128);
229            wr(mmio_base, regs::TIDV, 0);
230            wr(mmio_base, regs::TADV, 64);
231
232            wr(
233                mmio_base,
234                regs::IMS,
235                int_bits::RXT0
236                    | int_bits::LSC
237                    | int_bits::RXDMT0
238                    | int_bits::RXO
239                    | int_bits::TXDW
240                    | int_bits::TXQE,
241            );
242            let status = rd(mmio_base, regs::STATUS);
243            let link_up = (status & 0x02) != 0;
244
245            Ok(Self {
246                mmio: mmio_base,
247                rx: RxRing::new(rx_descs, NUM_RX),
248                rx_bufs,
249                rx_ring_phys: rx_ring_region.phys,
250                tx: TxRing::new(tx_descs, NUM_TX),
251                tx_bufs,
252                tx_ring_phys: tx_ring_region.phys,
253                mac,
254                link_up,
255                stats: E1000Stats::new(),
256                tx_since_last_reclaim: 0,
257                last_tdh: 0,
258            })
259        }
260    }
261
262    pub fn mac_address(&self) -> [u8; 6] {
263        self.mac
264    }
265
266    pub fn link_up(&self) -> bool {
267        self.link_up
268    }
269
270    pub fn check_link(&mut self) -> bool {
271        unsafe {
272            let status = rd(self.mmio, regs::STATUS);
273            self.link_up = (status & 0x02) != 0;
274        }
275        self.link_up
276    }
277
278    pub fn stats(&self) -> &E1000Stats {
279        &self.stats
280    }
281
282    /// Recycle an RX descriptor: clear status, restore buffer address, bump tail.
283    fn recycle_rx_desc(&mut self, idx: usize) {
284        self.rx.desc_mut(idx).clear_status();
285        self.rx
286            .desc_mut(idx)
287            .set_buffer_addr(self.rx_bufs[idx].phys);
288        let new_tail = self.rx.advance();
289        unsafe {
290            wr(self.mmio, regs::RDT, new_tail as u32);
291        }
292    }
293
294    pub fn receive(&mut self, buf: &mut [u8]) -> Result<usize, NetError> {
295        if !self.check_link() {
296            return Err(NetError::LinkDown);
297        }
298
299        let (idx, pkt_len) = self.rx.poll().ok_or(NetError::NoPacket)?;
300
301        // --- RX error checking ---
302        let err = self.rx.desc(idx).errors;
303        let st = self.rx.desc(idx).status;
304
305        if (err & rx_errors::CE) != 0 {
306            log::warn!("e1000: RX CRC error on descriptor {}", idx);
307            self.recycle_rx_desc(idx);
308            self.stats.rx_crc_errors += 1;
309            self.stats.rx_errors += 1;
310            return Err(NetError::NoPacket);
311        }
312        if (err & rx_errors::SE) != 0 {
313            log::warn!("e1000: RX symbol error on descriptor {}", idx);
314            self.recycle_rx_desc(idx);
315            self.stats.rx_errors += 1;
316            return Err(NetError::NoPacket);
317        }
318        if (err & rx_errors::TCPE) != 0 {
319            log::trace!("e1000: RX TCP/UDP checksum error on descriptor {}", idx);
320        }
321        if (err & rx_errors::IPE) != 0 {
322            log::trace!("e1000: RX IP checksum error on descriptor {}", idx);
323        }
324        if (st & rx_status::EOP) == 0 {
325            log::warn!(
326                "e1000: RX descriptor {} missing EOP : fragment dropped",
327                idx
328            );
329            self.recycle_rx_desc(idx);
330            self.stats.rx_length_errors += 1;
331            self.stats.rx_errors += 1;
332            return Err(NetError::NoPacket);
333        }
334
335        let len = pkt_len as usize;
336        if buf.len() < len {
337            self.recycle_rx_desc(idx);
338            return Err(NetError::BufferTooSmall);
339        }
340
341        unsafe {
342            ptr::copy_nonoverlapping(self.rx_bufs[idx].virt, buf.as_mut_ptr(), len);
343        }
344
345        self.recycle_rx_desc(idx);
346        self.stats.rx_ok += 1;
347        Ok(len)
348    }
349
350    pub fn transmit(&mut self, buf: &[u8]) -> Result<(), NetError> {
351        if !self.check_link() {
352            return Err(NetError::LinkDown);
353        }
354
355        if buf.len() > net_core::MTU {
356            return Err(NetError::BufferTooSmall);
357        }
358
359        let idx = self.tx.tail();
360
361        // Reclaim completed TX buffers before checking fullness.
362        self.reclaim_completed_tx_buffers();
363
364        // Check if slot is still busy after reclaim.
365        if self.tx.desc(idx).cmd != 0 && !self.tx.is_done(idx) {
366            self.stats.tx_dropped += 1;
367            return Err(NetError::TxQueueFull);
368        }
369
370        // Copy into the pre-allocated TX buffer pool slot.
371        unsafe {
372            ptr::copy_nonoverlapping(buf.as_ptr(), self.tx_bufs[idx].virt, buf.len());
373        }
374
375        let _submitted = self.tx.submit(self.tx_bufs[idx].phys, buf.len() as u16);
376        unsafe {
377            wr(self.mmio, regs::TDT, self.tx.tail() as u32);
378        }
379
380        self.tx_since_last_reclaim += 1;
381        self.stats.tx_ok += 1;
382        Ok(())
383    }
384
385    /// Reclaim TX descriptors that the hardware has completed (DD bit set).
386    /// No per-packet DMA free is needed because the buffer pool is
387    /// pre-allocated; we only reset the descriptor for reuse.
388    fn reclaim_completed_tx_buffers(&mut self) {
389        // Scan all slots that might be in-flight.
390        let head = unsafe { rd(self.mmio, regs::TDH) } as usize % NUM_TX;
391        let tail = self.tx.tail();
392
393        let mut idx = head;
394        while idx != tail {
395            if self.tx.is_done(idx) {
396                let st = self.tx.desc(idx).status;
397                if (st & tx_status::LC) != 0 {
398                    log::warn!("e1000: TX late collision at descriptor {}", idx);
399                    self.stats.tx_late_collision += 1;
400                    self.stats.tx_errors += 1;
401                }
402                if (st & tx_status::TU) != 0 {
403                    log::warn!("e1000: TX underrun at descriptor {}", idx);
404                    self.stats.tx_underrun += 1;
405                    self.stats.tx_errors += 1;
406                }
407                // Reset the descriptor for reuse.
408                self.tx.desc_mut(idx).clear();
409            }
410            idx = (idx + 1) % NUM_TX;
411        }
412        self.tx_since_last_reclaim = 0;
413    }
414
415    /// Non-blocking: check if the last submitted TX has completed.
416    pub fn is_transmit_complete(&self) -> bool {
417        let idx = self.tx.tail();
418        self.tx.is_done(idx)
419    }
420
421    /// Blocking spin until the last TX completes.
422    pub fn wait_for_transmit(&self) {
423        while !self.is_transmit_complete() {
424            core::hint::spin_loop();
425        }
426    }
427
428    pub fn tx_is_done(&self, idx: usize) -> bool {
429        self.tx.is_done(idx % NUM_TX)
430    }
431
432    /// Process a hardware interrupt.  Returns the raw ICR value so the
433    /// kernel adapter can decide what to do (e.g. wake a receive task).
434    pub fn handle_interrupt(&mut self) -> u32 {
435        let icr = unsafe { rd(self.mmio, regs::ICR) };
436
437        if (icr & int_bits::LSC) != 0 {
438            let status = unsafe { rd(self.mmio, regs::STATUS) };
439            let was_up = self.link_up;
440            self.link_up = (status & 0x02) != 0;
441            if was_up != self.link_up {
442                log::info!("e1000: link {}", if self.link_up { "up" } else { "down" });
443            }
444        }
445
446        if (icr & (int_bits::RXT0 | int_bits::RXDMT0 | int_bits::RXO)) != 0 {
447            log::trace!("e1000: RX interrupt (icr={:#x})", icr);
448        }
449
450        if (icr & int_bits::TXDW) != 0 {
451            self.reclaim_completed_tx_buffers();
452            log::trace!("e1000: TX descriptor writeback");
453        }
454
455        if (icr & int_bits::TXQE) != 0 {
456            log::trace!("e1000: TX queue empty");
457        }
458
459        if (icr & int_bits::RXO) != 0 {
460            log::warn!("e1000: RX overflow : descriptor ring full");
461        }
462
463        icr
464    }
465
466    /// Watchdog check: called periodically (e.g. from timer IRQ) to detect
467    /// and recover from a stalled link.  Returns `true` if a reset was
468    /// performed.
469    ///
470    /// Stall detection logic:
471    ///   - If `tx_since_last_reclaim` exceeds `WATCHDOG_TX_THRESHOLD` the
472    ///     software has been submitting without any reclaim happening.
473    ///   - If TDH (hardware head) == `last_tdh` → the hardware has not
474    ///     advanced → stall → reset.
475    ///   - If TDH advanced → update `last_tdh` and reset counter (progress).
476    ///   - If TDH == tail (ring empty) → normal idle → reset counter.
477    pub fn watchdog_tick(&mut self, alloc: &dyn DmaAllocator) -> bool {
478        if self.tx_since_last_reclaim < WATCHDOG_TX_THRESHOLD {
479            return false;
480        }
481
482        let tdh = unsafe { rd(self.mmio, regs::TDH) } as usize;
483        let tail = self.tx.tail();
484
485        if tdh != tail {
486            // Descriptors are in-flight : check if head has moved.
487            if tdh == self.last_tdh {
488                log::warn!(
489                    "e1000: watchdog : TX stalled (TDH={} TDT={} last_tdh={}), resetting",
490                    tdh,
491                    tail,
492                    self.last_tdh
493                );
494                self.reinit_hardware(alloc);
495                self.stats.watchdog_resets += 1;
496                return true;
497            }
498            // Head advanced: record new position, reset counter.
499            self.last_tdh = tdh;
500            self.tx_since_last_reclaim = 0;
501        } else {
502            // Ring empty : normal idle.
503            self.tx_since_last_reclaim = 0;
504        }
505        false
506    }
507
508    /// Re-initialise hardware without tearing down the DMA rings.  Used by
509    /// the watchdog to recover from a stuck state.
510    fn reinit_hardware(&mut self, _alloc: &dyn DmaAllocator) {
511        unsafe {
512            // Mask all interrupts while we re-init.
513            wr(self.mmio, regs::IMC, 0xFFFF_FFFF);
514            let _ = rd(self.mmio, regs::ICR);
515
516            // Soft-reset the controller.
517            let c = rd(self.mmio, regs::CTRL);
518            wr(self.mmio, regs::CTRL, c | ctrl::RST);
519            for _ in 0..RESET_MAX_POLLS {
520                if rd(self.mmio, regs::CTRL) & ctrl::RST == 0 {
521                    break;
522                }
523                core::hint::spin_loop();
524            }
525
526            // Re-point the rings using the stored physical addresses.
527            wr(self.mmio, regs::RDBAL, self.rx_ring_phys as u32);
528            wr(self.mmio, regs::RDBAH, (self.rx_ring_phys >> 32) as u32);
529            wr(
530                self.mmio,
531                regs::RDLEN,
532                (NUM_RX * core::mem::size_of::<LegacyRxDesc>()) as u32,
533            );
534            wr(self.mmio, regs::RDH, 0);
535            wr(self.mmio, regs::RDT, (NUM_RX - 1) as u32);
536
537            wr(self.mmio, regs::TDBAL, self.tx_ring_phys as u32);
538            wr(self.mmio, regs::TDBAH, (self.tx_ring_phys >> 32) as u32);
539            wr(
540                self.mmio,
541                regs::TDLEN,
542                (NUM_TX * core::mem::size_of::<LegacyTxDesc>()) as u32,
543            );
544            wr(self.mmio, regs::TDH, 0);
545            wr(self.mmio, regs::TDT, 0);
546
547            // Re-enable interrupts.
548            wr(
549                self.mmio,
550                regs::IMS,
551                int_bits::RXT0
552                    | int_bits::LSC
553                    | int_bits::RXDMT0
554                    | int_bits::RXO
555                    | int_bits::TXDW
556                    | int_bits::TXQE,
557            );
558
559            // Force link up.
560            let c = rd(self.mmio, regs::CTRL);
561            wr(self.mmio, regs::CTRL, c | ctrl::SLU);
562
563            // Reconfigure RX: BSIZE_4096.
564            wr(
565                self.mmio,
566                regs::RCTL,
567                rctl::EN | rctl::BAM | rctl::BSIZE_4096 | rctl::SECRC,
568            );
569
570            // Reconfigure TX.
571            wr(
572                self.mmio,
573                regs::TCTL,
574                tctl::EN | tctl::PSP | (0x10 << tctl::CT_SHIFT) | (0x40 << tctl::COLD_SHIFT),
575            );
576
577            // Reconfigure interrupt coalescing.
578            wr(self.mmio, regs::ITR, 488);
579            wr(self.mmio, regs::RDTR, 0);
580            wr(self.mmio, regs::RADV, 128);
581            wr(self.mmio, regs::TIDV, 0);
582            wr(self.mmio, regs::TADV, 64);
583        }
584
585        self.tx_since_last_reclaim = 0;
586        self.last_tdh = 0;
587        self.check_link();
588    }
589
590    unsafe fn read_mac(base: u64) -> Result<[u8; 6], NetError> {
591        let ral = rd(base, regs::RAL0);
592        let rah = rd(base, regs::RAH0);
593        log::trace!("e1000: RAL0={:#x} RAH0={:#x}", ral, rah);
594
595        if ral != 0 || rah != 0 {
596            return Ok([
597                (ral) as u8,
598                (ral >> 8) as u8,
599                (ral >> 16) as u8,
600                (ral >> 24) as u8,
601                (rah) as u8,
602                (rah >> 8) as u8,
603            ]);
604        }
605
606        log::trace!("e1000: RAL/RAH empty : reading MAC words from EEPROM");
607        let mut mac = [0u8; 6];
608        for i in 0u32..3 {
609            let w = Self::eeprom_read(base, i as u8)?;
610            mac[(i * 2) as usize] = w as u8;
611            mac[(i * 2 + 1) as usize] = (w >> 8) as u8;
612        }
613        if mac == [0; 6] || mac == [0xFF; 6] {
614            return Err(NetError::NotReady);
615        }
616        Ok(mac)
617    }
618
619    unsafe fn eeprom_read(base: u64, addr: u8) -> Result<u16, NetError> {
620        log::trace!("e1000: EEPROM read addr={}", addr);
621        wr(
622            base,
623            regs::EERD,
624            eerd::START | ((addr as u32) << eerd::ADDR_SHIFT),
625        );
626        for poll in 0..EEPROM_MAX_POLLS {
627            let v = rd(base, regs::EERD);
628            if v & eerd::DONE != 0 {
629                let data = ((v >> eerd::DATA_SHIFT) & 0xFFFF) as u16;
630                log::trace!(
631                    "e1000: EEPROM addr={} ok after {} polls data={:#x}",
632                    addr,
633                    poll + 1,
634                    data
635                );
636                return Ok(data);
637            }
638            core::hint::spin_loop();
639        }
640        log::warn!(
641            "e1000: EEPROM addr={} timeout after {} EERD polls",
642            addr,
643            EEPROM_MAX_POLLS
644        );
645        Err(NetError::NotReady)
646    }
647}