// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>

//! BSP Memory Management.
//!
//! The physical memory layout after the kernel has been loaded by the Raspberry's firmware, which
//! copies the binary to 0x8_0000:
//!
//! +---------------------------------------------+
//! |                                             | 0x0
//! | Unmapped                                    |
//! |                                             | 0x6_FFFF
//! +---------------------------------------------+
//! | BOOT_CORE_STACK_START                       | 0x7_0000
//! |                                             |            ^
//! | ...                                         |            | Stack growth direction
//! |                                             |            |
//! | BOOT_CORE_STACK_END_INCLUSIVE               | 0x7_FFFF
//! +---------------------------------------------+
//! | RO_START == BOOT_CORE_STACK_END             | 0x8_0000
//! |                                             |
//! |                                             |
//! | .text                                       |
//! | .exception_vectors                          |
//! | .rodata                                     |
//! |                                             |
//! | RO_END_INCLUSIVE                            | 0x8_0000 + __ro_size - 1
//! +---------------------------------------------+
//! | RO_END == DATA_START                        | 0x8_0000 + __ro_size
//! |                                             |
//! | .data                                       |
//! | .bss                                        |
//! |                                             |
//! | DATA_END_INCLUSIVE                          | 0x8_0000 + __ro_size + __data_size - 1
//! +---------------------------------------------+

pub mod mmu;

use crate::memory::mmu::{Address, Physical, Virtual};
use core::ops::Range;

//--------------------------------------------------------------------------------------------------
// Private Definitions
//--------------------------------------------------------------------------------------------------

// Symbols from the linker script.
extern "C" {
    static __ro_start: usize;
    static __ro_size: usize;
    static __bss_start: usize;
    static __bss_end: usize;
    static __data_size: usize;
}

//--------------------------------------------------------------------------------------------------
// Public Definitions
//--------------------------------------------------------------------------------------------------

/// The board's physical memory map.
#[rustfmt::skip]
pub(super) mod map {
    use super::*;

    pub const BOOT_CORE_STACK_SIZE:                  usize = 0x1_0000;

    /// Physical devices.
    #[cfg(feature = "bsp_rpi3")]
    pub mod mmio {
        use super::*;

        pub const PERIPHERAL_IC_START: Address<Physical> = Address::new(0x3F00_B200);
        pub const PERIPHERAL_IC_SIZE:  usize             =              0x24;

        pub const GPIO_START:          Address<Physical> = Address::new(0x3F20_0000);
        pub const GPIO_SIZE:           usize             =              0xA0;

        pub const PL011_UART_START:    Address<Physical> = Address::new(0x3F20_1000);
        pub const PL011_UART_SIZE:     usize             =              0x48;

        pub const LOCAL_IC_START:      Address<Physical> = Address::new(0x4000_0000);
        pub const LOCAL_IC_SIZE:       usize             =              0x100;

        pub const END:                 Address<Physical> = Address::new(0x4001_0000);
    }

    /// Physical devices.
    #[cfg(feature = "bsp_rpi4")]
    pub mod mmio {
        use super::*;

        pub const GPIO_START:       Address<Physical> = Address::new(0xFE20_0000);
        pub const GPIO_SIZE:        usize             =              0xA0;

        pub const PL011_UART_START: Address<Physical> = Address::new(0xFE20_1000);
        pub const PL011_UART_SIZE:  usize             =              0x48;

        pub const GICD_START:       Address<Physical> = Address::new(0xFF84_1000);
        pub const GICD_SIZE:        usize             =              0x824;

        pub const GICC_START:       Address<Physical> = Address::new(0xFF84_2000);
        pub const GICC_SIZE:        usize             =              0x14;

        pub const END:              Address<Physical> = Address::new(0xFF85_0000);
    }

    pub const END: Address<Physical> = mmio::END;
}

//--------------------------------------------------------------------------------------------------
// Private Code
//--------------------------------------------------------------------------------------------------

/// Start address of the Read-Only (RO) range.
///
/// # Safety
///
/// - Value is provided by the linker script and must be trusted as-is.
#[inline(always)]
fn virt_ro_start() -> Address<Virtual> {
    Address::new(unsafe { &__ro_start as *const _ as usize })
}

/// Size of the Read-Only (RO) range of the kernel binary.
///
/// # Safety
///
/// - Value is provided by the linker script and must be trusted as-is.
#[inline(always)]
fn ro_size() -> usize {
    unsafe { &__ro_size as *const _ as usize }
}

/// Start address of the data range.
#[inline(always)]
fn virt_data_start() -> Address<Virtual> {
    virt_ro_start() + ro_size()
}

/// Size of the data range.
///
/// # Safety
///
/// - Value is provided by the linker script and must be trusted as-is.
#[inline(always)]
fn data_size() -> usize {
    unsafe { &__data_size as *const _ as usize }
}

/// Start address of the boot core's stack.
#[inline(always)]
fn virt_boot_core_stack_start() -> Address<Virtual> {
    virt_ro_start() - map::BOOT_CORE_STACK_SIZE
}

/// Size of the boot core's stack.
#[inline(always)]
fn boot_core_stack_size() -> usize {
    map::BOOT_CORE_STACK_SIZE
}

/// Exclusive end address of the physical address space.
#[inline(always)]
fn phys_addr_space_end() -> Address<Physical> {
    map::END
}

//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------

/// Exclusive end address of the boot core's stack.
#[inline(always)]
pub fn phys_boot_core_stack_end() -> Address<Physical> {
    // The binary is still identity mapped, so we don't need to convert here.
    let end = virt_boot_core_stack_start().into_usize() + boot_core_stack_size();
    Address::new(end)
}

/// Return the range spanning the .bss section.
///
/// # Safety
///
/// - Values are provided by the linker script and must be trusted as-is.
/// - The linker-provided addresses must be u64 aligned.
pub fn bss_range() -> Range<*mut u64> {
    unsafe {
        Range {
            start: &__bss_start as *const _ as *mut u64,
            end: &__bss_end as *const _ as *mut u64,
        }
    }
}
