mirror of
https://github.com/m-labs/artiq.git
synced 2024-12-25 03:08:27 +08:00
firmware: port flash storage management to Rust.
This commit is contained in:
parent
0df8a24135
commit
3b54736c02
@ -16,6 +16,9 @@ pub mod uart;
|
||||
#[cfg(feature = "uart_console")]
|
||||
pub mod uart_console;
|
||||
|
||||
#[cfg(has_spiflash)]
|
||||
pub mod spiflash;
|
||||
|
||||
#[cfg(has_i2c)]
|
||||
pub mod i2c;
|
||||
#[cfg(has_i2c)]
|
||||
|
123
artiq/firmware/libboard/spiflash.rs
Normal file
123
artiq/firmware/libboard/spiflash.rs
Normal file
@ -0,0 +1,123 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use core::cmp;
|
||||
use csr;
|
||||
|
||||
const CMD_PP: u8 = 0x02;
|
||||
const CMD_WRDI: u8 = 0x04;
|
||||
const CMD_RDSR: u8 = 0x05;
|
||||
const CMD_WREN: u8 = 0x06;
|
||||
const CMD_SE: u8 = 0xd8;
|
||||
|
||||
const PIN_CLK: u8 = 1 << 1;
|
||||
const PIN_CS_N: u8 = 1 << 2;
|
||||
const PIN_DQ_I: u8 = 1 << 3;
|
||||
|
||||
const SR_WIP: u8 = 1;
|
||||
|
||||
fn write_byte(mut byte: u8) {
|
||||
unsafe {
|
||||
csr::spiflash::bitbang_write(0);
|
||||
for _ in 0..8 {
|
||||
csr::spiflash::bitbang_write((byte & 0x80) >> 7);
|
||||
csr::spiflash::bitbang_write((byte & 0x80) >> 7 | PIN_CLK);
|
||||
byte <<= 1;
|
||||
}
|
||||
csr::spiflash::bitbang_write(0);
|
||||
}
|
||||
}
|
||||
|
||||
fn write_addr(mut addr: usize) {
|
||||
unsafe {
|
||||
csr::spiflash::bitbang_write(0);
|
||||
for _ in 0..24 {
|
||||
csr::spiflash::bitbang_write(((addr & 0x800000) >> 23) as u8);
|
||||
csr::spiflash::bitbang_write(((addr & 0x800000) >> 23) as u8 | PIN_CLK);
|
||||
addr <<= 1;
|
||||
}
|
||||
csr::spiflash::bitbang_write(0);
|
||||
}
|
||||
}
|
||||
|
||||
fn wait_until_ready() {
|
||||
unsafe {
|
||||
loop {
|
||||
let mut sr = 0;
|
||||
write_byte(CMD_RDSR);
|
||||
for _ in 0..8 {
|
||||
sr <<= 1;
|
||||
csr::spiflash::bitbang_write(PIN_DQ_I | PIN_CLK);
|
||||
sr |= csr::spiflash::miso_read();
|
||||
csr::spiflash::bitbang_write(PIN_DQ_I);
|
||||
}
|
||||
csr::spiflash::bitbang_write(0);
|
||||
csr::spiflash::bitbang_write(PIN_CS_N);
|
||||
if sr & SR_WIP == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn erase_sector(addr: usize) {
|
||||
unsafe {
|
||||
let sector_addr = addr & !(csr::CONFIG_SPIFLASH_SECTOR_SIZE as usize - 1);
|
||||
|
||||
csr::spiflash::bitbang_en_write(1);
|
||||
|
||||
wait_until_ready();
|
||||
|
||||
write_byte(CMD_WREN);
|
||||
csr::spiflash::bitbang_write(PIN_CS_N);
|
||||
|
||||
write_byte(CMD_SE);
|
||||
write_addr(sector_addr);
|
||||
csr::spiflash::bitbang_write(PIN_CS_N);
|
||||
|
||||
wait_until_ready();
|
||||
|
||||
csr::spiflash::bitbang_en_write(0);
|
||||
}
|
||||
}
|
||||
|
||||
fn write_page(addr: usize, data: &[u8]) {
|
||||
unsafe {
|
||||
csr::spiflash::bitbang_en_write(1);
|
||||
|
||||
wait_until_ready();
|
||||
|
||||
write_byte(CMD_WREN);
|
||||
csr::spiflash::bitbang_write(PIN_CS_N);
|
||||
write_byte(CMD_PP);
|
||||
write_addr(addr);
|
||||
for &byte in data {
|
||||
write_byte(byte)
|
||||
}
|
||||
|
||||
csr::spiflash::bitbang_write(PIN_CS_N);
|
||||
csr::spiflash::bitbang_write(0);
|
||||
|
||||
wait_until_ready();
|
||||
|
||||
csr::spiflash::bitbang_en_write(0);
|
||||
}
|
||||
}
|
||||
|
||||
const PAGE_SIZE: usize = csr::CONFIG_SPIFLASH_PAGE_SIZE as usize;
|
||||
const PAGE_MASK: usize = PAGE_SIZE - 1;
|
||||
|
||||
pub fn write(mut addr: usize, mut data: &[u8]) {
|
||||
if addr & PAGE_MASK != 0 {
|
||||
let size = cmp::min((PAGE_SIZE - (addr & PAGE_MASK)) as usize, data.len());
|
||||
write_page(addr, &data[..size]);
|
||||
addr += size;
|
||||
data = &data[size..];
|
||||
}
|
||||
|
||||
while data.len() > 0 {
|
||||
let size = cmp::min(PAGE_SIZE as usize, data.len());
|
||||
write_page(addr, &data[..size]);
|
||||
addr += size;
|
||||
data = &data[size..];
|
||||
}
|
||||
}
|
@ -1,68 +1,192 @@
|
||||
use std::cmp;
|
||||
use std::vec::Vec;
|
||||
use std::string::String;
|
||||
use libc::{c_void, c_char, c_int, c_uint};
|
||||
use core::str;
|
||||
use std::btree_map::BTreeMap;
|
||||
use byteorder::{ByteOrder, BigEndian};
|
||||
use board::{mem, csr, cache, spiflash};
|
||||
|
||||
extern {
|
||||
fn fs_remove(key: *const c_char);
|
||||
fn fs_erase();
|
||||
fn fs_write(key: *const c_char, buffer: *const c_void, buflen: c_uint) -> c_int;
|
||||
fn fs_read(key: *const c_char, buffer: *mut c_void, buflen: c_uint,
|
||||
remain: *mut c_uint) -> c_uint;
|
||||
}
|
||||
const ADDR: usize = mem::FLASH_BOOT_ADDRESS + 0x80000 /* max runtime size */;
|
||||
const SIZE: usize = csr::CONFIG_SPIFLASH_SECTOR_SIZE as usize;
|
||||
|
||||
macro_rules! c_str {
|
||||
($s:ident) => {
|
||||
{
|
||||
let mut c = [0; 64 + 1];
|
||||
let len = cmp::min($s.len(), c.len() - 1);
|
||||
c[..len].copy_from_slice($s.as_bytes());
|
||||
c
|
||||
mod lock {
|
||||
use core::slice;
|
||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
static LOCKED: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
pub struct Lock;
|
||||
|
||||
impl Lock {
|
||||
pub fn take() -> Result<Lock, ()> {
|
||||
if LOCKED.swap(1, Ordering::SeqCst) != 0 {
|
||||
Err(()) // already locked
|
||||
} else {
|
||||
Ok(Lock) // locked now
|
||||
}
|
||||
}
|
||||
|
||||
pub fn data(&self) -> &'static [u8] {
|
||||
unsafe { slice::from_raw_parts(super::ADDR as *const u8, super::SIZE) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Lock {
|
||||
fn drop(&mut self) {
|
||||
LOCKED.store(0, Ordering::SeqCst)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(key: &str, buf: &mut [u8]) -> Result<usize, usize> {
|
||||
let key_c = c_str!(key);
|
||||
let mut remain: c_uint = 0;
|
||||
let result = unsafe {
|
||||
fs_read(key_c.as_ptr() as *const c_char,
|
||||
buf.as_mut_ptr() as *mut c_void, buf.len() as c_uint, &mut remain)
|
||||
};
|
||||
if remain == 0 { Ok(result as usize) } else { Err(remain as usize) }
|
||||
pub use self::lock::Lock;
|
||||
|
||||
struct Iter<'a> {
|
||||
data: &'a [u8],
|
||||
offset: usize
|
||||
}
|
||||
|
||||
pub fn read_to_end(key: &str) -> Vec<u8> {
|
||||
let mut value = Vec::new();
|
||||
match read(key, &mut []) {
|
||||
Ok(0) => (),
|
||||
Ok(_) => unreachable!(),
|
||||
Err(size) => {
|
||||
value.resize(size, 0);
|
||||
read(key, &mut value).unwrap();
|
||||
impl<'a> Iter<'a> {
|
||||
fn new(data: &'a [u8]) -> Iter<'a> {
|
||||
Iter { data: data, offset: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Iter<'a> {
|
||||
type Item = Result<(&'a [u8], &'a [u8]), ()>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let data = &self.data[self.offset..];
|
||||
|
||||
if data.len() < 4 {
|
||||
error!("offset {}: truncated record", self.offset);
|
||||
return Some(Err(()))
|
||||
}
|
||||
|
||||
let record_size = BigEndian::read_u32(data) as usize;
|
||||
if record_size < 4 {
|
||||
error!("offset {}: invalid record size", self.offset);
|
||||
return Some(Err(()))
|
||||
}
|
||||
if record_size == !0 /* all ones; erased flash */ {
|
||||
return None
|
||||
}
|
||||
|
||||
let record_body = &data[4..record_size];
|
||||
match record_body.iter().position(|&x| x == 0) {
|
||||
None => {
|
||||
error!("offset {}: missing separator", self.offset);
|
||||
Some(Err(()))
|
||||
}
|
||||
Some(pos) => {
|
||||
self.offset += record_size;
|
||||
|
||||
let (key, zero_and_value) = record_body.split_at(pos);
|
||||
Some(Ok((key, &zero_and_value[1..])))
|
||||
}
|
||||
}
|
||||
}
|
||||
value
|
||||
}
|
||||
|
||||
pub fn read_string(key: &str) -> String {
|
||||
String::from_utf8(read_to_end(key)).unwrap()
|
||||
pub fn read<F: FnOnce(Result<&[u8], ()>) -> R, R>(key: &str, f: F) -> R {
|
||||
f(Lock::take().and_then(|lock| {
|
||||
let mut iter = Iter::new(lock.data());
|
||||
let mut value = &[][..];
|
||||
while let Some(result) = iter.next() {
|
||||
let (record_key, record_value) = result?;
|
||||
if key.as_bytes() == record_key {
|
||||
// last write wins
|
||||
value = record_value
|
||||
}
|
||||
}
|
||||
Ok(value)
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn write(key: &str, buf: &[u8]) -> Result<(), ()> {
|
||||
let key_c = c_str!(key);
|
||||
let result = unsafe {
|
||||
fs_write(key_c.as_ptr() as *const c_char,
|
||||
buf.as_ptr() as *mut c_void, buf.len() as c_uint)
|
||||
pub fn read_str<F: FnOnce(Result<&str, ()>) -> R, R>(key: &str, f: F) -> R {
|
||||
read(key, |result| {
|
||||
f(result.and_then(|value| str::from_utf8(value).map_err(|_| ())))
|
||||
})
|
||||
}
|
||||
|
||||
fn append_at(mut offset: usize, key: &[u8], value: &[u8]) -> Result<usize, ()> {
|
||||
let record_size = 4 + key.len() + 1 + value.len();
|
||||
if offset + record_size > SIZE {
|
||||
return Err(())
|
||||
}
|
||||
|
||||
let mut record_size_bytes = [0u8; 4];
|
||||
BigEndian::write_u32(&mut record_size_bytes[..], record_size as u32);
|
||||
|
||||
spiflash::write(ADDR + offset, &record_size_bytes[..]);
|
||||
offset += record_size_bytes.len();
|
||||
|
||||
spiflash::write(ADDR + offset, key);
|
||||
offset += key.len();
|
||||
|
||||
spiflash::write(ADDR + offset, &[0]);
|
||||
offset += 1;
|
||||
|
||||
spiflash::write(ADDR + offset, value);
|
||||
offset += value.len();
|
||||
|
||||
cache::flush_l2_cache();
|
||||
Ok(offset)
|
||||
}
|
||||
|
||||
fn compact() -> Result<(), ()> {
|
||||
let lock = Lock::take()?;
|
||||
|
||||
let mut items = BTreeMap::new();
|
||||
{
|
||||
let mut iter = Iter::new(lock.data());
|
||||
while let Some(result) = iter.next() {
|
||||
let (key, value) = result?;
|
||||
items.insert(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
spiflash::erase_sector(ADDR);
|
||||
cache::flush_l2_cache();
|
||||
|
||||
let mut offset = 0;
|
||||
for (key, value) in items {
|
||||
offset = append_at(offset, key, value)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn append(key: &str, value: &[u8]) -> Result<(), ()> {
|
||||
let lock = Lock::take()?;
|
||||
|
||||
let free_offset = {
|
||||
let mut iter = Iter::new(lock.data());
|
||||
while let Some(result) = iter.next() {
|
||||
let _ = result?;
|
||||
}
|
||||
iter.offset
|
||||
};
|
||||
if result == 1 { Ok(()) } else { Err(()) }
|
||||
|
||||
append_at(free_offset, key.as_bytes(), value)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove(key: &str) {
|
||||
let key_c = c_str!(key);
|
||||
unsafe { fs_remove(key_c.as_ptr() as *const c_char) }
|
||||
pub fn write(key: &str, value: &[u8]) -> Result<(), ()> {
|
||||
match append(key, value) {
|
||||
Ok(()) => (),
|
||||
Err(()) => {
|
||||
compact()?;
|
||||
append(key, value)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn erase() {
|
||||
unsafe { fs_erase() }
|
||||
pub fn remove(key: &str) -> Result<(), ()> {
|
||||
write(key, &[])
|
||||
}
|
||||
|
||||
pub fn erase() -> Result<(), ()> {
|
||||
let _lock = Lock::take()?;
|
||||
|
||||
spiflash::erase_sector(ADDR);
|
||||
cache::flush_l2_cache();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
#![no_std]
|
||||
#![feature(libc, repr_simd)]
|
||||
#![feature(libc, repr_simd, const_fn)]
|
||||
|
||||
extern crate alloc_artiq;
|
||||
#[macro_use]
|
||||
@ -67,8 +67,8 @@ fn startup() {
|
||||
info!("press 'e' to erase startup and idle kernels...");
|
||||
while board::clock::get_ms() < t + 1000 {
|
||||
if unsafe { readchar_nonblock() != 0 && readchar() == b'e' as libc::c_char } {
|
||||
config::remove("startup_kernel");
|
||||
config::remove("idle_kernel");
|
||||
config::remove("startup_kernel").unwrap();
|
||||
config::remove("idle_kernel").unwrap();
|
||||
info!("startup and idle kernels erased");
|
||||
break
|
||||
}
|
||||
@ -83,7 +83,7 @@ fn startup() {
|
||||
board::ad9154::init().expect("cannot initialize ad9154");
|
||||
|
||||
let hardware_addr;
|
||||
match EthernetAddress::parse(&config::read_string("mac")) {
|
||||
match config::read_str("mac", |r| r.and_then(|s| EthernetAddress::parse(s))) {
|
||||
Err(()) => {
|
||||
hardware_addr = EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]);
|
||||
warn!("using default MAC address {}; consider changing it", hardware_addr);
|
||||
@ -95,7 +95,7 @@ fn startup() {
|
||||
}
|
||||
|
||||
let protocol_addr;
|
||||
match IpAddress::parse(&config::read_string("ip")) {
|
||||
match config::read_str("ip", |r| r.and_then(|s| IpAddress::parse(s))) {
|
||||
Err(()) | Ok(IpAddress::Unspecified) => {
|
||||
protocol_addr = IpAddress::v4(192, 168, 1, 50);
|
||||
info!("using default IP address {}", protocol_addr);
|
||||
|
@ -154,24 +154,25 @@ mod drtio {
|
||||
pub fn startup(io: &Io) {
|
||||
crg::init();
|
||||
|
||||
let mut opt = [b'i'];
|
||||
let clk;
|
||||
match config::read("startup_clock", &mut opt) {
|
||||
Ok(0) | Ok(1) if &opt == b"i" => {
|
||||
info!("startup RTIO clock: internal");
|
||||
clk = 0
|
||||
}
|
||||
Ok(1) if &opt == b"e" => {
|
||||
info!("startup RTIO clock: external");
|
||||
clk = 1
|
||||
}
|
||||
_ => {
|
||||
error!("unrecognized startup_clock configuration entry");
|
||||
clk = 0
|
||||
}
|
||||
#[derive(Debug)]
|
||||
enum RtioClock {
|
||||
Internal = 0,
|
||||
External = 1
|
||||
};
|
||||
|
||||
if !crg::switch_clock(clk) {
|
||||
let clk = config::read("startup_clock", |result| {
|
||||
match result {
|
||||
Ok(b"i") => RtioClock::Internal,
|
||||
Ok(b"e") => RtioClock::External,
|
||||
_ => {
|
||||
error!("unrecognized startup_clock configuration entry");
|
||||
RtioClock::Internal
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
info!("startup RTIO clock: {:?}", clk);
|
||||
if !crg::switch_clock(clk as u8) {
|
||||
error!("startup RTIO clock failed");
|
||||
warn!("this may cause the system initialization to fail");
|
||||
warn!("fix clocking and reset the device");
|
||||
|
@ -233,8 +233,12 @@ fn process_host_message(io: &Io,
|
||||
|
||||
// artiq_coreconfig
|
||||
host::Request::FlashRead { ref key } => {
|
||||
let value = config::read_to_end(key);
|
||||
host_write(stream, host::Reply::FlashRead(&value))
|
||||
config::read(key, |result| {
|
||||
match result {
|
||||
Ok(value) => host_write(stream, host::Reply::FlashRead(&value)),
|
||||
Err(()) => host_write(stream, host::Reply::FlashError)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
host::Request::FlashWrite { ref key, ref value } => {
|
||||
@ -245,13 +249,18 @@ fn process_host_message(io: &Io,
|
||||
}
|
||||
|
||||
host::Request::FlashRemove { ref key } => {
|
||||
config::remove(key);
|
||||
host_write(stream, host::Reply::FlashOk)
|
||||
match config::remove(key) {
|
||||
Ok(()) => host_write(stream, host::Reply::FlashOk),
|
||||
Err(_) => host_write(stream, host::Reply::FlashError),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
host::Request::FlashErase => {
|
||||
config::erase();
|
||||
host_write(stream, host::Reply::FlashOk)
|
||||
match config::erase() {
|
||||
Ok(()) => host_write(stream, host::Reply::FlashOk),
|
||||
Err(_) => host_write(stream, host::Reply::FlashError),
|
||||
}
|
||||
}
|
||||
|
||||
// artiq_run/artiq_master
|
||||
@ -599,12 +608,12 @@ fn flash_kernel_worker(io: &Io,
|
||||
config_key: &str) -> io::Result<()> {
|
||||
let mut session = Session::new(congress);
|
||||
|
||||
let kernel = config::read_to_end(config_key);
|
||||
if kernel.len() == 0 {
|
||||
return Err(io::Error::new(io::ErrorKind::NotFound, "kernel not found"))
|
||||
}
|
||||
|
||||
unsafe { kern_load(io, &mut session, &kernel)? };
|
||||
config::read(config_key, |result| {
|
||||
match result {
|
||||
Ok(kernel) if kernel.len() > 0 => unsafe { kern_load(io, &mut session, &kernel) },
|
||||
_ => Err(io::Error::new(io::ErrorKind::NotFound, "kernel not found")),
|
||||
}
|
||||
})?;
|
||||
kern_run(&mut session)?;
|
||||
|
||||
loop {
|
||||
|
@ -39,7 +39,7 @@ $(RUSTOUT)/libruntime.a:
|
||||
cargo build --target=or1k-unknown-none \
|
||||
--manifest-path $(realpath $(RUNTIME_DIRECTORY)/../firmware/runtime/Cargo.toml)
|
||||
|
||||
runtime.elf: $(RUSTOUT)/libruntime.a flash_storage.o ksupport_data.o
|
||||
runtime.elf: $(RUSTOUT)/libruntime.a ksupport_data.o
|
||||
$(LD) $(LDFLAGS) \
|
||||
-T $(RUNTIME_DIRECTORY)/runtime.ld \
|
||||
-o $@ \
|
||||
|
@ -1,308 +0,0 @@
|
||||
/*
|
||||
* Yann Sionneau <ys@m-labs.hk>, 2015
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <system.h>
|
||||
#include <spiflash.h>
|
||||
#include <generated/mem.h>
|
||||
#include <generated/csr.h>
|
||||
|
||||
#include "flash_storage.h"
|
||||
|
||||
#if (defined CSR_SPIFLASH_BASE && defined CONFIG_SPIFLASH_PAGE_SIZE)
|
||||
|
||||
#define STORAGE_ADDRESS ((char *)(FLASH_BOOT_ADDRESS + 0x80000 /* max runtime size */))
|
||||
#define STORAGE_SIZE CONFIG_SPIFLASH_SECTOR_SIZE
|
||||
#define END_MARKER (0xFFFFFFFF)
|
||||
|
||||
#define min(a, b) (a>b?b:a)
|
||||
#define max(a, b) (a>b?a:b)
|
||||
|
||||
struct record {
|
||||
char *key;
|
||||
unsigned int key_len;
|
||||
char *value;
|
||||
unsigned int value_len;
|
||||
char *raw_record;
|
||||
unsigned int size;
|
||||
};
|
||||
|
||||
struct iter_state {
|
||||
char *buffer;
|
||||
unsigned int seek;
|
||||
unsigned int buf_len;
|
||||
};
|
||||
|
||||
static unsigned int get_record_size(char *buff)
|
||||
{
|
||||
unsigned int record_size;
|
||||
|
||||
memcpy(&record_size, buff, 4);
|
||||
return record_size;
|
||||
}
|
||||
|
||||
static void record_iter_init(struct iter_state *is, char *buffer, unsigned int buf_len)
|
||||
{
|
||||
is->buffer = buffer;
|
||||
is->seek = 0;
|
||||
is->buf_len = buf_len;
|
||||
}
|
||||
|
||||
static int record_iter_next(struct iter_state *is, struct record *record, int *fatal)
|
||||
{
|
||||
if(is->seek >= is->buf_len)
|
||||
return 0;
|
||||
|
||||
record->raw_record = &is->buffer[is->seek];
|
||||
record->size = get_record_size(record->raw_record);
|
||||
|
||||
if(record->size == END_MARKER)
|
||||
return 0;
|
||||
|
||||
if(record->size < 6) {
|
||||
// core_log("flash_storage might be corrupted: record size is %u (<6) at address %08x\n",
|
||||
// record->size, record->raw_record);
|
||||
if(fatal)
|
||||
*fatal = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(is->seek > is->buf_len - sizeof(record->size) - 2) { /* 2 is the minimum key length */
|
||||
// core_log("flash_storage might be corrupted: END_MARKER missing at the end of "
|
||||
// "the storage sector\n");
|
||||
if(fatal)
|
||||
*fatal = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(record->size > is->buf_len - is->seek) {
|
||||
// core_log("flash_storage might be corrupted: invalid record_size %d at address %08x\n",
|
||||
// record->size, record->raw_record);
|
||||
if(fatal)
|
||||
*fatal = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
record->key = record->raw_record + sizeof(record->size);
|
||||
record->key_len = strnlen(record->key, record->size - sizeof(record->size)) + 1;
|
||||
|
||||
if(record->key_len == record->size - sizeof(record->size) + 1) {
|
||||
// core_log("flash_storage might be corrupted: invalid key length at address %08x\n",
|
||||
// record->raw_record);
|
||||
if(fatal)
|
||||
*fatal = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
record->value = record->key + record->key_len;
|
||||
record->value_len = record->size - record->key_len - sizeof(record->size);
|
||||
|
||||
is->seek += record->size;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static unsigned int get_free_space(void)
|
||||
{
|
||||
struct iter_state is;
|
||||
struct record record;
|
||||
|
||||
record_iter_init(&is, STORAGE_ADDRESS, STORAGE_SIZE);
|
||||
while(record_iter_next(&is, &record, NULL));
|
||||
return STORAGE_SIZE - is.seek;
|
||||
}
|
||||
|
||||
static int is_empty(struct record *record)
|
||||
{
|
||||
return record->value_len == 0;
|
||||
}
|
||||
|
||||
static int key_exists(char *buff, const char *key, char *end, char accept_empty,
|
||||
struct record *found_record)
|
||||
{
|
||||
struct iter_state is;
|
||||
struct record iter_record;
|
||||
int found = 0;
|
||||
|
||||
record_iter_init(&is, buff, end - buff);
|
||||
while(record_iter_next(&is, &iter_record, NULL)) {
|
||||
if(strcmp(iter_record.key, key) == 0) {
|
||||
found = 1;
|
||||
if(found_record)
|
||||
*found_record = iter_record;
|
||||
}
|
||||
}
|
||||
|
||||
if(found && is_empty(found_record) && !accept_empty)
|
||||
return 0;
|
||||
|
||||
if(found)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char check_for_duplicates(char *buff)
|
||||
{
|
||||
struct record record, following_record;
|
||||
struct iter_state is;
|
||||
int no_error;
|
||||
|
||||
record_iter_init(&is, buff, STORAGE_SIZE);
|
||||
no_error = record_iter_next(&is, &record, NULL);
|
||||
while(no_error) {
|
||||
no_error = record_iter_next(&is, &following_record, NULL);
|
||||
if(no_error && key_exists(following_record.raw_record, record.key, &buff[STORAGE_SIZE], 1, NULL))
|
||||
return 1;
|
||||
record = following_record;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char check_for_empty_records(char *buff)
|
||||
{
|
||||
struct iter_state is;
|
||||
struct record record;
|
||||
|
||||
record_iter_init(&is, buff, STORAGE_SIZE);
|
||||
while(record_iter_next(&is, &record, NULL))
|
||||
if(is_empty(&record))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned int try_to_flush_duplicates(const char *new_key, unsigned int buf_len)
|
||||
{
|
||||
unsigned int key_size, new_record_size, ret = 0, can_rollback = 0;
|
||||
struct record record, previous_record;
|
||||
char sector_buff[STORAGE_SIZE];
|
||||
struct iter_state is;
|
||||
|
||||
memcpy(sector_buff, STORAGE_ADDRESS, STORAGE_SIZE);
|
||||
if(check_for_duplicates(sector_buff)
|
||||
|| key_exists(sector_buff, new_key, §or_buff[STORAGE_SIZE], 0, NULL)
|
||||
|| check_for_empty_records(sector_buff)) {
|
||||
fs_erase();
|
||||
record_iter_init(&is, sector_buff, STORAGE_SIZE);
|
||||
while(record_iter_next(&is, &record, NULL)) {
|
||||
if(is_empty(&record))
|
||||
continue;
|
||||
if(!key_exists((char *)STORAGE_ADDRESS, record.key, STORAGE_ADDRESS + STORAGE_SIZE, 1, NULL)) {
|
||||
struct record rec;
|
||||
|
||||
if(!key_exists(sector_buff, record.key, §or_buff[STORAGE_SIZE], 0, &rec))
|
||||
continue;
|
||||
if(strcmp(new_key, record.key) == 0) { // If we are about to write this key we don't keep the old value.
|
||||
previous_record = rec; // This holds the old record in case we need it back (for instance if new record is too long)
|
||||
can_rollback = 1;
|
||||
} else
|
||||
fs_write(record.key, rec.value, rec.value_len);
|
||||
}
|
||||
}
|
||||
ret = 1;
|
||||
}
|
||||
|
||||
key_size = strlen(new_key) + 1;
|
||||
new_record_size = key_size + buf_len + sizeof(new_record_size);
|
||||
if(can_rollback && new_record_size > get_free_space()) {
|
||||
fs_write(new_key, previous_record.value, previous_record.value_len);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void write_at_offset(const char *key, const void *buffer,
|
||||
int buf_len, unsigned int sector_offset)
|
||||
{
|
||||
int key_len = strlen(key) + 1;
|
||||
unsigned int record_size = key_len + buf_len + sizeof(record_size);
|
||||
unsigned int flash_addr = (unsigned int)STORAGE_ADDRESS + sector_offset;
|
||||
|
||||
write_to_flash(flash_addr, (unsigned char *)&record_size, sizeof(record_size));
|
||||
write_to_flash(flash_addr+sizeof(record_size), (unsigned char *)key, key_len);
|
||||
write_to_flash(flash_addr+sizeof(record_size)+key_len, buffer, buf_len);
|
||||
flush_cpu_dcache();
|
||||
}
|
||||
|
||||
|
||||
int fs_write(const char *key, const void *buffer, unsigned int buf_len)
|
||||
{
|
||||
struct record record;
|
||||
unsigned int key_size = strlen(key) + 1;
|
||||
unsigned int new_record_size = key_size + sizeof(int) + buf_len;
|
||||
int no_error, fatal = 0;
|
||||
struct iter_state is;
|
||||
|
||||
record_iter_init(&is, STORAGE_ADDRESS, STORAGE_SIZE);
|
||||
while((no_error = record_iter_next(&is, &record, &fatal)));
|
||||
|
||||
if(fatal)
|
||||
goto fatal_error;
|
||||
|
||||
if(STORAGE_SIZE - is.seek >= new_record_size) {
|
||||
write_at_offset(key, buffer, buf_len, is.seek);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(!try_to_flush_duplicates(key, buf_len)) // storage is full, let's try to free some space up.
|
||||
return 0; // No duplicates found, cannot write the new key-value record: sector is full.
|
||||
// Now retrying to write, hoping enough flash was freed.
|
||||
|
||||
record_iter_init(&is, STORAGE_ADDRESS, STORAGE_SIZE);
|
||||
while((no_error = record_iter_next(&is, &record, &fatal)));
|
||||
|
||||
if(fatal)
|
||||
goto fatal_error;
|
||||
|
||||
if(STORAGE_SIZE - is.seek >= new_record_size) {
|
||||
write_at_offset(key, buffer, buf_len, is.seek);
|
||||
return 1; // We eventually succeeded in writing the record
|
||||
} else
|
||||
return 0; // Storage is definitely full.
|
||||
|
||||
fatal_error:
|
||||
// core_log("fatal error: flash storage might be corrupted\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void fs_erase(void)
|
||||
{
|
||||
erase_flash_sector((unsigned int)STORAGE_ADDRESS);
|
||||
flush_cpu_dcache();
|
||||
}
|
||||
|
||||
unsigned int fs_read(const char *key, void *buffer, unsigned int buf_len, unsigned int *remain)
|
||||
{
|
||||
unsigned int read_length = 0;
|
||||
struct iter_state is;
|
||||
struct record record;
|
||||
int fatal = 0;
|
||||
|
||||
if(remain)
|
||||
*remain = 0;
|
||||
|
||||
record_iter_init(&is, STORAGE_ADDRESS, STORAGE_SIZE);
|
||||
while(record_iter_next(&is, &record, &fatal)) {
|
||||
if(strcmp(record.key, key) == 0) {
|
||||
memcpy(buffer, record.value, min(record.value_len, buf_len));
|
||||
read_length = min(record.value_len, buf_len);
|
||||
if(remain)
|
||||
*remain = max(0, (int)record.value_len - (int)buf_len);
|
||||
}
|
||||
}
|
||||
|
||||
// if(fatal)
|
||||
// core_log("fatal error: flash storage might be corrupted\n");
|
||||
|
||||
return read_length;
|
||||
}
|
||||
|
||||
void fs_remove(const char *key)
|
||||
{
|
||||
fs_write(key, NULL, 0);
|
||||
}
|
||||
|
||||
#endif /* CSR_SPIFLASH_BASE && CONFIG_SPIFLASH_PAGE_SIZE */
|
@ -1,13 +0,0 @@
|
||||
/*
|
||||
* Yann Sionneau <ys@m-labs.hk>, 2015
|
||||
*/
|
||||
|
||||
#ifndef __FLASH_STORAGE_H
|
||||
#define __FLASH_STORAGE_H
|
||||
|
||||
void fs_remove(const char *key);
|
||||
void fs_erase(void);
|
||||
int fs_write(const char *key, const void *buffer, unsigned int buflen);
|
||||
unsigned int fs_read(const char *key, void *buffer, unsigned int buflen, unsigned int *remain);
|
||||
|
||||
#endif /* __FLASH_STORAGE_H */
|
Loading…
Reference in New Issue
Block a user