use alloc::boxed::Box;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::fmt;
use crate::bus::spi::{
BitOrder, ChipSelect, Format, MAX_CHIP_SELECTS, Mode, SlavePins, SpiSlave, buses,
pin as spi_pin,
};
use crate::core::device::{Device, DeviceClass, PropertySpec, RealizeCtx, ResetKind, SinkPin};
use crate::core::error::{Error, Result};
use crate::core::props::{Props, ValueKind};
use crate::core::space::RamStore;
use crate::core::state::{ChunkReader, ChunkWriter, Sink, Source};
use crate::core::sync::{LockRank, Mutex};
use crate::core::wire::{WireId, WireSource};
use crate::machine::realize::Instance;
use crate::machine::validate::{ClassSchema, PortDir, PropSchema};
pub const CLASS_NAME: &str = "flash.spinor";
const STATE_VERSION: u32 = 1;
pub const WINBOND: u8 = 0xef;
pub const TYPE_W25Q: u8 = 0x40;
pub const PAGE: u64 = 256;
pub const SECTOR: u64 = 4 * 1024;
pub const HALF_BLOCK: u64 = 32 * 1024;
pub const BLOCK: u64 = 64 * 1024;
pub const DEFAULT_SIZE: u64 = 16 * 1024 * 1024;
const CMD_WRITE_ENABLE: u8 = 0x06;
const CMD_VOLATILE_SR_WRITE_ENABLE: u8 = 0x50;
const CMD_WRITE_DISABLE: u8 = 0x04;
const CMD_READ_STATUS1: u8 = 0x05;
const CMD_READ_STATUS2: u8 = 0x35;
const CMD_READ_STATUS3: u8 = 0x15;
const CMD_WRITE_STATUS1: u8 = 0x01;
const CMD_WRITE_STATUS2: u8 = 0x31;
const CMD_WRITE_STATUS3: u8 = 0x11;
const CMD_READ: u8 = 0x03;
const CMD_FAST_READ: u8 = 0x0b;
const CMD_FAST_READ_DUAL_OUT: u8 = 0x3b;
const CMD_FAST_READ_DUAL_IO: u8 = 0xbb;
const CMD_FAST_READ_QUAD_OUT: u8 = 0x6b;
const CMD_FAST_READ_QUAD_IO: u8 = 0xeb;
const CMD_PAGE_PROGRAM: u8 = 0x02;
const CMD_QUAD_PAGE_PROGRAM: u8 = 0x32;
const CMD_SECTOR_ERASE: u8 = 0x20;
const CMD_HALF_BLOCK_ERASE: u8 = 0x52;
const CMD_BLOCK_ERASE: u8 = 0xd8;
const CMD_CHIP_ERASE: u8 = 0xc7;
const CMD_CHIP_ERASE_ALT: u8 = 0x60;
const CMD_JEDEC_ID: u8 = 0x9f;
const CMD_DEVICE_ID: u8 = 0x90;
const CMD_DEVICE_ID_DUAL: u8 = 0x92;
const CMD_DEVICE_ID_QUAD: u8 = 0x94;
const CMD_UNIQUE_ID: u8 = 0x4b;
const CMD_READ_SFDP: u8 = 0x5a;
const CMD_POWER_DOWN: u8 = 0xb9;
const CMD_RELEASE_POWER_DOWN: u8 = 0xab;
const CMD_ENABLE_RESET: u8 = 0x66;
const CMD_RESET: u8 = 0x99;
const CMD_ENTER_4B: u8 = 0xb7;
const CMD_EXIT_4B: u8 = 0xe9;
const SR1_BUSY: u8 = 1 << 0;
const SR1_WEL: u8 = 1 << 1;
const SR1_BP_SHIFT: u32 = 2;
const SR1_BP_MASK: u8 = 0x07;
const SR1_TB: u8 = 1 << 5;
const SR1_SEC: u8 = 1 << 6;
const SR1_SRP: u8 = 1 << 7;
const SR1_WRITABLE: u8 = SR1_BP_MASK << SR1_BP_SHIFT | SR1_TB | SR1_SEC | SR1_SRP;
const SR2_SRL: u8 = 1 << 0;
const SR2_QE: u8 = 1 << 1;
const SR2_CMP: u8 = 1 << 6;
const SR2_WRITABLE: u8 = 0x7f;
const SR3_ADS: u8 = 1 << 0;
const SR3_ADP: u8 = 1 << 1;
const SR3_WPS: u8 = 1 << 2;
const SR3_DRV: u8 = 3 << 5;
const SR3_WRITABLE: u8 = SR3_DRV | SR3_WPS;
const IDLE_BYTE: u8 = 0xff;
fn config(message: String) -> Error {
Error::Config {
at: CLASS_NAME.to_string(),
message,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Phase {
Opcode,
Address,
Dummy,
Data,
Ignored,
}
impl Phase {
const fn tag(self) -> u8 {
match self {
Phase::Opcode => 0,
Phase::Address => 1,
Phase::Dummy => 2,
Phase::Data => 3,
Phase::Ignored => 4,
}
}
fn from_tag(tag: u8) -> Result<Phase> {
match tag {
0 => Ok(Phase::Opcode),
1 => Ok(Phase::Address),
2 => Ok(Phase::Dummy),
3 => Ok(Phase::Data),
4 => Ok(Phase::Ignored),
other => Err(Error::State(format!("{other} is not an SPI flash phase"))),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Stream {
None,
Array,
Status(u8),
JedecId,
DeviceId,
UniqueId,
Sfdp,
Program,
StatusIn,
JustDeviceId,
}
impl Stream {
const fn tag(self) -> u8 {
match self {
Stream::None => 0,
Stream::Array => 1,
Stream::Status(_) => 2,
Stream::JedecId => 3,
Stream::DeviceId => 4,
Stream::UniqueId => 5,
Stream::Sfdp => 6,
Stream::Program => 7,
Stream::StatusIn => 8,
Stream::JustDeviceId => 9,
}
}
fn from_tag(tag: u8, which: u8) -> Result<Stream> {
match tag {
0 => Ok(Stream::None),
1 => Ok(Stream::Array),
2 => Ok(Stream::Status(which)),
3 => Ok(Stream::JedecId),
4 => Ok(Stream::DeviceId),
5 => Ok(Stream::UniqueId),
6 => Ok(Stream::Sfdp),
7 => Ok(Stream::Program),
8 => Ok(Stream::StatusIn),
9 => Ok(Stream::JustDeviceId),
other => Err(Error::State(format!("{other} is not an SPI flash stream"))),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Staged {
None,
WriteEnable,
VolatileWriteEnable,
WriteDisable,
Program {
base: u64,
},
Erase {
base: u64,
span: u64,
},
WriteStatus {
first: u8,
},
PowerDown,
ReleasePowerDown,
EnableReset,
Reset,
AddressBytes {
bytes: u8,
},
}
impl Staged {
const fn tag(self) -> u8 {
match self {
Staged::None => 0,
Staged::WriteEnable => 1,
Staged::VolatileWriteEnable => 2,
Staged::WriteDisable => 3,
Staged::Program { .. } => 4,
Staged::Erase { .. } => 5,
Staged::WriteStatus { .. } => 6,
Staged::PowerDown => 7,
Staged::ReleasePowerDown => 8,
Staged::EnableReset => 9,
Staged::Reset => 10,
Staged::AddressBytes { .. } => 11,
}
}
}
#[derive(Clone)]
struct PageLatch {
data: [u8; PAGE as usize],
touched: [bool; PAGE as usize],
at: u8,
latched: u32,
}
impl fmt::Debug for PageLatch {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PageLatch")
.field("at", &self.at)
.field("latched", &self.latched)
.finish_non_exhaustive()
}
}
impl PageLatch {
fn new() -> PageLatch {
PageLatch {
data: [0xff; PAGE as usize],
touched: [false; PAGE as usize],
at: 0,
latched: 0,
}
}
fn reset(&mut self, start: u8) {
self.data = [0xff; PAGE as usize];
self.touched = [false; PAGE as usize];
self.at = start;
self.latched = 0;
}
fn push(&mut self, byte: u8) {
let at = usize::from(self.at);
self.data[at] &= byte;
self.touched[at] = true;
self.at = self.at.wrapping_add(1);
self.latched = self.latched.saturating_add(1);
}
}
#[derive(Debug, Clone)]
struct State {
phase: Phase,
stream: Stream,
out: u8,
addr: u64,
got: u8,
dummy: u8,
span: u64,
count: u64,
sr1: u8,
sr2: u8,
sr3: u8,
powered_down: bool,
reset_armed: bool,
staged: Staged,
sr_in: [u8; 3],
sr_in_len: u8,
page: PageLatch,
}
impl State {
fn new(addr_bytes: u8, qe_fixed: bool) -> State {
State {
phase: Phase::Opcode,
stream: Stream::None,
out: IDLE_BYTE,
addr: 0,
got: 0,
dummy: 0,
span: 0,
count: 0,
sr1: 0,
sr2: if qe_fixed { SR2_QE } else { 0 },
sr3: SR3_DRV
| if addr_bytes == 4 {
SR3_ADS | SR3_ADP
} else {
0
},
powered_down: false,
reset_armed: false,
staged: Staged::None,
sr_in: [0; 3],
sr_in_len: 0,
page: PageLatch::new(),
}
}
const fn addr_bytes(&self) -> u8 {
if self.sr3 & SR3_ADS != 0 { 4 } else { 3 }
}
fn begin_frame(&mut self) {
self.phase = Phase::Opcode;
self.stream = Stream::None;
self.addr = 0;
self.got = 0;
self.dummy = 0;
self.span = 0;
self.count = 0;
self.staged = Staged::None;
self.sr_in_len = 0;
self.out = IDLE_BYTE;
}
}
struct Shared {
array: Arc<RamStore>,
size: u64,
format: Format,
jedec: [u8; 3],
device_id: u8,
unique_id: u64,
addr_bytes: u8,
qe_fixed: bool,
write_protect: bool,
state: Mutex<State>,
}
impl fmt::Debug for Shared {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("SpiNor");
s.field("size", &self.size).field("jedec", &self.jedec);
match self.state.try_lock() {
Some(state) => s.field("state", &*state).finish(),
None => s.field("state", &"<in use>").finish(),
}
}
}
impl Shared {
fn byte(&self, addr: u64) -> u8 {
self.array.read_u8(addr % self.size.max(1)).unwrap_or(0xff)
}
fn protection(&self, state: &State) -> Option<(u64, u64)> {
let bp = (state.sr1 >> SR1_BP_SHIFT) & SR1_BP_MASK;
let sec = state.sr1 & SR1_SEC != 0;
let bottom = state.sr1 & SR1_TB != 0;
let complement = state.sr2 & SR2_CMP != 0;
let len = if bp == 0 {
0
} else if sec {
(SECTOR << (bp - 1)).min(BLOCK).min(self.size)
} else {
(self.size >> (7 - u32::from(bp))).min(self.size)
};
let plain = if len == 0 {
None
} else if bottom {
Some((0, len))
} else {
Some((self.size - len, len))
};
if !complement {
return plain;
}
match plain {
None => Some((0, self.size)),
Some((_, len)) if len >= self.size => None,
Some((0, len)) => Some((len, self.size - len)),
Some((base, _)) => Some((0, base)),
}
}
fn writable(&self, state: &State, base: u64, span: u64) -> bool {
match self.protection(state) {
None => true,
Some((p_base, p_len)) => base >= p_base + p_len || base + span <= p_base,
}
}
fn present(&self, state: &mut State) {
state.out = match state.stream {
Stream::Array => self.byte(state.addr),
Stream::Status(1) => state.sr1 & !SR1_BUSY,
Stream::Status(2) => state.sr2,
Stream::Status(3) => state.sr3,
Stream::Status(_) => IDLE_BYTE,
Stream::JedecId => self.jedec[(state.count % 3) as usize],
Stream::DeviceId => {
if state.count.is_multiple_of(2) {
self.jedec[0]
} else {
self.device_id
}
}
Stream::UniqueId => (self.unique_id >> (8 * (7 - (state.count % 8)))) as u8,
Stream::Sfdp => IDLE_BYTE,
Stream::JustDeviceId => self.device_id,
Stream::None | Stream::Program | Stream::StatusIn => IDLE_BYTE,
};
}
fn step(&self, state: &mut State, mosi: u8) {
match state.phase {
Phase::Opcode => self.opcode(state, mosi),
Phase::Address => {
state.addr = (state.addr << 8) | u64::from(mosi);
state.got += 1;
if state.got >= state.addr_bytes() {
self.addressed(state);
}
}
Phase::Dummy => {
let _ = mosi;
if state.dummy > 0 {
state.dummy -= 1;
}
if state.dummy == 0 {
state.phase = Phase::Data;
state.count = 0;
self.present(state);
}
}
Phase::Data => match state.stream {
Stream::Program => {
state.page.push(mosi);
if !matches!(state.staged, Staged::Program { .. }) {
state.staged = Staged::Program {
base: state.addr & !(PAGE - 1),
};
}
}
Stream::StatusIn => {
let at = usize::from(state.sr_in_len);
if at < state.sr_in.len() {
state.sr_in[at] = mosi;
state.sr_in_len += 1;
}
}
_ => {
state.count = state.count.saturating_add(1);
if matches!(state.stream, Stream::Array | Stream::Sfdp) {
state.addr = (state.addr + 1) % self.size.max(1);
}
self.present(state);
}
},
Phase::Ignored => {}
}
}
fn opcode(&self, state: &mut State, cmd: u8) {
if state.powered_down && cmd != CMD_RELEASE_POWER_DOWN {
state.phase = Phase::Ignored;
return;
}
let armed = state.reset_armed;
state.reset_armed = false;
let writes_enabled = state.sr1 & SR1_WEL != 0;
match cmd {
CMD_WRITE_ENABLE => {
state.staged = Staged::WriteEnable;
state.phase = Phase::Ignored;
}
CMD_VOLATILE_SR_WRITE_ENABLE => {
state.staged = Staged::VolatileWriteEnable;
state.phase = Phase::Ignored;
}
CMD_WRITE_DISABLE => {
state.staged = Staged::WriteDisable;
state.phase = Phase::Ignored;
}
CMD_READ_STATUS1 | CMD_READ_STATUS2 | CMD_READ_STATUS3 => {
state.stream = Stream::Status(match cmd {
CMD_READ_STATUS1 => 1,
CMD_READ_STATUS2 => 2,
_ => 3,
});
state.phase = Phase::Data;
state.count = 0;
self.present(state);
}
CMD_WRITE_STATUS1 | CMD_WRITE_STATUS2 | CMD_WRITE_STATUS3 => {
if !writes_enabled {
state.phase = Phase::Ignored;
return;
}
state.stream = Stream::StatusIn;
state.phase = Phase::Data;
state.sr_in_len = 0;
state.staged = Staged::WriteStatus {
first: match cmd {
CMD_WRITE_STATUS1 => 1,
CMD_WRITE_STATUS2 => 2,
_ => 3,
},
};
}
CMD_READ => start_read(state, 0),
CMD_FAST_READ | CMD_FAST_READ_DUAL_OUT => start_read(state, 1),
CMD_FAST_READ_QUAD_OUT => {
if state.sr2 & SR2_QE == 0 {
state.phase = Phase::Ignored;
} else {
start_read(state, 1);
}
}
CMD_FAST_READ_DUAL_IO => start_read(state, 1),
CMD_FAST_READ_QUAD_IO => {
if state.sr2 & SR2_QE == 0 {
state.phase = Phase::Ignored;
} else {
start_read(state, 3);
}
}
CMD_PAGE_PROGRAM | CMD_QUAD_PAGE_PROGRAM => {
if !writes_enabled {
state.phase = Phase::Ignored;
return;
}
if cmd == CMD_QUAD_PAGE_PROGRAM && state.sr2 & SR2_QE == 0 {
state.phase = Phase::Ignored;
return;
}
state.stream = Stream::Program;
state.phase = Phase::Address;
}
CMD_SECTOR_ERASE | CMD_HALF_BLOCK_ERASE | CMD_BLOCK_ERASE => {
if !writes_enabled {
state.phase = Phase::Ignored;
return;
}
state.stream = Stream::None;
state.phase = Phase::Address;
state.span = erase_span(cmd).unwrap_or(SECTOR);
}
CMD_CHIP_ERASE | CMD_CHIP_ERASE_ALT => {
if !writes_enabled {
state.phase = Phase::Ignored;
return;
}
state.staged = Staged::Erase {
base: 0,
span: self.size,
};
state.phase = Phase::Ignored;
}
CMD_JEDEC_ID => {
state.stream = Stream::JedecId;
state.phase = Phase::Data;
state.count = 0;
self.present(state);
}
CMD_DEVICE_ID | CMD_DEVICE_ID_DUAL | CMD_DEVICE_ID_QUAD => {
state.stream = Stream::DeviceId;
state.phase = Phase::Address;
state.got = state.addr_bytes().saturating_sub(3);
}
CMD_UNIQUE_ID => {
state.stream = Stream::UniqueId;
state.phase = Phase::Dummy;
state.dummy = 4;
}
CMD_READ_SFDP => {
state.stream = Stream::Sfdp;
state.phase = Phase::Address;
state.got = state.addr_bytes().saturating_sub(3);
state.dummy = 1;
}
CMD_POWER_DOWN => {
state.staged = Staged::PowerDown;
state.phase = Phase::Ignored;
}
CMD_RELEASE_POWER_DOWN => {
state.staged = Staged::ReleasePowerDown;
state.stream = Stream::JustDeviceId;
state.phase = Phase::Dummy;
state.dummy = 3;
}
CMD_ENABLE_RESET => {
state.staged = Staged::EnableReset;
state.phase = Phase::Ignored;
}
CMD_RESET => {
state.staged = if armed { Staged::Reset } else { Staged::None };
state.phase = Phase::Ignored;
}
CMD_ENTER_4B | CMD_EXIT_4B => {
state.staged = Staged::AddressBytes {
bytes: if cmd == CMD_ENTER_4B { 4 } else { 3 },
};
state.phase = Phase::Ignored;
}
_ => state.phase = Phase::Ignored,
}
}
fn addressed(&self, state: &mut State) {
state.addr %= self.size.max(1);
match state.stream {
Stream::Array | Stream::Sfdp => {
if state.dummy > 0 {
state.phase = Phase::Dummy;
} else {
state.phase = Phase::Data;
state.count = 0;
self.present(state);
}
}
Stream::DeviceId => {
state.phase = Phase::Data;
state.count = 0;
self.present(state);
}
Stream::Program => {
state.phase = Phase::Data;
state.page.reset((state.addr % PAGE) as u8);
}
_ => {
if state.span > 0 {
state.staged = Staged::Erase {
base: state.addr & !(state.span - 1),
span: state.span,
};
}
state.phase = Phase::Ignored;
}
}
}
}
impl SpiSlave for Shared {
fn format(&self) -> Format {
self.format
}
fn select(&self, selected: bool) {
let mut state = self.state.lock();
if selected {
state.begin_frame();
self.present(&mut state);
return;
}
let staged = core::mem::replace(&mut state.staged, Staged::None);
self.commit(&mut state, staged);
state.phase = Phase::Opcode;
state.stream = Stream::None;
state.out = IDLE_BYTE;
}
fn transfer(&self, mosi: u32) -> u32 {
let mut state = self.state.lock();
let presented = state.out;
self.step(&mut state, mosi as u8);
u32::from(presented)
}
fn peek(&self) -> u32 {
u32::from(self.state.lock().out)
}
}
impl Shared {
fn commit(&self, state: &mut State, staged: Staged) {
match staged {
Staged::None => {}
Staged::WriteEnable | Staged::VolatileWriteEnable => state.sr1 |= SR1_WEL,
Staged::WriteDisable => state.sr1 &= !SR1_WEL,
Staged::Program { base } => {
if state.page.latched > 0 && self.writable(state, base, PAGE) {
for i in 0..PAGE as usize {
if !state.page.touched[i] {
continue;
}
let at = base + i as u64;
let Ok(old) = self.array.read_u8(at) else {
continue;
};
let _ = self.array.write_u8(at, old & state.page.data[i]);
}
}
state.sr1 &= !SR1_WEL;
state.page.reset(0);
}
Staged::Erase { base, span } => {
if self.writable(state, base, span) {
let _ = self.array.fill(base, span.min(self.size - base), 0xff);
}
state.sr1 &= !SR1_WEL;
}
Staged::WriteStatus { first } => {
let locked =
state.sr2 & SR2_SRL != 0 || (state.sr1 & SR1_SRP != 0 && self.write_protect);
if locked {
state.sr1 &= !SR1_WEL;
return;
}
for i in 0..usize::from(state.sr_in_len) {
let value = state.sr_in[i];
match first + i as u8 {
1 => state.sr1 = (state.sr1 & !SR1_WRITABLE) | (value & SR1_WRITABLE),
2 => state.sr2 = (state.sr2 & !SR2_WRITABLE) | (value & SR2_WRITABLE),
3 => state.sr3 = (state.sr3 & !SR3_WRITABLE) | (value & SR3_WRITABLE),
_ => {}
}
}
if self.qe_fixed {
state.sr2 |= SR2_QE;
}
state.sr1 &= !SR1_WEL;
}
Staged::PowerDown => state.powered_down = true,
Staged::ReleasePowerDown => state.powered_down = false,
Staged::EnableReset => state.reset_armed = true,
Staged::Reset => {
let (sr1, sr2, sr3) = (state.sr1 & SR1_WRITABLE, state.sr2, state.sr3);
*state = State::new(self.addr_bytes, self.qe_fixed);
state.sr1 = sr1;
state.sr2 = sr2;
state.sr3 |= sr3 & SR3_WRITABLE;
}
Staged::AddressBytes { bytes } => {
if bytes == 4 {
state.sr3 |= SR3_ADS;
} else {
state.sr3 &= !SR3_ADS;
}
}
}
}
}
fn start_read(state: &mut State, dummy: u8) {
state.stream = Stream::Array;
state.phase = Phase::Address;
state.dummy = dummy;
}
const fn erase_span(cmd: u8) -> Option<u64> {
match cmd {
CMD_SECTOR_ERASE => Some(SECTOR),
CMD_HALF_BLOCK_ERASE => Some(HALF_BLOCK),
CMD_BLOCK_ERASE => Some(BLOCK),
_ => None,
}
}
#[derive(Debug)]
pub struct SpiNor {
shared: Arc<Shared>,
pins: Arc<SlavePins>,
}
impl SpiNor {
pub fn new(props: &Props) -> Result<SpiNor> {
let mut r = props.reader();
let size = r.or_size("size", DEFAULT_SIZE)?;
let image = r
.optional_media("image")?
.map(crate::core::props::Media::to_bytes);
let bus_name = r.optional_str("bus")?.map(String::from);
let cs = r.or_range("cs", 0u64, 0..=(MAX_CHIP_SELECTS as u64 - 1))?;
let mode = r.or_range("mode", 0u64, 0..=3)?;
let manufacturer = r.or_range("manufacturer", u64::from(WINBOND), 0..=0xff)?;
let kind = r.or_range("type", u64::from(TYPE_W25Q), 0..=0xff)?;
let unique_id: u64 = r.or("unique-id", 0)?;
let addr_bytes = r.or_range("address-bytes", 3u64, 3..=4)?;
let write_protect = r.or("readonly", false)?;
let capacity = r.or_range("capacity", log2(size), 0..=0xff)?;
let device_id = r.or_range("device", capacity.saturating_sub(1), 0..=0xff)?;
r.finish()?;
if size < BLOCK || !size.is_power_of_two() {
return Err(config(format!(
"a serial flash of {size} byte(s): the capacity byte `9Fh` reports is a \
base-two logarithm, so the part is a power of two of at least one {BLOCK}-byte \
block"
)));
}
if usize::try_from(size).is_err() {
return Err(config(format!(
"a flash of {size} byte(s) is larger than this host's address space"
)));
}
if mode != 0 && mode != 3 {
return Err(config(format!(
"`mode` is {mode}; a W25Q samples on the rising edge of a clock that idles \
either low or high, which is SPI mode 0 or mode 3"
)));
}
if let Some(image) = &image
&& image.len() as u64 > size
{
return Err(config(format!(
"the bound image is {} byte(s) and the flash is {size}",
image.len()
)));
}
let array = Arc::new(RamStore::new(size));
array
.fill(0, size, 0xff)
.map_err(|_| config(String::from("the flash array could not be erased")))?;
if let Some(image) = image {
array
.write_at(0, &image)
.map_err(|_| config(String::from("the flash refused its initial image")))?;
}
let addr_bytes = addr_bytes as u8;
let qe_fixed = kind as u8 == TYPE_W25Q;
let shared = Arc::new(Shared {
array,
size,
format: Format::new(
if mode == 3 { Mode::Mode3 } else { Mode::Mode0 },
8,
BitOrder::MsbFirst,
),
jedec: [manufacturer as u8, kind as u8, capacity as u8],
device_id: device_id as u8,
unique_id,
addr_bytes,
qe_fixed,
write_protect,
state: Mutex::with_rank(LockRank::DEVICE, State::new(addr_bytes, qe_fixed)),
});
let pins = Arc::new(SlavePins::new(Arc::clone(&shared) as Arc<dyn SpiSlave>));
let part = SpiNor { shared, pins };
if let Some(name) = bus_name {
let bus = buses::attach(props, &name)?;
bus.attach(
ChipSelect(cs as u8),
Arc::clone(&part.shared) as Arc<dyn SpiSlave>,
)?;
}
Ok(part)
}
#[must_use]
pub fn size(&self) -> u64 {
self.shared.size
}
#[must_use]
pub fn jedec_id(&self) -> [u8; 3] {
self.shared.jedec
}
#[must_use]
pub fn pins(&self) -> &Arc<SlavePins> {
&self.pins
}
#[must_use]
pub fn slave(&self) -> Arc<dyn SpiSlave> {
Arc::clone(&self.shared) as Arc<dyn SpiSlave>
}
pub fn read_contents(&self, offset: u64, dst: &mut [u8]) -> Result<()> {
self.shared
.array
.read_at(offset, dst)
.map_err(|_| Error::State(format!("{offset:#x} is outside this flash")))
}
#[must_use]
pub fn contents(&self) -> Vec<u8> {
let mut out = alloc::vec![0u8; self.shared.size as usize];
let _ = self.shared.array.read_at(0, &mut out);
out
}
pub fn load_image(&self, offset: u64, bytes: &[u8]) -> Result<()> {
self.shared.array.write_at(offset, bytes).map_err(|_| {
config(format!(
"an image of {} byte(s) at {offset:#x} does not fit in a flash of {}",
bytes.len(),
self.shared.size
))
})
}
#[must_use]
pub fn status(&self, which: u8) -> u8 {
let state = self.shared.state.lock();
match which {
1 => state.sr1,
2 => state.sr2,
3 => state.sr3,
_ => 0,
}
}
}
fn log2(value: u64) -> u64 {
u64::from(63 - value.max(1).leading_zeros())
}
impl Device for SpiNor {
fn class(&self) -> &'static DeviceClass {
&CLASS
}
fn realize(&self, _ctx: &mut RealizeCtx<'_>) -> Result<()> {
Ok(())
}
fn reset(&self, _kind: ResetKind) {
{
let mut state = self.shared.state.lock();
*state = State::new(self.shared.addr_bytes, self.shared.qe_fixed);
}
self.pins.reset();
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
w.write_bytes(&self.contents())?;
let state = self.shared.state.lock();
w.write_u8(state.phase.tag())?;
w.write_u8(state.stream.tag())?;
w.write_u8(match state.stream {
Stream::Status(n) => n,
_ => 0,
})?;
w.write_u8(state.out)?;
w.write_u64(state.addr)?;
w.write_u8(state.got)?;
w.write_u8(state.dummy)?;
w.write_u64(state.span)?;
w.write_u64(state.count)?;
w.write_u8(state.sr1)?;
w.write_u8(state.sr2)?;
w.write_u8(state.sr3)?;
w.write_bool(state.powered_down)?;
w.write_bool(state.reset_armed)?;
w.write_u8(state.staged.tag())?;
let (a, b) = match state.staged {
Staged::Program { base } => (base, 0),
Staged::Erase { base, span } => (base, span),
Staged::WriteStatus { first } => (u64::from(first), 0),
Staged::AddressBytes { bytes } => (u64::from(bytes), 0),
_ => (0, 0),
};
w.write_u64(a)?;
w.write_u64(b)?;
w.write_u8(state.sr_in_len)?;
for byte in state.sr_in {
w.write_u8(byte)?;
}
w.write_u8(state.page.at)?;
w.write_u32(state.page.latched)?;
w.write_bytes(&state.page.data)?;
let mut touched = alloc::vec![0u8; PAGE as usize / 8];
for (i, set) in state.page.touched.iter().enumerate() {
if *set {
touched[i / 8] |= 1 << (i % 8);
}
}
w.write_bytes(&touched)
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
let bytes: &[u8] = r.read_bytes()?;
if bytes.len() as u64 != self.shared.size {
return Err(Error::State(format!(
"snapshot has {} byte(s) of flash, this part has {}",
bytes.len(),
self.shared.size
)));
}
self.shared
.array
.write_at(0, bytes)
.map_err(|_| Error::State(String::from("the flash array refused the snapshot")))?;
let phase = Phase::from_tag(r.read_u8()?)?;
let stream_tag = r.read_u8()?;
let which = r.read_u8()?;
let stream = Stream::from_tag(stream_tag, which)?;
let out = r.read_u8()?;
let addr = r.read_u64()?;
let got = r.read_u8()?;
let dummy = r.read_u8()?;
let span = r.read_u64()?.min(self.shared.size);
let count = r.read_u64()?;
let sr1 = r.read_u8()?;
let sr2 = r.read_u8()?;
let sr3 = r.read_u8()?;
let powered_down = r.read_bool()?;
let reset_armed = r.read_bool()?;
let staged_tag = r.read_u8()?;
let a = r.read_u64()?;
let b = r.read_u64()?;
let staged = match staged_tag {
0 => Staged::None,
1 => Staged::WriteEnable,
2 => Staged::VolatileWriteEnable,
3 => Staged::WriteDisable,
4 => Staged::Program {
base: a % self.shared.size,
},
5 => Staged::Erase {
base: a % self.shared.size,
span: b.min(self.shared.size),
},
6 => Staged::WriteStatus {
first: (a as u8).clamp(1, 3),
},
7 => Staged::PowerDown,
8 => Staged::ReleasePowerDown,
9 => Staged::EnableReset,
10 => Staged::Reset,
11 => Staged::AddressBytes {
bytes: if a == 4 { 4 } else { 3 },
},
other => {
return Err(Error::State(format!(
"{other} is not an SPI flash staged command"
)));
}
};
let sr_in_len = r.read_u8()?.min(3);
let mut sr_in = [0u8; 3];
for byte in &mut sr_in {
*byte = r.read_u8()?;
}
let page_at = r.read_u8()?;
let latched = r.read_u32()?;
let data: &[u8] = r.read_bytes()?;
if data.len() != PAGE as usize {
return Err(Error::State(format!(
"a page-program latch of {} byte(s), and a page is {PAGE}",
data.len()
)));
}
let touched_bits: &[u8] = r.read_bytes()?;
if touched_bits.len() != PAGE as usize / 8 {
return Err(Error::State(String::from(
"the page-program latch's coverage map is the wrong length",
)));
}
let mut page = PageLatch::new();
page.at = page_at;
page.latched = latched;
page.data.copy_from_slice(data);
for i in 0..PAGE as usize {
page.touched[i] = touched_bits[i / 8] & (1 << (i % 8)) != 0;
}
let mut state = self.shared.state.lock();
*state = State {
phase,
stream,
out,
addr: addr % self.shared.size,
got,
dummy,
span,
count,
sr1,
sr2,
sr3,
powered_down,
reset_armed,
staged,
sr_in,
sr_in_len,
page,
};
Ok(())
}
fn sink(&self, port: &str, _sources: &[WireId]) -> Option<SinkPin> {
let line = match port {
spi_pin::SCK_NAME => spi_pin::SCK,
spi_pin::MOSI_NAME => spi_pin::MOSI,
spi_pin::CS_NAME => spi_pin::CS,
_ => return None,
};
Some(SinkPin {
sink: self.pins.sink(line),
line,
})
}
fn connect(&self, port: &str, source: WireSource) -> Result<()> {
if port != spi_pin::MISO_NAME {
return Err(Error::Config {
at: String::from(port),
message: format!("a serial flash drives only `{}`", spi_pin::MISO_NAME),
});
}
self.pins.connect_miso(source);
Ok(())
}
fn announce(&self, port: &str) {
if port == spi_pin::MISO_NAME {
self.pins.publish_miso();
}
}
}
impl Instance for SpiNor {}
pub static CLASS: DeviceClass = DeviceClass {
name: CLASS_NAME,
version: STATE_VERSION,
summary: "Winbond W25Q-class SPI NOR flash: JEDEC id, fast/dual/quad reads, page program, \
sector and block erase, the three status registers",
properties: &[
PropertySpec {
name: "size",
kind: ValueKind::Size,
required: false,
summary: "how many bytes the part holds, a power of two (default 16M, a W25Q128)",
},
PropertySpec {
name: "image",
kind: ValueKind::Media,
required: false,
summary: "the media slot holding the initial contents; the rest stays erased",
},
PropertySpec {
name: "bus",
kind: ValueKind::Str,
required: false,
summary: "the named SPI bus to attach to, for a transactional link",
},
PropertySpec {
name: "cs",
kind: ValueKind::Uint,
required: false,
summary: "which chip select on that bus (default 0)",
},
PropertySpec {
name: "mode",
kind: ValueKind::Uint,
required: false,
summary: "SPI mode 0 or 3; the part accepts both and the fabric names one",
},
PropertySpec {
name: "manufacturer",
kind: ValueKind::Uint,
required: false,
summary: "the JEP106 manufacturer byte `9Fh` returns (default 0xef, Winbond)",
},
PropertySpec {
name: "type",
kind: ValueKind::Uint,
required: false,
summary: "the memory-type byte `9Fh` returns (default 0x40, W25Q in standard SPI)",
},
PropertySpec {
name: "capacity",
kind: ValueKind::Uint,
required: false,
summary: "the capacity byte `9Fh` returns (default log2 of `size`)",
},
PropertySpec {
name: "device",
kind: ValueKind::Uint,
required: false,
summary: "the device id `90h` and `ABh` return (default one less than `capacity`)",
},
PropertySpec {
name: "unique-id",
kind: ValueKind::Uint,
required: false,
summary: "the 64-bit factory serial `4Bh` reports",
},
PropertySpec {
name: "address-bytes",
kind: ValueKind::Uint,
required: false,
summary: "3 or 4; which mode the part powers up in (default 3)",
},
PropertySpec {
name: "readonly",
kind: ValueKind::Bool,
required: false,
summary: "hold WP# low, so a status register with SRP0 set cannot be changed",
},
],
construct: |props| Ok(Box::new(SpiNor::new(props)?)),
};
pub fn register(registry: &mut crate::core::Registry) -> Result<()> {
registry.add(&CLASS)
}
pub fn bind(bindings: &mut crate::machine::Bindings) -> Result<()> {
bindings.bind(CLASS_NAME, |props| Ok(Arc::new(SpiNor::new(props)?)))
}
#[must_use]
pub fn schema() -> ClassSchema {
ClassSchema::new(CLASS_NAME)
.prop(PropSchema::new("size", ValueKind::Size))
.prop(PropSchema::new("image", ValueKind::Media))
.prop(PropSchema::new("bus", ValueKind::Str))
.prop(PropSchema::new("cs", ValueKind::Uint).range(0, MAX_CHIP_SELECTS as u64 - 1))
.prop(PropSchema::new("mode", ValueKind::Uint).range(0, 3))
.prop(PropSchema::new("manufacturer", ValueKind::Uint).range(0, 0xff))
.prop(PropSchema::new("type", ValueKind::Uint).range(0, 0xff))
.prop(PropSchema::new("capacity", ValueKind::Uint).range(0, 0xff))
.prop(PropSchema::new("device", ValueKind::Uint).range(0, 0xff))
.prop(PropSchema::new("unique-id", ValueKind::Uint))
.prop(PropSchema::new("address-bytes", ValueKind::Uint).range(3, 4))
.prop(PropSchema::new("readonly", ValueKind::Bool))
.port(spi_pin::SCK_NAME, PortDir::In)
.port(spi_pin::MOSI_NAME, PortDir::In)
.port(spi_pin::CS_NAME, PortDir::In)
.port(spi_pin::MISO_NAME, PortDir::Out)
}
#[cfg(test)]
mod tests;