Skip to content
Snippets Groups Projects
syscall.rs 6.04 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 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.
    ///
    /// # Safety
    ///
    /// The `storage` must be readable.
Jean-Michel Picod's avatar
Jean-Michel Picod committed
    ///
    /// # Errors
    ///
    /// Returns `BadFlash` if any of the following conditions do not hold:
    /// - The word size is not a power of two.
    /// - The page size is not a power of two.
    /// - The page size is not a multiple of the word size.
    ///
    /// Returns `NotAligned` if any of the following conditions do not hold:
    /// - `storage` is page-aligned.
    /// - `storage.len()` is a multiple of the page size.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # extern crate ctap2;
    /// # use ctap2::embedded_flash::SyscallStorage;
    /// # use ctap2::embedded_flash::StorageResult;
    /// # const STORAGE_ADDR: usize = 0x1000;
    /// # const STORAGE_SIZE: usize = 0x1000;
Jean-Michel Picod's avatar
Jean-Michel Picod committed
    /// # fn foo() -> StorageResult<SyscallStorage> {
    /// // This is safe because we create and use `storage` only once in the whole program.
    /// let storage = unsafe {
    ///     core::slice::from_raw_parts_mut(STORAGE_ADDR as *mut u8, STORAGE_SIZE)
    /// };
    /// // This is safe because `storage` is readable.
    /// unsafe { SyscallStorage::new(storage) }
Jean-Michel Picod's avatar
Jean-Michel Picod committed
    /// # }
    /// ```
    pub unsafe fn new(storage: &'static mut [u8]) -> StorageResult<SyscallStorage> {
        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)?;
        if !word_size.is_power_of_two() || !page_size.is_power_of_two() {
            return Err(StorageError::BadFlash);
        }
        let syscall = SyscallStorage {
            word_size,
            page_size,
            max_word_writes,
            max_page_erases,
            storage,
        };
        if !syscall.is_word_aligned(page_size) {
            return Err(StorageError::BadFlash);
        }
        if syscall.is_page_aligned(syscall.storage.as_ptr() as usize)
            && syscall.is_page_aligned(syscall.storage.len())
        {
            Ok(syscall)
        } else {
            Err(StorageError::NotAligned)
        }
    }

    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(())
    }
}