You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
956 lines
23 KiB
956 lines
23 KiB
/* |
|
* Copyright (c) 2021 Intel Corporation |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
/** |
|
* @file |
|
* @brief Driver to utilize TLB on Intel Audio DSP |
|
* |
|
* TLB (Translation Lookup Buffer) table is used to map between |
|
* physical and virtual memory. This is global to all cores |
|
* on the DSP, as changes to the TLB table are visible to |
|
* all cores. |
|
* |
|
* Note that all passed in addresses should be in cached range |
|
* (aka cached addresses). Due to the need to calculate TLB |
|
* indexes, virtual addresses will be converted internally to |
|
* cached one via sys_cache_cached_ptr_get(). However, physical addresses |
|
* are untouched. |
|
*/ |
|
|
|
#include "mm_drv_intel_adsp.h" |
|
#include <soc_util.h> |
|
#include <zephyr/drivers/mm/mm_drv_intel_adsp_mtl_tlb.h> |
|
#include <zephyr/drivers/mm/mm_drv_bank.h> |
|
#include <zephyr/debug/sparse.h> |
|
#include <zephyr/cache.h> |
|
#include <kernel_arch_interface.h> |
|
|
|
#define SRAM_BANK_PAGE_NUM (SRAM_BANK_SIZE / CONFIG_MM_DRV_PAGE_SIZE) |
|
|
|
static struct k_spinlock tlb_lock; |
|
extern struct k_spinlock sys_mm_drv_common_lock; |
|
|
|
static struct sys_mm_drv_bank hpsram_bank[L2_SRAM_BANK_NUM]; |
|
|
|
#ifdef CONFIG_SOC_INTEL_COMM_WIDGET |
|
#include <adsp_comm_widget.h> |
|
|
|
static uint32_t used_pages; |
|
/* PMC uses 32 KB banks */ |
|
static uint32_t used_pmc_banks_reported; |
|
#endif |
|
|
|
|
|
/* Define a marker which is placed by the linker script just after |
|
* last explicitly defined section. All .text, .data, .bss and .heap |
|
* sections should be placed before this marker in the memory. |
|
* This driver is using the location of the marker to |
|
* unmap the unused L2 memory and power off corresponding memory banks. |
|
*/ |
|
__attribute__((__section__(".unused_ram_start_marker"))) |
|
static int unused_l2_sram_start_marker = 0xba0babce; |
|
#define UNUSED_L2_START_ALIGNED ROUND_UP(POINTER_TO_UINT(&unused_l2_sram_start_marker), \ |
|
CONFIG_MM_DRV_PAGE_SIZE) |
|
|
|
/* declare L2 physical memory block */ |
|
SYS_MEM_BLOCKS_DEFINE_WITH_EXT_BUF( |
|
L2_PHYS_SRAM_REGION, |
|
CONFIG_MM_DRV_PAGE_SIZE, |
|
L2_SRAM_PAGES_NUM, |
|
(uint8_t *) L2_SRAM_BASE); |
|
|
|
/** |
|
* Calculate the index to the TLB table. |
|
* |
|
* @param vaddr Page-aligned virutal address. |
|
* @return Index to the TLB table. |
|
*/ |
|
static uint32_t get_tlb_entry_idx(uintptr_t vaddr) |
|
{ |
|
return (POINTER_TO_UINT(vaddr) - CONFIG_KERNEL_VM_BASE) / |
|
CONFIG_MM_DRV_PAGE_SIZE; |
|
} |
|
|
|
/** |
|
* Calculate the index of the HPSRAM bank. |
|
* |
|
* @param pa physical address. |
|
* @return Index of the HPSRAM bank. |
|
*/ |
|
static uint32_t get_hpsram_bank_idx(uintptr_t pa) |
|
{ |
|
uint32_t phys_offset = pa - L2_SRAM_BASE; |
|
|
|
return (phys_offset / SRAM_BANK_SIZE); |
|
} |
|
|
|
/** |
|
* Convert the SYS_MM_MEM_PERM_* flags into TLB entry permission bits. |
|
* |
|
* @param flags Access flags (SYS_MM_MEM_PERM_*) |
|
* @return TLB entry permission bits |
|
*/ |
|
static uint16_t flags_to_tlb_perms(uint32_t flags) |
|
{ |
|
#if defined(CONFIG_SOC_SERIES_INTEL_ADSP_ACE) |
|
uint16_t perms = 0; |
|
|
|
if ((flags & SYS_MM_MEM_PERM_RW) == SYS_MM_MEM_PERM_RW) { |
|
perms |= TLB_WRITE_BIT; |
|
} |
|
|
|
if ((flags & SYS_MM_MEM_PERM_EXEC) == SYS_MM_MEM_PERM_EXEC) { |
|
perms |= TLB_EXEC_BIT; |
|
} |
|
|
|
return perms; |
|
#else |
|
return 0; |
|
#endif |
|
} |
|
|
|
#if defined(CONFIG_SOC_SERIES_INTEL_ADSP_ACE) |
|
/** |
|
* Convert TLB entry permission bits to the SYS_MM_MEM_PERM_* flags. |
|
* |
|
* @param perms TLB entry permission bits |
|
* @return Access flags (SYS_MM_MEM_PERM_*) |
|
*/ |
|
static uint16_t tlb_perms_to_flags(uint16_t perms) |
|
{ |
|
uint32_t flags = 0; |
|
|
|
if ((perms & TLB_WRITE_BIT) == TLB_WRITE_BIT) { |
|
flags |= SYS_MM_MEM_PERM_RW; |
|
} |
|
|
|
if ((perms & TLB_EXEC_BIT) == TLB_EXEC_BIT) { |
|
flags |= SYS_MM_MEM_PERM_EXEC; |
|
} |
|
|
|
return flags; |
|
} |
|
#endif |
|
|
|
static int sys_mm_drv_hpsram_pwr(uint32_t bank_idx, bool enable, bool non_blocking) |
|
{ |
|
#if defined(CONFIG_SOC_SERIES_INTEL_ADSP_ACE) |
|
if (bank_idx > ace_hpsram_get_bank_count()) { |
|
return -1; |
|
} |
|
|
|
HPSRAM_REGS(bank_idx)->HSxPGCTL = !enable; |
|
|
|
if (!non_blocking) { |
|
while (HPSRAM_REGS(bank_idx)->HSxPGISTS == enable) { |
|
k_busy_wait(1); |
|
} |
|
} |
|
#endif |
|
return 0; |
|
} |
|
|
|
#ifdef CONFIG_SOC_INTEL_COMM_WIDGET |
|
static void sys_mm_drv_report_page_usage(void) |
|
{ |
|
/* PMC uses 32 KB banks */ |
|
uint32_t pmc_banks = DIV_ROUND_UP(used_pages, KB(32) / CONFIG_MM_DRV_PAGE_SIZE); |
|
|
|
if (used_pmc_banks_reported != pmc_banks) { |
|
if (!adsp_comm_widget_pmc_send_ipc(pmc_banks)) { |
|
/* Store reported value if message was sent successfully. */ |
|
used_pmc_banks_reported = pmc_banks; |
|
} |
|
} |
|
} |
|
#endif |
|
|
|
int sys_mm_drv_map_page(void *virt, uintptr_t phys, uint32_t flags) |
|
{ |
|
k_spinlock_key_t key; |
|
uint32_t entry_idx, bank_idx; |
|
uint16_t entry; |
|
volatile uint16_t *tlb_entries = UINT_TO_POINTER(TLB_BASE); |
|
int ret = 0; |
|
void *phys_block_ptr; |
|
|
|
/* |
|
* Cached addresses for both physical and virtual. |
|
* |
|
* As the main memory is in cached address ranges, |
|
* the cached physical address is needed to perform |
|
* bound check. |
|
*/ |
|
uintptr_t pa = POINTER_TO_UINT(sys_cache_cached_ptr_get(UINT_TO_POINTER(phys))); |
|
uintptr_t va = POINTER_TO_UINT(sys_cache_cached_ptr_get(virt)); |
|
|
|
/* Make sure VA is page-aligned */ |
|
CHECKIF(!sys_mm_drv_is_addr_aligned(va)) { |
|
ret = -EINVAL; |
|
goto out; |
|
} |
|
|
|
/* Check bounds of virtual address space */ |
|
CHECKIF((va < UNUSED_L2_START_ALIGNED) || |
|
(va >= (CONFIG_KERNEL_VM_BASE + CONFIG_KERNEL_VM_SIZE))) { |
|
ret = -EINVAL; |
|
goto out; |
|
} |
|
|
|
/* |
|
* When the provided physical address is NULL |
|
* then it is a signal to the Intel ADSP TLB driver to |
|
* select the first available free physical address |
|
* autonomously within the driver. |
|
*/ |
|
if (UINT_TO_POINTER(phys) == NULL) { |
|
ret = sys_mem_blocks_alloc_contiguous(&L2_PHYS_SRAM_REGION, 1, |
|
&phys_block_ptr); |
|
if (ret != 0) { |
|
__ASSERT(false, |
|
"unable to assign free phys page %d\n", ret); |
|
goto out; |
|
} |
|
pa = POINTER_TO_UINT(sys_cache_cached_ptr_get(phys_block_ptr)); |
|
} |
|
|
|
/* Check bounds of physical address space */ |
|
CHECKIF((pa < L2_SRAM_BASE) || |
|
(pa >= (L2_SRAM_BASE + L2_SRAM_SIZE))) { |
|
ret = -EINVAL; |
|
goto out; |
|
} |
|
|
|
/* Make sure PA is page-aligned */ |
|
CHECKIF(!sys_mm_drv_is_addr_aligned(pa)) { |
|
ret = -EINVAL; |
|
goto out; |
|
} |
|
|
|
key = k_spin_lock(&tlb_lock); |
|
|
|
entry_idx = get_tlb_entry_idx(va); |
|
|
|
#ifdef CONFIG_SOC_INTEL_COMM_WIDGET |
|
used_pages++; |
|
sys_mm_drv_report_page_usage(); |
|
#endif |
|
|
|
bank_idx = get_hpsram_bank_idx(pa); |
|
if (sys_mm_drv_bank_page_mapped(&hpsram_bank[bank_idx]) == 1) { |
|
sys_mm_drv_hpsram_pwr(bank_idx, true, false); |
|
} |
|
|
|
/* |
|
* The address part of the TLB entry takes the lowest |
|
* TLB_PADDR_SIZE bits of the physical page number, |
|
* and discards the highest bits. This is due to the |
|
* architecture design where the same physical page |
|
* can be accessed via two addresses. One address goes |
|
* through the cache, and the other one accesses |
|
* memory directly (without cache). The difference |
|
* between these two addresses are in the higher bits, |
|
* and the lower bits are the same. And this is why |
|
* TLB only cares about the lower part of the physical |
|
* address. |
|
*/ |
|
entry = pa_to_tlb_entry(pa); |
|
|
|
/* Enable the translation in the TLB entry */ |
|
entry |= TLB_ENABLE_BIT; |
|
|
|
/* Set permissions for this entry */ |
|
entry |= flags_to_tlb_perms(flags); |
|
|
|
tlb_entries[entry_idx] = entry; |
|
|
|
#ifdef CONFIG_MMU |
|
arch_mem_map(virt, va, CONFIG_MM_DRV_PAGE_SIZE, flags); |
|
#endif |
|
/* |
|
* Invalid the cache of the newly mapped virtual page to |
|
* avoid stale data. |
|
*/ |
|
sys_cache_data_invd_range(virt, CONFIG_MM_DRV_PAGE_SIZE); |
|
|
|
k_spin_unlock(&tlb_lock, key); |
|
|
|
out: |
|
return ret; |
|
} |
|
|
|
int sys_mm_drv_map_region(void *virt, uintptr_t phys, |
|
size_t size, uint32_t flags) |
|
{ |
|
k_spinlock_key_t key; |
|
int ret = 0; |
|
size_t offset; |
|
uintptr_t pa; |
|
uint8_t *va; |
|
|
|
CHECKIF(!sys_mm_drv_is_addr_aligned(phys) || |
|
!sys_mm_drv_is_virt_addr_aligned(virt) || |
|
!sys_mm_drv_is_size_aligned(size)) { |
|
ret = -EINVAL; |
|
goto out; |
|
} |
|
|
|
va = (__sparse_force uint8_t *)sys_cache_cached_ptr_get(virt); |
|
pa = phys; |
|
|
|
key = k_spin_lock(&sys_mm_drv_common_lock); |
|
|
|
for (offset = 0; offset < size; offset += CONFIG_MM_DRV_PAGE_SIZE) { |
|
int ret2 = sys_mm_drv_map_page(va, pa, flags); |
|
|
|
if (ret2 != 0) { |
|
__ASSERT(false, "cannot map 0x%lx to %p\n", pa, va); |
|
|
|
ret = ret2; |
|
} |
|
va += CONFIG_MM_DRV_PAGE_SIZE; |
|
if (phys != 0) { |
|
pa += CONFIG_MM_DRV_PAGE_SIZE; |
|
} |
|
} |
|
|
|
k_spin_unlock(&sys_mm_drv_common_lock, key); |
|
|
|
out: |
|
return ret; |
|
} |
|
|
|
int sys_mm_drv_map_array(void *virt, uintptr_t *phys, |
|
size_t cnt, uint32_t flags) |
|
{ |
|
void *va = (__sparse_force void *)sys_cache_cached_ptr_get(virt); |
|
|
|
return sys_mm_drv_simple_map_array(va, phys, cnt, flags); |
|
} |
|
|
|
static int sys_mm_drv_unmap_page_wflush(void *virt, bool flush_data) |
|
{ |
|
k_spinlock_key_t key; |
|
uint32_t entry_idx, bank_idx; |
|
uint16_t *tlb_entries = UINT_TO_POINTER(TLB_BASE); |
|
uint16_t entry; |
|
uintptr_t pa; |
|
int ret = 0; |
|
|
|
/* Use cached virtual address */ |
|
uintptr_t va = POINTER_TO_UINT(sys_cache_cached_ptr_get(virt)); |
|
|
|
/* Check bounds of virtual address space */ |
|
CHECKIF((va < UNUSED_L2_START_ALIGNED) || |
|
(va >= (CONFIG_KERNEL_VM_BASE + CONFIG_KERNEL_VM_SIZE))) { |
|
ret = -EINVAL; |
|
goto out; |
|
} |
|
|
|
/* Make sure inputs are page-aligned */ |
|
CHECKIF(!sys_mm_drv_is_addr_aligned(va)) { |
|
ret = -EINVAL; |
|
goto out; |
|
} |
|
|
|
key = k_spin_lock(&tlb_lock); |
|
|
|
entry_idx = get_tlb_entry_idx(va); |
|
entry = tlb_entries[entry_idx]; |
|
|
|
/* Check if the translation is enabled in the TLB entry. |
|
* Attempt to flush the cache of an inactive address will result in a cpu exception. |
|
*/ |
|
if (!(entry & TLB_ENABLE_BIT)) { |
|
ret = -EFAULT; |
|
goto out_unlock; |
|
} |
|
|
|
/* |
|
* Flush the cache to make sure the backing physical page |
|
* has the latest data. |
|
* No flush when called from sys_mm_drv_mm_init(). |
|
*/ |
|
if (flush_data) { |
|
sys_cache_data_flush_range(virt, CONFIG_MM_DRV_PAGE_SIZE); |
|
#ifdef CONFIG_MMU |
|
arch_mem_unmap(virt, CONFIG_MM_DRV_PAGE_SIZE); |
|
#endif |
|
} |
|
|
|
pa = tlb_entry_to_pa(entry); |
|
|
|
/* Restore default entry settings with cleared the enable bit. */ |
|
tlb_entries[entry_idx] = 0; |
|
|
|
/* Check bounds of physical address space. |
|
* Initial TLB mappings could point to non existing physical pages. |
|
*/ |
|
if ((pa >= L2_SRAM_BASE) && (pa < (L2_SRAM_BASE + L2_SRAM_SIZE))) { |
|
sys_mem_blocks_free_contiguous(&L2_PHYS_SRAM_REGION, |
|
UINT_TO_POINTER(pa), 1); |
|
|
|
bank_idx = get_hpsram_bank_idx(pa); |
|
#ifdef CONFIG_SOC_INTEL_COMM_WIDGET |
|
used_pages--; |
|
sys_mm_drv_report_page_usage(); |
|
#endif |
|
|
|
if (sys_mm_drv_bank_page_unmapped(&hpsram_bank[bank_idx]) == SRAM_BANK_PAGE_NUM) { |
|
sys_mm_drv_hpsram_pwr(bank_idx, false, false); |
|
} |
|
} |
|
|
|
out_unlock: |
|
k_spin_unlock(&tlb_lock, key); |
|
|
|
out: |
|
return ret; |
|
} |
|
|
|
int sys_mm_drv_unmap_page(void *virt) |
|
{ |
|
return sys_mm_drv_unmap_page_wflush(virt, true); |
|
} |
|
|
|
int sys_mm_drv_unmap_region(void *virt, size_t size) |
|
{ |
|
void *va = (__sparse_force void *)sys_cache_cached_ptr_get(virt); |
|
|
|
return sys_mm_drv_simple_unmap_region(va, size); |
|
} |
|
|
|
int sys_mm_drv_update_page_flags(void *virt, uint32_t flags) |
|
{ |
|
k_spinlock_key_t key; |
|
uint32_t entry_idx; |
|
uint16_t entry; |
|
uint16_t *tlb_entries = UINT_TO_POINTER(TLB_BASE); |
|
int ret = 0; |
|
|
|
/* Use cached virtual address */ |
|
uintptr_t va = POINTER_TO_UINT(sys_cache_cached_ptr_get(virt)); |
|
|
|
/* Make sure inputs are page-aligned and check bounds of virtual address space */ |
|
CHECKIF(!sys_mm_drv_is_addr_aligned(va) || |
|
(va < UNUSED_L2_START_ALIGNED) || |
|
(va >= (CONFIG_KERNEL_VM_BASE + CONFIG_KERNEL_VM_SIZE))) { |
|
return -EINVAL; |
|
} |
|
|
|
key = k_spin_lock(&tlb_lock); |
|
|
|
entry_idx = get_tlb_entry_idx(va); |
|
|
|
entry = tlb_entries[entry_idx]; |
|
|
|
/* Check entry is already mapped */ |
|
if (!(entry & TLB_ENABLE_BIT)) { |
|
ret = -EFAULT; |
|
goto out; |
|
} |
|
|
|
/* Clear the access flags */ |
|
entry &= ~(TLB_EXEC_BIT | TLB_WRITE_BIT); |
|
|
|
/* Set new permissions for this entry */ |
|
entry |= flags_to_tlb_perms(flags); |
|
|
|
tlb_entries[entry_idx] = entry; |
|
|
|
#ifdef CONFIG_MMU |
|
arch_mem_map(virt, va, CONFIG_MM_DRV_PAGE_SIZE, flags); |
|
#endif |
|
|
|
out: |
|
k_spin_unlock(&tlb_lock, key); |
|
return ret; |
|
} |
|
|
|
#ifdef CONFIG_MM_DRV_INTEL_ADSP_TLB_REMAP_UNUSED_RAM |
|
static int sys_mm_drv_unmap_region_initial(void *virt_in, size_t size) |
|
{ |
|
void *virt = (__sparse_force void *)sys_cache_cached_ptr_get(virt_in); |
|
|
|
k_spinlock_key_t key; |
|
int ret = 0; |
|
size_t offset; |
|
|
|
CHECKIF(!sys_mm_drv_is_virt_addr_aligned(virt) || |
|
!sys_mm_drv_is_size_aligned(size)) { |
|
ret = -EINVAL; |
|
goto out; |
|
} |
|
|
|
key = k_spin_lock(&sys_mm_drv_common_lock); |
|
|
|
for (offset = 0; offset < size; offset += CONFIG_MM_DRV_PAGE_SIZE) { |
|
uint8_t *va = (uint8_t *)virt + offset; |
|
|
|
int ret2 = sys_mm_drv_unmap_page_wflush(va, false); |
|
|
|
/* -EFAULT means that this page is not mapped. |
|
* This is not an error since we want to unmap all virtual memory without knowing |
|
* which pages are mapped. |
|
*/ |
|
if (ret2 != 0 && ret2 != -EFAULT) { |
|
__ASSERT(false, "cannot unmap %p\n", va); |
|
|
|
ret = ret2; |
|
} |
|
} |
|
|
|
k_spin_unlock(&sys_mm_drv_common_lock, key); |
|
|
|
out: |
|
return ret; |
|
} |
|
#endif |
|
|
|
int sys_mm_drv_page_phys_get(void *virt, uintptr_t *phys) |
|
{ |
|
uint16_t *tlb_entries = UINT_TO_POINTER(TLB_BASE); |
|
uintptr_t ent; |
|
int ret = 0; |
|
|
|
/* Use cached address */ |
|
uintptr_t va = POINTER_TO_UINT(sys_cache_cached_ptr_get(virt)); |
|
|
|
CHECKIF(!sys_mm_drv_is_addr_aligned(va)) { |
|
ret = -EINVAL; |
|
goto out; |
|
} |
|
|
|
/* Check bounds of virtual address space */ |
|
CHECKIF((va < CONFIG_KERNEL_VM_BASE) || |
|
(va >= (CONFIG_KERNEL_VM_BASE + CONFIG_KERNEL_VM_SIZE))) { |
|
ret = -EINVAL; |
|
goto out; |
|
} |
|
|
|
ent = tlb_entries[get_tlb_entry_idx(va)]; |
|
|
|
if ((ent & TLB_ENABLE_BIT) != TLB_ENABLE_BIT) { |
|
ret = -EFAULT; |
|
} else { |
|
if (phys != NULL) { |
|
*phys = (ent & TLB_PADDR_MASK) * |
|
CONFIG_MM_DRV_PAGE_SIZE + TLB_PHYS_BASE; |
|
} |
|
|
|
ret = 0; |
|
} |
|
|
|
out: |
|
return ret; |
|
} |
|
|
|
int sys_mm_drv_page_flag_get(void *virt, uint32_t *flags) |
|
{ |
|
ARG_UNUSED(virt); |
|
int ret = 0; |
|
|
|
#if defined(CONFIG_SOC_SERIES_INTEL_ADSP_ACE) |
|
uint16_t *tlb_entries = UINT_TO_POINTER(TLB_BASE); |
|
uint16_t ent; |
|
|
|
/* Use cached address */ |
|
uintptr_t va = POINTER_TO_UINT(sys_cache_cached_ptr_get(virt)); |
|
|
|
CHECKIF(!sys_mm_drv_is_addr_aligned(va)) { |
|
ret = -EINVAL; |
|
goto out; |
|
} |
|
|
|
/* Check bounds of virtual address space */ |
|
CHECKIF((va < CONFIG_KERNEL_VM_BASE) || |
|
(va >= (CONFIG_KERNEL_VM_BASE + CONFIG_KERNEL_VM_SIZE))) { |
|
ret = -EINVAL; |
|
goto out; |
|
} |
|
|
|
ent = tlb_entries[get_tlb_entry_idx(va)]; |
|
|
|
if ((ent & TLB_ENABLE_BIT) != TLB_ENABLE_BIT) { |
|
ret = -EFAULT; |
|
} else { |
|
*flags = tlb_perms_to_flags(ent); |
|
} |
|
|
|
out: |
|
#else |
|
/* |
|
* There are no caching mode, or R/W, or eXecution (etc.) bits. |
|
* So just return 0. |
|
*/ |
|
|
|
*flags = 0U; |
|
#endif |
|
|
|
return ret; |
|
} |
|
|
|
int sys_mm_drv_remap_region(void *virt_old, size_t size, |
|
void *virt_new) |
|
{ |
|
void *va_new = (__sparse_force void *)sys_cache_cached_ptr_get(virt_new); |
|
void *va_old = (__sparse_force void *)sys_cache_cached_ptr_get(virt_old); |
|
|
|
return sys_mm_drv_simple_remap_region(va_old, size, va_new); |
|
} |
|
|
|
int sys_mm_drv_move_region(void *virt_old, size_t size, void *virt_new, |
|
uintptr_t phys_new) |
|
{ |
|
k_spinlock_key_t key; |
|
size_t offset; |
|
int ret = 0; |
|
|
|
virt_new = (__sparse_force void *)sys_cache_cached_ptr_get(virt_new); |
|
virt_old = (__sparse_force void *)sys_cache_cached_ptr_get(virt_old); |
|
|
|
CHECKIF(!sys_mm_drv_is_virt_addr_aligned(virt_old) || |
|
!sys_mm_drv_is_virt_addr_aligned(virt_new) || |
|
!sys_mm_drv_is_size_aligned(size)) { |
|
ret = -EINVAL; |
|
goto out; |
|
} |
|
|
|
if ((POINTER_TO_UINT(virt_new) >= POINTER_TO_UINT(virt_old)) && |
|
(POINTER_TO_UINT(virt_new) < (POINTER_TO_UINT(virt_old) + size))) { |
|
ret = -EINVAL; /* overlaps */ |
|
goto out; |
|
} |
|
|
|
/* |
|
* The function's behavior has been updated to accept |
|
* phys_new == NULL and get the physical addresses from |
|
* the actual TLB instead of from the caller. |
|
*/ |
|
if (phys_new != POINTER_TO_UINT(NULL) && |
|
!sys_mm_drv_is_addr_aligned(phys_new)) { |
|
ret = -EINVAL; |
|
goto out; |
|
} |
|
|
|
key = k_spin_lock(&sys_mm_drv_common_lock); |
|
|
|
if (!sys_mm_drv_is_virt_region_mapped(virt_old, size) || |
|
!sys_mm_drv_is_virt_region_unmapped(virt_new, size)) { |
|
ret = -EINVAL; |
|
goto unlock_out; |
|
} |
|
|
|
for (offset = 0; offset < size; offset += CONFIG_MM_DRV_PAGE_SIZE) { |
|
uint8_t *va_old = (uint8_t *)virt_old + offset; |
|
uint8_t *va_new = (uint8_t *)virt_new + offset; |
|
uintptr_t pa; |
|
uint32_t flags; |
|
int ret2; |
|
|
|
ret2 = sys_mm_drv_page_flag_get(va_old, &flags); |
|
if (ret2 != 0) { |
|
__ASSERT(false, "cannot query page flags %p\n", va_old); |
|
|
|
ret = ret2; |
|
goto unlock_out; |
|
} |
|
|
|
ret2 = sys_mm_drv_page_phys_get(va_old, &pa); |
|
if (ret2 != 0) { |
|
__ASSERT(false, "cannot query page paddr %p\n", va_old); |
|
|
|
ret = ret2; |
|
goto unlock_out; |
|
} |
|
|
|
/* |
|
* Only map the new page when we can retrieve |
|
* flags and phys addr of the old mapped page as We don't |
|
* want to map with unknown random flags. |
|
*/ |
|
ret2 = sys_mm_drv_map_page(va_new, pa, flags); |
|
if (ret2 != 0) { |
|
__ASSERT(false, "cannot map 0x%lx to %p\n", pa, va_new); |
|
|
|
ret = ret2; |
|
} |
|
|
|
ret2 = sys_mm_drv_unmap_page(va_old); |
|
if (ret2 != 0) { |
|
__ASSERT(false, "cannot unmap %p\n", va_old); |
|
|
|
ret = ret2; |
|
} |
|
} |
|
|
|
unlock_out: |
|
k_spin_unlock(&sys_mm_drv_common_lock, key); |
|
|
|
out: |
|
/* |
|
* Since move is done in virtual space, need to |
|
* flush the cache to make sure the backing physical |
|
* pages have the new data. |
|
*/ |
|
sys_cache_data_flush_range(virt_new, size); |
|
sys_cache_data_flush_and_invd_range(virt_old, size); |
|
|
|
return ret; |
|
} |
|
|
|
int sys_mm_drv_move_array(void *virt_old, size_t size, void *virt_new, |
|
uintptr_t *phys_new, size_t phys_cnt) |
|
{ |
|
int ret; |
|
|
|
void *va_new = (__sparse_force void *)sys_cache_cached_ptr_get(virt_new); |
|
void *va_old = (__sparse_force void *)sys_cache_cached_ptr_get(virt_old); |
|
|
|
ret = sys_mm_drv_simple_move_array(va_old, size, va_new, |
|
phys_new, phys_cnt); |
|
|
|
/* |
|
* Since memcpy() is done in virtual space, need to |
|
* flush the cache to make sure the backing physical |
|
* pages have the new data. |
|
*/ |
|
sys_cache_data_flush_range(va_new, size); |
|
|
|
return ret; |
|
} |
|
|
|
static int sys_mm_drv_mm_init(const struct device *dev) |
|
{ |
|
int ret; |
|
|
|
ARG_UNUSED(dev); |
|
|
|
/* |
|
* Change size of avalible physical memory according to fw register information |
|
* in runtime. |
|
*/ |
|
|
|
uint32_t avalible_memory_size = ace_hpsram_get_bank_count() * SRAM_BANK_SIZE; |
|
|
|
L2_PHYS_SRAM_REGION.info.num_blocks = avalible_memory_size / CONFIG_MM_DRV_PAGE_SIZE; |
|
|
|
ret = calculate_memory_regions(UNUSED_L2_START_ALIGNED); |
|
CHECKIF(ret != 0) { |
|
return ret; |
|
} |
|
/* |
|
* Initialize memblocks that will store physical |
|
* page usage. Initially all physical pages are |
|
* mapped in linear way to virtual address space |
|
* so mark all pages as allocated. |
|
*/ |
|
|
|
ret = sys_mem_blocks_get(&L2_PHYS_SRAM_REGION, |
|
(void *) L2_SRAM_BASE, L2_SRAM_PAGES_NUM); |
|
CHECKIF(ret != 0) { |
|
return ret; |
|
} |
|
|
|
/* |
|
* Initialize refcounts for all HPSRAM banks |
|
* as fully used because entire HPSRAM is powered on |
|
* at system boot. Set reference count to a number |
|
* of pages within single memory bank. |
|
*/ |
|
for (int i = 0; i < L2_SRAM_BANK_NUM; i++) { |
|
sys_mm_drv_bank_init(&hpsram_bank[i], |
|
SRAM_BANK_PAGE_NUM); |
|
} |
|
#ifdef CONFIG_SOC_INTEL_COMM_WIDGET |
|
used_pages = L2_SRAM_BANK_NUM * SRAM_BANK_SIZE / CONFIG_MM_DRV_PAGE_SIZE; |
|
#endif |
|
|
|
#ifdef CONFIG_MM_DRV_INTEL_ADSP_TLB_REMAP_UNUSED_RAM |
|
/* |
|
* find virtual address range which are unused |
|
* in the system |
|
*/ |
|
if (L2_SRAM_BASE + L2_SRAM_SIZE < UNUSED_L2_START_ALIGNED || |
|
L2_SRAM_BASE > UNUSED_L2_START_ALIGNED) { |
|
|
|
__ASSERT(false, |
|
"unused l2 pointer is outside of l2 sram range %p\n", |
|
(void *)UNUSED_L2_START_ALIGNED); |
|
return -EFAULT; |
|
} |
|
|
|
/* |
|
* Unmap all unused physical pages from the entire |
|
* virtual address space to save power |
|
*/ |
|
size_t unused_size = CONFIG_KERNEL_VM_BASE + CONFIG_KERNEL_VM_SIZE - |
|
UNUSED_L2_START_ALIGNED; |
|
|
|
ret = sys_mm_drv_unmap_region_initial(UINT_TO_POINTER(UNUSED_L2_START_ALIGNED), |
|
unused_size); |
|
|
|
|
|
/* Need to reset max pages statistics after unmap */ |
|
for (int i = 0; i < L2_SRAM_BANK_NUM; i++) { |
|
sys_mm_drv_bank_stats_reset_max(&hpsram_bank[i]); |
|
} |
|
#endif |
|
|
|
/* |
|
* Notify PMC about used HP-SRAM pages. |
|
*/ |
|
#ifdef CONFIG_SOC_INTEL_COMM_WIDGET |
|
sys_mm_drv_report_page_usage(); |
|
#endif |
|
|
|
return 0; |
|
} |
|
|
|
static void adsp_mm_save_context(void *storage_buffer) |
|
{ |
|
uint16_t entry; |
|
uint32_t entry_idx; |
|
int page_idx; |
|
uint32_t phys_addr; |
|
volatile uint16_t *tlb_entries = UINT_TO_POINTER(TLB_BASE); |
|
uint8_t *location = (uint8_t *) storage_buffer; |
|
|
|
/* first, store the existing TLB */ |
|
memcpy(location, UINT_TO_POINTER(TLB_BASE), TLB_SIZE); |
|
location += TLB_SIZE; |
|
|
|
/* save context of all the pages */ |
|
for (page_idx = 0; page_idx < L2_SRAM_PAGES_NUM; page_idx++) { |
|
phys_addr = POINTER_TO_UINT(L2_SRAM_BASE) + |
|
CONFIG_MM_DRV_PAGE_SIZE * page_idx; |
|
if (sys_mem_blocks_is_region_free( |
|
&L2_PHYS_SRAM_REGION, |
|
UINT_TO_POINTER(phys_addr), 1)) { |
|
/* skip a free page */ |
|
continue; |
|
} |
|
|
|
/* map the physical addr 1:1 to virtual address */ |
|
entry_idx = get_tlb_entry_idx(phys_addr); |
|
entry = pa_to_tlb_entry(phys_addr); |
|
|
|
if (((tlb_entries[entry_idx] & TLB_PADDR_MASK) != entry) || |
|
((tlb_entries[entry_idx] & TLB_ENABLE_BIT) != TLB_ENABLE_BIT)) { |
|
/* This page needs remapping */ |
|
|
|
/* Enable the translation in the TLB entry */ |
|
entry |= TLB_ENABLE_BIT; |
|
|
|
/* map the page 1:1 virtual to physical */ |
|
tlb_entries[entry_idx] = entry; |
|
|
|
#ifdef CONFIG_MMU |
|
arch_mem_map(UINT_TO_POINTER(phys_addr), phys_addr, CONFIG_MM_DRV_PAGE_SIZE, |
|
K_MEM_CACHE_WB | K_MEM_PERM_RW); |
|
#endif |
|
|
|
/* Invalidate cache to avoid stalled data |
|
* all cache data has been flushed before |
|
* do this for pages to remap only |
|
*/ |
|
sys_cache_data_invd_range(UINT_TO_POINTER(phys_addr), |
|
CONFIG_MM_DRV_PAGE_SIZE); |
|
} |
|
|
|
/* save physical address */ |
|
*((uint32_t *) location) = phys_addr; |
|
location += sizeof(uint32_t); |
|
|
|
/* save the page */ |
|
memcpy(location, |
|
UINT_TO_POINTER(phys_addr), |
|
CONFIG_MM_DRV_PAGE_SIZE); |
|
location += CONFIG_MM_DRV_PAGE_SIZE; |
|
} |
|
|
|
/* write end marker - a null address */ |
|
*((uint32_t *) location) = 0; |
|
location += sizeof(uint32_t); |
|
|
|
sys_cache_data_flush_range( |
|
storage_buffer, |
|
(uint32_t)location - (uint32_t)storage_buffer); |
|
|
|
|
|
/* system state is frozen, ready to poweroff, no further changes will be stored */ |
|
} |
|
|
|
__imr void adsp_mm_restore_context(void *storage_buffer) |
|
{ |
|
/* at this point system must be in a startup state |
|
* TLB must be set to initial state |
|
* Note! the stack must NOT be in the area being restored |
|
*/ |
|
uint32_t phys_addr; |
|
uint8_t *location; |
|
|
|
/* restore context of all the pages */ |
|
location = (uint8_t *) storage_buffer + TLB_SIZE; |
|
|
|
phys_addr = *((uint32_t *) location); |
|
|
|
while (phys_addr != 0) { |
|
uint32_t phys_addr_uncached = |
|
POINTER_TO_UINT(sys_cache_uncached_ptr_get( |
|
(void __sparse_cache *)UINT_TO_POINTER(phys_addr))); |
|
uint32_t phys_offset = phys_addr - L2_SRAM_BASE; |
|
uint32_t bank_idx = (phys_offset / SRAM_BANK_SIZE); |
|
|
|
location += sizeof(uint32_t); |
|
|
|
/* turn on memory bank power, wait till the power is on */ |
|
__ASSERT_NO_MSG(bank_idx <= ace_hpsram_get_bank_count()); |
|
HPSRAM_REGS(bank_idx)->HSxPGCTL = 0; |
|
while (HPSRAM_REGS(bank_idx)->HSxPGISTS == 1) { |
|
/* k_busy_wait cannot be used here - not available */ |
|
} |
|
|
|
/* copy data to uncached alias and invalidate cache */ |
|
bmemcpy(UINT_TO_POINTER(phys_addr_uncached), |
|
location, |
|
CONFIG_MM_DRV_PAGE_SIZE); |
|
sys_cache_data_invd_range(UINT_TO_POINTER(phys_addr), CONFIG_MM_DRV_PAGE_SIZE); |
|
|
|
location += CONFIG_MM_DRV_PAGE_SIZE; |
|
phys_addr = *((uint32_t *) location); |
|
} |
|
|
|
/* restore original TLB table */ |
|
bmemcpy(UINT_TO_POINTER(TLB_BASE), storage_buffer, TLB_SIZE); |
|
|
|
/* HPSRAM memory is restored */ |
|
} |
|
|
|
static uint32_t adsp_mm_get_storage_size(void) |
|
{ |
|
/* |
|
* FIXME - currently the function returns a maximum possible size of the buffer |
|
* as L3 memory is generally a huge area its OK (and fast) |
|
* in future the function may go through the mapping and calculate a required size |
|
*/ |
|
return L2_SRAM_SIZE + TLB_SIZE + (L2_SRAM_PAGES_NUM * sizeof(void *)) |
|
+ sizeof(void *); |
|
} |
|
|
|
static const struct intel_adsp_tlb_api adsp_tlb_api_func = { |
|
.save_context = adsp_mm_save_context, |
|
.get_storage_size = adsp_mm_get_storage_size |
|
}; |
|
|
|
DEVICE_DT_DEFINE(DT_INST(0, intel_adsp_mtl_tlb), |
|
sys_mm_drv_mm_init, |
|
NULL, |
|
NULL, |
|
NULL, |
|
POST_KERNEL, |
|
0, |
|
&adsp_tlb_api_func);
|
|
|