aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--Cargo.toml18
-rw-r--r--LICENSE-APACHE201
-rw-r--r--LICENSE-MIT21
-rw-r--r--README.md24
-rw-r--r--src/bd.rs279
-rw-r--r--src/bd/rxbd.rs95
-rw-r--r--src/bd/txbd.rs91
-rw-r--r--src/lib.rs361
9 files changed, 1094 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e7a1d1e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+/target
+/Cargo.lock
+/.vscode
+
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..f031df4
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "imxrt-enet"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+ral-registers = "0.1"
+imxrt-ral = "0.5"
+mdio = "0.1"
+defmt = "0.3"
+
+[dependencies.smoltcp]
+version = "0.10"
+default-features = false
+features = [
+ "medium-ethernet",
+ "proto-ipv4",
+]
diff --git a/LICENSE-APACHE b/LICENSE-APACHE
new file mode 100644
index 0000000..f49a4e1
--- /dev/null
+++ b/LICENSE-APACHE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License. \ No newline at end of file
diff --git a/LICENSE-MIT b/LICENSE-MIT
new file mode 100644
index 0000000..f4f828d
--- /dev/null
+++ b/LICENSE-MIT
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Ian McIntyre
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b85fc79
--- /dev/null
+++ b/README.md
@@ -0,0 +1,24 @@
+# imxrt-enet
+
+An Ethernet driver for i.MX RT MCUs with support for `smoltcp`.
+
+## Development
+
+To build the driver in this repo, enable
+
+- an `imxrt-ral` feature.
+- a `smoltcp` socket feature.
+
+For example,
+
+```
+cargo build --features=imxrt-ral/imxrt1062,smoltcp/socket-raw
+```
+
+If you're depending on this driver, you're expected to enable similar features
+somewhere in your dependency graph.
+
+To test the driver on hardware, see the various ENET examples maintained with
+[`imxrt-hal`].
+
+[`imxrt-hal`]: https://github.com/imxrt-rs/imxrt-hal
diff --git a/src/bd.rs b/src/bd.rs
new file mode 100644
index 0000000..1bfe9f7
--- /dev/null
+++ b/src/bd.rs
@@ -0,0 +1,279 @@
+//! Enhanced buffer descriptors.
+//!
+//! Buffer descriptors (BD) are defined with register access layer (RAL) compatibility.
+//! These definitions come from the i.MX RT 1170 reference manual, revision 2.
+
+macro_rules! bdfields {
+ ($name:ident, $type:ty $(, $field:ident [ offset = $offset:expr, bits = $bits:expr, $( $enum:ident = $val:expr $(,)? )* ] $(,)? )*) => {
+ pub mod $name {
+ $(
+ pub mod $field {
+ #![allow(non_snake_case, non_upper_case_globals, dead_code)]
+ // Assuming all fields are read-write.
+ pub mod R {}
+ pub mod W {}
+ pub mod RW {
+ $(
+ pub const $enum: $type = $val;
+ )*
+ }
+ pub const offset: $type = $offset;
+ pub const mask: $type = ((1 << $bits) - 1) << offset;
+ }
+ )*
+ }
+ };
+}
+
+pub(crate) mod rxbd;
+pub(crate) mod txbd;
+
+use core::{cell::UnsafeCell, mem::MaybeUninit, sync::atomic::Ordering};
+
+#[repr(align(64))]
+struct DescriptorRing<D, const N: usize>(UnsafeCell<MaybeUninit<[D; N]>>);
+unsafe impl<D, const N: usize> Sync for DescriptorRing<D, N> {}
+
+impl<D, const N: usize> DescriptorRing<D, N> {
+ const fn new() -> Self {
+ Self(UnsafeCell::new(MaybeUninit::uninit()))
+ }
+
+ /// # Safety
+ ///
+ /// Can only be called once. Multiple calls will release multiple mutable references
+ /// to the same memory.
+ unsafe fn init(&mut self) -> &mut [D] {
+ let ring: *mut MaybeUninit<[D; N]> = self.0.get();
+ // Transparent elements let us treat each element as uninitialized.
+ let ring: *mut [MaybeUninit<D>; N] = ring.cast();
+ // Array pointer == pointer to first element.
+ let ring: *mut MaybeUninit<D> = ring.cast();
+
+ for descriptor in 0..N {
+ // Safety: D is either a TX or RX buffer descriptor. It's OK
+ // to initialize them to a bitpattern of zero. This pointer
+ // is valid for all descriptor offsets.
+ unsafe { ring.add(descriptor).write(MaybeUninit::zeroed()) };
+ }
+
+ // Safety: all descriptors are initialized to zero.
+ unsafe { core::slice::from_raw_parts_mut(ring.cast(), N) }
+ }
+}
+
+#[repr(align(64))]
+#[derive(Clone, Copy)]
+struct DataBuffer<const N: usize>([u8; N]);
+unsafe impl<const N: usize> Sync for DataBuffer<N> {}
+
+pub struct IoBuffers<D, const COUNT: usize, const MTU: usize> {
+ ring: DescriptorRing<D, COUNT>,
+ buffers: UnsafeCell<[DataBuffer<MTU>; COUNT]>,
+}
+unsafe impl<D, const COUNT: usize, const MTU: usize> Sync for IoBuffers<D, COUNT, MTU> {}
+
+pub type TransmitBuffers<const COUNT: usize, const MTU: usize> = IoBuffers<txbd::TxBD, COUNT, MTU>;
+pub type ReceiveBuffers<const COUNT: usize, const MTU: usize> = IoBuffers<rxbd::RxBD, COUNT, MTU>;
+
+impl<D, const COUNT: usize, const MTU: usize> IoBuffers<D, COUNT, MTU> {
+ const MTU_IS_MULTIPLE_OF_16: () = assert!(MTU % 16 == 0);
+
+ pub const fn new() -> Self {
+ #[allow(clippy::let_unit_value)] // Force evaluation.
+ let _: () = Self::MTU_IS_MULTIPLE_OF_16;
+ Self {
+ ring: DescriptorRing::new(),
+ buffers: UnsafeCell::new([DataBuffer([0; MTU]); COUNT]),
+ }
+ }
+
+ fn init(
+ &'static mut self,
+ init_descriptors: impl Fn(&mut [D], &mut [DataBuffer<MTU>]),
+ ) -> IoSlices<D> {
+ // Safety: by taking 'static mut reference, we
+ // ensure that we can only be called once.
+ let ring = unsafe { self.ring.init() };
+ // Safety: since this is only called once, we're taking the only
+ // mutable reference available to the program.
+ let buffers = unsafe { &mut *self.buffers.get() };
+ let buffers = buffers.as_mut_slice();
+ init_descriptors(ring, buffers);
+ IoSlices::new(ring, MTU)
+ }
+}
+
+impl<const COUNT: usize, const MTU: usize> IoBuffers<txbd::TxBD, COUNT, MTU> {
+ pub fn take(&'static mut self) -> IoSlices<'static, txbd::TxBD> {
+ self.init(|descriptors, buffers| {
+ for (descriptor, buffer) in descriptors.iter_mut().zip(buffers.iter_mut()) {
+ ral_registers::write_reg!(
+ txbd,
+ descriptor,
+ data_buffer_pointer,
+ buffer.0.as_mut_ptr() as _
+ );
+ }
+
+ // When the DMA engine reaches this descriptor, it needs to wrap
+ // around to the first descriptor.
+ if let Some(descriptor) = descriptors.last_mut() {
+ ral_registers::modify_reg!(txbd, descriptor, flags, wrap: 1);
+ }
+ })
+ }
+}
+
+impl<const COUNT: usize, const MTU: usize> IoBuffers<rxbd::RxBD, COUNT, MTU> {
+ pub fn take(&'static mut self) -> IoSlices<'static, rxbd::RxBD> {
+ self.init(|descriptors, buffers| {
+ for (descriptor, buffer) in descriptors.iter_mut().zip(buffers.iter_mut()) {
+ ral_registers::write_reg!(
+ rxbd,
+ descriptor,
+ data_buffer_pointer,
+ buffer.0.as_mut_ptr() as _
+ );
+ // Zero all other flags.
+ ral_registers::write_reg!(rxbd, descriptor, flags, empty: 1);
+ }
+
+ // When the DMA engine reaches this descriptor, it needs to wrap
+ // around to the first descriptor.
+ if let Some(descriptor) = descriptors.last_mut() {
+ ral_registers::modify_reg!(rxbd, descriptor, flags, wrap: 1);
+ }
+ })
+ }
+}
+
+pub struct IoSlices<'a, D> {
+ ring: &'a mut [D],
+ mtu: usize,
+ index: usize,
+}
+
+pub type ReceiveSlices<'a> = IoSlices<'a, rxbd::RxBD>;
+pub type TransmitSlices<'a> = IoSlices<'a, txbd::TxBD>;
+
+impl<'a, D> IoSlices<'a, D> {
+ fn new(ring: &'a mut [D], mtu: usize) -> Self {
+ Self {
+ ring,
+ mtu,
+ index: 0,
+ }
+ }
+ pub(crate) fn as_ptr(&self) -> *const D {
+ self.ring.as_ptr()
+ }
+ pub(crate) fn mtu(&self) -> usize {
+ self.mtu
+ }
+}
+
+impl<D> IoSlices<'_, D> {
+ fn next_impl<'a, R: 'a>(
+ &'a mut self,
+ check: impl FnOnce(&D) -> bool,
+ ready: R,
+ ) -> Option<IoToken<'a, D, R>> {
+ let next = (self.index + 1) % self.ring.len();
+ let descriptor = self.ring.get_mut(self.index).unwrap();
+ if check(descriptor) {
+ Some(IoToken {
+ descriptor,
+ index: &mut self.index,
+ next,
+ mtu: self.mtu,
+ ready,
+ })
+ } else {
+ None
+ }
+ }
+}
+
+pub struct IoToken<'a, D, R> {
+ descriptor: &'a mut D,
+ index: &'a mut usize,
+ next: usize,
+ mtu: usize,
+ ready: R,
+}
+
+pub type TxToken<'a> = IoToken<'a, txbd::TxBD, crate::TxReady<'a>>;
+pub type RxToken<'a> = IoToken<'a, rxbd::RxBD, crate::RxReady<'a>>;
+
+impl ReceiveSlices<'_> {
+ pub(crate) fn next_token<'a>(&'a mut self, ready: crate::RxReady<'a>) -> Option<RxToken<'a>> {
+ self.next_impl(
+ |rxbd| ral_registers::read_reg!(rxbd, rxbd, flags, empty == 0),
+ ready,
+ )
+ }
+}
+
+impl TransmitSlices<'_> {
+ pub(crate) fn next_token<'a>(&'a mut self, ready: crate::TxReady<'a>) -> Option<TxToken<'a>> {
+ self.next_impl(
+ |txbd| ral_registers::read_reg!(txbd, txbd, flags, ready == 0),
+ ready,
+ )
+ }
+}
+
+impl smoltcp::phy::TxToken for TxToken<'_> {
+ fn consume<R, F>(self, len: usize, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R,
+ {
+ // Safety: we ensure that smoltcp isn't exceeding the size of the buffer.
+ // We know that the pointer is valid. Module inspection reveals that this is the
+ // only mutable reference to the pointer; it's tracked through the descriptor
+ // lifetimes.
+ let buffer = unsafe {
+ assert!(len <= self.mtu);
+ let ptr =
+ ral_registers::read_reg!(txbd, self.descriptor, data_buffer_pointer) as *mut u8;
+ core::slice::from_raw_parts_mut(ptr, len)
+ };
+
+ let result = f(buffer);
+ core::sync::atomic::fence(Ordering::SeqCst);
+
+ ral_registers::write_reg!(txbd, self.descriptor, data_length, len as _);
+ ral_registers::modify_reg!(txbd, self.descriptor, flags,
+ ready: 1,
+ last_in: 1,
+ transmit_crc: 1,
+ );
+ self.ready.consume();
+ *self.index = self.next;
+ result
+ }
+}
+
+impl smoltcp::phy::RxToken for RxToken<'_> {
+ fn consume<R, F>(self, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R,
+ {
+ // Safety: hardware will not exceed our maximum frame length. We know that
+ // the pointer is valid; see discussion above.
+ let buffer = unsafe {
+ let len = ral_registers::read_reg!(rxbd, self.descriptor, data_length) as usize;
+ assert!(len <= self.mtu);
+ let ptr =
+ ral_registers::read_reg!(rxbd, self.descriptor, data_buffer_pointer) as *mut u8;
+ core::slice::from_raw_parts_mut(ptr, len)
+ };
+
+ let result = f(buffer);
+ ral_registers::modify_reg!(rxbd, self.descriptor, flags, empty: 1);
+ self.ready.consume();
+ *self.index = self.next;
+ result
+ }
+}
diff --git a/src/bd/rxbd.rs b/src/bd/rxbd.rs
new file mode 100644
index 0000000..8523d6e
--- /dev/null
+++ b/src/bd/rxbd.rs
@@ -0,0 +1,95 @@
+//! Enhanced receive buffer descriptor layout and fields.
+
+use ral_registers::{RORegister, RWRegister};
+
+#[repr(C)]
+pub struct RxBD {
+ pub data_length: RORegister<u16>,
+ pub flags: RWRegister<u16>,
+ pub data_buffer_pointer: RWRegister<u32>,
+ pub status: RWRegister<u16>,
+ pub control: RWRegister<u16>,
+ pub checksum: RORegister<u16>,
+ pub header: RORegister<u16>,
+ _reserved0: [u16; 1],
+ pub last_bdu: RWRegister<u16>,
+ pub timestamp_1588: RORegister<u32>,
+ _reserved1: [u16; 4],
+}
+
+bdfields!(flags, u16,
+ empty [ offset = 15, bits = 1, ],
+ ro1 [ offset = 14, bits = 1, ],
+ wrap [ offset = 13, bits = 1, ],
+ ro2 [ offset = 12, bits = 1, ],
+ last [ offset = 11, bits = 1, ],
+
+ miss [ offset = 8, bits = 1, ],
+ broadcast [ offset = 7, bits = 1, ],
+ multicast [ offset = 6, bits = 1, ],
+ length_violation [ offset = 5, bits = 1, ],
+ non_octet_violation [ offset = 4, bits = 1, ],
+
+ crc_error [ offset = 2, bits = 1, ],
+ overrun [ offset = 1, bits = 1, ],
+ truncated [ offset = 0, bits = 1, ],
+);
+
+bdfields!(status, u16,
+ vlan_priority [ offset = 13, bits = 3, ],
+ ip_checksum_error [ offset = 5, bits = 1, ],
+ protocol_checksum_error [ offset = 4, bits = 1, ],
+ vlan [ offset = 2, bits = 1, ],
+ ipv6 [ offset = 1, bits = 1, ],
+ frag [ offset = 0, bits = 1, ],
+);
+
+bdfields!(control, u16,
+ mac_error [ offset = 15, bits = 1, ],
+ phy_error [ offset = 10, bits = 1, ],
+ collision [ offset = 9, bits = 1, ],
+ unicast [ offset = 8, bits = 1, ],
+ interrupt [ offset = 7, bits = 1, ],
+);
+
+bdfields!(header, u16,
+ length [ offset = 11, bits = 5, ],
+ protocol [ offset = 0, bits = 8, ],
+);
+
+bdfields!(last_bdu, u16,
+ last_bdu [ offset = 15, bits = 1, ],
+);
+
+#[cfg(test)]
+mod tests {
+ use core::ptr::addr_of;
+
+ use super::RxBD;
+
+ fn zeroed() -> RxBD {
+ // Safety: zero bitpattern is fine for primitive fields.
+ unsafe { core::mem::MaybeUninit::zeroed().assume_init() }
+ }
+
+ #[test]
+ fn field_offsets() {
+ let rxbd = zeroed();
+ let start = &rxbd as *const _ as *const u8;
+ assert_eq!(unsafe { start.add(0x0) }, addr_of!(rxbd.data_length).cast());
+ assert_eq!(unsafe { start.add(0x2) }, addr_of!(rxbd.flags).cast());
+ assert_eq!(
+ unsafe { start.add(0x4) },
+ addr_of!(rxbd.data_buffer_pointer).cast()
+ );
+ assert_eq!(unsafe { start.add(0x8) }, addr_of!(rxbd.status).cast());
+ assert_eq!(unsafe { start.add(0xA) }, addr_of!(rxbd.control).cast());
+ assert_eq!(unsafe { start.add(0xC) }, addr_of!(rxbd.checksum).cast());
+ assert_eq!(unsafe { start.add(0xE) }, addr_of!(rxbd.header).cast());
+ assert_eq!(unsafe { start.add(0x12) }, addr_of!(rxbd.last_bdu).cast());
+ assert_eq!(
+ unsafe { start.add(0x14) },
+ addr_of!(rxbd.timestamp_1588).cast()
+ );
+ }
+}
diff --git a/src/bd/txbd.rs b/src/bd/txbd.rs
new file mode 100644
index 0000000..9550eff
--- /dev/null
+++ b/src/bd/txbd.rs
@@ -0,0 +1,91 @@
+//! Enhanced transmit buffer descriptor layout and fields.
+
+use ral_registers::RWRegister;
+
+#[repr(C)]
+pub struct TxBD {
+ pub data_length: RWRegister<u16>,
+ pub flags: RWRegister<u16>,
+ pub data_buffer_pointer: RWRegister<u32>,
+ pub errors: RWRegister<u16>,
+ pub control: RWRegister<u16>,
+ pub launch_time: RWRegister<u32>,
+ _reserved0: [u16; 1],
+ pub last_bdu: RWRegister<u16>,
+ pub timestamp_1588: RWRegister<u32>,
+ _reserved1: [u16; 4],
+}
+
+bdfields!(flags, u16,
+ ready [ offset = 15, bits = 1, ],
+ to1 [ offset = 14, bits = 1, ],
+ wrap [ offset = 13, bits = 1, ],
+ to2 [ offset = 12, bits = 1, ],
+ last_in [ offset = 11, bits = 1, ],
+ transmit_crc [ offset = 10, bits = 1, ],
+);
+
+bdfields!(errors, u16,
+ transmit [ offset = 15, bits = 1, ],
+ underflow [ offset = 13, bits = 1, ],
+ excess_collision [ offset = 12, bits = 1, ],
+ frame_error [ offset = 11, bits = 1, ],
+ late_collision [ offset = 10, bits = 1, ],
+ overflow [ offset = 9, bits = 1, ],
+ timestamp [ offset = 8, bits = 1, ],
+);
+
+bdfields!(control, u16,
+ interrupt [ offset = 14, bits = 1, ],
+ timestamp [ offset = 13, bits = 1, ],
+ pins [ offset = 12, bits = 1, ],
+ iins [ offset = 11, bits = 1, ],
+ utlt [ offset = 8, bits = 1, ],
+ ftype [ offset = 4, bits = 4, NON_AVB = 0, AVB_A = 1, AVB_B = 2 ],
+);
+
+bdfields!(last_bdu, u16,
+ last_bdu [ offset = 15, bits = 1, ],
+);
+
+#[cfg(test)]
+mod tests {
+ use super::TxBD;
+ use std::ptr::addr_of;
+
+ fn zeroed() -> TxBD {
+ // Safety: zero bitpattern is fine for primitive fields.
+ unsafe { core::mem::MaybeUninit::zeroed().assume_init() }
+ }
+
+ #[test]
+ fn field_offsets() {
+ let txbd = zeroed();
+ let start = &txbd as *const _ as *const u8;
+ assert_eq!(unsafe { start.add(0x0) }, addr_of!(txbd.data_length).cast());
+ assert_eq!(unsafe { start.add(0x2) }, addr_of!(txbd.flags).cast());
+ assert_eq!(
+ unsafe { start.add(0x4) },
+ addr_of!(txbd.data_buffer_pointer).cast()
+ );
+ assert_eq!(unsafe { start.add(0x8) }, addr_of!(txbd.errors).cast());
+ assert_eq!(unsafe { start.add(0xA) }, addr_of!(txbd.control).cast());
+ assert_eq!(unsafe { start.add(0xC) }, addr_of!(txbd.launch_time).cast());
+ assert_eq!(unsafe { start.add(0x12) }, addr_of!(txbd.last_bdu).cast());
+ assert_eq!(
+ unsafe { start.add(0x14) },
+ addr_of!(txbd.timestamp_1588).cast()
+ );
+ }
+
+ #[test]
+ fn bdfields_enum() {
+ let txbd = zeroed();
+ ral_registers::modify_reg!(super, &txbd, control, ftype: AVB_B);
+ assert_eq!(txbd.control.read(), 0x2 << 4);
+ ral_registers::modify_reg!(super, &txbd, control, interrupt: 1);
+ assert_eq!(txbd.control.read(), 0x2 << 4 | 1 << 14);
+ ral_registers::modify_reg!(super, &txbd, control, ftype: 0);
+ assert_eq!(txbd.control.read(), 1 << 14);
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..816d944
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,361 @@
+//! Ethernet driver for i.MX RT MCUs.
+
+#![cfg_attr(all(target_arch = "arm", target_os = "none"), no_std)]
+#![deny(unsafe_op_in_unsafe_fn)]
+
+mod bd;
+
+pub use bd::{IoBuffers, IoSlices, ReceiveBuffers, ReceiveSlices, TransmitBuffers, TransmitSlices};
+use imxrt_ral as ral;
+
+pub use mdio::miim::{Read as MiimRead, Write as MiimWrite};
+pub use smoltcp;
+
+/// Allows independent transmit and receive functions.
+#[derive(Debug, Clone, Copy, defmt::Format)]
+pub enum Duplex {
+ /// Transmit and receive functions cannot overlap.
+ ///
+ /// Specifically, you cannot transmit frames while you're receiving frames.
+ /// Similarly, you cannot receive frames while you're sending frames.
+ Half,
+ /// The MAC can transmit and receive simultaneously.
+ ///
+ /// Specifically, the receive path operates independent of the transmit
+ /// path. You can transmit frames without concern for carrier sense and
+ /// collision signals.
+ Full,
+}
+
+/// Ethernet MAC and related functions.
+///
+/// The MDIO interface is always enabled. To generally use the MDIO interface,
+/// use [`MiimRead`] and [`MiimWrite`]. Once your driver is configured, use
+/// [`enable_mac`](Enet::enable_mac) to enable the transmit and receive datapaths.
+///
+/// The MAC implements the `phy` interfaces from [`smoltcp`]. The driver optimizes
+/// for hardware-based checksumming as much as possible, but this only applies to
+/// the network and transport layers.
+pub struct Enet<const N: u8> {
+ enet: ral::enet::Instance<N>,
+ tx_ring: TransmitSlices<'static>,
+ rx_ring: ReceiveSlices<'static>,
+}
+
+impl<const N: u8> Enet<N> {
+ pub fn new(
+ enet: ral::enet::Instance<N>,
+ tx_ring: TransmitSlices<'static>,
+ rx_ring: ReceiveSlices<'static>,
+ source_clock_hz: u32,
+ mac: &[u8; 6],
+ ) -> Self {
+ // Reset the module.
+ ral::modify_reg!(ral::enet, enet, ECR, RESET: 1);
+
+ ral::modify_reg!(ral::enet, enet, ECR,
+ DBSWP: 1, // Swap data for this little endian device.
+ EN1588: 1, // Use enhanced buffer descriptors.
+ RESET: 0, // I think this auto-clears, but just in case...
+ DBGEN: 0, // Keep running the MAC in debug mode.
+ );
+
+ // Turn off all interrupts.
+ ral::write_reg!(ral::enet, enet, EIMR, 0);
+
+ // The maximum receive buffer size includes four low bits of the register.
+ // The user's buffer needs to be a non-zero multiple of 16 to account for
+ // those extra bytes. We double-check this by asserting the requirement at
+ // compile time in the IoBuffer types.
+ debug_assert!(rx_ring.mtu() != 0 && rx_ring.mtu() & 0xF == rx_ring.mtu());
+ ral::write_reg!(ral::enet, enet, MRBR, R_BUF_SIZE: (rx_ring.mtu() >> 4) as u32);
+
+ // Descriptor rings are pre-configured when the user acquires the slices.
+ ral::write_reg!(ral::enet, enet, TDSR, tx_ring.as_ptr() as _);
+ ral::write_reg!(ral::enet, enet, RDSR, rx_ring.as_ptr() as _);
+
+ const SMI_MDC_FREQUENCY_HZ: u32 = 2_500_000;
+ let mii_speed =
+ (source_clock_hz + 2 * SMI_MDC_FREQUENCY_HZ - 1) / (2 * SMI_MDC_FREQUENCY_HZ) - 1;
+ let hold_time =
+ (10 + 1_000_000_000 / source_clock_hz - 1) / (1_000_000_000 / source_clock_hz) - 1;
+ // TODO no way to enable / disable the MII management frame preamble. Maybe a new method
+ // for the user?
+ ral::modify_reg!(ral::enet, enet, MSCR, HOLDTIME: hold_time, MII_SPEED: mii_speed);
+
+ ral::modify_reg!(ral::enet, enet, RCR,
+ // Default max frame length without VLAN tags.
+ MAX_FL: 1518,
+ // Since we're providing half-duplex control to the user, we
+ // can't also enabled loopback.
+ LOOP: 0,
+ // No need to snoop.
+ PROM: 0,
+ // Do not reject broadcast frames; we might be interested
+ // in these.
+ BC_REJ: 0,
+ // The MAC doesn't supply pause frames to the application.
+ PAUFWD: 0,
+ // Drop padding, along with the CRC, when supplying frames
+ // to our software. This configuration implicitly includes
+ // the CRC, so the CRCFWD below has no effect.
+ PADEN: 1,
+ // Drop the CRC in received frames. This doesn't turn off
+ // CRC checking at the hardware level.
+ //
+ // If PADEN is set, this configuration does nothing.
+ CRCFWD: 1,
+ // Check the payload length based on the expected frame type /
+ // frame length (encoded in the frame).
+ NLC: 1,
+ // Enable flow control; react to pause frames by pausing the data
+ // transmit paths.
+ FCE: 1,
+ // MII or RMII mode; must be set.
+ MII_MODE: 1,
+ // Default to MII; users can enable RMII later.
+ RMII_MODE: 0,
+ // Default to 100Mbit/sec; users can throttle later.
+ RMII_10T: 0,
+ );
+
+ ral::modify_reg!(ral::enet, enet, TCR,
+ // smoltcp is not including frame CRCs from software. Let
+ // the hardware handle it.
+ CRCFWD: 0,
+ // smoltcp is including the address in its frames, so we'll
+ // pass it through.
+ ADDINS: 0,
+ );
+
+ // Enable store-and-forward: start transmitting once you have a complete
+ // frame in the FIFO.
+ ral::modify_reg!(ral::enet, enet, TFWR, STRFWD: 1);
+ // Maintain store-and-forward on the receive path: use the receive queue
+ // as a buffer until an entire frame is received.
+ ral::write_reg!(ral::enet, enet, RSFL, 0);
+
+ // These accelerator options assume store-and-forward operations on both
+ // data paths. See above.
+ ral::modify_reg!(ral::enet, enet, RACC,
+ // Discard frames with MAC errors (checksumming, length, PHY errors).
+ LINEDIS: 1,
+ // Discard frames with the wrong checksums for the protocol and headers.
+ PRODIS: 1,
+ IPDIS: 1,
+ // Discard any padding within a short IP datagram.
+ PADREM: 1,
+ );
+ ral::modify_reg!(ral::enet, enet, TACC,
+ // Enable protocol checksums. Assumes that smoltcp sets these fields
+ // to zero on our behalf.
+ PROCHK: 1,
+ // Enable IP checksum injection into the IPv4 header. Assumes that smoltcp
+ // sets these fields to zero on our behalf.
+ IPCHK: 1,
+ );
+
+ // Commit the MAC address so we can match against it in the receive path.
+ ral::write_reg!(
+ ral::enet,
+ enet,
+ PALR,
+ (mac[0] as u32) << 24 | (mac[1] as u32) << 16 | (mac[2] as u32) << 8 | (mac[3] as u32)
+ );
+ ral::write_reg!(
+ ral::enet,
+ enet,
+ PAUR,
+ (mac[4] as u32) << 24 | (mac[5] as u32) << 16
+ );
+
+ Self {
+ enet,
+ tx_ring,
+ rx_ring,
+ }
+ }
+
+ /// Enable (`true`) or disable (`false`) the MAC.
+ ///
+ /// A disabled MAC cannot receive or send frames. By default, the MAC is disabled,
+ /// and you'll need to enable it once you've completed driver configuration.
+ #[inline]
+ pub fn enable_mac(&mut self, enable: bool) {
+ ral::modify_reg!(ral::enet, self.enet, ECR, ETHEREN: enable as u32);
+ if enable {
+ ral::write_reg!(ral::enet, self.enet, RDAR, RDAR: 1);
+ }
+ }
+
+ /// Indicates if the ENET MAC is (`true`) or is not (`false`) enabled.
+ #[inline]
+ pub fn is_mac_enabled(&self) -> bool {
+ ral::read_reg!(ral::enet, self.enet, ECR, ETHEREN == 1)
+ }
+
+ /// Enable (`true`) or disable (`false`) RMII mode.
+ ///
+ /// By default, the driver is in MII mode.
+ ///
+ /// # Panics
+ ///
+ /// Panics if called while the MAC is enabled.
+ // TODO(mciantyre) enums for MII modes, speeds, duplex?
+ #[inline]
+ pub fn enable_rmii_mode(&mut self, enable: bool) {
+ debug_assert!(!self.is_mac_enabled());
+ ral::modify_reg!(ral::enet, self.enet, RCR, RMII_MODE: enable as u32);
+ }
+
+ /// Throttle the receive pathway to 10Mbit/s.
+ ///
+ /// When enabled, the recieve pathway operates in 10Mbit/s.
+ /// By default, or when disabled, the receive pathway is at
+ /// 100Mbit/s.
+ ///
+ /// # Panics
+ ///
+ /// Panics if called while the MAC is enabled.
+ // TODO(mciantyre) enums for MII modes, speeds, duplex?
+ #[inline]
+ pub fn enable_10t_mode(&mut self, enable: bool) {
+ debug_assert!(!self.is_mac_enabled());
+ ral::modify_reg!(ral::enet, self.enet, RCR, RMII_10T: enable as u32);
+ }
+
+ /// Set the half-/full-duplex operation of the MAC.
+ ///
+ /// For more information, see the [`Duplex`] documentation.
+ ///
+ /// # Panics
+ ///
+ /// Panics if called while the MAC is enabled.
+ #[inline]
+ pub fn set_duplex(&mut self, duplex: Duplex) {
+ debug_assert!(!self.is_mac_enabled());
+ match duplex {
+ Duplex::Full => {
+ ral::modify_reg!(ral::enet, self.enet, TCR, FDEN: 1);
+ ral::modify_reg!(ral::enet, self.enet, RCR, DRT: 0);
+ }
+ Duplex::Half => {
+ ral::modify_reg!(ral::enet, self.enet, TCR, FDEN: 0);
+ ral::modify_reg!(ral::enet, self.enet, RCR, DRT: 1);
+ }
+ }
+ }
+
+ /// Enable (`true`) or disable (`false`) management information database
+ /// (MIB) statistic indicators.
+ ///
+ /// When enabled, the hardware tracks various types of errors in the
+ /// MIB and remote network monitoring registers.
+ #[inline]
+ pub fn enable_mib(&mut self, enable: bool) {
+ ral::modify_reg!(ral::enet, self.enet, MIBC, MIB_DIS: !enable as u32);
+ }
+
+ /// Set to zero all management information database (MIB) statistic indicators.
+ #[inline]
+ pub fn clear_mib(&mut self) {
+ ral::modify_reg!(ral::enet, self.enet, MIBC, MIB_CLEAR: 1);
+ ral::modify_reg!(ral::enet, self.enet, MIBC, MIB_CLEAR: 0);
+ }
+}
+
+#[doc(hidden)]
+pub struct TxReady<'a> {
+ enet: &'a ral::enet::RegisterBlock,
+}
+
+impl TxReady<'_> {
+ fn consume(self) {
+ ral::write_reg!(ral::enet, self.enet, TDAR, TDAR: 1);
+ }
+}
+
+#[doc(hidden)]
+pub struct RxReady<'a> {
+ enet: &'a ral::enet::RegisterBlock,
+}
+
+impl RxReady<'_> {
+ fn consume(self) {
+ ral::write_reg!(ral::enet, self.enet, RDAR, RDAR: 1);
+ }
+}
+
+/// An error during an MII transfer.
+///
+/// TODO where are they?
+#[non_exhaustive]
+#[derive(Debug, defmt::Format)]
+pub enum MiiError {}
+
+impl<const N: u8> mdio::Read for Enet<N> {
+ type Error = MiiError;
+
+ #[inline]
+ fn read(&mut self, ctrl_bits: u16) -> Result<u16, Self::Error> {
+ // Place the control bits in to the high half-word of the register.
+ let mmfr = (ctrl_bits as u32) << 16;
+ ral::write_reg!(ral::enet, self.enet, MMFR, mmfr);
+
+ while ral::read_reg!(ral::enet, self.enet, EIR, MII == 0) {}
+ ral::write_reg!(ral::enet, self.enet, EIR, MII: 1);
+
+ // Automatically discards control bits.
+ Ok(ral::read_reg!(ral::enet, self.enet, MMFR, DATA) as u16)
+ }
+}
+
+impl<const N: u8> mdio::Write for Enet<N> {
+ type Error = MiiError;
+
+ #[inline]
+ fn write(&mut self, ctrl_bits: u16, data_bits: u16) -> Result<(), Self::Error> {
+ // Place control bits into high half-word of register.
+ let mmfr = (ctrl_bits as u32) << 16 | data_bits as u32;
+ ral::write_reg!(ral::enet, self.enet, MMFR, mmfr);
+
+ while ral::read_reg!(ral::enet, self.enet, EIR, MII == 0) {}
+ ral::write_reg!(ral::enet, self.enet, EIR, MII: 1);
+
+ Ok(())
+ }
+}
+
+impl<const N: u8> smoltcp::phy::Device for Enet<N> {
+ type RxToken<'a> = bd::RxToken<'a>;
+ type TxToken<'a> = bd::TxToken<'a>;
+
+ fn receive(
+ &mut self,
+ _: smoltcp::time::Instant,
+ ) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
+ let tx = self.tx_ring.next_token(TxReady { enet: &self.enet })?;
+ let rx = self.rx_ring.next_token(RxReady { enet: &self.enet })?;
+ Some((rx, tx))
+ }
+
+ fn transmit(&mut self, _: smoltcp::time::Instant) -> Option<Self::TxToken<'_>> {
+ self.tx_ring.next_token(TxReady { enet: &self.enet })
+ }
+
+ fn capabilities(&self) -> smoltcp::phy::DeviceCapabilities {
+ let mtu = self.tx_ring.mtu().min(self.rx_ring.mtu());
+
+ let mut caps = smoltcp::phy::DeviceCapabilities::default();
+ caps.medium = smoltcp::phy::Medium::Ethernet;
+ caps.max_transmission_unit = mtu;
+ caps.max_burst_size = Some(mtu);
+
+ caps.checksum.ipv4 = smoltcp::phy::Checksum::None;
+ caps.checksum.udp = smoltcp::phy::Checksum::None;
+ caps.checksum.tcp = smoltcp::phy::Checksum::None;
+ caps.checksum.icmpv4 = smoltcp::phy::Checksum::None;
+
+ caps
+ }
+}