Compare commits

..

1 Commits

Author SHA1 Message Date
c2eaeedce1 Ethernet and smoltcp/embassy-net support
Some checks failed
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
2025-07-16 10:24:11 +02:00
4 changed files with 187 additions and 169 deletions

View File

@@ -1,40 +1,42 @@
#![no_std]
#![no_main]
use core::{
cell::UnsafeCell, mem::MaybeUninit, net::Ipv4Addr, panic::PanicInfo, sync::atomic::AtomicBool,
};
use core::{net::Ipv4Addr, panic::PanicInfo};
use cortex_ar::asm::nop;
use embassy_executor::Spawner;
use embassy_net::{Ipv4Cidr, StaticConfigV4};
use embassy_time::{Duration, Ticker, Timer};
use embedded_hal::digital::StatefulOutputPin;
use embassy_net::{Ipv4Cidr, StaticConfigV4, udp::UdpSocket};
use embassy_time::Timer;
use embedded_io::Write;
use log::{debug, error, info, LevelFilter};
use log::{LevelFilter, debug, error, info};
use rand::{RngCore, SeedableRng};
use zedboard::{
phy_marvell::{LatchingLinkStatus, MARVELL_88E1518_OUI},
PS_CLOCK_FREQUENCY,
phy_marvell::{LatchingLinkStatus, MARVELL_88E1518_OUI},
};
use zynq7000_hal::{
BootMode,
clocks::Clocks,
configure_level_shifter,
eth::{
embassy_net::InterruptResult, AlignedBuffer, ClkDivCollection, EthernetConfig,
EthernetLowLevel,
AlignedBuffer, ClkDivCollection, EthernetConfig, EthernetLowLevel,
embassy_net::InterruptResult,
},
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
gpio::{GpioPins, Output, PinState},
gtc::Gtc,
uart::{ClkConfigRaw, Uart, UartConfig},
BootMode,
};
use zynq7000::{slcr::LevelShifterConfig, PsPeripherals};
use zynq7000::{PsPeripherals, slcr::LevelShifterConfig};
use zynq7000_rt::{self as _, mmu::section_attrs::SHAREABLE_DEVICE, mmu_l1_table_mut};
const INIT_STRING: &str = "-- Zynq 7000 Zedboard Ethernet Example --\n\r";
const USE_DHCP: bool = true;
const PRINT_PACKET_STATS: bool = false;
const LOG_LEVEL: LevelFilter = LevelFilter::Info;
const NUM_RX_SLOTS: usize = 16;
const NUM_TX_SLOTS: usize = 16;
const INIT_STRING: &str = "-- Zynq 7000 Zedboard Ethernet Example --\n\r";
// Unicast address with OUI of the Marvell 88E1518 PHY.
const MAC_ADDRESS: [u8; 6] = [
@@ -50,64 +52,14 @@ const MAC_ADDRESS: [u8; 6] = [
/// MMU.
const UNCACHED_ADDR: u32 = 0x4000000;
const NUM_RX_SLOTS: usize = 16;
const NUM_TX_SLOTS: usize = 16;
static RX_DESCR_TAKEN: AtomicBool = AtomicBool::new(false);
static TX_DESCR_TAKEN: AtomicBool = AtomicBool::new(false);
#[repr(transparent)]
pub struct RxDescriptor(
pub UnsafeCell<MaybeUninit<[zynq7000_hal::eth::rx_descr::Descriptor; NUM_RX_SLOTS]>>,
);
unsafe impl Sync for RxDescriptor {}
impl RxDescriptor {
/// Initializes the RX descriptors and returns a mutable reference to them.
pub fn take(
&self,
) -> Option<&'static mut [zynq7000_hal::eth::rx_descr::Descriptor; NUM_RX_SLOTS]> {
if RX_DESCR_TAKEN.swap(true, core::sync::atomic::Ordering::SeqCst) {
return None; // Already taken, return None
}
let descr = unsafe { &mut *self.0.get() };
descr.write([const { zynq7000_hal::eth::rx_descr::Descriptor::new() }; NUM_RX_SLOTS]);
Some(unsafe { descr.assume_init_mut() })
}
}
// These descriptors must be placed in uncached memory. The MMU will be used to configure the
// .uncached memory segment as device memory.
#[unsafe(link_section = ".uncached")]
static RX_DESCRIPTORS: RxDescriptor = RxDescriptor(UnsafeCell::new(MaybeUninit::uninit()));
#[repr(transparent)]
pub struct TxDescriptor(
pub UnsafeCell<MaybeUninit<[zynq7000_hal::eth::tx_descr::Descriptor; NUM_TX_SLOTS]>>,
);
unsafe impl Sync for TxDescriptor {}
impl TxDescriptor {
/// Initializes the TX descriptors and returns a mutable reference to them.
pub fn take(
&self,
) -> Option<&'static mut [zynq7000_hal::eth::tx_descr::Descriptor; NUM_TX_SLOTS]> {
if TX_DESCR_TAKEN.swap(true, core::sync::atomic::Ordering::SeqCst) {
return None; // Already taken, return None
}
let descr = unsafe { &mut *self.0.get() };
descr.write([const { zynq7000_hal::eth::tx_descr::Descriptor::new() }; NUM_TX_SLOTS]);
Some(unsafe { descr.assume_init_mut() })
}
}
static RX_DESCRIPTORS: zynq7000_hal::eth::rx_descr::DescriptorList<NUM_RX_SLOTS> =
zynq7000_hal::eth::rx_descr::DescriptorList::new();
#[unsafe(link_section = ".uncached")]
static TX_DESCRIPTORS: TxDescriptor = TxDescriptor(UnsafeCell::new(MaybeUninit::uninit()));
static ETH_RX_BUFS: static_cell::ConstStaticCell<[AlignedBuffer; NUM_RX_SLOTS]> =
static_cell::ConstStaticCell::new([AlignedBuffer([0; zynq7000_hal::eth::MTU]); NUM_RX_SLOTS]);
static ETH_TX_BUFS: static_cell::ConstStaticCell<[AlignedBuffer; NUM_TX_SLOTS]> =
static_cell::ConstStaticCell::new([AlignedBuffer([0; zynq7000_hal::eth::MTU]); NUM_TX_SLOTS]);
static TX_DESCRIPTORS: zynq7000_hal::eth::tx_descr::DescriptorList<NUM_TX_SLOTS> =
zynq7000_hal::eth::tx_descr::DescriptorList::new();
static ETH_ERR_QUEUE: embassy_sync::channel::Channel<
embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex,
@@ -139,12 +91,27 @@ async fn embassy_net_task(
runner.run().await
}
/// Simple UDP echo task.
#[embassy_executor::task]
async fn led_task(mut mio_led: Output) -> ! {
let mut ticker = Ticker::every(Duration::from_millis(200));
async fn udp_task(mut udp: UdpSocket<'static>) -> ! {
let mut rx_buf = [0; zynq7000_hal::eth::MTU];
udp.bind(8000)
.expect("failed to bind UDP socket to port 8000");
loop {
mio_led.toggle().unwrap();
ticker.next().await; // Wait for the next cycle of the ticker
match udp.recv_from(&mut rx_buf).await {
Ok((data, meta)) => {
log::info!("UDP: rx {data} bytes from {meta:?}");
match udp.send_to(&rx_buf[0..data], meta).await {
Ok(_) => (),
Err(e) => {
log::warn!("udp send error: {e:?}");
}
}
}
Err(e) => {
log::warn!("udp receive error: {e:?}");
}
}
}
}
@@ -169,7 +136,7 @@ async fn main(spawner: Spawner) -> ! {
unsafe {
gic.enable_interrupts();
}
let mut gpio_pins = GpioPins::new(dp.gpio);
let gpio_pins = GpioPins::new(dp.gpio);
// Set up global timer counter and embassy time driver.
let gtc = Gtc::new(dp.gtc, clocks.arm_clocks());
@@ -192,6 +159,14 @@ async fn main(spawner: Spawner) -> ! {
let boot_mode = BootMode::new();
info!("Boot mode: {:?}", boot_mode);
static ETH_RX_BUFS: static_cell::ConstStaticCell<[AlignedBuffer; NUM_RX_SLOTS]> =
static_cell::ConstStaticCell::new(
[AlignedBuffer([0; zynq7000_hal::eth::MTU]); NUM_RX_SLOTS],
);
static ETH_TX_BUFS: static_cell::ConstStaticCell<[AlignedBuffer; NUM_TX_SLOTS]> =
static_cell::ConstStaticCell::new(
[AlignedBuffer([0; zynq7000_hal::eth::MTU]); NUM_TX_SLOTS],
);
let rx_bufs = ETH_RX_BUFS.take();
let tx_bufs = ETH_TX_BUFS.take();
@@ -199,11 +174,9 @@ async fn main(spawner: Spawner) -> ! {
let tx_descr = TX_DESCRIPTORS.take().unwrap();
// Unwraps okay, list length is not 0
let mut rx_descr_ref =
zynq7000_hal::eth::rx_descr::DescriptorList::new(rx_descr.as_mut_slice()).unwrap();
// Create an unsafe copy for error handling.
// let rx_descr_copy_for_error_handling = unsafe { rx_descr_ref.clone_unchecked() };
zynq7000_hal::eth::rx_descr::DescriptorListWrapper::new(rx_descr.as_mut_slice()).unwrap();
let mut tx_descr_ref =
zynq7000_hal::eth::tx_descr::DescriptorList::new(tx_descr.as_mut_slice());
zynq7000_hal::eth::tx_descr::DescriptorListWrapper::new(tx_descr.as_mut_slice());
rx_descr_ref.init_with_aligned_bufs(rx_bufs.as_slice());
tx_descr_ref.init_or_reset();
@@ -273,14 +246,15 @@ async fn main(spawner: Spawner) -> ! {
)
.unwrap(),
);
let config = embassy_net::Config::dhcpv4(Default::default());
/*
let config = embassy_net::Config::ipv4_static(StaticConfigV4 {
address: Ipv4Cidr::new(Ipv4Addr::new(192, 168, 179, 25), 24),
gateway: None,
dns_servers: Default::default(),
});
*/
let config = if USE_DHCP {
embassy_net::Config::dhcpv4(Default::default())
} else {
embassy_net::Config::ipv4_static(StaticConfigV4 {
address: Ipv4Cidr::new(Ipv4Addr::new(192, 168, 179, 25), 24),
gateway: None,
dns_servers: Default::default(),
})
};
static RESOURCES: static_cell::StaticCell<embassy_net::StackResources<3>> =
static_cell::StaticCell::new();
let mut rng = rand::rngs::SmallRng::seed_from_u64(1);
@@ -290,20 +264,29 @@ async fn main(spawner: Spawner) -> ! {
RESOURCES.init(embassy_net::StackResources::new()),
rng.next_u64(),
);
static RX_UDP_META: static_cell::ConstStaticCell<[embassy_net::udp::PacketMetadata; 8]> =
static_cell::ConstStaticCell::new([embassy_net::udp::PacketMetadata::EMPTY; 8]);
static TX_UDP_META: static_cell::ConstStaticCell<[embassy_net::udp::PacketMetadata; 8]> =
static_cell::ConstStaticCell::new([embassy_net::udp::PacketMetadata::EMPTY; 8]);
// Ensure those are in the data section by making them static.
static TX_UDP_BUFS: static_cell::ConstStaticCell<[u8; zynq7000_hal::eth::MTU]> =
static_cell::ConstStaticCell::new([0; zynq7000_hal::eth::MTU]);
static RX_UDP_BUFS: static_cell::ConstStaticCell<[u8; zynq7000_hal::eth::MTU]> =
static_cell::ConstStaticCell::new([0; zynq7000_hal::eth::MTU]);
let udp_socket = UdpSocket::new(
stack,
RX_UDP_META.take(),
RX_UDP_BUFS.take(),
TX_UDP_META.take(),
TX_UDP_BUFS.take(),
);
spawner.spawn(embassy_net_task(runner)).unwrap();
spawner.spawn(udp_task(udp_socket)).unwrap();
let mut mio_led = Output::new_for_mio(gpio_pins.mio.mio7, PinState::Low);
let mut _emio_leds: [Output; 8] = [
Output::new_for_emio(gpio_pins.emio.take(0).unwrap(), PinState::Low),
Output::new_for_emio(gpio_pins.emio.take(1).unwrap(), PinState::Low),
Output::new_for_emio(gpio_pins.emio.take(2).unwrap(), PinState::Low),
Output::new_for_emio(gpio_pins.emio.take(3).unwrap(), PinState::Low),
Output::new_for_emio(gpio_pins.emio.take(4).unwrap(), PinState::Low),
Output::new_for_emio(gpio_pins.emio.take(5).unwrap(), PinState::Low),
Output::new_for_emio(gpio_pins.emio.take(6).unwrap(), PinState::Low),
Output::new_for_emio(gpio_pins.emio.take(7).unwrap(), PinState::Low),
];
let mut ip_mode = IpMode::LinkDown;
let mut transmitted_frames = 0;
let mut received_frames = 0;
@@ -312,17 +295,21 @@ async fn main(spawner: Spawner) -> ! {
while let Ok(msg) = receiver.try_receive() {
info!("Received interrupt result: {msg:?}");
}
let sent_frames_since_last = eth.ll().regs.statistics().read_tx_count();
if sent_frames_since_last > 0 {
transmitted_frames += sent_frames_since_last;
info!("Frame sent count: {transmitted_frames}");
}
let received_frames_since_last = eth.ll().regs.statistics().read_rx_count();
if received_frames_since_last > 0 {
received_frames += received_frames_since_last;
info!("Frame received count: {received_frames}");
if PRINT_PACKET_STATS {
let sent_frames_since_last = eth.ll().regs.statistics().read_tx_count();
if sent_frames_since_last > 0 {
transmitted_frames += sent_frames_since_last;
info!("Frame sent count: {transmitted_frames}");
}
let received_frames_since_last = eth.ll().regs.statistics().read_rx_count();
if received_frames_since_last > 0 {
received_frames += received_frames_since_last;
info!("Frame received count: {received_frames}");
}
}
// This is basically a linker checker task. It also takes care of notifying the
// embassy stack of link state changes.
match ip_mode {
// Assuming that auto-negotiation is performed automatically.
IpMode::LinkDown => {
@@ -435,54 +422,3 @@ fn panic(info: &PanicInfo) -> ! {
error!("Panic: {info:?}");
loop {}
}
/*
pub fn udp_tcp_test() {
let mut rx_buffer = [0; MTU];
let mut reception_buf = [0; MTU];
let mut tx_buffer = [0; MTU];
// For UDP, we can use a fixed size array.
let mut rx_meta: [icmp::PacketMetadata; 8] = [icmp::PacketMetadata::EMPTY; 8];
let mut tx_meta: [icmp::PacketMetadata; 8] = [icmp::PacketMetadata::EMPTY; 8];
let mut rx_meta: [udp::PacketMetadata; 8] = [udp::PacketMetadata::EMPTY; 8];
let mut tx_meta: [udp::PacketMetadata; 8] = [udp::PacketMetadata::EMPTY; 8];
// Now what? maybe UDP Server?
let mut socket = UdpSocket::new(
stack,
&mut rx_meta,
&mut rx_buffer,
&mut tx_meta,
&mut tx_buffer,
);
//socket.set_timeout(Some(embassy_time::Duration::from_secs(10)));
// You need to start a server on the host machine, for example: `nc -ul 9000`
let remote_endpoint = (Ipv4Addr::new(192, 168, 178, 105), 9000);
info!("connecting...");
socket.bind(0).unwrap();
let port = socket.endpoint().port;
//let r = socket.(remote_endpoint).await;
if let Err(e) = r {
info!("connect error: {e:?}");
Timer::after_secs(1).await;
continue;
}
info!("connected to port {port}!");
let mut sent_once = true;
loop {
if sent_once {
let r = socket.send_to(b"Hello\n", remote_endpoint).await;
if let Err(e) = r {
info!("write error: {e:?}");
break;
}
sent_once = false;
}
let received = socket.recv_from(&mut reception_buf).await;
log::info!("received: {received:?}");
Timer::after_secs(1).await;
}
}
*/

View File

@@ -165,17 +165,17 @@ pub fn on_interrupt(eth_id: super::EthernetId) -> InterruptResult {
}
pub struct DescriptorsAndBuffers {
pub rx_descr: rx_descr::DescriptorList<'static>,
pub rx_descr: rx_descr::DescriptorListWrapper<'static>,
pub rx_bufs: &'static mut [super::AlignedBuffer],
pub tx_descr: tx_descr::DescriptorList<'static>,
pub tx_descr: tx_descr::DescriptorListWrapper<'static>,
pub tx_bufs: &'static mut [super::AlignedBuffer],
}
impl DescriptorsAndBuffers {
pub fn new(
rx_descr: rx_descr::DescriptorList<'static>,
rx_descr: rx_descr::DescriptorListWrapper<'static>,
rx_bufs: &'static mut [super::AlignedBuffer],
tx_descr: tx_descr::DescriptorList<'static>,
tx_descr: tx_descr::DescriptorListWrapper<'static>,
tx_bufs: &'static mut [super::AlignedBuffer],
) -> Option<Self> {
if rx_descr.len() != rx_bufs.len() || tx_descr.len() != tx_bufs.len() {
@@ -214,7 +214,7 @@ impl Driver {
}
pub struct EmbassyNetRxToken<'a> {
descr_list: &'a mut rx_descr::DescriptorList<'static>,
descr_list: &'a mut rx_descr::DescriptorListWrapper<'static>,
slot_index: usize,
rx_buf: &'a mut super::AlignedBuffer,
rx_size: usize,
@@ -246,7 +246,7 @@ impl embassy_net_driver::RxToken for EmbassyNetRxToken<'_> {
pub struct EmbassyNetTxToken<'a> {
eth_id: super::EthernetId,
descr_list: &'a mut tx_descr::DescriptorList<'static>,
descr_list: &'a mut tx_descr::DescriptorListWrapper<'static>,
tx_bufs: &'a mut [super::AlignedBuffer],
}

View File

@@ -1,10 +1,14 @@
//! RX buffer descriptor module.
use core::{cell::UnsafeCell, mem::MaybeUninit, sync::atomic::AtomicBool};
use crate::{cache::clean_and_invalidate_data_cache_range, eth::AlignedBuffer};
pub use super::shared::Ownership;
use arbitrary_int::{Number, u2, u3, u13, u30};
use vcell::VolatileCell;
static RX_DESCR_TAKEN: AtomicBool = AtomicBool::new(false);
/// RX buffer descriptor.
///
/// The user should declare an array of this structure inside uncached memory.
@@ -117,6 +121,38 @@ impl Default for Descriptor {
}
}
/// This is a low level wrapper to simplify declaring a global descriptor list.
///
/// It allows placing the descriptor structure statically in memory which might not
/// be zero-initialized.
#[repr(transparent)]
pub struct DescriptorList<const SLOTS: usize>(pub UnsafeCell<MaybeUninit<[Descriptor; SLOTS]>>);
unsafe impl<const SLOTS: usize> Sync for DescriptorList<SLOTS> {}
impl<const SLOTS: usize> DescriptorList<SLOTS> {
#[inline]
pub const fn new() -> Self {
Self(UnsafeCell::new(MaybeUninit::uninit()))
}
/// Initializes the RX descriptors and returns a mutable reference to them.
pub fn take(&self) -> Option<&'static mut [Descriptor; SLOTS]> {
if RX_DESCR_TAKEN.swap(true, core::sync::atomic::Ordering::SeqCst) {
return None; // Already taken, return None
}
let descr = unsafe { &mut *self.0.get() };
descr.write([const { Descriptor::new() }; SLOTS]);
Some(unsafe { descr.assume_init_mut() })
}
}
impl<const SLOTS: usize> Default for DescriptorList<SLOTS> {
fn default() -> Self {
Self::new()
}
}
pub enum FrameScanResult {
NoFrames,
SingleFrame {
@@ -134,12 +170,16 @@ pub enum FrameScanResult {
},
}
pub struct DescriptorList<'a> {
/// This is a thin wrapper around a descriptor list.
///
/// It provides a basic higher-level API to perform tasks like descriptor initialization
/// and handling of received frames.
pub struct DescriptorListWrapper<'a> {
list: &'a mut [Descriptor],
current_idx: usize,
}
impl<'a> DescriptorList<'a> {
impl<'a> DescriptorListWrapper<'a> {
#[inline]
pub fn new(descr_list: &'a mut [Descriptor]) -> Option<Self> {
if descr_list.is_empty() {
@@ -166,7 +206,7 @@ impl<'a> DescriptorList<'a> {
}
}
impl DescriptorList<'_> {
impl DescriptorListWrapper<'_> {
#[allow(clippy::len_without_is_empty)]
#[inline]
pub fn len(&self) -> usize {
@@ -202,6 +242,11 @@ impl DescriptorList<'_> {
last.set_word_0(word_0);
}
/// Initialize the descriptor list with a provided RX buffer.
///
/// This function performs important initialization routines for the descriptors
/// and also sets the addresses of the provided buffers. The number of buffers and the length
/// of the descriptor list need to be the same.
pub fn init_with_aligned_bufs(&mut self, aligned_bufs: &[AlignedBuffer]) {
self.current_idx = 0;
let list_len = self.list.len();
@@ -231,6 +276,7 @@ impl DescriptorList<'_> {
);
}
/// This function tries to scan for received frames and handles the frame if it finds one.
pub fn scan_and_handle_next_received_frame(
&mut self,
scan_full_descr_list: bool,

View File

@@ -1,3 +1,5 @@
use core::{cell::UnsafeCell, mem::MaybeUninit, sync::atomic::AtomicBool};
use arbitrary_int::u14;
pub use super::shared::Ownership;
@@ -17,6 +19,40 @@ pub struct Descriptor {
pub word1: VolatileCell<Word1>,
}
static TX_DESCR_TAKEN: AtomicBool = AtomicBool::new(false);
/// This is a low level wrapper to simplify declaring a global descriptor list.
///
/// It allows placing the descriptor structure statically in memory which might not
/// be zero-initialized.
#[repr(transparent)]
pub struct DescriptorList<const SLOTS: usize>(pub UnsafeCell<MaybeUninit<[Descriptor; SLOTS]>>);
unsafe impl<const SLOTS: usize> Sync for DescriptorList<SLOTS> {}
impl<const SLOTS: usize> DescriptorList<SLOTS> {
#[inline]
pub const fn new() -> Self {
Self(UnsafeCell::new(MaybeUninit::uninit()))
}
/// Initializes the TX descriptors and returns a mutable reference to them.
pub fn take(&self) -> Option<&'static mut [Descriptor; SLOTS]> {
if TX_DESCR_TAKEN.swap(true, core::sync::atomic::Ordering::SeqCst) {
return None; // Already taken, return None
}
let descr = unsafe { &mut *self.0.get() };
descr.write([const { Descriptor::new() }; SLOTS]);
Some(unsafe { descr.assume_init_mut() })
}
}
impl<const SLOTS: usize> Default for DescriptorList<SLOTS> {
fn default() -> Self {
Self::new()
}
}
#[bitbybit::bitenum(u3, exhaustive = true)]
#[derive(Debug, PartialEq, Eq)]
pub enum TransmitChecksumGenerationStatus {
@@ -157,7 +193,7 @@ pub enum BusyHandlingResult {
Complete,
}
pub struct DescriptorList<'a> {
pub struct DescriptorListWrapper<'a> {
list: &'a mut [Descriptor],
/// The head index is used to handle the transmission of new frames.
tx_idx: usize,
@@ -165,7 +201,7 @@ pub struct DescriptorList<'a> {
busy_idx: usize,
}
impl core::fmt::Debug for DescriptorList<'_> {
impl core::fmt::Debug for DescriptorListWrapper<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("DescriptorList")
.field("tx_idx", &self.tx_idx)
@@ -175,7 +211,7 @@ impl core::fmt::Debug for DescriptorList<'_> {
}
}
impl<'a> DescriptorList<'a> {
impl<'a> DescriptorListWrapper<'a> {
#[inline]
pub fn new(descr_list: &'a mut [Descriptor]) -> Self {
Self {
@@ -186,7 +222,7 @@ impl<'a> DescriptorList<'a> {
}
}
impl DescriptorList<'_> {
impl DescriptorListWrapper<'_> {
#[allow(clippy::len_without_is_empty)]
#[inline]
pub fn len(&self) -> usize {