Skip to content
Snippets Groups Projects
syscall.rs 5.55 KiB
Newer Older
Jean-Michel Picod's avatar
Jean-Michel Picod committed
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use super::{Index, Storage, StorageError, StorageResult};
use libtock::syscalls;

const DRIVER_NUMBER: usize = 0x50003;

mod command_nr {
    pub const GET_INFO: usize = 1;
    pub mod get_info_nr {
        pub const WORD_SIZE: usize = 0;
        pub const PAGE_SIZE: usize = 1;
        pub const MAX_WORD_WRITES: usize = 2;
        pub const MAX_PAGE_ERASES: usize = 3;
        pub const STORAGE_PTR: usize = 4;
        pub const STORAGE_LEN: usize = 5;
Jean-Michel Picod's avatar
Jean-Michel Picod committed
    }
    pub const WRITE_SLICE: usize = 2;
    pub const ERASE_PAGE: usize = 3;
}

mod allow_nr {
    pub const WRITE_SLICE: usize = 0;
}

fn get_info(nr: usize) -> StorageResult<usize> {
    let code = unsafe { syscalls::command(DRIVER_NUMBER, command_nr::GET_INFO, nr, 0) };
    if code < 0 {
        Err(StorageError::KernelError { code })
    } else {
        Ok(code as usize)
    }
}

pub struct SyscallStorage {
    word_size: usize,
    page_size: usize,
    max_word_writes: usize,
    max_page_erases: usize,
    storage: &'static mut [u8],
}

impl SyscallStorage {
    /// Provides access to the embedded flash if available.
    ///
    /// # Errors
    ///
    /// Returns `BadFlash` if any of the following conditions do not hold:
    /// - The word size is a power of two.
    /// - The page size is a power of two.
    /// - The page size is a multiple of the word size.
    /// - The storage is page-aligned.
Jean-Michel Picod's avatar
Jean-Michel Picod committed
    ///
    /// Returns `OutOfBounds` the number of pages does not fit in the storage.
    pub fn new(num_pages: usize) -> StorageResult<SyscallStorage> {
Jean-Michel Picod's avatar
Jean-Michel Picod committed
        let word_size = get_info(command_nr::get_info_nr::WORD_SIZE)?;
        let page_size = get_info(command_nr::get_info_nr::PAGE_SIZE)?;
        let max_word_writes = get_info(command_nr::get_info_nr::MAX_WORD_WRITES)?;
        let max_page_erases = get_info(command_nr::get_info_nr::MAX_PAGE_ERASES)?;
        let storage_ptr = get_info(command_nr::get_info_nr::STORAGE_PTR)?;
        let max_storage_len = get_info(command_nr::get_info_nr::STORAGE_LEN)?;
Jean-Michel Picod's avatar
Jean-Michel Picod committed
        if !word_size.is_power_of_two() || !page_size.is_power_of_two() {
            return Err(StorageError::BadFlash);
        }
        let storage_len = num_pages * page_size;
        if storage_len > max_storage_len {
            return Err(StorageError::OutOfBounds);
        }
        let storage =
            unsafe { core::slice::from_raw_parts_mut(storage_ptr as *mut u8, storage_len) };
Jean-Michel Picod's avatar
Jean-Michel Picod committed
        let syscall = SyscallStorage {
            word_size,
            page_size,
            max_word_writes,
            max_page_erases,
            storage,
        };
        if !syscall.is_word_aligned(page_size) || !syscall.is_page_aligned(storage_ptr) {
Jean-Michel Picod's avatar
Jean-Michel Picod committed
            return Err(StorageError::BadFlash);
        }
Jean-Michel Picod's avatar
Jean-Michel Picod committed
    }

    fn is_word_aligned(&self, x: usize) -> bool {
        x & (self.word_size - 1) == 0
    }

    fn is_page_aligned(&self, x: usize) -> bool {
        x & (self.page_size - 1) == 0
    }
}

impl Storage for SyscallStorage {
    fn word_size(&self) -> usize {
        self.word_size
    }

    fn page_size(&self) -> usize {
        self.page_size
    }

    fn num_pages(&self) -> usize {
        self.storage.len() / self.page_size
    }

    fn max_word_writes(&self) -> usize {
        self.max_word_writes
    }

    fn max_page_erases(&self) -> usize {
        self.max_page_erases
    }

    fn read_slice(&self, index: Index, length: usize) -> StorageResult<&[u8]> {
        Ok(&self.storage[index.range(length, self)?])
    }

    fn write_slice(&mut self, index: Index, value: &[u8]) -> StorageResult<()> {
        if !self.is_word_aligned(index.byte) || !self.is_word_aligned(value.len()) {
            return Err(StorageError::NotAligned);
        }
        let range = index.range(value.len(), self)?;
        let code = unsafe {
            syscalls::allow_ptr(
                DRIVER_NUMBER,
                allow_nr::WRITE_SLICE,
                // We rely on the driver not writing to the slice. This should use read-only allow
                // when available. See https://github.com/tock/tock/issues/1274.
                value.as_ptr() as *mut u8,
                value.len(),
            )
        };
        if code < 0 {
            return Err(StorageError::KernelError { code });
        }
        let code = unsafe {
            syscalls::command(
                DRIVER_NUMBER,
                command_nr::WRITE_SLICE,
                self.storage[range].as_ptr() as usize,
                0,
            )
        };
        if code < 0 {
            return Err(StorageError::KernelError { code });
        }
        Ok(())
    }

    fn erase_page(&mut self, page: usize) -> StorageResult<()> {
        let range = Index { page, byte: 0 }.range(self.page_size(), self)?;
        let code = unsafe {
            syscalls::command(
                DRIVER_NUMBER,
                command_nr::ERASE_PAGE,
                self.storage[range].as_ptr() as usize,
                0,
            )
        };
        if code < 0 {
            return Err(StorageError::KernelError { code });
        }
        Ok(())
    }
}