1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
#![no_std]

/// Re-export
#[cfg(feature = "stm32f7xx-hal")]
pub use stm32f7xx_hal as hal;
/// Re-export
#[cfg(feature = "stm32f7xx-hal")]
pub use stm32f7xx_hal::pac as stm32;

/// Re-export
#[cfg(feature = "stm32f4xx-hal")]
pub use stm32f4xx_hal as hal;
/// Re-export
#[cfg(feature = "stm32f4xx-hal")]
pub use stm32f4xx_hal::pac as stm32;

/// Re-export
#[cfg(feature = "stm32f1xx-hal")]
pub use stm32f1xx_hal as hal;
/// Re-export
#[cfg(feature = "stm32f1xx-hal")]
pub use stm32f1xx_hal::pac as stm32;

use hal::rcc::Clocks;
use stm32::{Interrupt, ETHERNET_DMA, ETHERNET_MAC, ETHERNET_MMC, NVIC};

mod ring;
#[cfg(feature = "smi")]
pub mod smi;
pub use ring::RingEntry;
mod desc;
mod rx;
pub use rx::{RxDescriptor, RxError, RxRingEntry};
use rx::{RxPacket, RxRing};
mod tx;
use tx::TxRing;
pub use tx::{TxDescriptor, TxError, TxRingEntry};
pub mod setup;
pub use setup::EthPins;
use setup::{
    AlternateVeryHighSpeed, RmiiCrsDv, RmiiRefClk, RmiiRxD0, RmiiRxD1, RmiiTxD0, RmiiTxD1, RmiiTxEN,
};

#[cfg(feature = "smoltcp-phy")]
pub use smoltcp;
#[cfg(feature = "smoltcp-phy")]
mod smoltcp_phy;
#[cfg(feature = "smoltcp-phy")]
pub use smoltcp_phy::{EthRxToken, EthTxToken};

/// From the datasheet: *VLAN Frame maxsize = 1522*
const MTU: usize = 1522;

mod consts {
    /* For HCLK 60-100 MHz */
    pub const ETH_MACMIIAR_CR_HCLK_DIV_42: u8 = 0;
    /* For HCLK 100-150 MHz */
    pub const ETH_MACMIIAR_CR_HCLK_DIV_62: u8 = 1;
    /* For HCLK 20-35 MHz */
    pub const ETH_MACMIIAR_CR_HCLK_DIV_16: u8 = 2;
    /* For HCLK 35-60 MHz */
    pub const ETH_MACMIIAR_CR_HCLK_DIV_26: u8 = 3;
    /* For HCLK over 150 MHz */
    pub const ETH_MACMIIAR_CR_HCLK_DIV_102: u8 = 4;
}
use self::consts::*;

/// HCLK must be at least 25MHz to use the ethernet peripheral.
#[derive(Debug)]
pub struct WrongClock;

/// Ethernet DMA.
pub struct EthernetDMA<'rx, 'tx> {
    eth_dma: ETHERNET_DMA,
    rx_ring: RxRing<'rx>,
    tx_ring: TxRing<'tx>,
}
/// Ethernet media access control (MAC).
pub struct EthernetMAC {
    eth_mac: ETHERNET_MAC,
}

/// Create and initialise the ethernet driver.
///
/// Initialize and start tx and rx DMA engines.
/// Sets up the peripheral clocks and GPIO configuration,
/// and configures the ETH MAC and DMA peripherals.
/// Automatically sets slew rate to VeryHigh.
/// If you wish to use another configuration, please see
/// [new_unchecked](new_unchecked).
///
/// This method does not initialise the external PHY. However it does return an
/// [EthernetMAC](EthernetMAC) which implements the
/// [StationManagement](smi::StationManagement) trait. This can be used to
/// communicate with the external PHY.
///
/// # Note
/// - Make sure that the buffers reside in a memory region that is
/// accessible by the peripheral. Core-Coupled Memory (CCM) is
/// usually not accessible.
/// - HCLK must be at least 25 MHz.
pub fn new<'rx, 'tx, REFCLK, CRS, TXEN, TXD0, TXD1, RXD0, RXD1>(
    eth_mac: ETHERNET_MAC,
    eth_mmc: ETHERNET_MMC,
    eth_dma: ETHERNET_DMA,
    rx_buffer: &'rx mut [RxRingEntry],
    tx_buffer: &'tx mut [TxRingEntry],
    clocks: Clocks,
    pins: EthPins<REFCLK, CRS, TXEN, TXD0, TXD1, RXD0, RXD1>,
) -> Result<(EthernetDMA<'rx, 'tx>, EthernetMAC), WrongClock>
where
    REFCLK: RmiiRefClk + AlternateVeryHighSpeed,
    CRS: RmiiCrsDv + AlternateVeryHighSpeed,
    TXEN: RmiiTxEN + AlternateVeryHighSpeed,
    TXD0: RmiiTxD0 + AlternateVeryHighSpeed,
    TXD1: RmiiTxD1 + AlternateVeryHighSpeed,
    RXD0: RmiiRxD0 + AlternateVeryHighSpeed,
    RXD1: RmiiRxD1 + AlternateVeryHighSpeed,
{
    pins.setup_pins();
    unsafe { new_unchecked(eth_mac, eth_mmc, eth_dma, rx_buffer, tx_buffer, clocks) }
}

/// Create and initialise the ethernet driver (without GPIO configuration and validation).
///
/// This method does not initialise the external PHY. However it does return an
/// [EthernetMAC](EthernetMAC) which implements the
/// [StationManagement](smi::StationManagement) trait. This can be used to
/// communicate with the external PHY.
///
/// # Note
/// - Make sure that the buffers reside in a memory region that is
/// accessible by the peripheral. Core-Coupled Memory (CCM) is
/// usually not accessible.
/// - HCLK must be at least 25MHz.
pub unsafe fn new_unchecked<'rx, 'tx>(
    eth_mac: ETHERNET_MAC,
    eth_mmc: ETHERNET_MMC,
    eth_dma: ETHERNET_DMA,
    rx_buffer: &'rx mut [RxRingEntry],
    tx_buffer: &'tx mut [TxRingEntry],
    clocks: Clocks,
) -> Result<(EthernetDMA<'rx, 'tx>, EthernetMAC), WrongClock> {
    setup::setup();

    let clock_frequency = clocks.hclk().to_Hz();

    let clock_range = match clock_frequency {
        0..=24_999_999 => return Err(WrongClock),
        25_000_000..=34_999_999 => ETH_MACMIIAR_CR_HCLK_DIV_16,
        35_000_000..=59_999_999 => ETH_MACMIIAR_CR_HCLK_DIV_26,
        60_000_000..=99_999_999 => ETH_MACMIIAR_CR_HCLK_DIV_42,
        100_000_000..=149_999_999 => ETH_MACMIIAR_CR_HCLK_DIV_62,
        _ => ETH_MACMIIAR_CR_HCLK_DIV_102,
    };

    // reset DMA bus mode register
    eth_dma.dmabmr.modify(|_, w| w.sr().set_bit());

    // Wait until done
    while eth_dma.dmabmr.read().sr().bit_is_set() {}

    // set clock range in MAC MII address register
    eth_mac.macmiiar.modify(|_, w| w.cr().bits(clock_range));

    // Configuration Register
    eth_mac.maccr.modify(|_, w| {
        // CRC stripping for Type frames. STM32F1xx do not have this bit.
        #[cfg(any(feature = "stm32f4xx-hal", feature = "stm32f7xx-hal"))]
        let w = w.cstf().set_bit();

        // Fast Ethernet speed
        w.fes()
            .set_bit()
            // Duplex mode
            .dm()
            .set_bit()
            // IPv4 checksum offload
            .ipco()
            .set_bit()
            // Automatic pad/CRC stripping
            .apcs()
            .set_bit()
            // Retry disable in half-duplex mode
            .rd()
            .set_bit()
            // Receiver enable
            .re()
            .set_bit()
            // Transmitter enable
            .te()
            .set_bit()
    });
    // frame filter register
    eth_mac.macffr.modify(|_, w| {
        // Receive All
        w.ra()
            .set_bit()
            // Promiscuous mode
            .pm()
            .set_bit()
    });
    // Flow Control Register
    eth_mac.macfcr.modify(|_, w| {
        // Pause time
        w.pt().bits(0x100)
    });
    // operation mode register
    eth_dma.dmaomr.modify(|_, w| {
        // Dropping of TCP/IP checksum error frames disable
        w.dtcefd()
            .set_bit()
            // Receive store and forward
            .rsf()
            .set_bit()
            // Disable flushing of received frames
            .dfrf()
            .set_bit()
            // Transmit store and forward
            .tsf()
            .set_bit()
            // Forward error frames
            .fef()
            .set_bit()
            // Operate on second frame
            .osf()
            .set_bit()
    });

    // disable all MMC RX interrupts
    eth_mmc
        .mmcrimr
        .write(|w| w.rgufm().set_bit().rfaem().set_bit().rfcem().set_bit());
    // disable all MMC TX interrupts
    eth_mmc
        .mmctimr
        .write(|w| w.tgfm().set_bit().tgfmscm().set_bit().tgfscm().set_bit());
    // fix incorrect TGFM bit position until https://github.com/stm32-rs/stm32-rs/pull/689
    // is released and used by HALs.
    eth_mmc.mmctimr.modify(|r, w| w.bits(r.bits() | (1 << 21)));

    // bus mode register
    eth_dma.dmabmr.modify(|_, w| {
        // For any non-f107 chips, we must use enhanced descriptor format to support checksum
        // offloading and/or timestamps.
        #[cfg(not(feature = "stm32f107"))]
        let w = w.edfe().set_bit();

        // Address-aligned beats
        w.aab()
            .set_bit()
            // Fixed burst
            .fb()
            .set_bit()
            // Rx DMA PBL
            .rdp()
            .bits(32)
            // Programmable burst length
            .pbl()
            .bits(32)
            // Rx Tx priority ratio 2:1
            .pm()
            .bits(0b01)
            // Use separate PBL
            .usp()
            .set_bit()
    });

    let mut dma = EthernetDMA {
        eth_dma,
        rx_ring: RxRing::new(rx_buffer),
        tx_ring: TxRing::new(tx_buffer),
    };
    let mac = EthernetMAC { eth_mac };

    dma.rx_ring.start(&dma.eth_dma);
    dma.tx_ring.start(&dma.eth_dma);

    Ok((dma, mac))
}

impl<'rx, 'tx> EthernetDMA<'rx, 'tx> {
    /// Enable RX and TX interrupts
    ///
    /// In your handler you must call
    /// [`eth_interrupt_handler()`](fn.eth_interrupt_handler.html) to
    /// clear interrupt pending bits. Otherwise the interrupt will
    /// reoccur immediately.
    pub fn enable_interrupt(&self) {
        self.eth_dma.dmaier.modify(|_, w| {
            w
                // Normal interrupt summary enable
                .nise()
                .set_bit()
                // Receive Interrupt Enable
                .rie()
                .set_bit()
                // Transmit Interrupt Enable
                .tie()
                .set_bit()
        });

        // Enable ethernet interrupts
        unsafe {
            NVIC::unmask(Interrupt::ETH);
        }
    }

    /// Calls [`eth_interrupt_handler()`](fn.eth_interrupt_handler.html)
    pub fn interrupt_handler(&self) {
        eth_interrupt_handler(&self.eth_dma);
    }

    /// Is Rx DMA currently running?
    ///
    /// It stops if the ring is full. Call `recv_next()` to free an
    /// entry and to demand poll from the hardware.
    pub fn rx_is_running(&self) -> bool {
        self.rx_ring.running_state(&self.eth_dma).is_running()
    }

    /// Receive the next packet (if any is ready), or return `None`
    /// immediately.
    pub fn recv_next(&mut self) -> Result<RxPacket, RxError> {
        self.rx_ring.recv_next(&self.eth_dma)
    }

    /// Is Tx DMA currently running?
    pub fn tx_is_running(&self) -> bool {
        self.tx_ring.is_running(&self.eth_dma)
    }

    /// Send a packet
    pub fn send<F: FnOnce(&mut [u8]) -> R, R>(
        &mut self,
        length: usize,
        f: F,
    ) -> Result<R, TxError> {
        let result = self.tx_ring.send(length, f);
        self.tx_ring.demand_poll(&self.eth_dma);
        result
    }
}

#[cfg(feature = "smi")]
impl EthernetMAC {
    /// Borrow access to the MAC's SMI.
    ///
    /// Allows for controlling and monitoring any PHYs that may be accessible via the MDIO/MDC
    /// pins.
    ///
    /// Exclusive access to the `MDIO` and `MDC` is required to ensure that are not used elsewhere
    /// for the duration of SMI communication.
    pub fn smi<'eth, 'pins, Mdio, Mdc>(
        &'eth mut self,
        mdio: &'pins mut Mdio,
        mdc: &'pins mut Mdc,
    ) -> smi::Smi<'eth, 'pins, Mdio, Mdc>
    where
        Mdio: smi::MdioPin,
        Mdc: smi::MdcPin,
    {
        smi::Smi::new(&self.eth_mac.macmiiar, &self.eth_mac.macmiidr, mdio, mdc)
    }
}

/// Call in interrupt handler to clear interrupt reason, when
/// [`enable_interrupt()`](struct.EthernetDMA.html#method.enable_interrupt).
///
/// There are two ways to call this:
///
/// * Via the [`EthernetDMA`](struct.EthernetDMA.html) driver instance that your interrupt handler has access to.
/// * By unsafely getting `Peripherals`.
///
/// TODO: could return interrupt reason
pub fn eth_interrupt_handler(eth_dma: &ETHERNET_DMA) {
    eth_dma
        .dmasr
        .write(|w| w.nis().set_bit().rs().set_bit().ts().set_bit());
}

/// This block ensures that README.md is checked when `cargo test` is run.
///
/// Taken from https://github.com/rp-rs/pio-rs/blob/b52d3ba9c031ffa72bdd6f16b5fa8c0c04f0e2e0/src/lib.rs#L963
#[cfg(doctest)]
mod test_readme {
    macro_rules! external_doc_test {
        ($x:expr) => {
            #[doc = $x]
            extern "C" {}
        };
    }
    external_doc_test!(include_str!("../README.md"));
}