blob: ddb6e9d2b8569aaf1aa21dc9fafa9de985da2ed5 [file]
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0
#![no_std]
use core::num::NonZero;
use earlgrey_util::{AsMubi, EarlgreyFlashAddress};
use flash_ctrl_core::{self, regs::ControlWriteVal};
use hal_flash_driver::{FlashAddress, FlashDriver};
use util_error::{self as error, ErrorCode};
use util_regcpy::{copy_from_reg_unaligned, copy_to_reg_unaligned};
use util_types::PowerOf2Usize;
pub struct Permission {
pub read: bool,
pub write: bool,
pub erase: bool,
}
impl Permission {
pub const FULL_ACCESS: Permission = Permission {
read: true,
write: true,
erase: true,
};
pub const READ_ONLY: Permission = Permission {
read: true,
write: false,
erase: false,
};
}
const FLASH_SIZE: NonZero<usize> = NonZero::new(1024 * 1024).unwrap();
pub struct EmbeddedFlash {
mmio: flash_ctrl_core::FlashCtrl,
busy: bool,
}
impl EmbeddedFlash {
// TODO: unify these with the trait consts.
const BYTES_PER_BANK: u32 = 0x80000;
const BYTES_PER_PAGE: u32 = 2048;
pub fn new(mmio: flash_ctrl_core::FlashCtrl) -> Self {
Self { mmio, busy: false }
}
pub fn new_with_interrupts(mut mmio: flash_ctrl_core::FlashCtrl) -> Self {
mmio.regs_mut().intr_state().write(|w| w.op_done_clear());
mmio.regs_mut().intr_enable().write(|w| w.op_done(true));
Self { mmio, busy: false }
}
pub fn set_default_permission(&mut self, perm: Permission) {
let reg = self.mmio.regs_mut();
reg.default_region().modify(|v| {
v.rd_en(perm.read.as_mubi())
.prog_en(perm.write.as_mubi())
.erase_en(perm.erase.as_mubi())
});
}
pub fn set_info_permission(
&mut self,
address: FlashAddress,
perm: Permission,
) -> Result<(), ErrorCode> {
if !address.is_info() {
return Err(error::FLASH_GENERIC_ADDR_OUT_OF_BOUNDS);
}
let page = address.page() as usize;
if page >= 10 {
return Err(error::FLASH_GENERIC_ADDR_OUT_OF_BOUNDS);
}
let reg = self.mmio.regs_mut();
if address.bank() == 0 {
reg.bank0_info0_page_cfg().at(page).modify(|v| {
v.en(true.as_mubi())
.rd_en(perm.read.as_mubi())
.rd_en(perm.read.as_mubi())
.prog_en(perm.write.as_mubi())
.erase_en(perm.erase.as_mubi())
});
} else {
reg.bank1_info0_page_cfg().at(page).modify(|v| {
v.en(true.as_mubi())
.rd_en(perm.read.as_mubi())
.prog_en(perm.write.as_mubi())
.erase_en(perm.erase.as_mubi())
});
}
Ok(())
}
}
//fn u32_from_usize(val: usize) -> u32 {
// u32::try_from(val).unwrap()
//}
impl FlashDriver for EmbeddedFlash {
type Error = ErrorCode;
// BytesPerWord: 8
// WordsPerPage: 256
// BytesPerBank: 524288
// program_resolution: 8 (max flash words to program at one time)
// RegBusPgmResBytes = 64
const PAGE_SIZE: usize = 2048;
const PROGRAM_WINDOW_SIZE: usize = 64;
const MAX_READ_SIZE: usize = 4096;
const READ_ALIGNMENT: usize = 4;
const PROGRAM_ALIGNMENT: usize = 8;
fn erasable_sizes_bitmap(&mut self) -> Result<u32, Self::Error> {
Ok(1 << 11)
}
fn size(&self) -> core::num::NonZero<usize> {
FLASH_SIZE
}
#[inline(never)]
fn read(&mut self, start_addr: FlashAddress, buf: &mut [u8]) -> Result<(), Self::Error> {
if buf.is_empty() {
return Ok(());
}
let start_offset = if start_addr.is_info() {
start_addr.bank() * Self::BYTES_PER_BANK
+ start_addr.page() * Self::BYTES_PER_PAGE
+ start_addr.earlgrey_offset()
} else {
start_addr.offset()
};
if (start_offset & 3) != 0 {
return Err(error::FLASH_GENERIC_BAD_ALIGNMENT);
}
if buf.len() > Self::MAX_READ_SIZE {
return Err(error::FLASH_GENERIC_READ_TOO_LONG);
}
self.check_busy()?;
self.mmio.regs_mut().addr().write(|w| w.start(start_offset));
self.start_op(|w| {
w.op(|s| s.read())
.partition_sel(start_addr.is_info())
.num((buf.len() as u32 + 3) / 4 - 1)
});
copy_from_reg_unaligned(buf, &self.mmio.regs_mut().rd_fifo());
while self.is_busy() {}
self.complete_op()
}
fn start_erase(
&mut self,
start_addr: FlashAddress,
size: PowerOf2Usize,
) -> Result<(), Self::Error> {
if size.get() != 2048 {
return Err(error::FLASH_GENERIC_ERASE_INVALID_SIZE);
}
let start_offset = if start_addr.is_info() {
start_addr.bank() * Self::BYTES_PER_BANK
+ start_addr.page() * Self::BYTES_PER_PAGE
+ start_addr.earlgrey_offset()
} else {
start_addr.offset()
};
let start_offset = start_offset as usize;
if start_offset & (size.get() - 1) != 0 {
return Err(error::FLASH_GENERIC_ERASE_INVALID_ADDR);
}
self.check_busy()?;
self.mmio
.regs_mut()
.addr()
.write(|w| w.start(start_offset as u32));
self.start_op(|w| {
w.op(|s| s.erase())
.erase_sel(|s| s.page_erase())
.partition_sel(start_addr.is_info())
.start(true)
});
Ok(())
}
fn start_program(&mut self, start_addr: FlashAddress, data: &[u8]) -> Result<(), Self::Error> {
if data.is_empty() {
return Ok(());
}
if data.len() > Self::PROGRAM_WINDOW_SIZE {
return Err(error::FLASH_GENERIC_PROGRAM_EXCEEDS_WINDOW_SIZE);
}
let start_offset = if start_addr.is_info() {
start_addr.bank() * Self::BYTES_PER_BANK
+ start_addr.page() * Self::BYTES_PER_PAGE
+ start_addr.earlgrey_offset()
} else {
start_addr.offset()
};
let start_offset = start_offset as usize;
let end_offset = start_offset.wrapping_add(data.len());
if start_offset / Self::PROGRAM_WINDOW_SIZE != (end_offset - 1) / Self::PROGRAM_WINDOW_SIZE
{
return Err(error::FLASH_GENERIC_PROGRAM_SPANS_WINDOW_BOUNDARY);
}
self.check_busy()?;
// reset the op status register
self.mmio
.regs_mut()
.addr()
.write(|w| w.start(start_offset as u32));
self.start_op(|w| {
w.op(|s| s.prog())
.prog_sel(|s| s.normal_program())
.partition_sel(start_addr.is_info())
.num(((data.len() + 3) / 4) as u32 - 1)
});
copy_to_reg_unaligned(&self.mmio.regs_mut().prog_fifo(), data);
Ok(())
}
fn is_busy(&mut self) -> bool {
if self.busy && self.mmio.regs_mut().intr_state().read().op_done() {
self.busy = false;
self.mmio
.regs_mut()
.intr_state()
.write(|w| w.op_done_clear());
}
self.busy
}
fn complete_op(&mut self) -> Result<(), Self::Error> {
if self.is_busy() {
return Err(error::FLASH_GENERIC_BUSY);
}
let status = self.mmio.regs().op_status().read();
if status.err() {
let err_code = u32::from(self.mmio.regs().err_code().read());
Err(error::FLASH_OPENTITAN.error(err_code as u16))
} else {
Ok(())
}
}
}
impl EmbeddedFlash {
fn start_op(&mut self, f: impl FnOnce(ControlWriteVal) -> ControlWriteVal) {
self.busy = true;
self.mmio.regs_mut().err_code().write(|_| 0xff.into());
self.mmio.regs_mut().op_status().write(|w| w);
self.mmio
.regs_mut()
.intr_state()
.write(|w| w.op_done_clear());
self.mmio.regs_mut().control().write(|w| f(w).start(true));
}
fn check_busy(&mut self) -> Result<(), ErrorCode> {
if self.is_busy() {
Err(error::FLASH_GENERIC_BUSY)
} else {
Ok(())
}
}
}