drone-sd-core 0.2.2

Secure Digital cards driver for Drone.
//! SD card tokens.

use drone_core::bitfield::Bitfield;
use errors::{R1Error, ReadError, WriteError};

/// Start Block Token.
pub const BLOCK_START: u8 = 0xFE;

/// Start Block Token for Multiple Block Write.
pub const BLOCK_START_MULTI_WRITE: u8 = 0xFC;

/// Stop Tran Token.
pub const STOP_TRAN: u8 = 0xFD;

const CSD20_C_MULT: u32 = 8;
const CSD20_READ_BL_LEN: u16 = 9;
const CSD20_WRITE_BL_LEN: u16 = 9;
const CSD20_SECTOR_SIZE: u32 = 0x7F;

/// Operation conditions register.
#[derive(Clone, Copy, Bitfield)]
#[bitfield(
  supports_2v8(rw, 15, "Supports 2.7-2.8V."),
  supports_2v9(rw, 16, "Supports 2.8-2.9V."),
  supports_3v0(rw, 17, "Supports 2.9-3.0V."),
  supports_3v1(rw, 18, "Supports 3.0-3.1V."),
  supports_3v2(rw, 19, "Supports 3.1-3.2V."),
  supports_3v3(rw, 20, "Supports 3.2-3.3V."),
  supports_3v4(rw, 21, "Supports 3.3-3.4V."),
  supports_3v5(rw, 22, "Supports 3.4-3.5V."),
  supports_3v6(rw, 23, "Supports 3.5-3.6V."),
  s18a(r, 24, "Switching to 1.8V Accepted."),
  card_status(r, 29, "UHS-II Card Status."),
  ccs(r, 30, "Card Capacity Status."),
  busy(r, 31, "Card power up status.")
)]
pub struct Ocr(u32);

/// The Card-Specific Data register version 1.0.
#[derive(Clone, Copy, Bitfield)]
#[bitfield(
  crc(
    rw,
    1,
    7,
    "The CRC field carries the check sum for the CSD contents."
  ),
  file_format(rw, 10, 2, "Indicates the file format on the card."),
  tmp_write_protect(
    rw,
    12,
    1,
    "Temporarily protects the entire card content from being overwritten or \
     erased (all write and erase commands for this card are temporarily \
     disabled)."
  ),
  perm_write_protect(
    rw,
    13,
    1,
    "Permanently protects the entire card content against overwriting or \
     erasing (all write and erase commands for this card are permanently \
     disabled)."
  ),
  copy(
    rw,
    14,
    1,
    "Defines whether the contents is original (`0`) or has been copied (`1`)."
  ),
  file_format_grp(rw, 15, 1, "Indicates the selected group of file formats."),
  write_bl_partial(
    r,
    21,
    1,
    "Defines whether partial block sizes can be used in block write commands."
  ),
  write_bl_len(
    r,
    22,
    4,
    "The maximum write data block length is computed as \
     2<sup>`WRITE_BL_LEN`</sup>."
  ),
  r2w_factor(
    r,
    26,
    3,
    "Defines the typical block program time as a multiple of the read access \
     time."
  ),
  wp_grp_enable(
    r,
    31,
    1,
    "A value of 0 means no group write protection possible."
  ),
  wp_grp_size(r, 32, 7, "The size of a write protected group."),
  sector_size(r, 39, 7, "The size of an erasable sector."),
  erase_blk_en(
    r,
    46,
    1,
    "Defines the granularity of the unit size of the data to be erased."
  ),
  c_size_mult(
    r,
    47,
    3,
    "This parameter is used for coding a factor `MULT` for computing the total \
     device size (see `c_size`). The factor `MULT` is defined as \
     2<sup>`C_SIZE_MULT+2`</sup>."
  ),
  vdd_w_curr_max(
    r,
    50,
    3,
    "The maximum value for write current at the maximal power supply \
     V<sub>DD</sub>."
  ),
  vdd_w_curr_min(
    r,
    53,
    3,
    "The minimum value for write current at the minimal power supply \
     V<sub>DD</sub>."
  ),
  vdd_r_curr_max(
    r,
    56,
    3,
    "The maximum value for read current at the maximal power supply \
     V<sub>DD</sub>."
  ),
  vdd_r_curr_min(
    r,
    59,
    3,
    "The minimum value for read current at the minimal power supply \
     V<sub>DD</sub>."
  ),
  c_size(
    r,
    62,
    12,
    "This parameter is used to compute the user's data card capacity (not \
     include the security protected area)."
  ),
  dsr_imp(
    r,
    76,
    1,
    "Defines if the configurable driver stage is integrated on the card."
  ),
  read_blk_misalign(
    r,
    77,
    1,
    "Defines if the data block to be read by one command can be spread over \
     more than one physical block of the memory device."
  ),
  write_blk_misalign(
    r,
    78,
    1,
    "Defines if the data block to be written by one command can be spread over \
     more than one physical block of the memory device."
  ),
  read_bl_partial(
    r,
    79,
    1,
    "Partial Block Read is always allowed in an SD Memory Card."
  ),
  read_bl_len(
    r,
    80,
    4,
    "The maximum read data block length is computed as \
     2<sup>`READ_BL_LEN`</sup>."
  ),
  ccc(
    r,
    84,
    12,
    "The SD Memory Card command set is divided into subsets (command classes)."
  ),
  tran_speed(r, 96, 8, "The maximum data transfer rate."),
  nsac(
    r,
    104,
    8,
    "Defines the worst case for the clock-dependent factor of the data access \
     time."
  ),
  taac(
    r,
    112,
    8,
    "Defines the asynchronous part of the data access time."
  )
)]
pub struct Csd10(u128);

/// The Card-Specific Data register version 2.0.
#[derive(Clone, Copy, Bitfield)]
#[bitfield(
  crc(
    rw,
    1,
    7,
    "The CRC field carries the check sum for the CSD contents."
  ),
  file_format(r, 10, 2, "Indicates the file format on the card."),
  tmp_write_protect(
    rw,
    12,
    1,
    "Temporarily protects the entire card content from being overwritten or \
     erased (all write and erase commands for this card are temporarily \
     disabled)."
  ),
  perm_write_protect(
    rw,
    13,
    1,
    "Permanently protects the entire card content against overwriting or \
     erasing (all write and erase commands for this card are permanently \
     disabled)."
  ),
  copy(
    rw,
    14,
    1,
    "Defines whether the contents is original (`0`) or has been copied (`1`)."
  ),
  file_format_grp(r, 15, 1, "Indicates the selected group of file formats."),
  write_bl_partial(
    r,
    21,
    1,
    "Defines whether partial block sizes can be used in block write commands."
  ),
  write_bl_len(
    r,
    22,
    4,
    "The maximum write data block length is computed as \
     2<sup>`WRITE_BL_LEN`</sup>."
  ),
  r2w_factor(
    r,
    26,
    3,
    "Defines the typical block program time as a multiple of the read access \
     time."
  ),
  wp_grp_enable(
    r,
    31,
    1,
    "A value of 0 means no group write protection possible."
  ),
  wp_grp_size(r, 32, 7, "The size of a write protected group."),
  sector_size(r, 39, 7, "The size of an erasable sector."),
  erase_blk_en(
    r,
    46,
    1,
    "Defines the granularity of the unit size of the data to be erased."
  ),
  c_size(
    r,
    48,
    22,
    "This parameter is used to calculate the user data area capacity in the SD \
     memory card (not include the protected area)."
  ),
  dsr_imp(
    r,
    76,
    1,
    "Defines if the configurable driver stage is integrated on the card."
  ),
  read_blk_misalign(
    r,
    77,
    1,
    "Defines if the data block to be read by one command can be spread over \
     more than one physical block of the memory device."
  ),
  write_blk_misalign(
    r,
    78,
    1,
    "Defines if the data block to be written by one command can be spread over \
     more than one physical block of the memory device."
  ),
  read_bl_partial(
    r,
    79,
    1,
    "Partial Block Read is always allowed in an SD Memory Card."
  ),
  read_bl_len(
    r,
    80,
    4,
    "The maximum read data block length is computed as \
     2<sup>`READ_BL_LEN`</sup>."
  ),
  ccc(
    r,
    84,
    12,
    "The SD Memory Card command set is divided into subsets (command classes)."
  ),
  tran_speed(r, 96, 8, "The maximum data transfer rate."),
  nsac(
    r,
    104,
    8,
    "Defines the worst case for the clock-dependent factor of the data access \
     time."
  ),
  taac(
    r,
    112,
    8,
    "Defines the asynchronous part of the data access time."
  )
)]
pub struct Csd20(u128);

/// The Card-Specific Data register provides information regarding access to the
/// card contents.
#[derive(Clone, Copy)]
pub union Csd {
  bits: u128,
  csd10: Csd10,
  csd20: Csd20,
}

/// This response token is sent by the card after every command with the
/// exception of `SEND_STATUS` commands.
#[derive(Clone, Copy, Bitfield)]
#[bitfield(
  in_idle_state(
    r,
    0,
    "The card is in idle state and running the initializing process."
  ),
  erase_reset(
    r,
    1,
    "An erase sequence was cleared before executing because an out of erase \
     sequence command was received."
  ),
  illegal_command(r, 2, "An illegal command code was detected."),
  com_crc_error(r, 3, "The CRC check of the last command failed."),
  erase_sequence_error(
    r, 4, "An error in the sequence of erase commands occurred."
  ),
  address_error(
    r,
    5,
    "A misaligned address that did not match the block length was used in the \
     command."
  ),
  parameter_error(
    r,
    6,
    "The command's argument (e.g. address, block length) was outside the \
     allowed range for this card."
  )
)]
pub struct R1(u8);

/// Payload bytes for [`R2`](R2).
#[derive(Clone, Copy, Bitfield)]
#[bitfield(
  card_is_locked(
    r,
    0,
    "Set when the card is locked by the user. Reset when it is unlocked."
  ),
  wp_erase_skip_or_lck_failed(
    r,
    1,
    "This status bit has two functions overloaded. It is set when the host \
     attempts to erase a write-protected sector or makes a sequence or \
     password errors during card lock/unlock operation."
  ),
  error(
    r,
    2,
    "A general or an unknown error occurred during the operation."
  ),
  cc_error(r, 3, "Internal card controller error."),
  card_ecc_failed(
    r,
    4,
    "Card internal ECC was applied but failed to correct the data."
  ),
  wp_violation(r, 5, "The command tried to write a write-protected block."),
  erase_param(r, 6, "An invalid selection for erase, sectors or groups."),
  out_of_range_or_csd_overwrite(
    r,
    7,
    "Can be either of the following errors:\n\n*The command argument was out \
     of the allowed range for this card.\n*The read only section of the CSD \
     does not match the card content.\n*An attempt to reverse the copy (set as \
     original) or permanent WP (unprotected) bits was made."
  )
)]
pub struct Status(u8);

/// Payload bytes for [`R7`](R7).
#[derive(Clone, Copy, Bitfield)]
#[bitfield(
  check_pattern(r, 0, 8, "Echo back of check pattern in argument."),
  voltage_accepted(r, 8, 4, "The card operating voltage."),
  command_version(r, 28, 4, "Command version.")
)]
pub struct Cond(u32);

/// Start block token for Single Block Read, Single Block Write and Multiple
/// Block Read.
#[derive(Clone, Copy, Bitfield)]
#[bitfield(
  error(
    r,
    0,
    "A general or an unknown error occurred during the operation."
  ),
  cc_error(r, 1, "Internal card controller error."),
  card_ecc_failed(
    r,
    2,
    "Card internal ECC was applied but failed to correct the data."
  ),
  out_of_range(
    r,
    3,
    "The command argument was out of the allowed range for this card."
  )
)]
pub struct ReadResponse(u8);

/// Written data block acknowledge token.
#[derive(Clone, Copy)]
pub struct WriteResponse(u8);

impl Ocr {
  #[cfg_attr(feature = "cargo-clippy", allow(cast_ptr_alignment))]
  pub(crate) fn from_buf(buf: &[u8]) -> Self {
    assert!(buf.len() >= 4);
    unsafe { Ocr(u32::from_be(*(buf.as_ptr() as *const u32))) }
  }

  /// Creates a new `Ocr`.
  pub fn new<F: FnOnce(&mut Self) -> &mut Self>(f: F) -> Self {
    let mut ocr = Ocr(0);
    f(&mut ocr);
    ocr
  }

  /// Checks for voltage support.
  pub fn supports(self, other: Self) -> bool {
    self.0 | other.0 != 0
  }
}

impl Csd {
  #[cfg_attr(feature = "cargo-clippy", allow(cast_ptr_alignment))]
  pub(crate) fn from_buf(buf: &[u8]) -> Self {
    assert!(buf.len() >= 16);
    let bits = unsafe { u128::from_be(*(buf.as_ptr() as *const u128)) };
    Self { bits }
  }

  /// If the version is 1.0 returns `Csd10` variant.
  pub fn csd10(self) -> Option<Csd10> {
    unsafe {
      if (self.bits >> 126) as u8 == 0 {
        Some(self.csd10)
      } else {
        None
      }
    }
  }

  /// If the version is 2.0 returns `Csd20` variant.
  pub fn csd20(self) -> Option<Csd20> {
    unsafe {
      if (self.bits >> 126) as u8 == 1 {
        Some(self.csd20)
      } else {
        None
      }
    }
  }
}

impl Csd10 {
  /// Returns the number of available sectors on the device.
  #[inline(always)]
  pub fn sec_count(self) -> u32 {
    (self.c_size() as u32 + 1) << (self.c_size_mult() as u32 + 2)
  }

  /// Returns sector size of the device.
  #[inline(always)]
  pub fn sec_size(self) -> u16 {
    1 << self.read_bl_len() as u16
  }

  /// Returns erase block size of the device in unit of sector.
  #[inline(always)]
  pub fn erase_size(self) -> u32 {
    (self.sector_size() as u32 + 1) << self.write_bl_len() as u32
  }
}

impl Csd20 {
  /// Returns the number of available sectors on the device.
  #[inline(always)]
  pub fn sec_count(self) -> u32 {
    (self.c_size() as u32 + 1) << (CSD20_C_MULT + 2)
  }

  /// Returns sector size of the device.
  #[inline(always)]
  pub const fn sec_size() -> u16 {
    1 << CSD20_READ_BL_LEN
  }

  /// Returns erase block size of the device in unit of sector.
  #[inline(always)]
  pub const fn erase_size() -> u32 {
    (CSD20_SECTOR_SIZE + 1) << CSD20_WRITE_BL_LEN
  }
}

impl R1 {
  /// Creates a new `R1` if `value` is a valid sequence of bits.
  pub fn try_from(value: u8) -> Option<Self> {
    if value >> 7 == 0 {
      Some(R1(value))
    } else {
      None
    }
  }

  /// Checks for R1 errors.
  pub fn errck(self, can_precede: bool) -> Result<(), R1Error> {
    if self.illegal_command() {
      return Err(R1Error::Illegal);
    }
    if self.com_crc_error() {
      return Err(R1Error::Crc);
    }
    if !can_precede {
      if self.erase_sequence_error() {
        return Err(R1Error::InvalidEraseSequence);
      }
      if self.address_error() {
        return Err(R1Error::Address);
      }
      if self.parameter_error() {
        return Err(R1Error::Parameter);
      }
    }
    Ok(())
  }
}

impl Status {
  /// Creates a new `Status`.
  pub fn new(value: u8) -> Self {
    Status(value)
  }

  /// Checks for write errors.
  pub fn errck(self) -> Result<(), WriteError> {
    if self.card_is_locked() {
      return Err(WriteError::Locked);
    }
    if self.wp_erase_skip_or_lck_failed() {
      return Err(WriteError::WriteProtectEraseOrLockFailed);
    }
    if self.error() {
      return Err(WriteError::Unknown);
    }
    if self.cc_error() {
      return Err(WriteError::Card);
    }
    if self.card_ecc_failed() {
      return Err(WriteError::Ecc);
    }
    if self.wp_violation() {
      return Err(WriteError::WriteProtect);
    }
    if self.erase_param() {
      return Err(WriteError::EraseParam);
    }
    if self.out_of_range_or_csd_overwrite() {
      return Err(WriteError::OutOfRangeOrCsdOverwrite);
    }
    Ok(())
  }
}

impl Cond {
  #[cfg_attr(feature = "cargo-clippy", allow(cast_ptr_alignment))]
  pub(crate) fn from_buf(buf: &[u8]) -> Self {
    assert!(buf.len() >= 4);
    unsafe { Cond(u32::from_be(*(buf.as_ptr() as *const u32))) }
  }
}

impl ReadResponse {
  /// Creates a new `ReadResponse` if `value` is a valid sequence of bits.
  pub fn try_from(value: u8) -> Option<Self> {
    let value = ReadResponse(value);
    if value.is_ok() || value.is_err() {
      Some(value)
    } else {
      None
    }
  }

  /// Checks for read errors.
  pub fn errck(self) -> Result<(), ReadError> {
    if self.is_err() {
      if self.error() {
        return Err(ReadError::Unknown);
      }
      if self.cc_error() {
        return Err(ReadError::Card);
      }
      if self.card_ecc_failed() {
        return Err(ReadError::Ecc);
      }
      if self.out_of_range() {
        return Err(ReadError::OutOfRange);
      }
    }
    Ok(())
  }

  /// Returns `true` if this is a data error token.
  pub fn is_err(self) -> bool {
    self.bits() >> 4 == 0
  }

  /// Returns `true` if this is a start block token.
  pub fn is_ok(self) -> bool {
    self.bits() == BLOCK_START
  }
}

impl WriteResponse {
  /// Checks for write errors.
  pub fn errck(self) -> Result<(), WriteError> {
    match self.0 >> 1 & 0b1110 {
      0b010 => Ok(()),
      0b101 => Err(WriteError::Crc),
      _ => Err(WriteError::Unknown),
    }
  }
}