diff --git a/Cargo.lock b/Cargo.lock index c296e6f7..5fe88a4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5132,6 +5132,9 @@ dependencies = [ "futures", "mnemos", "mnemos-bitslab", + "mycelium-bitfield 0.1.3 (git+https://github.com/hawkw/mycelium.git?rev=101a4abaa19afdd131b334a16d92c9fb4909c064)", + "proptest", + "proptest-derive", "riscv", "riscv-rt", "serde", @@ -6334,6 +6337,17 @@ dependencies = [ "unarray", ] +[[package]] +name = "proptest-derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf16337405ca084e9c78985114633b6827711d22b9e6ef6c6c0d665eb3f0b6e" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "prost" version = "0.11.9" diff --git a/platforms/allwinner-d1/Cargo.toml b/platforms/allwinner-d1/Cargo.toml index 9afa5533..496c4e81 100644 --- a/platforms/allwinner-d1/Cargo.toml +++ b/platforms/allwinner-d1/Cargo.toml @@ -12,7 +12,7 @@ repository = "https://github.com/tosc-rs/mnemos" homepage = "https://mnemos.dev" readme = "./README.md" license = "MIT OR Apache-2.0" -forced-target = "riscv64imac-unknown-none-elf" +default-target = "riscv64imac-unknown-none-elf" [lib] test = false diff --git a/platforms/allwinner-d1/d1-core/Cargo.toml b/platforms/allwinner-d1/d1-core/Cargo.toml index 7a32a49d..ce0a6dc3 100644 --- a/platforms/allwinner-d1/d1-core/Cargo.toml +++ b/platforms/allwinner-d1/d1-core/Cargo.toml @@ -17,6 +17,7 @@ sharp-display = [] [dependencies] serde = { version = "1.0.178", features = ["derive"], default-features = false } mnemos-bitslab = { path = "../../../source/bitslab" } +mycelium-bitfield = "0.1.3" d1-pac = "0.0.31" critical-section = "1.1.1" @@ -41,4 +42,8 @@ version = "0.7.1" [dependencies.futures] version = "0.3.21" features = ["async-await"] -default-features = false \ No newline at end of file +default-features = false + +[dev-dependencies] +proptest = "1" +proptest-derive = "0.4.0" \ No newline at end of file diff --git a/platforms/allwinner-d1/d1-core/proptest-regressions/dmac/descriptor.txt b/platforms/allwinner-d1/d1-core/proptest-regressions/dmac/descriptor.txt new file mode 100644 index 00000000..bb15037d --- /dev/null +++ b/platforms/allwinner-d1/d1-core/proptest-regressions/dmac/descriptor.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 4f86fc0f074d90d51a18ecfed5bace9a7d75c234378f2ff334584ffb761abe86 # shrinks to cfg = ArbitraryConfig { src_drq_type: Sram, src_block_size: Byte1, src_addr_mode: LinearMode, src_data_width: Bit8, dest_drq_type: Sram, dest_block_size: Byte1, dest_addr_mode: LinearMode, dest_data_width: Bit16, bmode_sel: Normal } diff --git a/platforms/allwinner-d1/d1-core/src/dmac/descriptor.rs b/platforms/allwinner-d1/d1-core/src/dmac/descriptor.rs index 5632bcdf..a1a74bc5 100644 --- a/platforms/allwinner-d1/d1-core/src/dmac/descriptor.rs +++ b/platforms/allwinner-d1/d1-core/src/dmac/descriptor.rs @@ -5,302 +5,798 @@ // separate the bits by which field they represent, rather than by their byte. #![allow(clippy::unusual_byte_groupings)] +use self::errors::*; +use core::{cmp, mem, ptr::NonNull}; +use d1_pac::generic::{Reg, RegisterSpec}; +use mycelium_bitfield::{bitfield, enum_from_bits}; + #[derive(Clone, Debug)] #[repr(C, align(4))] pub struct Descriptor { - configuration: u32, + configuration: Cfg, source_address: u32, destination_address: u32, byte_counter: u32, - parameter: u32, + parameter: Param, link: u32, } -// TODO: THIS COULD PROBABLY BE A BITFIELD LIBRARY -pub struct DescriptorConfig { - pub source: *const (), - pub destination: *mut (), - - // NOTE: Max is < 2^25, or < 32MiB - pub byte_counter: usize, - pub link: Option<*const ()>, - pub wait_clock_cycles: u8, +/// A builder for constructing DMA [`Descriptor`]s. +#[derive(Copy, Clone, Debug)] +#[must_use = "a `DescriptorBuilder` does nothing unless `DescriptorBuilder::build()` is called"] +pub struct DescriptorBuilder { + cfg: Cfg, + param: Param, + link: u32, + source: S, + dest: D, +} - pub bmode: BModeSel, +enum_from_bits! { + #[derive(Debug, Eq, PartialEq)] + #[cfg_attr(test, derive(proptest_derive::Arbitrary))] + pub enum SrcDrqType { + Sram = 0, + Dram = 1, + OwaRx = 2, + I2sPcm0Rx = 3, + I2sPcm1Rx = 4, + I2sPcm2Rx = 5, + AudioCodec = 7, + Dmic = 8, + GpADC = 12, + TpADC = 13, + Uart0Rx = 14, + Uart1Rx = 15, + Uart2Rx = 16, + Uart3Rx = 17, + Uart4Rx = 18, + Uart5Rx = 19, + Spi0Rx = 22, + Spi1Rx = 23, + Usb0Ep1 = 30, + Usb0Ep2 = 31, + Usb0Ep3 = 32, + Usb0Ep4 = 33, + Usb0Ep5 = 34, + Twi0 = 43, + Twi1 = 44, + Twi2 = 45, + Twi3 = 46, + } +} - pub dest_width: DataWidth, - pub dest_addr_mode: AddressMode, - pub dest_block_size: BlockSize, - pub dest_drq_type: DestDrqType, +enum_from_bits! { + #[derive(Debug, Eq, PartialEq)] + #[cfg_attr(test, derive(proptest_derive::Arbitrary))] + pub enum DestDrqType { + Sram = 0, + Dram = 1, + OwaTx = 2, + I2sPcm0Tx = 3, + I2sPcm1Tx = 4, + I2sPcm2Tx = 5, + AudioCodec = 7, + IrTx = 13, + Uart0Tx = 14, + Uart1Tx = 15, + Uart2Tx = 16, + Uart3Tx = 17, + Uart4Tx = 18, + Uart5Tx = 19, + Spi0Tx = 22, + Spi1Tx = 23, + Usb0Ep1 = 30, + Usb0Ep2 = 31, + Usb0Ep3 = 32, + Usb0Ep4 = 33, + Usb0Ep5 = 34, + Ledc = 42, + Twi0 = 43, + Twi1 = 44, + Twi2 = 45, + Twi3 = 46, + } +} - pub src_data_width: DataWidth, - pub src_addr_mode: AddressMode, - pub src_block_size: BlockSize, - pub src_drq_type: SrcDrqType, +// TODO: Verify bits or bytes? +enum_from_bits! { + #[derive(Debug, Eq, PartialEq)] + #[cfg_attr(test, derive(proptest_derive::Arbitrary))] + pub enum BlockSize { + Byte1 = 0b00, + Byte4 = 0b01, + Byte8 = 0b10, + Byte16 = 0b11, + } } -#[derive(Eq, PartialEq, Clone, Copy)] -#[repr(u8)] -pub enum SrcDrqType { - Sram = 0, - Dram = 1, - OwaRx = 2, - I2sPcm0Rx = 3, - I2sPcm1Rx = 4, - I2sPcm2Rx = 5, - AudioCodec = 7, - Dmic = 8, - GpADC = 12, - TpADC = 13, - Uart0Rx = 14, - Uart1Rx = 15, - Uart2Rx = 16, - Uart3Rx = 17, - Uart4Rx = 18, - Uart5Rx = 19, - Spi0Rx = 22, - Spi1Rx = 23, - Usb0Ep1 = 30, - Usb0Ep2 = 31, - Usb0Ep3 = 32, - Usb0Ep4 = 33, - Usb0Ep5 = 34, - Twi0 = 43, - Twi1 = 44, - Twi2 = 45, - Twi3 = 46, +enum_from_bits! { + #[derive(Debug, Eq, PartialEq)] + #[cfg_attr(test, derive(proptest_derive::Arbitrary))] + pub enum AddressMode { + LinearMode = 0, + IoMode = 1, + } } -#[derive(Eq, PartialEq, Clone, Copy)] -#[repr(u8)] -pub enum DestDrqType { - Sram = 0, - Dram = 1, - OwaTx = 2, - I2sPcm0Tx = 3, - I2sPcm1Tx = 4, - I2sPcm2Tx = 5, - AudioCodec = 7, - IrTx = 13, - Uart0Tx = 14, - Uart1Tx = 15, - Uart2Tx = 16, - Uart3Tx = 17, - Uart4Tx = 18, - Uart5Tx = 19, - Spi0Tx = 22, - Spi1Tx = 23, - Usb0Ep1 = 30, - Usb0Ep2 = 31, - Usb0Ep3 = 32, - Usb0Ep4 = 33, - Usb0Ep5 = 34, - Ledc = 42, - Twi0 = 43, - Twi1 = 44, - Twi2 = 45, - Twi3 = 46, +enum_from_bits! { + #[derive(Debug, Eq, PartialEq)] + #[cfg_attr(test, derive(proptest_derive::Arbitrary))] + pub enum DataWidth { + Bit8 = 0b00, + Bit16 = 0b01, + Bit32 = 0b10, + Bit64 = 0b11, + } } -// TODO: Verify bits or bytes? -#[derive(Eq, PartialEq, Clone, Copy)] -#[repr(u8)] -pub enum BlockSize { - Byte1 = 0b00, - Byte4 = 0b01, - Byte8 = 0b10, - Byte16 = 0b11, +enum_from_bits! { + #[derive(Debug, Eq, PartialEq)] + #[cfg_attr(test, derive(proptest_derive::Arbitrary))] + pub enum BModeSel { + Normal = 0, + BMode = 1, + } } -#[derive(Eq, PartialEq, Clone, Copy)] -#[repr(u8)] -pub enum AddressMode { - LinearMode = 0, - IoMode = 1, +bitfield! { + /// A DMAC descriptor `Configuration` field. + struct Cfg { + /// DMA source DRQ type. + const SRC_DRQ_TYPE: SrcDrqType; + + /// DMA source block size. + const SRC_BLOCK_SIZE: BlockSize; + + /// DMA source address mode. + const SRC_ADDR_MODE: AddressMode; + + /// DMA source data width. + const SRC_DATA_WIDTH: DataWidth; + + const _RESERVED_0 = 5; + + /// DMA destination DRQ type + const DEST_DRQ_TYPE: DestDrqType; + + /// DMA destination block size. + const DEST_BLOCK_SIZE: BlockSize; + + /// DMA destination address mode. + const DEST_ADDR_MODE: AddressMode; + + /// DMA destination data width. + const DEST_DATA_WIDTH: DataWidth; + + const _RESERVED_1 = 3; + + /// BMODE select + const BMODE_SEL: BModeSel; + } } -#[derive(Eq, PartialEq, Clone, Copy)] -#[repr(u8)] -pub enum DataWidth { - Bit8 = 0b00, - Bit16 = 0b01, - Bit32 = 0b10, - Bit64 = 0b11, +bitfield! { + /// A DMAC descriptor `Parameter` field. + struct Param { + /// Wait clock cycles. + /// + /// Sets the wait time in DRQ mode. + const WAIT_CLOCK_CYCLES: u8; + + const _RESERVED_0 = 8; + + /// The highest two bits of the 34-bit source address. + const SRC_HIGH = 2; + + /// The highest two bits of the 34-bit destination address. + const DEST_HIGH = 2; + + } } -#[derive(Eq, PartialEq, Clone, Copy)] -#[repr(u8)] -pub enum BModeSel { - Normal, - BMode, +impl DescriptorBuilder { + pub const fn new() -> Self { + Self { + cfg: Cfg::new(), + param: Param::new(), + link: Descriptor::END_LINK, + source: (), + dest: (), + } + } } -// Descriptor +type DestBuf<'dest> = &'dest mut [mem::MaybeUninit]; -impl Descriptor { - pub fn set_source(&mut self, source: u64) { - assert!(source < (1 << 34)); - self.source_address = source as u32; - // 332222222222 11 11 11111100 00000000 - // 109876543210 98 76 54321098 76543210 - self.parameter &= 0b111111111111_11_00_11111111_11111111; - self.parameter |= (((source >> 32) & 0b11) << 16) as u32; +impl DescriptorBuilder { + pub fn src_block_size(self, val: BlockSize) -> Self { + Self { + cfg: self.cfg.with(Cfg::SRC_BLOCK_SIZE, val), + ..self + } + } + + pub fn src_data_width(self, val: DataWidth) -> Self { + Self { + cfg: self.cfg.with(Cfg::SRC_DATA_WIDTH, val), + ..self + } } - pub fn set_dest(&mut self, dest: u64) { - assert!(dest < (1 << 34)); - self.destination_address = dest as u32; - // 332222222222 11 11 11111100 00000000 - // 109876543210 98 76 54321098 76543210 - self.parameter &= 0b111111111111_00_11_11111111_11111111; - self.parameter |= (((dest >> 32) & 0b11) << 18) as u32; + pub fn dest_block_size(self, val: BlockSize) -> Self { + Self { + cfg: self.cfg.with(Cfg::DEST_BLOCK_SIZE, val), + ..self + } } - pub fn end_link(&mut self) { - self.link = 0xFFFF_F800; + pub fn dest_data_width(self, val: DataWidth) -> Self { + Self { + cfg: self.cfg.with(Cfg::DEST_DATA_WIDTH, val), + ..self + } } -} -impl TryFrom for Descriptor { - type Error = (); + pub fn bmode_sel(self, val: BModeSel) -> Self { + Self { + cfg: self.cfg.with(Cfg::BMODE_SEL, val), + ..self + } + } - fn try_from(value: DescriptorConfig) -> Result { - let source = value.source as usize; - let destination = value.destination as usize; + pub fn link(self, link: impl Into>>) -> Result { + let link = link + .into() + .map(Descriptor::addr_to_link) + .transpose()? + .unwrap_or(Descriptor::END_LINK); + Ok(Self { link, ..self }) + } - if source >= (1 << 34) { - return Err(()); + pub fn wait_clock_cycles(self, wait_clock_cycles: u8) -> Self { + Self { + param: self.param.with(Param::WAIT_CLOCK_CYCLES, wait_clock_cycles), + ..self } - if destination >= (1 << 34) { - return Err(()); + } + + /// Sets the provided slice as the source for the DMA transfer. Bytes will + /// be copied out of this slice to the destination operand of the transfer. + /// + /// Since the slice is in memory, this automatically sets the source address + /// mode to [`AddressMode::LinearMode`] and the source DRQ type to + /// [`SrcDrqType::Dram`]. + /// + /// # Returns + /// + /// - [`Ok`]`(`[`DescriptorBuilder`]`)` with `dest` as the destination + /// operand, if the provided slice's address is a valid DMA destination. + /// - [`Err`]`(`[`InvalidOperand`]`)` with [`InvalidOperandReason::TooLong`] + /// if the provided slice is longer than [`Descriptor::MAX_LEN`]. + /// - [`Err`]`(`[`InvalidOperand`]`)` with + /// [`InvalidOperandReason::AddrTooHigh`] if the provided slice's address + /// is higher than [`Descriptor::ADDR_MAX`]. + pub fn source_slice( + self, + source: &'_ [u8], + ) -> Result, InvalidOperand> { + let high_bits = Self::high_bits(source as *const _ as *const (), Operand::Source)?; + + if source.len() > Descriptor::MAX_LEN as usize { + return Err(InvalidOperand::too_long(Operand::Source, source.len())); } - if value.byte_counter >= (1 << 25) { - return Err(()); + + Ok(DescriptorBuilder { + cfg: self + .cfg + .with(Cfg::SRC_ADDR_MODE, AddressMode::LinearMode) + .with(Cfg::SRC_DRQ_TYPE, SrcDrqType::Dram), + param: self.param.with(Param::SRC_HIGH, high_bits), + link: self.link, + source, + dest: self.dest, + }) + } + + /// Sets the provided slice as the destination of the DMA transfer. Bytes will + /// be copied from the source operand of the transfer into this slice. + /// + /// Since the slice is in memory, this automatically sets the destination address + /// mode to [`AddressMode::LinearMode`], and the destination DRQ type to + /// [`DestDrqType::Dram]. + /// + /// # Returns + /// + /// - [`Ok`]`(`[`DescriptorBuilder`]`)` with `dest` as the destination + /// operand, if the provided slice's address is a valid DMA destination. + /// - [`Err`]`(`[`InvalidOperand`]`)` with [`InvalidOperandReason::TooLong`] + /// if the provided slice is longer than [`Descriptor::MAX_LEN`]. + /// - [`Err`]`(`[`InvalidOperand`]`)` with + /// [`InvalidOperandReason::AddrTooHigh`] if the provided slice's address + /// is higher than [`Descriptor::ADDR_MAX`]. + pub fn dest_slice( + self, + dest: DestBuf<'_>, + ) -> Result>, InvalidOperand> { + let high_bits = Self::high_bits(dest as *const _ as *const (), Operand::Destination)?; + + if dest.len() > Descriptor::MAX_LEN as usize { + return Err(InvalidOperand::too_long(Operand::Destination, dest.len())); } - if let Some(link) = value.link { - let link = link as usize; - if (link & 0b11) != 0 { - return Err(()); - } + + Ok(DescriptorBuilder { + cfg: self + .cfg + .with(Cfg::DEST_ADDR_MODE, AddressMode::LinearMode) + .with(Cfg::DEST_DRQ_TYPE, DestDrqType::Dram), + param: self.param.with(Param::DEST_HIGH, high_bits), + link: self.link, + source: self.source, + dest, + }) + } + + /// Sets the provided pointer to a memory-mapped IO register as the source + /// for the DMA transfer. Bytes will be copied from this register to the + /// destination operand of the transfer. + /// + /// Since the source is a memory-mapped IO register, this automatically sets + /// the source address mode to [`AddressMode::IoMode`]. The provided + /// [`SrcDrqType`] describes the type of DRQ signal that should be used + /// when transferring from this register. Note that if this is not the correct + /// DRQ for this register, the DMA transfer may never complete. + /// + /// # Safety + /// + /// `source` MUST point to a memory-mapped IO register which is a valid + /// source for a DMA transfer. Otherwise, you will have a bad time. + /// + /// # Returns + /// + /// - [`Ok`]`(`[`DescriptorBuilder`]`)` with `source` as the source operand, + /// if the provided register's address is a valid DMA source. + /// - [`Err`]`(`[`InvalidOperand`]`)` with + /// [`InvalidOperandReason::AddrTooHigh`] if the provided register's address + /// is higher than [`Descriptor::ADDR_MAX`]. + pub fn source_reg( + self, + source: &Reg, + drq_type: SrcDrqType, + ) -> Result, InvalidOperand> { + let source = source.as_ptr().cast() as *const _; + let high_bits = Self::high_bits(source, Operand::Source)?; + + Ok(DescriptorBuilder { + cfg: self + .cfg + .with(Cfg::SRC_ADDR_MODE, AddressMode::IoMode) + .with(Cfg::SRC_DRQ_TYPE, drq_type), + param: self.param.with(Param::SRC_HIGH, high_bits), + link: self.link, + source, + dest: self.dest, + }) + } + + /// Sets the provided memory-mapped IO register as the destination for the + /// DMA transfer. Bytes will be copied from the source operand to the + /// pointed MMIO register. + /// + /// Since the destination is a memory-mapped IO register, this automatically sets + /// the destination address mode to [`AddressMode::IoMode`]. The provided + /// [`DestDrqType`] describes the type of DRQ signal that should be used + /// when transferring to this register. Note that if this is not the correct + /// DRQ for this register, the DMA transfer may never complete. + /// + /// # Returns + /// + /// - [`Ok`]`(`[`DescriptorBuilder`]`)` with `dest` as the destination operand, + /// if the provided register's address is a valid DMA destination. + /// - [`Err`]`(`[`InvalidOperand`]`)` with + /// [`InvalidOperandReason::AddrTooHigh`] if the provided register's address + /// is higher than [`Descriptor::ADDR_MAX`]. + pub fn dest_reg( + self, + dest: &Reg, + drq_type: DestDrqType, + ) -> Result, InvalidOperand> { + let dest = dest.as_ptr().cast(); + let high_bits = Self::high_bits(dest as *const _, Operand::Destination)?; + + Ok(DescriptorBuilder { + cfg: self + .cfg + .with(Cfg::DEST_ADDR_MODE, AddressMode::IoMode) + .with(Cfg::DEST_DRQ_TYPE, drq_type), + param: self.param.with(Param::DEST_HIGH, high_bits), + link: self.link, + dest, + source: self.source, + }) + } + + #[inline] + fn high_bits(addr: *const (), kind: Operand) -> Result { + let addr = addr as usize; + if addr > Descriptor::ADDR_MAX as usize { + return Err(InvalidOperand::addr_too_high(kind, addr)); } - let mut descriptor = Descriptor { - configuration: 0, - source_address: 0, - destination_address: 0, - byte_counter: 0, - parameter: 0, - link: 0, - }; - - // Set source - descriptor.source_address = source as u32; - // 332222222222 11 11 11111100 00000000 - // 109876543210 98 76 54321098 76543210 - descriptor.parameter &= 0b111111111111_11_00_11111111_11111111; - descriptor.parameter |= (((source >> 32) & 0b11) << 16) as u32; - - // Set dest - descriptor.destination_address = destination as u32; - // 332222222222 11 11 11111100 00000000 - // 109876543210 98 76 54321098 76543210 - descriptor.parameter &= 0b111111111111_00_11_11111111_11111111; - descriptor.parameter |= (((destination >> 32) & 0b11) << 18) as u32; - - descriptor.byte_counter = value.byte_counter as u32; - - // Set configuration - descriptor.configuration |= value.bmode.to_desc_bits(); - descriptor.configuration |= value.dest_width.to_desc_bits_dest(); - descriptor.configuration |= value.dest_addr_mode.to_desc_bits_dest(); - descriptor.configuration |= value.dest_block_size.to_desc_bits_dest(); - descriptor.configuration |= value.dest_drq_type.to_desc_bits(); - descriptor.configuration |= value.src_data_width.to_desc_bits_src(); - descriptor.configuration |= value.src_addr_mode.to_desc_bits_src(); - descriptor.configuration |= value.src_block_size.to_desc_bits_src(); - descriptor.configuration |= value.src_drq_type.to_desc_bits(); - - if let Some(link) = value.link { - descriptor.link = link as u32; - // We already verified above the low bits of `value.link` are clear, - // no need to re-mask the current state of `descriptor.link`. - descriptor.link |= ((link as usize >> 32) as u32) & 0b11 - } else { - descriptor.end_link(); + Ok((addr >> 32 & 0b11) as u32) + } + + /// This method assumes that the value of `byte_counter`, as well as the + /// source, destination, and link addresses, have already been validated. + #[inline] + fn build_inner(self, source_addr: usize, dest_addr: usize, byte_counter: u32) -> Descriptor { + debug_assert!( + source_addr <= Descriptor::ADDR_MAX as usize, + "source address should already have been validated" + ); + + debug_assert!( + dest_addr <= Descriptor::ADDR_MAX as usize, + "destination address should already have been validated" + ); + + debug_assert!( + byte_counter <= Descriptor::MAX_LEN, + "byte counter length should already have been validated" + ); + + debug_assert!( + self.link <= Descriptor::LINK_ADDR_MAX as u32, + "link address should already have been validated" + ); + + Descriptor { + configuration: self.cfg, + source_address: source_addr as u32, + destination_address: dest_addr as u32, + byte_counter, + parameter: self.param, + // link address field was already validated by + // `DescriptorBuilder::link`. + link: self.link, } + } +} - Ok(descriptor) +impl DescriptorBuilder<&'_ [u8], DestBuf<'_>> { + pub fn build(self) -> Descriptor { + // if the source buffer is shorter than the dest, we will be copying + // only `source.len()` bytes into `dest`. if the dest buffer is + // shorter than `source`, we will be copying only enough bytes to + // fill the dest. + let len = cmp::min(self.source.len(), self.dest.len()) as u32; + let dest = self.dest.as_mut_ptr() as *mut _ as usize; + let source = self.source.as_ptr() as *const _ as usize; + self.build_inner(source, dest, len) } } -// DescriptorConfig +impl DescriptorBuilder<*const (), DestBuf<'_>> { + pub fn build(self) -> Descriptor { + let len = self.dest.len() as u32; + let source = self.source as usize; + let dest = self.dest.as_mut_ptr() as *mut _ as usize; + self.build_inner(source, dest, len) + } +} -// SrcDrqType +impl DescriptorBuilder<&'_ [u8], *mut ()> { + pub fn build(self) -> Descriptor { + let len = self.source.len() as u32; + self.build_inner( + self.source.as_ptr() as *const _ as usize, + self.dest as usize, + len, + ) + } +} -impl SrcDrqType { - #[inline(always)] - fn to_desc_bits(self) -> u32 { - // 6 bits, no shift - ((self as u8) & 0b11_1111) as u32 +impl DescriptorBuilder<*const (), *mut ()> { + pub fn try_build(self, len: u32) -> Result { + if len > Descriptor::MAX_LEN { + return Err(InvalidDescriptor::ByteCounterTooLong(len as usize)); + } + Ok(self.build_inner(self.source as usize, self.dest as usize, len)) } } -// DestDrqType +// Descriptor + +impl Descriptor { + const END_LINK: u32 = 0xFFFF_F800; + + /// Maximum length for arguments to [`DescriptorBuilder::source_slice`] and + /// [`DescriptorBuilder::dest_slice`], and to the `len` argument to + /// [`DescriptorBuilder::try_build`] --- byte counters must be 25 bits wide + /// or less. + pub const MAX_LEN: u32 = (1 << 25) - 1; + + /// Highest allowable address for arguments to + /// [`DescriptorBuilder::source_slice`], [`DescriptorBuilder::dest_slice`], + /// [`DescriptorBuilder::source_reg`], and [`DescriptorBuilder::dest_reg`] + /// --- addresses must be 34 bits wide or less. + pub const ADDR_MAX: u64 = (1 << 34) - 1; + + /// Maximum value for the `link` address passed to + /// [`DescriptorBuilder::link`] -- link addresses must be 32 bits wide or + /// less. + pub const LINK_ADDR_MAX: usize = u32::MAX as usize; + + pub const fn builder() -> DescriptorBuilder { + DescriptorBuilder::new() + } + + pub fn set_link(&mut self, link: impl Into>>) -> Result<(), InvalidLink> { + self.link = link + .into() + .map(Self::addr_to_link) + .transpose()? + .unwrap_or(Self::END_LINK); + Ok(()) + } + + fn addr_to_link(link: NonNull) -> Result { + let addr = link.as_ptr() as usize; + if addr > Self::LINK_ADDR_MAX { + return Err(InvalidLink::TooLong(addr)); + } -impl DestDrqType { - #[inline(always)] - fn to_desc_bits(self) -> u32 { - (((self as u8) & 0b11_1111) as u32) << 16 + if addr & (mem::align_of::() - 1) > 0 { + return Err(InvalidLink::Misaligned(addr)); + } + + // We already verified above the low bits of `link` are clear, + // no need to re-mask them. + Ok(addr as u32 | ((addr >> 32) as u32) & 0b11) } } -// BlockSize +pub mod errors { + use super::*; + use core::fmt; -impl BlockSize { - #[inline(always)] - fn to_desc_bits_dest(self) -> u32 { - (((self as u8) & 0b11) as u32) << 22 + /// Errors returned by [`DescriptorBuilder::build`]. + #[derive(Clone, Debug, Eq, PartialEq)] + pub enum InvalidDescriptor { + ByteCounterTooLong(usize), + LinkAddr(InvalidLink), } - #[inline(always)] - fn to_desc_bits_src(self) -> u32 { - (((self as u8) & 0b11) as u32) << 6 + /// Errors returned by [`DescriptorBuilder::source_slice`], + /// [`DescriptorBuilder::dest_slice`], [`DescriptorBuilder::source_reg`], and [`DescriptorBuilder::dest_reg`]. + #[derive(Clone, Debug, Eq, PartialEq)] + pub struct InvalidOperand { + reason: InvalidOperandReason, + kind: Operand, + } + + #[derive(Clone, Debug, Eq, PartialEq)] + #[non_exhaustive] + pub enum InvalidOperandReason { + /// Indicates that the address of the provided operand was too high in + /// memory. Operand addresses for DMA transfers may not exceed + /// [`Descriptor::ADDR_MAX`] (34 bits). + AddrTooHigh(usize), + /// A slice was longer than the maximum supported DMA transfer size of + /// [`Descriptor::MAX_LEN`] (25 bits). + TooLong(usize), } -} -// AddressMode + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub enum Operand { + Source, + Destination, + } -impl AddressMode { - #[inline(always)] - fn to_desc_bits_src(self) -> u32 { - (((self as u8) & 0b1) as u32) << 8 + /// Errors returned by [`Descriptor::set_link`] and [`DescriptorBuilder::build`]. + #[derive(Clone, Debug, Eq, PartialEq)] + pub enum InvalidLink { + TooLong(usize), + Misaligned(usize), } - #[inline(always)] - fn to_desc_bits_dest(self) -> u32 { - (((self as u8) & 0b1) as u32) << 24 + // === InvalidDescriptor === + + impl fmt::Display for InvalidDescriptor { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + InvalidDescriptor::ByteCounterTooLong(counter) => write!( + f, + "byte counter {counter} is greater than `Descriptor::BYTE_COUNTER_MAX` ({})", + Descriptor::MAX_LEN + ), + InvalidDescriptor::LinkAddr(error) => fmt::Display::fmt(error, f), + } + } } -} -// DataWidth + // === InvalidLink === + + impl fmt::Display for InvalidLink { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + InvalidLink::TooLong(addr) => write!( + f, + "link address {addr:#x} is greater than `Descriptor::LINK_ADDR_MAX` ({:#x})", + Descriptor::LINK_ADDR_MAX + ), + InvalidLink::Misaligned(addr) => { + write!(f, "link address {addr:#x} is not at least 4-byte aligned!",) + } + } + } + } + + // === InvalidOperand === + + impl InvalidOperand { + /// Returns whether this error describes the source or destination operand of + /// a DMA transfer. + #[must_use] + pub fn operand(&self) -> Operand { + self.kind + } + + /// Returns an [`InvalidOperandReason`] describing why the operand was + /// invalid. + #[must_use] + pub fn reason(&self) -> &InvalidOperandReason { + &self.reason + } + + #[must_use] + pub(super) fn too_long(kind: Operand, len: usize) -> Self { + Self { + reason: InvalidOperandReason::TooLong(len), + kind, + } + } -impl DataWidth { - #[inline(always)] - fn to_desc_bits_dest(self) -> u32 { - (((self as u8) & 0b11) as u32) << 25 + #[must_use] + pub(super) fn addr_too_high(kind: Operand, addr: usize) -> Self { + Self { + reason: InvalidOperandReason::AddrTooHigh(addr), + kind, + } + } + } + + impl fmt::Display for InvalidOperand { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { reason, kind } = self; + match kind { + Operand::Source => f.write_str("invalid source ")?, + Operand::Destination => f.write_str("invalid destination ")?, + } + fmt::Display::fmt(reason, f) + } } - #[inline(always)] - fn to_desc_bits_src(self) -> u32 { - (((self as u8) & 0b11) as u32) << 9 + impl fmt::Display for InvalidOperandReason { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::AddrTooHigh(addr) => write!( + f, + "address {addr:#x} must be less than `Descriptor::ADDR_MAX` ({:#x})", + Descriptor::ADDR_MAX + ), + Self::TooLong(len) => write!( + f, + "length {len} is greater than `Descriptor::MAX_LEN` ({})", + Descriptor::MAX_LEN + ), + } + } } } -// BModeSel +#[cfg(test)] +mod tests { + use proptest::{prop_assert_eq, proptest}; + + use super::*; + + #[test] + fn configuration_is_valid() { + Cfg::assert_valid(); + } + + #[derive(proptest_derive::Arbitrary, Debug)] + struct ArbitraryConfig { + src_drq_type: SrcDrqType, + src_block_size: BlockSize, + src_addr_mode: AddressMode, + src_data_width: DataWidth, + dest_drq_type: DestDrqType, + dest_block_size: BlockSize, + dest_addr_mode: AddressMode, + dest_data_width: DataWidth, + bmode_sel: BModeSel, + } + + impl ArbitraryConfig { + fn manual_pack(&self) -> u32 { + // 6 bits, no shift + let src_drq_type = ((self.src_drq_type as u8) & 0b11_1111) as u32; + let src_block_size = (((self.src_block_size as u8) & 0b11) as u32) << 6; + let src_addr_mode = (((self.src_addr_mode as u8) & 0b1) as u32) << 8; + let src_data_width = (((self.src_data_width as u8) & 0b11) as u32) << 9; + + let dest_drq_type = (((self.dest_drq_type as u8) & 0b11_1111) as u32) << 16; + let dest_block_size = (((self.dest_block_size as u8) & 0b11) as u32) << 22; + let dest_addr_mode = (((self.dest_addr_mode as u8) & 0b1) as u32) << 24; + let dest_data_width = (((self.dest_data_width as u8) & 0b11) as u32) << 25; + + let bmode_sel = (((self.bmode_sel as u8) & 0b1) as u32) << 30; + + src_drq_type + | src_block_size + | src_addr_mode + | src_data_width + | dest_drq_type + | dest_block_size + | dest_addr_mode + | dest_data_width + | bmode_sel + } + } -impl BModeSel { - #[inline(always)] - fn to_desc_bits(self) -> u32 { - (((self as u8) & 0b1) as u32) << 30 + proptest! { + #[test] + fn pack_configuration(cfg: ArbitraryConfig) { + let mut config = DescriptorBuilder::new() + .src_block_size(cfg.src_block_size) + .src_data_width(cfg.src_data_width) + .dest_block_size(cfg.dest_block_size) + .dest_data_width(cfg.dest_data_width) + .bmode_sel(cfg.bmode_sel).cfg; + config + .set(Cfg::SRC_ADDR_MODE, cfg.src_addr_mode) + .set(Cfg::DEST_ADDR_MODE, cfg.dest_addr_mode) + .set(Cfg::SRC_DRQ_TYPE, cfg.src_drq_type) + .set(Cfg::DEST_DRQ_TYPE, cfg.dest_drq_type); + + prop_assert_eq!( + cfg.manual_pack(), + config.bits(), + "\n{:032b} (expected), vs:\n{}", + cfg.manual_pack(), + config + ); + } + + #[test] + fn pack_param(src_high in 0b00u32..0b11u32, dest_high in 0b00u32..0b11u32, wait_clock_cycles: u8) { + let mut manual = wait_clock_cycles as u32; + + // Set source + // 332222222222 11 11 11111100 00000000 + // 109876543210 98 76 54321098 76543210 + manual &= 0b111111111111_11_00_11111111_11111111; + manual |= src_high << 16; + + // Set dest + // 332222222222 11 11 11111100 00000000 + // 109876543210 98 76 54321098 76543210 + manual &= 0b111111111111_00_11_11111111_11111111; + manual |= dest_high << 18; + + let param = Param::new() + .with(Param::WAIT_CLOCK_CYCLES, wait_clock_cycles) + .with(Param::SRC_HIGH, src_high) + .with(Param::DEST_HIGH, dest_high); + prop_assert_eq!( + manual, + param.bits(), + "\n{:032b} (expected), vs:\n{}", + manual, + param, + ) + } } } diff --git a/platforms/allwinner-d1/d1-core/src/drivers/spim.rs b/platforms/allwinner-d1/d1-core/src/drivers/spim.rs index 00ff6683..06eb9697 100644 --- a/platforms/allwinner-d1/d1-core/src/drivers/spim.rs +++ b/platforms/allwinner-d1/d1-core/src/drivers/spim.rs @@ -7,9 +7,7 @@ use core::ptr::NonNull; use crate::ccu::Ccu; use crate::dmac::{ - descriptor::{ - AddressMode, BModeSel, BlockSize, DataWidth, DescriptorConfig, DestDrqType, SrcDrqType, - }, + descriptor::{BlockSize, DataWidth, Descriptor, DestDrqType}, ChannelMode, Dmac, }; use d1_pac::{GPIO, SPI_DBI}; @@ -120,62 +118,76 @@ impl SpiSenderServer { .spawn(async move { let spi = unsafe { &*SPI_DBI::PTR }; - let txd_ptr: *mut u32 = spi.spi_txd.as_ptr(); - let txd_ptr: *mut u8 = txd_ptr.cast(); - let txd_ptr: *mut () = txd_ptr.cast(); + let descr_cfg = Descriptor::builder() + .dest_data_width(DataWidth::Bit8) + .dest_block_size(BlockSize::Byte1) + .src_data_width(DataWidth::Bit8) + .src_block_size(BlockSize::Byte1) + .wait_clock_cycles(0) + .dest_reg(&spi.spi_txd, DestDrqType::Spi1Tx) + .expect( + "SPI_TXD register should be a valid destination register for DMA transfers", + ); loop { let Message { msg, reply } = reqs.next_request().await; let SpiSenderRequest::Send(ref payload) = msg.body; - let len = payload.as_slice().len(); - - spi.spi_bcc.modify(|_r, w| { - // "Single Mode Transmit Counter" - the number of bytes to send - w.stc().variant(len as u32); - w - }); - spi.spi_mbc.modify(|_r, w| { - // Master Burst Counter - w.mbc().variant(len as u32); - w - }); - spi.spi_mtc.modify(|_r, w| { - w.mwtc().variant(len as u32); - w - }); - // Start transfer - spi.spi_tcr.modify(|_r, w| { - w.xch().initiate_exchange(); - w - }); - - let d_cfg = DescriptorConfig { - source: payload.as_slice().as_ptr().cast(), - destination: txd_ptr, - byte_counter: len, - link: None, - wait_clock_cycles: 0, - bmode: BModeSel::Normal, - dest_width: DataWidth::Bit8, - dest_addr_mode: AddressMode::IoMode, - dest_block_size: BlockSize::Byte1, - dest_drq_type: DestDrqType::Spi1Tx, - src_data_width: DataWidth::Bit8, - src_addr_mode: AddressMode::LinearMode, - src_block_size: BlockSize::Byte1, - src_drq_type: SrcDrqType::Dram, - }; - let descriptor = d_cfg.try_into().map_err(drop)?; - - // start the DMA transfer. + let mut chan = dmac.claim_channel().await; unsafe { - dmac.transfer( - ChannelMode::Wait, - ChannelMode::Handshake, - NonNull::from(&descriptor), - ) - .await; + chan.set_channel_modes(ChannelMode::Wait, ChannelMode::Handshake); + } + + // if the payload is longer than the maximum DMA buffer + // length, split it down to the maximum size and send each + // chunk as a separate DMA transfer. + let chunks = payload + .as_slice() + // TODO(eliza): since the `MAX_LEN` is a constant, + // we could consider using `slice::array_chunks` instead to + // get these as fixed-size arrays, once that function is stable? + .chunks(Descriptor::MAX_LEN as usize); + + for chunk in chunks { + // this cast will never truncate because + // `BYTE_COUNTER_MAX` is less than 32 bits. + debug_assert!(chunk.len() <= Descriptor::MAX_LEN as usize); + let len = chunk.len() as u32; + + spi.spi_bcc.modify(|_r, w| { + // "Single Mode Transmit Counter" - the number of bytes to send + w.stc().variant(len); + w + }); + spi.spi_mbc.modify(|_r, w| { + // Master Burst Counter + w.mbc().variant(len); + w + }); + spi.spi_mtc.modify(|_r, w| { + w.mwtc().variant(len); + w + }); + // Start transfer + spi.spi_tcr.modify(|_r, w| { + w.xch().initiate_exchange(); + w + }); + + // TODO(eliza): we could use the `Descriptor::link` + // field to chain all the chunks together, instead of + // doing a bunch of transfers. But, we would need some + // place to put all the descriptors...let's figure that + // out later. + let descriptor = descr_cfg + .source_slice(chunk) + .expect("slice should be a valid DMA source") + .build(); + + // start the DMA transfer. + unsafe { + chan.transfer(NonNull::from(&descriptor)).await; + } } reply diff --git a/platforms/allwinner-d1/d1-core/src/drivers/uart.rs b/platforms/allwinner-d1/d1-core/src/drivers/uart.rs index a6633e82..2410fad4 100644 --- a/platforms/allwinner-d1/d1-core/src/drivers/uart.rs +++ b/platforms/allwinner-d1/d1-core/src/drivers/uart.rs @@ -10,9 +10,7 @@ use core::{ use crate::ccu::Ccu; use crate::dmac::{ - descriptor::{ - AddressMode, BModeSel, BlockSize, DataWidth, DescriptorConfig, DestDrqType, SrcDrqType, - }, + descriptor::{BlockSize, DataWidth, Descriptor, DestDrqType}, ChannelMode, Dmac, }; use kernel::{ @@ -113,42 +111,49 @@ impl D1Uart { // Send loop that listens to the bbqueue consumer, and sends it as DMA transactions on the UART async fn sending(cons: Consumer, dmac: Dmac) { + let thr = unsafe { (*UART0::PTR).thr() }; + + let descr_cfg = Descriptor::builder() + .dest_data_width(DataWidth::Bit8) + .dest_block_size(BlockSize::Byte1) + .src_data_width(DataWidth::Bit8) + .src_block_size(BlockSize::Byte1) + .wait_clock_cycles(0) + .dest_reg(thr, DestDrqType::Uart0Tx) + .expect("UART0 THR register should be a valid destination register for DMA transfers"); + loop { let rx = cons.read_grant().await; - let len = rx.len(); - let thr_addr = unsafe { &*UART0::PTR }.thr() as *const _ as *mut (); - - let rx_sli: &[u8] = ℞ - - let d_cfg = DescriptorConfig { - source: rx_sli.as_ptr().cast(), - destination: thr_addr, - byte_counter: rx_sli.len(), - link: None, - wait_clock_cycles: 0, - bmode: BModeSel::Normal, - dest_width: DataWidth::Bit8, - dest_addr_mode: AddressMode::IoMode, - dest_block_size: BlockSize::Byte1, - dest_drq_type: DestDrqType::Uart0Tx, - src_data_width: DataWidth::Bit8, - src_addr_mode: AddressMode::LinearMode, - src_block_size: BlockSize::Byte1, - src_drq_type: SrcDrqType::Dram, - }; - let descriptor = d_cfg.try_into().unwrap(); - - // start the DMA transfer. + let rx_len = rx.len(); + + let mut chan = dmac.claim_channel().await; unsafe { - dmac.transfer( - ChannelMode::Wait, - ChannelMode::Handshake, - NonNull::from(&descriptor), - ) - .await + chan.set_channel_modes(ChannelMode::Wait, ChannelMode::Handshake); + } + + // if the payload is longer than the maximum DMA buffer + // length, split it down to the maximum size and send each + // chunk as a separate DMA transfer. + let chunks = rx[..] + // TODO(eliza): since the `byte_counter_max` is a constant, + // we could consider using `slice::array_chunks` instead to + // get these as fixed-size arrays, once that function is stable? + .chunks(Descriptor::MAX_LEN as usize); + + for chunk in chunks { + // this cast will never truncate because + // `BYTE_COUNTER_MAX` is less than 32 bits. + debug_assert!(chunk.len() <= Descriptor::MAX_LEN as usize); + let descriptor = descr_cfg + .source_slice(chunk) + .expect("slice should be a valid DMA source operand") + .build(); + + // start the DMA transfer. + unsafe { chan.transfer(NonNull::from(&descriptor)).await } } - rx.release(len); + rx.release(rx_len); } }