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::core::device::{Device, DeviceClass, PropertySpec, RealizeCtx, ResetKind};
use crate::core::error::{BusError, Error, Result};
use crate::core::props::{Props, Value, ValueKind};
use crate::core::space::{
AccessConstraints, MemAttrs, MemOps, MemResult, RamStore, Region, RegionRef,
};
use crate::core::state::{ChunkReader, ChunkWriter, Sink, Source};
use crate::core::sync::{AtomicBool, LockRank, Mutex, Ordering};
use crate::core::value::{Endian, Width};
use crate::machine::realize::Instance;
use crate::machine::validate::{ClassSchema, PropSchema};
pub const CLASS_NAME: &str = "flash.cfi";
pub const DEFAULT_BLOCK: u64 = 256 * 1024;
pub const DEFAULT_WIDTH: u64 = 4;
pub const DEFAULT_INTERLEAVE: u64 = 2;
pub const DEFAULT_MANUFACTURER: u16 = 0x0089;
const CMD_READ_ARRAY: u8 = 0xff;
const CMD_READ_STATUS: u8 = 0x70;
const CMD_CLEAR_STATUS: u8 = 0x50;
const CMD_READ_ID: u8 = 0x90;
const CMD_READ_CFI: u8 = 0x98;
const CMD_PROGRAM: u8 = 0x40;
const CMD_PROGRAM_ALT: u8 = 0x10;
const CMD_ERASE: u8 = 0x20;
const CMD_BUFFER: u8 = 0xe8;
const CMD_LOCK_SETUP: u8 = 0x60;
const CMD_LOCK: u8 = 0x01;
const CMD_LOCK_DOWN: u8 = 0x2f;
const CMD_READ_CONFIG: u8 = 0x03;
const CMD_SUSPEND: u8 = 0xb0;
const CMD_CONFIRM: u8 = 0xd0;
const SR_READY: u16 = 0x80;
const SR_ERASE_ERROR: u16 = 0x20;
const SR_PROGRAM_ERROR: u16 = 0x10;
const SR_VPP_ERROR: u16 = 0x08;
const SR_LOCK_ERROR: u16 = 0x02;
const SR_RESET: u16 = SR_READY;
const BUFFER_BYTES_PER_DEVICE: u64 = 64;
const QUERY_REGIONS: usize = 0x2d;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BlockRegion {
pub count: u64,
pub size: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Geometry {
bus_width: u64,
interleave: u64,
regions: Vec<BlockRegion>,
size: u64,
}
impl Geometry {
pub fn uniform(size: u64, block: u64, bus_width: u64, interleave: u64) -> Result<Geometry> {
check_widths(bus_width, interleave)?;
if block == 0 || size == 0 || !size.is_multiple_of(block) {
return Err(config(format!(
"a flash of {size} byte(s) does not divide into blocks of {block}"
)));
}
Geometry::new(
alloc::vec![BlockRegion {
count: size / block,
size: block
}],
bus_width,
interleave,
)
}
pub fn new(regions: Vec<BlockRegion>, bus_width: u64, interleave: u64) -> Result<Geometry> {
check_widths(bus_width, interleave)?;
if regions.is_empty() {
return Err(config(String::from(
"a flash with no erase blocks cannot be erased, and so cannot be written",
)));
}
let mut size = 0u64;
for region in ®ions {
if region.count == 0 || region.size == 0 {
return Err(config(String::from(
"an erase-block region holds at least one block of at least one byte",
)));
}
if region.size % bus_width != 0 {
return Err(config(format!(
"an erase block of {} byte(s) is not a whole number of {bus_width}-byte \
bus words",
region.size
)));
}
let per_device = region.size / interleave;
if !per_device.is_multiple_of(256) {
return Err(config(format!(
"an erase block of {} bus byte(s) is {per_device} byte(s) in each of \
{interleave} device(s), and the CFI query states a block size in units \
of 256",
region.size
)));
}
size = size
.checked_add(region.count.checked_mul(region.size).ok_or_else(|| {
config(String::from(
"an erase-block region larger than the address space",
))
})?)
.ok_or_else(|| config(String::from("a flash larger than the address space")))?;
}
Ok(Geometry {
bus_width,
interleave,
regions,
size,
})
}
#[must_use]
pub fn size(&self) -> u64 {
self.size
}
#[must_use]
pub fn bus_width(&self) -> u64 {
self.bus_width
}
#[must_use]
pub fn interleave(&self) -> u64 {
self.interleave
}
#[must_use]
pub fn device_width(&self) -> u64 {
self.bus_width / self.interleave
}
#[must_use]
pub fn regions(&self) -> &[BlockRegion] {
&self.regions
}
#[must_use]
pub fn block_count(&self) -> u64 {
self.regions.iter().map(|r| r.count).sum()
}
#[must_use]
pub fn block_at(&self, offset: u64) -> Option<(u64, u64, u64)> {
let mut index = 0u64;
let mut base = 0u64;
for region in &self.regions {
let span = region.count * region.size;
if offset < base + span {
let within = (offset - base) / region.size;
return Some((index + within, base + within * region.size, region.size));
}
index += region.count;
base += span;
}
None
}
}
fn check_widths(bus_width: u64, interleave: u64) -> Result<()> {
if !matches!(bus_width, 1 | 2 | 4 | 8) {
return Err(config(format!(
"a bus is 1, 2, 4 or 8 bytes wide, not {bus_width}"
)));
}
if interleave == 0 || !bus_width.is_multiple_of(interleave) {
return Err(config(format!(
"{interleave} device(s) do not share a {bus_width}-byte bus evenly"
)));
}
let device_width = bus_width / interleave;
if !matches!(device_width, 1 | 2) {
return Err(config(format!(
"a CFI part is x8 or x16, so {interleave} of them on a {bus_width}-byte bus would \
each be {device_width} byte(s) wide"
)));
}
Ok(())
}
fn config(message: String) -> Error {
Error::Config {
at: CLASS_NAME.to_string(),
message,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Mode {
Array,
Status,
Id,
Cfi,
}
impl Mode {
const fn tag(self) -> u8 {
match self {
Mode::Array => 0,
Mode::Status => 1,
Mode::Id => 2,
Mode::Cfi => 3,
}
}
fn from_tag(tag: u8) -> Result<Mode> {
match tag {
0 => Ok(Mode::Array),
1 => Ok(Mode::Status),
2 => Ok(Mode::Id),
3 => Ok(Mode::Cfi),
other => Err(Error::State(format!("{other} is not a flash read mode"))),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum Pending {
None,
Program,
Erase,
Lock,
BufferCount,
BufferData {
left: u64,
},
BufferConfirm,
}
impl Pending {
const fn tag(&self) -> u8 {
match self {
Pending::None => 0,
Pending::Program => 1,
Pending::Erase => 2,
Pending::Lock => 3,
Pending::BufferCount => 4,
Pending::BufferData { .. } => 5,
Pending::BufferConfirm => 6,
}
}
}
#[derive(Debug, Clone)]
struct Chip {
mode: Mode,
pending: Pending,
status: u16,
buffer: Vec<(u64, u16)>,
locked: Vec<bool>,
locked_down: Vec<bool>,
}
impl Chip {
fn new(blocks: usize, locked: bool, down: bool) -> Chip {
Chip {
mode: Mode::Array,
pending: Pending::None,
status: SR_RESET,
buffer: Vec::new(),
locked: alloc::vec![locked; blocks],
locked_down: alloc::vec![down; blocks],
}
}
}
pub struct Array {
geom: Geometry,
array: Arc<RamStore>,
chips: Mutex<Vec<Chip>>,
all_array: AtomicBool,
query: Vec<u8>,
manufacturer: u16,
device_id: u16,
power_up_locked: bool,
read_only: bool,
}
impl fmt::Debug for Array {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Array")
.field("geometry", &self.geom)
.field("read_only", &self.read_only)
.finish_non_exhaustive()
}
}
impl Array {
pub fn new(geom: Geometry) -> Result<Array> {
Array::with_options(geom, DEFAULT_MANUFACTURER, 0, true, false)
}
pub fn with_options(
geom: Geometry,
manufacturer: u16,
device_id: u16,
power_up_locked: bool,
read_only: bool,
) -> Result<Array> {
if usize::try_from(geom.size()).is_err() {
return Err(config(format!(
"a flash of {} byte(s) is larger than this host's address space",
geom.size()
)));
}
let array = Arc::new(RamStore::new(geom.size()));
array.fill(0, geom.size(), 0xff).map_err(|_| {
config(String::from(
"the flash array could not be erased at construction",
))
})?;
let blocks = usize::try_from(geom.block_count())
.map_err(|_| config(String::from("more erase blocks than this host can index")))?;
let query = build_query(&geom);
let chips = (0..geom.interleave())
.map(|_| Chip::new(blocks, power_up_locked || read_only, read_only))
.collect();
Ok(Array {
geom,
array,
chips: Mutex::with_rank(LockRank::DEVICE, chips),
all_array: AtomicBool::new(true),
query,
manufacturer,
device_id,
power_up_locked,
read_only,
})
}
#[must_use]
pub fn geometry(&self) -> &Geometry {
&self.geom
}
pub fn read_contents(&self, offset: u64, dst: &mut [u8]) -> Result<()> {
self.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.array.len() as usize];
let _ = self.array.read_at(0, &mut out);
out
}
pub fn load_image(&self, offset: u64, bytes: &[u8]) -> Result<()> {
self.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.geom.size()
))
})
}
#[must_use]
pub fn is_reading_array(&self) -> bool {
self.all_array.load(Ordering::Relaxed)
}
pub fn reset(&self) {
let mut chips = self.chips.lock();
for chip in chips.iter_mut() {
chip.mode = Mode::Array;
chip.pending = Pending::None;
chip.status = SR_RESET;
chip.buffer.clear();
let locked = self.power_up_locked || self.read_only;
chip.locked.fill(locked);
chip.locked_down.fill(self.read_only);
}
self.all_array.store(true, Ordering::Relaxed);
}
#[must_use]
pub fn status(&self, lane: usize) -> Option<u16> {
self.chips.lock().get(lane).map(|c| c.status)
}
#[must_use]
pub fn is_locked(&self, lane: usize, block: u64) -> Option<bool> {
let chips = self.chips.lock();
let chip = chips.get(lane)?;
chip.locked.get(usize::try_from(block).ok()?).copied()
}
fn peek(&self, chips: &[Chip], offset: u64) -> MemResult<u8> {
let dw = self.geom.device_width();
let lane = ((offset / dw) % self.geom.interleave()) as usize;
let chip = &chips[lane];
let word = match chip.mode {
Mode::Array => return self.array.read_u8(offset),
Mode::Status => chip.status,
Mode::Id => self.identifier(chip, offset),
Mode::Cfi => self.query_word(offset),
};
Ok((word >> (8 * (offset % dw))) as u8)
}
fn identifier(&self, chip: &Chip, offset: u64) -> u16 {
let Some((block, base, _)) = self.geom.block_at(offset) else {
return 0;
};
match (offset - base) / self.geom.bus_width() {
0 => self.manufacturer,
1 => self.device_id,
2 => {
let block = block as usize;
u16::from(chip.locked.get(block).copied().unwrap_or(false))
| (u16::from(chip.locked_down.get(block).copied().unwrap_or(false)) << 1)
}
5 => 0,
_ => 0,
}
}
fn query_word(&self, offset: u64) -> u16 {
let index = offset / self.geom.bus_width();
usize::try_from(index)
.ok()
.and_then(|i| self.query.get(i))
.map_or(0, |b| u16::from(*b))
}
fn command(&self, chip: &mut Chip, offset: u64, value: u16) {
let cmd = (value & 0xff) as u8;
match core::mem::replace(&mut chip.pending, Pending::None) {
Pending::None => self.first_cycle(chip, cmd),
Pending::Program => {
self.program(chip, offset, value);
chip.mode = Mode::Status;
}
Pending::Erase => {
if cmd == CMD_CONFIRM {
self.erase(chip, offset);
} else {
chip.status |= SR_ERASE_ERROR | SR_PROGRAM_ERROR;
}
chip.mode = Mode::Status;
}
Pending::Lock => self.lock_cycle(chip, offset, cmd),
Pending::BufferCount => {
let words = u64::from(value) + 1;
if words > BUFFER_BYTES_PER_DEVICE / self.geom.device_width() {
chip.status |= SR_ERASE_ERROR | SR_PROGRAM_ERROR;
chip.mode = Mode::Status;
} else {
chip.buffer.clear();
chip.pending = Pending::BufferData { left: words };
}
}
Pending::BufferData { left } => {
chip.buffer.push((offset, value));
chip.pending = match left.saturating_sub(1) {
0 => Pending::BufferConfirm,
left => Pending::BufferData { left },
};
}
Pending::BufferConfirm => {
if cmd == CMD_CONFIRM {
for (at, word) in core::mem::take(&mut chip.buffer) {
self.program(chip, at, word);
}
} else {
chip.buffer.clear();
chip.status |= SR_ERASE_ERROR | SR_PROGRAM_ERROR;
}
chip.mode = Mode::Status;
}
}
}
fn first_cycle(&self, chip: &mut Chip, cmd: u8) {
match cmd {
CMD_READ_ARRAY => chip.mode = Mode::Array,
CMD_READ_STATUS => chip.mode = Mode::Status,
CMD_READ_ID => chip.mode = Mode::Id,
CMD_READ_CFI => chip.mode = Mode::Cfi,
CMD_CLEAR_STATUS => {
chip.status &= !(SR_ERASE_ERROR | SR_PROGRAM_ERROR | SR_VPP_ERROR | SR_LOCK_ERROR);
}
CMD_PROGRAM | CMD_PROGRAM_ALT => {
chip.pending = Pending::Program;
chip.mode = Mode::Status;
}
CMD_ERASE => {
chip.pending = Pending::Erase;
chip.mode = Mode::Status;
}
CMD_BUFFER => {
chip.buffer.clear();
chip.pending = Pending::BufferCount;
chip.mode = Mode::Status;
}
CMD_LOCK_SETUP => {
chip.pending = Pending::Lock;
chip.mode = Mode::Status;
}
CMD_SUSPEND | CMD_CONFIRM => chip.mode = Mode::Status,
_ => {
chip.status |= SR_ERASE_ERROR | SR_PROGRAM_ERROR;
chip.mode = Mode::Status;
}
}
}
fn lock_cycle(&self, chip: &mut Chip, offset: u64, cmd: u8) {
if cmd == CMD_READ_CONFIG {
chip.mode = Mode::Id;
return;
}
chip.mode = Mode::Status;
let Some((block, _, _)) = self.geom.block_at(offset) else {
chip.status |= SR_ERASE_ERROR | SR_PROGRAM_ERROR;
return;
};
let Ok(block) = usize::try_from(block) else {
chip.status |= SR_ERASE_ERROR | SR_PROGRAM_ERROR;
return;
};
match cmd {
CMD_LOCK => chip.locked[block] = true,
CMD_CONFIRM => {
if chip.locked_down[block] {
chip.status |= SR_LOCK_ERROR;
} else {
chip.locked[block] = false;
}
}
CMD_LOCK_DOWN => {
chip.locked[block] = true;
chip.locked_down[block] = true;
}
_ => chip.status |= SR_ERASE_ERROR | SR_PROGRAM_ERROR,
}
}
fn program(&self, chip: &mut Chip, offset: u64, value: u16) {
let Some((block, _, _)) = self.geom.block_at(offset) else {
chip.status |= SR_PROGRAM_ERROR;
return;
};
if usize::try_from(block).is_ok_and(|b| chip.locked.get(b).copied().unwrap_or(false)) {
chip.status |= SR_LOCK_ERROR | SR_PROGRAM_ERROR;
return;
}
for i in 0..self.geom.device_width() {
let at = offset + i;
let Ok(old) = self.array.read_u8(at) else {
chip.status |= SR_PROGRAM_ERROR;
return;
};
let new = old & (value >> (8 * i)) as u8;
if self.array.write_u8(at, new).is_err() {
chip.status |= SR_PROGRAM_ERROR;
return;
}
}
}
fn erase(&self, chip: &mut Chip, offset: u64) {
let Some((block, base, size)) = self.geom.block_at(offset) else {
chip.status |= SR_ERASE_ERROR;
return;
};
if usize::try_from(block).is_ok_and(|b| chip.locked.get(b).copied().unwrap_or(false)) {
chip.status |= SR_LOCK_ERROR | SR_ERASE_ERROR;
return;
}
let dw = self.geom.device_width();
let stride = self.geom.bus_width();
let mut at = base + offset % stride;
while at < base + size {
if self.array.fill(at, dw, 0xff).is_err() {
chip.status |= SR_ERASE_ERROR;
return;
}
at += stride;
}
}
fn refresh_fast_path(&self, chips: &[Chip]) {
let all = chips.iter().all(|c| c.mode == Mode::Array);
self.all_array.store(all, Ordering::Relaxed);
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
w.write_bytes(&self.contents())?;
let chips = self.chips.lock();
w.write_seq_len(chips.len() as u64)?;
for chip in chips.iter() {
w.write_u8(chip.mode.tag())?;
w.write_u16(chip.status)?;
w.write_u8(chip.pending.tag())?;
if let Pending::BufferData { left } = chip.pending {
w.write_u64(left)?;
}
w.write_seq_len(chip.buffer.len() as u64)?;
for (at, word) in &chip.buffer {
w.write_u64(*at)?;
w.write_u16(*word)?;
}
w.write_seq_len(chip.locked.len() as u64)?;
for i in 0..chip.locked.len() {
w.write_u8(u8::from(chip.locked[i]) | (u8::from(chip.locked_down[i]) << 1))?;
}
}
Ok(())
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
let bytes: &[u8] = r.read_bytes()?;
if bytes.len() as u64 != self.geom.size() {
return Err(Error::State(format!(
"snapshot has {} byte(s) of flash, this part has {}",
bytes.len(),
self.geom.size()
)));
}
self.array
.write_at(0, bytes)
.map_err(|_| Error::State(String::from("the flash array refused the snapshot")))?;
let lanes = r.read_seq_len(1)?;
let mut chips = self.chips.lock();
if lanes != chips.len() as u64 {
return Err(Error::State(format!(
"snapshot has {lanes} device(s) on the bus, this part has {}",
chips.len()
)));
}
for chip in chips.iter_mut() {
chip.mode = Mode::from_tag(r.read_u8()?)?;
chip.status = r.read_u16()?;
chip.pending = match r.read_u8()? {
0 => Pending::None,
1 => Pending::Program,
2 => Pending::Erase,
3 => Pending::Lock,
4 => Pending::BufferCount,
5 => {
let left = r.read_u64()?;
let max = BUFFER_BYTES_PER_DEVICE / self.geom.device_width();
if left == 0 || left > max {
return Err(Error::State(format!(
"a write buffer with {left} word(s) outstanding, and this part \
holds {max}"
)));
}
Pending::BufferData { left }
}
6 => Pending::BufferConfirm,
other => {
return Err(Error::State(format!(
"{other} is not a flash command sequence"
)));
}
};
let staged = r.read_seq_len(10)?;
chip.buffer.clear();
for _ in 0..staged {
chip.buffer.push((r.read_u64()?, r.read_u16()?));
}
let blocks = r.read_seq_len(1)?;
if blocks != chip.locked.len() as u64 {
return Err(Error::State(format!(
"snapshot has {blocks} erase block(s), this part has {}",
chip.locked.len()
)));
}
for i in 0..chip.locked.len() {
let bits = r.read_u8()?;
chip.locked[i] = bits & 1 != 0;
chip.locked_down[i] = bits & 2 != 0;
}
}
self.refresh_fast_path(&chips);
Ok(())
}
}
impl MemOps for Array {
fn read(&self, offset: u64, dst: &mut [u8], attrs: MemAttrs) -> MemResult {
let end = offset
.checked_add(dst.len() as u64)
.ok_or(BusError::BadAccess)?;
if end > self.geom.size() {
return Err(BusError::BadAccess);
}
if attrs.debug || self.all_array.load(Ordering::Relaxed) {
return self.array.read_at(offset, dst);
}
let chips = self.chips.lock();
for (i, byte) in dst.iter_mut().enumerate() {
*byte = self.peek(&chips, offset + i as u64)?;
}
Ok(())
}
fn write(&self, offset: u64, src: &[u8], attrs: MemAttrs) -> MemResult {
if attrs.debug {
return Err(BusError::BadAccess);
}
let dw = self.geom.device_width();
let len = src.len() as u64;
if src.is_empty() || !len.is_multiple_of(dw) || !offset.is_multiple_of(dw) {
return Err(BusError::BadAccess);
}
let end = offset.checked_add(len).ok_or(BusError::BadAccess)?;
if end > self.geom.size() {
return Err(BusError::BadAccess);
}
let interleave = self.geom.interleave();
let mut chips = self.chips.lock();
let mut at = offset;
for chunk in src.chunks(dw as usize) {
let lane = ((at / dw) % interleave) as usize;
let mut value = 0u16;
for (i, byte) in chunk.iter().enumerate() {
value |= u16::from(*byte) << (8 * i);
}
self.command(&mut chips[lane], at, value);
at += dw;
}
self.refresh_fast_path(&chips);
Ok(())
}
fn constraints(&self) -> AccessConstraints {
AccessConstraints::ANY
.with_widths(Width::U8, Width::U64)
.with_endian(Endian::Little)
}
}
fn build_query(geom: &Geometry) -> Vec<u8> {
let device_size = geom.size() / geom.interleave();
let regions = geom.regions().len();
let extended = QUERY_REGIONS + regions * 4;
let mut q = alloc::vec![0u8; extended + 0x10];
q[0x10] = b'Q';
q[0x11] = b'R';
q[0x12] = b'Y';
q[0x13] = 0x01;
q[0x14] = 0x00;
q[0x15] = extended as u8;
q[0x16] = (extended >> 8) as u8;
q[0x1b] = 0x27;
q[0x1c] = 0x36;
q[0x1d] = 0x00;
q[0x1e] = 0x00;
q[0x1f] = 0x07; q[0x20] = 0x07; q[0x21] = 0x0a; q[0x22] = 0x00; q[0x23] = 0x01; q[0x24] = 0x01; q[0x25] = 0x02; q[0x26] = 0x00;
q[0x27] = log2(device_size);
q[0x28] = 0x02; q[0x29] = 0x00;
q[0x2a] = log2(BUFFER_BYTES_PER_DEVICE); q[0x2b] = 0x00;
q[0x2c] = regions as u8;
for (i, region) in geom.regions().iter().enumerate() {
let at = QUERY_REGIONS + i * 4;
let count = region.count - 1;
let size = region.size / geom.interleave() / 256;
q[at] = count as u8;
q[at + 1] = (count >> 8) as u8;
q[at + 2] = size as u8;
q[at + 3] = (size >> 8) as u8;
}
q[extended] = b'P';
q[extended + 1] = b'R';
q[extended + 2] = b'I';
q[extended + 3] = b'1'; q[extended + 4] = b'1'; q[extended + 5] = 0b0010_1110;
q[extended + 9] = 0x01; q[extended + 10] = 0x03; q[extended + 11] = 0x00;
q[extended + 12] = 0x30; q[extended + 13] = 0x00; q
}
fn log2(value: u64) -> u8 {
(63 - value.max(1).leading_zeros()) as u8
}
#[derive(Debug)]
pub struct Cfi {
array: Arc<Array>,
region: RegionRef,
}
impl Cfi {
pub fn new(props: &Props) -> Result<Cfi> {
let mut r = props.reader();
let size = r.require_size("size")?;
let width = r.or_range("width", DEFAULT_WIDTH, 1..=8)?;
let interleave = r.or_range("interleave", DEFAULT_INTERLEAVE, 1..=8)?;
let block = r.or_size("block", DEFAULT_BLOCK)?;
let blocks = r.optional_list("blocks")?.map(<[Value]>::to_vec);
let manufacturer =
r.or_range("manufacturer", u64::from(DEFAULT_MANUFACTURER), 0..=0xffff)?;
let device_id = r.or_range("device", 0u64, 0..=0xffff)?;
let read_only = r.or("readonly", false)?;
let power_up_locked = r.or("locked", true)?;
let image = r
.optional_media("image")?
.map(crate::core::props::Media::to_bytes);
r.finish()?;
let geom = match blocks {
Some(list) => Geometry::new(block_regions(&list)?, width, interleave)?,
None => Geometry::uniform(size, block, width, interleave)?,
};
if geom.size() != size {
return Err(config(format!(
"the erase-block regions add up to {} byte(s) and `size` says {size}",
geom.size()
)));
}
let array = Arc::new(Array::with_options(
geom,
manufacturer as u16,
device_id as u16,
power_up_locked,
read_only,
)?);
if let Some(image) = image {
if image.len() as u64 > size {
return Err(config(format!(
"the bound image is {} byte(s) and the flash is {size}",
image.len()
)));
}
array.load_image(0, &image)?;
}
Ok(Cfi::from_array(array))
}
#[must_use]
pub fn from_array(array: Arc<Array>) -> Cfi {
let region: RegionRef = Arc::new(Region::io(
CLASS_NAME,
array.geometry().size(),
Arc::clone(&array) as Arc<dyn MemOps>,
));
Cfi { array, region }
}
#[must_use]
pub fn array(&self) -> &Arc<Array> {
&self.array
}
}
#[cfg(feature = "dev-riscv")]
impl crate::dev::riscv::dt::DtSource for Array {
fn dt_spec(&self) -> crate::dev::riscv::dt::NodeSpec {
crate::dev::riscv::dt::NodeSpec::peripheral("flash", &["cfi-flash"])
.with_cells("bank-width", alloc::vec![self.geom.bus_width() as u32])
}
}
fn block_regions(list: &[Value]) -> Result<Vec<BlockRegion>> {
if list.is_empty() || !list.len().is_multiple_of(2) {
return Err(config(String::from(
"`blocks` is a list of count, size pairs, as in `blocks = [4, 16K, 63, 64K]`",
)));
}
let number = |value: &Value| -> Result<u64> {
match value {
Value::Size(n) | Value::Uint(n) | Value::Addr(n) => Ok(*n),
other => Err(config(format!(
"`blocks` holds counts and sizes, not {other}"
))),
}
};
let mut regions = Vec::with_capacity(list.len() / 2);
for pair in list.chunks(2) {
regions.push(BlockRegion {
count: number(&pair[0])?,
size: number(&pair[1])?,
});
}
Ok(regions)
}
pub static CLASS: DeviceClass = DeviceClass {
name: CLASS_NAME,
version: 1,
summary: "CFI NOR flash: real program and erase semantics, Intel/Sharp command set",
properties: &[
PropertySpec {
name: "size",
kind: ValueKind::Size,
required: true,
summary: "how many bytes the whole window holds, as in `size = 32M`",
},
PropertySpec {
name: "image",
kind: ValueKind::Media,
required: false,
summary: "the media slot holding the initial contents; the rest stays erased",
},
PropertySpec {
name: "block",
kind: ValueKind::Size,
required: false,
summary: "the erase-block size when they are all the same (default 256K)",
},
PropertySpec {
name: "blocks",
kind: ValueKind::List,
required: false,
summary: "count, size pairs for a part whose blocks differ: `[4, 16K, 63, 64K]`",
},
PropertySpec {
name: "width",
kind: ValueKind::Uint,
required: false,
summary: "bus width in bytes: 1, 2, 4 or 8 (default 4)",
},
PropertySpec {
name: "interleave",
kind: ValueKind::Uint,
required: false,
summary: "how many parts share that bus (default 2, so two x16 on 32 bits)",
},
PropertySpec {
name: "manufacturer",
kind: ValueKind::Uint,
required: false,
summary: "the JEDEC manufacturer identifier read back in identifier mode",
},
PropertySpec {
name: "device",
kind: ValueKind::Uint,
required: false,
summary: "the device code read back in identifier mode",
},
PropertySpec {
name: "readonly",
kind: ValueKind::Bool,
required: false,
summary: "hold WP# low: every block is locked down and nothing can be written",
},
PropertySpec {
name: "locked",
kind: ValueKind::Bool,
required: false,
summary: "whether blocks power up locked, as an Intel P30 does (default true)",
},
],
construct: |props| Ok(Box::new(Cfi::new(props)?)),
};
impl Device for Cfi {
fn class(&self) -> &'static DeviceClass {
&CLASS
}
#[cfg_attr(not(feature = "dev-riscv"), expect(unused_variables))]
fn realize(&self, ctx: &mut RealizeCtx<'_>) -> Result<()> {
#[cfg(feature = "dev-riscv")]
crate::dev::riscv::dt::publish(
ctx.hosts(),
&self.region,
alloc::sync::Arc::downgrade(&self.array)
as alloc::sync::Weak<dyn crate::dev::riscv::dt::DtSource>,
)?;
Ok(())
}
fn reset(&self, _kind: ResetKind) {
self.array.reset();
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
self.array.save(w)
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
self.array.load(r)
}
fn region(&self, name: &str) -> Option<RegionRef> {
matches!(name, "" | "flash").then(|| Arc::clone(&self.region))
}
}
impl Instance for Cfi {}
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(Cfi::new(props)?)))
}
#[must_use]
pub fn schema() -> ClassSchema {
ClassSchema::new(CLASS_NAME)
.prop(PropSchema::new("size", ValueKind::Size).required())
.prop(PropSchema::new("image", ValueKind::Media))
.prop(PropSchema::new("block", ValueKind::Size))
.prop(PropSchema::new("blocks", ValueKind::List))
.prop(PropSchema::new("width", ValueKind::Uint).range(1, 8))
.prop(PropSchema::new("interleave", ValueKind::Uint).range(1, 8))
.prop(PropSchema::new("manufacturer", ValueKind::Uint).range(0, 0xffff))
.prop(PropSchema::new("device", ValueKind::Uint).range(0, 0xffff))
.prop(PropSchema::new("readonly", ValueKind::Bool))
.prop(PropSchema::new("locked", ValueKind::Bool))
.region("")
.region("flash")
}
#[cfg(test)]
mod tests;