Files
android/drivers/misc/mediatek/memory-ssmr/memory_ssmr.c
T
threeeye bc1d038eac REVVL 6 Pro kernel bring-up: full working tree snapshot (2026-09-12)
Squashed backup commit (no shallow-clone history issues) of
xiaomi-mt6833-dev/kernel_xiaomi_mt6833 (lineage-23.2 branch, upstream
commit 9eece662e) plus all fixes applied to get everpal_defconfig building
clean against a modern (GCC 15.2.0) toolchain -- see the previous commit
message on the lineage-23.2 branch (also in this repo history is not
available here since this is a squashed snapshot; full incremental history
lives in the local DL380 checkout and the upstream GitHub remote).

Upstream: https://github.com/xiaomi-mt6833-dev/kernel_xiaomi_mt6833 (lineage-23.2)
2026-09-12 13:36:46 +00:00

1483 lines
35 KiB
C

/*
* Copyright (C) 2016 MediaTek Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See http://www.gnu.org/licenses/gpl-2.0.html for more details.
*/
#define pr_fmt(fmt) "memory-ssmr: " fmt
#include <linux/types.h>
#include <linux/of.h>
#include <linux/of_fdt.h>
#include <linux/of_reserved_mem.h>
#include <linux/printk.h>
#include <linux/cma.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/mutex.h>
#include <linux/highmem.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/proc_fs.h>
#include <linux/uaccess.h>
#include <linux/memblock.h>
#include <asm/cacheflush.h>
#ifdef CONFIG_ARM64
#include <asm/tlbflush.h>
#include <asm/pgtable.h>
#endif
#include "ssmr_internal.h"
#ifdef CONFIG_MTK_SEC_VIDEO_PATH_SUPPORT
#define COUNT_DOWN_MS 10000
#define COUNT_DOWN_INTERVAL 500
#define COUNT_DOWN_LIMIT (COUNT_DOWN_MS / COUNT_DOWN_INTERVAL)
static atomic_t svp_online_count_down;
static atomic_t svp_ref_count;
static struct task_struct *_svp_online_task; /* NULL */
static DEFINE_MUTEX(svp_online_task_lock);
#endif
/* 16 MB alignment */
#define SSMR_CMA_ALIGN_PAGE_ORDER 12
#define SSMR_ALIGN_SHIFT (SSMR_CMA_ALIGN_PAGE_ORDER + PAGE_SHIFT)
#define SSMR_ALIGN (1 << (PAGE_SHIFT + (MAX_ORDER - 1)))
static u64 ssmr_upper_limit = UPPER_LIMIT64;
static int reserved_for_unmap_ready;
/* Flag indicates if all SSMR features use reserved memory as source */
static int is_pre_reserve_memory;
#include <mt-plat/aee.h>
#include "mt-plat/mtk_meminfo.h"
static unsigned long ssmr_usage_count;
static struct cma *cma;
struct page_change_data {
pgprot_t set_mask;
pgprot_t clear_mask;
};
#define memory_unmapping(virt, size) set_memory_mapping(virt, size, 0)
#define memory_mapping(virt, size) set_memory_mapping(virt, size, 1)
/*
* Use for zone-movable-cma callback
*/
#define SSMR_FEATURES_DT_UNAME "memory-ssmr-features"
static void __init get_feature_size_by_dt_prop(unsigned long node,
const char *feature_name, u64 *fsize)
{
const __be32 *reg;
int l;
reg = of_get_flat_dt_prop(node, feature_name, &l);
if (reg) {
/*
* Only override the size if we can found its associated prop
* in DTS node, otherwise, keep the previous settings.
*/
*fsize = dt_mem_next_cell(dt_root_addr_cells, &reg);
}
}
/*
* If target DT node is found, try to parse each feature required size.
*/
static int __init dt_scan_for_ssmr_features(unsigned long node,
const char *uname, int depth, void *data)
{
int i = 0;
if (__MAX_NR_SSMR_FEATURES <= 0)
return 0;
if (!strncmp(uname, SSMR_FEATURES_DT_UNAME,
strlen(SSMR_FEATURES_DT_UNAME))) {
for (; i < __MAX_NR_SSMR_FEATURES; i++) {
get_feature_size_by_dt_prop(node,
_ssmr_feats[i].dt_prop_name,
&_ssmr_feats[i].req_size);
}
return 1;
}
return 0;
}
static void __init setup_feature_size(struct reserved_mem *rmem)
{
int i = 0;
if (of_scan_flat_dt(dt_scan_for_ssmr_features, NULL) == 0)
pr_info("%s, can't find DT node, %s\n",
__func__, SSMR_FEATURES_DT_UNAME);
pr_info("%s, rmem->size: %pa\n", __func__, &rmem->size);
for (; i < __MAX_NR_SSMR_FEATURES; i++) {
pr_info("%s, %s: %pa\n", __func__,
_ssmr_feats[i].dt_prop_name,
&_ssmr_feats[i].req_size);
}
}
static void __init finalize_scenario_size(void)
{
int i = 0;
for (; i < __MAX_NR_SCHEME; i++) {
u64 total_size = 0;
int j = 0;
if (__MAX_NR_SSMR_FEATURES <= 0)
break;
for (; j < __MAX_NR_SSMR_FEATURES; j++) {
if (_ssmr_feats[j].scheme_flag & _ssmrscheme[i].flags)
total_size += _ssmr_feats[j].req_size;
}
_ssmrscheme[i].usable_size = total_size;
pr_info("%s, %s: %pa\n", __func__, _ssmrscheme[i].name,
&_ssmrscheme[i].usable_size);
}
}
static u64 __init get_final_target_size(void)
{
int i = 0;
u64 final_size = 0;
for (; i < __MAX_NR_SCHEME; i++)
final_size = max(_ssmrscheme[i].usable_size, final_size);
return final_size;
}
/*
* The "memory_ssmr_registration" is __initdata, and will be reclaimed
* after init. We need to perserve this structure for zmc_cam_alloc() API.
* (will access "prio" field)
*/
static struct single_cma_registration saved_memory_ssmr_registration = {
.align = SSMR_ALIGN,
.name = "memory-ssmr",
.prio = ZMC_SSMR,
};
/*
* Parse all feature size requirements from DTS and
* check if reserved memory can meet the requirement.
*/
static int __init ssmr_preinit(struct reserved_mem *rmem)
{
int rc = 1;
u64 final_target_size;
setup_feature_size(rmem);
finalize_scenario_size();
final_target_size = get_final_target_size();
pr_info("%s, final target size: %pa\n", __func__, &final_target_size);
memory_ssmr_registration.size = final_target_size;
saved_memory_ssmr_registration.size = final_target_size;
if (final_target_size == 0) {
/*
* size is 0 and along with default alignment, zmc_memory_init
* will bypass this registration and therefore ZONE_MOVABLE
* will NOT be present.
*/
pr_alert("%s, SSMR init: skipped, no requirement\n", __func__);
} else if (final_target_size > 0 && final_target_size <= rmem->size) {
memory_ssmr_registration.align = (1 << SSMR_ALIGN_SHIFT);
saved_memory_ssmr_registration.align = (1 << SSMR_ALIGN_SHIFT);
pr_info("%s, SSMR init: continue\n", __func__);
rc = 0;
} else {
pr_err("%s, SSMR init: skip, rmem->size can't meet target\n",
__func__);
}
return rc;
}
static void __init zmc_ssmr_init(struct cma *zmc_cma)
{
phys_addr_t base = cma_get_base(zmc_cma), size = cma_get_size(zmc_cma);
cma = zmc_cma;
pr_info("%s, base: %pa, size: %pa\n", __func__, &base, &size);
}
/* size will be always reset in ssmr_preinit, only provide default value here */
struct single_cma_registration __initdata memory_ssmr_registration = {
.align = SSMR_ALIGN,
.size = SSMR_ALIGN,
.name = "memory-ssmr",
.preinit = ssmr_preinit,
.init = zmc_ssmr_init,
.prio = ZMC_SSMR,
};
/*
* This is only used for all SSMR users want dedicated reserved memory
*/
static int __init memory_ssmr_init(struct reserved_mem *rmem)
{
int ret;
pr_info("%s, name: %s, base: 0x%pa, size: 0x%pa\n",
__func__, rmem->name,
&rmem->base, &rmem->size);
if (ssmr_preinit(rmem))
return 1;
/* init cma area */
ret = cma_init_reserved_mem(rmem->base, rmem->size, 0, rmem->name,
&cma);
if (ret) {
pr_err("%s cma failed, ret: %d\n", __func__, ret);
return 1;
}
is_pre_reserve_memory = 1;
return 0;
}
RESERVEDMEM_OF_DECLARE(memory_ssmr, "mediatek,memory-ssmr",
memory_ssmr_init);
/*
* This is for unmap in 1G block descriptor
* reserve a 4KB around movable zone to make sure it's not 1G block descriptor
*/
static int __init ssmr_reserve_init(struct reserved_mem *rmem)
{
pr_info("%s, name: %s, base: %pa, size: %pa\n",
__func__, rmem->name,
&rmem->base, &rmem->size);
if (__MAX_NR_SSMR_FEATURES <= 0) {
memblock_free(rmem->base, rmem->size);
memblock_add(rmem->base, rmem->size);
return 0;
}
reserved_for_unmap_ready = 1;
return 0;
}
RESERVEDMEM_OF_DECLARE(ssmr_reserve, "mediatek,reserve-memory-ssmr",
ssmr_reserve_init);
#if defined(CONFIG_MTK_SEC_VIDEO_PATH_SUPPORT) ||\
defined(CONFIG_MTK_TEE_GP_SUPPORT)
static int __init dedicate_svp_memory(struct reserved_mem *rmem)
{
struct SSMR_Feature *feature;
feature = &_ssmr_feats[SSMR_FEAT_SVP];
pr_info("%s, name: %s, base: 0x%pa, size: 0x%pa\n",
__func__, rmem->name,
&rmem->base, &rmem->size);
if (ssmr_preinit(rmem))
return 1;
feature->use_cache_memory = true;
feature->is_unmapping = true;
feature->count = rmem->size / PAGE_SIZE;
feature->cache_page = phys_to_page(rmem->base);
return 0;
}
RESERVEDMEM_OF_DECLARE(svp_memory, "mediatek,memory-svp",
dedicate_svp_memory);
#endif
#if defined(CONFIG_TRUSTONIC_TRUSTED_UI) ||\
defined(CONFIG_BLOWFISH_TUI_SUPPORT)
static int __init dedicate_tui_memory(struct reserved_mem *rmem)
{
struct SSMR_Feature *feature;
feature = &_ssmr_feats[SSMR_FEAT_TUI];
pr_info("%s, name: %s, base: 0x%pa, size: 0x%pa\n",
__func__, rmem->name,
&rmem->base, &rmem->size);
if (ssmr_preinit(rmem))
return 1;
feature->use_cache_memory = true;
feature->is_unmapping = true;
feature->count = rmem->size / PAGE_SIZE;
feature->cache_page = phys_to_page(rmem->base);
return 0;
}
RESERVEDMEM_OF_DECLARE(tui_memory, "mediatek,memory-tui",
dedicate_tui_memory);
#endif
#ifdef CONFIG_MTK_PROT_MEM_SUPPORT
static int __init dedicate_prot_sharedmem_memory(struct reserved_mem *rmem)
{
struct SSMR_Feature *feature;
feature = &_ssmr_feats[SSMR_FEAT_PROT_SHAREDMEM];
pr_info("%s, name: %s, base: 0x%pa, size: 0x%pa\n",
__func__, rmem->name,
&rmem->base, &rmem->size);
if (ssmr_preinit(rmem))
return 1;
feature->use_cache_memory = true;
feature->is_unmapping = true;
feature->count = rmem->size / PAGE_SIZE;
feature->cache_page = phys_to_page(rmem->base);
return 0;
}
RESERVEDMEM_OF_DECLARE(prot_sharedmem_memory, "mediatek,memory-prot-sharedmem",
dedicate_prot_sharedmem_memory);
#endif
#ifdef CONFIG_MTK_HAPP_MEM_SUPPORT
static int __init dedicate_ta_elf_memory(struct reserved_mem *rmem)
{
struct SSMR_Feature *feature;
feature = &_ssmr_feats[SSMR_FEAT_TA_ELF];
pr_info("%s, name: %s, base: 0x%pa, size: 0x%pa\n",
__func__, rmem->name,
&rmem->base, &rmem->size);
feature->use_cache_memory = true;
feature->is_unmapping = true;
feature->count = rmem->size / PAGE_SIZE;
feature->cache_page = phys_to_page(rmem->base);
return 0;
}
RESERVEDMEM_OF_DECLARE(ta_elf_memory, "mediatek,ta_elf",
dedicate_ta_elf_memory);
static int __init dedicate_ta_stack_heap_memory(struct reserved_mem *rmem)
{
struct SSMR_Feature *feature;
feature = &_ssmr_feats[SSMR_FEAT_TA_STACK_HEAP];
pr_info("%s, name: %s, base: 0x%pa, size: 0x%pa\n",
__func__, rmem->name,
&rmem->base, &rmem->size);
feature->use_cache_memory = true;
feature->is_unmapping = true;
feature->count = rmem->size / PAGE_SIZE;
feature->cache_page = phys_to_page(rmem->base);
return 0;
}
RESERVEDMEM_OF_DECLARE(ta_stack_heap_memory, "mediatek,ta_stack_heap",
dedicate_ta_stack_heap_memory);
#endif
#ifdef CONFIG_MTK_SDSP_SHARED_MEM_SUPPORT
static int __init dedicate_sdsp_sharedmem_memory(struct reserved_mem *rmem)
{
struct SSMR_Feature *feature;
feature = &_ssmr_feats[SSMR_FEAT_SDSP_TEE_SHAREDMEM];
pr_info("%s, name: %s, base: 0x%pa, size: 0x%pa\n",
__func__, rmem->name,
&rmem->base, &rmem->size);
feature->use_cache_memory = true;
feature->is_unmapping = true;
feature->count = rmem->size / PAGE_SIZE;
feature->cache_page = phys_to_page(rmem->base);
return 0;
}
RESERVEDMEM_OF_DECLARE(sdsp_sharedmem_memory, "mediatek,sdsp_sharedmem",
dedicate_sdsp_sharedmem_memory);
#endif
#ifdef CONFIG_MTK_SDSP_MEM_SUPPORT
static int __init dedicate_sdsp_firmware_memory(struct reserved_mem *rmem)
{
struct SSMR_Feature *feature;
feature = &_ssmr_feats[SSMR_FEAT_SDSP_FIRMWARE];
pr_info("%s, name: %s, base: 0x%pa, size: 0x%pa\n",
__func__, rmem->name,
&rmem->base, &rmem->size);
feature->use_cache_memory = true;
feature->is_unmapping = true;
feature->count = rmem->size / PAGE_SIZE;
feature->cache_page = phys_to_page(rmem->base);
return 0;
}
RESERVEDMEM_OF_DECLARE(sdsp_firmware_memory, "mediatek,sdsp_firmware",
dedicate_sdsp_firmware_memory);
#endif
static bool has_dedicate_resvmem_region(void)
{
bool ret = false;
int i = 0;
if (__MAX_NR_SSMR_FEATURES <= 0)
return ret;
for (; i < __MAX_NR_SSMR_FEATURES; i++) {
if (_ssmr_feats[i].use_cache_memory) {
pr_info("%s uses dedicate reserved memory\n",
_ssmr_feats[i].feat_name);
ret = true;
}
}
return ret;
}
/*
* Check whether memory_ssmr is initialized
*/
bool memory_ssmr_inited(void)
{
return (has_dedicate_resvmem_region() || cma != NULL);
}
#ifdef CONFIG_ARM64
#ifdef CONFIG_DEBUG_PAGEALLOC
static int change_page_range(pte_t *ptep, pgtable_t token, unsigned long addr,
void *data)
{
struct page_change_data *cdata = data;
pte_t pte = *ptep;
pte = clear_pte_bit(pte, cdata->clear_mask);
pte = set_pte_bit(pte, cdata->set_mask);
set_pte(ptep, pte);
return 0;
}
/*
* Unmapping memory region kernel mapping
* SSMR protect memory region with EMI MPU. While protecting, memory prefetch
* will access memory region and trigger warning.
* To avoid false alarm of protection, We unmap kernel mapping while protecting
*
* @start: start address
* @size: memory region size
* @map: 1 for mapping, 0 for unmapping.
*
* @return: success return 0, failed return -1;
*/
static int set_memory_mapping(unsigned long start, phys_addr_t size, int map)
{
struct page_change_data data;
int ret;
/* flush dcache when unmap */
if (!map)
__flush_dcache_area((void *)start, size);
if (map) {
data.set_mask = __pgprot(PTE_VALID);
data.clear_mask = __pgprot(0);
} else {
data.set_mask = __pgprot(0);
data.clear_mask = __pgprot(PTE_VALID);
}
ret = apply_to_page_range(&init_mm, start, size, change_page_range,
&data);
flush_tlb_kernel_range(start, start + size);
return ret;
}
#else
static int set_memory_mapping(unsigned long start, phys_addr_t size, int map)
{
unsigned long address = start;
pud_t *pud;
pmd_t *pmd;
pgd_t *pgd;
spinlock_t *plt;
if ((start != (start & PMD_MASK))
|| (size != (size & PMD_MASK))
|| !memblock_is_memory(virt_to_phys((void *)start))
|| !size || !start) {
pr_info("[invalid parameter]: start=0x%lx, size=%pa\n",
start, &size);
return -1;
}
pr_debug("start=0x%lx, size=%pa, address=0x%p, map=%d\n",
start, &size, (void *)address, map);
/* flush dcache when unmap */
if (!map)
__flush_dcache_area((void *)start, size);
while (address < (start + size)) {
pgd = pgd_offset_k(address);
if (pgd_none(*pgd) || pgd_bad(*pgd)) {
pr_info("bad pgd break\n");
goto fail;
}
pud = pud_offset(pgd, address);
if (pud_none(*pud) || pud_bad(*pud)) {
pr_info("bad pud break\n");
goto fail;
}
pmd = pmd_offset(pud, address);
if (pmd_none(*pmd)) {
pr_info("none ");
goto fail;
}
if (pmd_table(*pmd)) {
pr_info("pmd_table not set PMD\n");
goto fail;
}
plt = pmd_lock(&init_mm, pmd);
if (map)
set_pmd(pmd, __pmd(pmd_val(*pmd) | PMD_SECT_VALID));
else
set_pmd(pmd, __pmd(pmd_val(*pmd) & ~PMD_SECT_VALID));
spin_unlock(plt);
address += PMD_SIZE;
}
flush_tlb_all();
return 0;
fail:
pr_info("start=0x%lx, size=%pa, address=0x%p, map=%d\n",
start, &size, (void *)address, map);
show_pte(NULL, address);
return -1;
}
#endif
#else
static inline int set_memory_mapping(unsigned long start, phys_addr_t size,
int map)
{
pr_debug("start=0x%lx, size=%pa, map=%d\n", start, &size, map);
if (!map) {
pr_info("Flush kmap page table\n");
kmap_flush_unused();
}
return 0;
}
#endif
static int memory_region_offline(struct SSMR_Feature *feature,
phys_addr_t *pa, unsigned long *size, u64 upper_limit)
{
int offline_retry = 0;
int ret_map;
struct page *page;
unsigned long alloc_pages;
phys_addr_t page_phys;
/* Determine alloc pages by feature */
if (feature->cache_page)
alloc_pages = feature->count;
else
alloc_pages = feature->req_size /
PAGE_SIZE;
feature->alloc_pages = alloc_pages;
/* compare with function and system wise upper limit */
upper_limit = min(upper_limit, ssmr_upper_limit);
if (feature->cache_page)
page_phys = page_to_phys(feature->cache_page);
else
page_phys = 0;
pr_info("%s[%d]: upper_limit: %llx, region{ alloc_pages : %lu",
__func__, __LINE__, upper_limit, alloc_pages);
pr_info("count: %lu, is_unmapping: %c, use_cache_memory: %c,",
feature->count, feature->is_unmapping ? 'Y' : 'N',
feature->use_cache_memory ? 'Y' : 'N');
pr_info("cache_page: %pa}\n", &page_phys);
if (feature->use_cache_memory) {
if (!feature->cache_page) {
pr_info("[NO_CACHE_MEMORY]:\n");
feature->alloc_pages = 0;
return -EFAULT;
}
page = feature->cache_page;
goto out;
}
do {
pr_info("[SSMR-ALLOCATION]: retry: %d\n", offline_retry);
page = zmc_cma_alloc(cma, alloc_pages,
SSMR_CMA_ALIGN_PAGE_ORDER,
&saved_memory_ssmr_registration);
if (page == NULL) {
offline_retry++;
msleep(100);
}
} while (page == NULL && offline_retry < 20);
if (page) {
phys_addr_t start = page_to_phys(page);
phys_addr_t end = start + (alloc_pages << PAGE_SHIFT);
if (end > upper_limit) {
pr_err("Reserve Over Limit, region end: %pa\n", &end);
cma_release(cma, page, alloc_pages);
page = NULL;
}
}
if (!page) {
feature->alloc_pages = 0;
return -EBUSY;
}
ssmr_usage_count += alloc_pages;
ret_map = memory_unmapping((unsigned long)__va((page_to_phys(page))),
alloc_pages << PAGE_SHIFT);
if (ret_map < 0) {
pr_err("[unmapping fail]: virt:0x%lx, size:0x%lx",
(unsigned long)__va((page_to_phys(page))),
alloc_pages << PAGE_SHIFT);
aee_kernel_warning_api(__FILE__, __LINE__, DB_OPT_DEFAULT
| DB_OPT_DUMPSYS_ACTIVITY
| DB_OPT_LOW_MEMORY_KILLER
| DB_OPT_PID_MEMORY_INFO /* smaps and hprof */
| DB_OPT_PROCESS_COREDUMP
| DB_OPT_PAGETYPE_INFO
| DB_OPT_DUMPSYS_PROCSTATS,
"offline unmap fail\nCRDISPATCH_KEY:SVP_SS1\n",
"[unmapping fail]: virt:0x%lx, size:0x%lx",
(unsigned long)__va((page_to_phys(page))),
alloc_pages << PAGE_SHIFT);
feature->is_unmapping = false;
} else
feature->is_unmapping = true;
out:
feature->page = page;
if (pa)
*pa = page_to_phys(page);
if (size)
*size = alloc_pages << PAGE_SHIFT;
return 0;
}
static int memory_region_online(struct SSMR_Feature *feature)
{
unsigned long alloc_pages;
alloc_pages = feature->alloc_pages;
if (feature->use_cache_memory) {
feature->alloc_pages = 0;
feature->page = NULL;
return 0;
}
/* remapping if unmapping while offline */
if (feature->is_unmapping) {
int ret_map;
unsigned long region_page_va =
(unsigned long)__va(page_to_phys(feature->page));
ret_map = memory_mapping(region_page_va,
feature->alloc_pages << PAGE_SHIFT);
if (ret_map < 0) {
pr_err("[remapping fail]: virt:0x%lx, size:0x%lx",
region_page_va,
feature->alloc_pages << PAGE_SHIFT);
aee_kernel_warning_api(__FILE__, __LINE__,
DB_OPT_DEFAULT
| DB_OPT_DUMPSYS_ACTIVITY
| DB_OPT_LOW_MEMORY_KILLER
| DB_OPT_PID_MEMORY_INFO /* smaps and hprof */
| DB_OPT_PROCESS_COREDUMP
| DB_OPT_PAGETYPE_INFO
| DB_OPT_DUMPSYS_PROCSTATS,
"online remap fail\nCRDISPATCH_KEY:SVP_SS1\n",
"[remapping fail]: virt:0x%lx, size:0x%lx",
region_page_va,
feature->alloc_pages << PAGE_SHIFT);
feature->use_cache_memory = true;
feature->cache_page = feature->page;
} else
feature->is_unmapping = false;
}
if (!feature->is_unmapping && !feature->use_cache_memory) {
zmc_cma_release(cma, feature->page, feature->alloc_pages);
ssmr_usage_count -= feature->alloc_pages;
feature->alloc_pages = 0;
}
feature->page = NULL;
return 0;
}
#if defined(CONFIG_TRUSTONIC_TRUSTED_UI) ||\
defined(CONFIG_BLOWFISH_TUI_SUPPORT)
int _tui_region_offline(phys_addr_t *pa, unsigned long *size,
u64 upper_limit)
{
int retval = 0;
struct SSMR_Feature *feature = &_ssmr_feats[SSMR_FEAT_TUI];
pr_info("%s: >>>>>> state: %s, upper_limit:0x%llx\n", __func__,
ssmr_state_text[feature->state], upper_limit);
if (feature->state != SSMR_STATE_ON) {
retval = -EBUSY;
goto out;
}
feature->state = SSMR_STATE_OFFING;
retval = memory_region_offline(feature, pa, size, upper_limit);
if (retval < 0) {
feature->state = SSMR_STATE_ON;
retval = -EAGAIN;
goto out;
}
feature->state = SSMR_STATE_OFF;
pr_info("%s: [reserve done]: pa 0x%lx, size 0x%lx\n",
__func__, (unsigned long)page_to_phys(feature->page),
feature->count << PAGE_SHIFT);
out:
pr_info("%s: <<<<<< state: %s, retval: %d\n", __func__,
ssmr_state_text[feature->state], retval);
return retval;
}
int tui_region_offline(phys_addr_t *pa, unsigned long *size)
{
return _tui_region_offline(pa, size, UPPER_LIMIT32);
}
EXPORT_SYMBOL(tui_region_offline);
int tui_region_offline64(phys_addr_t *pa, unsigned long *size)
{
return _tui_region_offline(pa, size, UPPER_LIMIT64);
}
EXPORT_SYMBOL(tui_region_offline64);
int tui_region_online(void)
{
struct SSMR_Feature *feature = &_ssmr_feats[SSMR_FEAT_TUI];
int retval;
pr_info("%s: >>>>>> enter state: %s\n", __func__,
ssmr_state_text[feature->state]);
if (feature->state != SSMR_STATE_OFF) {
retval = -EBUSY;
goto out;
}
feature->state = SSMR_STATE_ONING_WAIT;
retval = memory_region_online(feature);
feature->state = SSMR_STATE_ON;
out:
pr_info("%s: <<<<<< leave state: %s, retval: %d\n",
__func__, ssmr_state_text[feature->state], retval);
return retval;
}
EXPORT_SYMBOL(tui_region_online);
#endif
#ifdef CONFIG_MTK_SEC_VIDEO_PATH_SUPPORT
static void reset_svp_online_task(void)
{
mutex_lock(&svp_online_task_lock);
_svp_online_task = NULL;
mutex_unlock(&svp_online_task_lock);
}
static int _svp_wdt_kthread_func(void *data)
{
atomic_set(&svp_online_count_down, COUNT_DOWN_LIMIT);
pr_info("[START COUNT DOWN]: %dms/%dms\n",
COUNT_DOWN_MS, COUNT_DOWN_INTERVAL);
for (; atomic_read(&svp_online_count_down) > 0;
atomic_dec(&svp_online_count_down)) {
msleep(COUNT_DOWN_INTERVAL);
/*
* some component need ssmr memory,
* and stop count down watch dog
*/
if (atomic_read(&svp_ref_count) > 0) {
pr_info("[STOP COUNT DOWN]: new request for ssmr\n");
reset_svp_online_task();
return 0;
}
if (_ssmr_feats[SSMR_FEAT_SVP].state == SSMR_STATE_ON) {
pr_info("[STOP COUNT DOWN]: SSMR has online\n");
reset_svp_online_task();
return 0;
}
pr_info("[COUNT DOWN]: %d\n",
atomic_read(&svp_online_count_down));
}
pr_err("[COUNT DOWN FAIL]\n");
ion_sec_heap_dump_info();
pr_debug("Shareable SecureMemoryRegion trigger kernel warning");
aee_kernel_warning_api(__FILE__, __LINE__, DB_OPT_DEFAULT
| DB_OPT_DUMPSYS_ACTIVITY
| DB_OPT_LOW_MEMORY_KILLER
| DB_OPT_PID_MEMORY_INFO /* smaps and hprof */
| DB_OPT_PROCESS_COREDUMP
| DB_OPT_DUMPSYS_SURFACEFLINGER
| DB_OPT_DUMPSYS_GFXINFO
| DB_OPT_DUMPSYS_PROCSTATS,
"SVP online fail.\nCRDISPATCH_KEY:SVP_SS1\n",
"[SSMR ONLINE FAIL]: online timeout.\n");
reset_svp_online_task();
return 0;
}
int svp_start_wdt(void)
{
mutex_lock(&svp_online_task_lock);
if (_svp_online_task == NULL) {
_svp_online_task = kthread_create(_svp_wdt_kthread_func,
NULL, "svp_online_kthread");
if (IS_ERR_OR_NULL(_svp_online_task)) {
mutex_unlock(&svp_online_task_lock);
pr_warn("%s: fail to create svp_online_task\n",
__func__);
return -1;
}
} else {
atomic_set(&svp_online_count_down, COUNT_DOWN_LIMIT);
mutex_unlock(&svp_online_task_lock);
pr_info("%s: svp_online_task is already created\n", __func__);
return -1;
}
wake_up_process(_svp_online_task);
mutex_unlock(&svp_online_task_lock);
return 0;
}
int _secmem_region_offline(phys_addr_t *pa, unsigned long *size,
u64 upper_limit)
{
int retval = 0;
struct SSMR_Feature *feature = &_ssmr_feats[SSMR_FEAT_SVP];
pr_info("%s: >>>>>> state: %s, upper_limit:0x%llx\n", __func__,
ssmr_state_text[feature->state], upper_limit);
if (feature->state != SSMR_STATE_ON) {
retval = -EBUSY;
goto out;
}
feature->state = SSMR_STATE_OFFING;
retval = memory_region_offline(feature, pa, size, upper_limit);
if (retval < 0) {
feature->state = SSMR_STATE_ON;
retval = -EAGAIN;
goto out;
}
feature->state = SSMR_STATE_OFF;
pr_info("%s: [reserve done]: pa 0x%lx, size 0x%lx\n",
__func__, (unsigned long)page_to_phys(feature->page),
feature->count << PAGE_SHIFT);
out:
pr_info("%s: <<<<<< state: %s, retval: %d\n", __func__,
ssmr_state_text[feature->state], retval);
return retval;
}
int svp_region_offline64(phys_addr_t *pa, unsigned long *size)
{
return _secmem_region_offline(pa, size, UPPER_LIMIT64);
}
EXPORT_SYMBOL(svp_region_offline64);
int svp_region_offline(phys_addr_t *pa, unsigned long *size)
{
return _secmem_region_offline(pa, size, UPPER_LIMIT32);
}
EXPORT_SYMBOL(svp_region_offline);
int secmem_region_online(void)
{
struct SSMR_Feature *feature = &_ssmr_feats[SSMR_FEAT_SVP];
int retval;
pr_info("%s: >>>>>> enter state: %s\n", __func__,
ssmr_state_text[feature->state]);
if (feature->state != SSMR_STATE_OFF) {
retval = -EBUSY;
goto out;
}
feature->state = SSMR_STATE_ONING_WAIT;
retval = memory_region_online(feature);
feature->state = SSMR_STATE_ON;
out:
pr_info("%s: <<<<<< leave state: %s, retval: %d\n",
__func__, ssmr_state_text[feature->state], retval);
return retval;
}
EXPORT_SYMBOL(secmem_region_online);
int svp_region_online(void)
{
return secmem_region_online();
}
EXPORT_SYMBOL(svp_region_online);
#endif
static bool is_valid_feature(unsigned int feat)
{
if (SSMR_INVALID_FEATURE(feat)) {
pr_info("%s: invalid feature_type: %d\n",
__func__, feat);
return false;
}
return true;
}
static void show_scheme_status(u64 size)
{
int i = 0;
if (cma) {
pr_info("total cma size: 0x%lx, remaining size: 0x%lx\n",
cma_get_size(cma),
((cma_get_size(cma) >> PAGE_SHIFT) -
ssmr_usage_count)*PAGE_SIZE);
pr_info("request size: %pa\n", &size);
}
for (; i < __MAX_NR_SCHEME; i++) {
int j = 0;
if (__MAX_NR_SSMR_FEATURES <= 0)
break;
pr_info("**** %s (size: %pa)****\n", _ssmrscheme[i].name,
&_ssmrscheme[i].usable_size);
for (; j < __MAX_NR_SSMR_FEATURES; j++) {
if (_ssmr_feats[j].scheme_flag & _ssmrscheme[i].flags) {
pr_info("%s: size= %pa, state=%s\n",
_ssmr_feats[j].feat_name,
&_ssmr_feats[j].req_size,
ssmr_state_text[_ssmr_feats[j].state]);
}
}
}
}
static int _ssmr_offline_internal(phys_addr_t *pa, unsigned long *size,
u64 upper_limit, unsigned int feat)
{
int retval = 0;
struct SSMR_Feature *feature = NULL;
feature = &_ssmr_feats[feat];
pr_info("%s %d: >>>>>> feat: %s, state: %s, upper_limit:0x%llx\n",
__func__, __LINE__,
feat < __MAX_NR_SSMR_FEATURES ?
feature->feat_name : "NULL",
ssmr_state_text[feature->state], upper_limit);
if (feature->state != SSMR_STATE_ON) {
retval = -EBUSY;
goto out;
}
feature->state = SSMR_STATE_OFFING;
retval = memory_region_offline(feature, pa, size, upper_limit);
if (retval < 0) {
feature->state = SSMR_STATE_ON;
retval = -EAGAIN;
goto out;
}
feature->state = SSMR_STATE_OFF;
pr_info("%s %d: [reserve done]: pa 0x%lx, size 0x%lx\n",
__func__, __LINE__, (unsigned long)page_to_phys(feature->page),
feature->alloc_pages << PAGE_SHIFT);
out:
pr_info("%s %d: <<<<< request feat: %s, state: %s, retval: %d\n",
__func__, __LINE__,
feat < __MAX_NR_SSMR_FEATURES ?
_ssmr_feats[feat].feat_name : "NULL",
ssmr_state_text[feature->state],
retval);
if (retval < 0)
show_scheme_status(feature->req_size);
return retval;
}
int ssmr_offline(phys_addr_t *pa, unsigned long *size, bool is_64bit,
unsigned int feat)
{
if (!is_valid_feature(feat))
return -EINVAL;
return _ssmr_offline_internal(pa, size,
is_64bit ? UPPER_LIMIT64 : UPPER_LIMIT32,
feat);
}
EXPORT_SYMBOL(ssmr_offline);
static int _ssmr_online_internal(unsigned int feat)
{
int retval;
struct SSMR_Feature *feature = NULL;
feature = &_ssmr_feats[feat];
pr_info("%s %d: >>>>>> enter state: %s\n", __func__, __LINE__,
ssmr_state_text[feature->state]);
if (feature->state != SSMR_STATE_OFF) {
retval = -EBUSY;
goto out;
}
feature->state = SSMR_STATE_ONING_WAIT;
retval = memory_region_online(feature);
feature->state = SSMR_STATE_ON;
out:
pr_info("%s %d: <<<<<< request feature: %s, ",
__func__, __LINE__,
feat < __MAX_NR_SSMR_FEATURES ?
_ssmr_feats[feat].feat_name : "NULL");
pr_info("leave state: %s, retval: %d\n",
ssmr_state_text[feature->state], retval);
return retval;
}
int ssmr_online(unsigned int feat)
{
if (!is_valid_feature(feat))
return -EINVAL;
return _ssmr_online_internal(feat);
}
EXPORT_SYMBOL(ssmr_online);
#ifdef CONFIG_MTK_SEC_VIDEO_PATH_SUPPORT
static long svp_cma_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
int ret = 0;
pr_info("%s: cmd: %x\n", __func__, cmd);
switch (cmd) {
case SVP_REGION_ACQUIRE:
atomic_inc(&svp_ref_count);
break;
case SVP_REGION_RELEASE:
atomic_dec(&svp_ref_count);
if (atomic_read(&svp_ref_count) == 0)
svp_start_wdt();
break;
default:
return -ENOTTY;
}
return ret;
}
#if IS_ENABLED(CONFIG_COMPAT)
static long svp_cma_COMPAT_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
pr_info("%s: cmd: %x\n", __func__, cmd);
if (!filp->f_op || !filp->f_op->unlocked_ioctl)
return -ENOTTY;
return filp->f_op->unlocked_ioctl(filp, cmd,
(unsigned long)compat_ptr(arg));
}
#else
#define svp_cma_COMPAT_ioctl NULL
#endif
static const struct file_operations ssmr_cma_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = svp_cma_ioctl,
.compat_ioctl = svp_cma_COMPAT_ioctl,
};
#endif // end of CONFIG_MTK_SEC_VIDEO_PATH_SUPPORT
#if CONFIG_SYSFS
static ssize_t ssmr_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *cmd, size_t count)
{
char buf[64];
int buf_size;
int feat = 0, ret;
ret = snprintf(buf, sizeof(buf), "%s", cmd);
if (ret && ret < sizeof(buf)) {
buf_size = min(count - 1, (sizeof(buf) - 1));
if (buf_size < 0)
return -EINVAL;
buf[buf_size] = 0;
pr_info("%s[%d]: cmd> %s\n", __func__, __LINE__, buf);
if (0 == __MAX_NR_SSMR_FEATURES)
return -EINVAL;
for (feat = 0; feat < __MAX_NR_SSMR_FEATURES; feat++) {
if (!strncmp(buf, _ssmr_feats[feat].cmd_offline,
strlen(buf))) {
ssmr_offline(NULL, NULL, ssmr_upper_limit,
feat);
break;
} else if (!strncmp(buf, _ssmr_feats[feat].cmd_online,
strlen(buf))) {
ssmr_online(feat);
break;
}
}
} else {
pr_info("%s[%d]: get invalid cmd\n", __func__, __LINE__);
}
return count;
}
static ssize_t ssmr_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
int ret = 0;
int i = 0;
phys_addr_t cma_base = cma_get_base(cma);
phys_addr_t cma_end = cma_base + cma_get_size(cma);
ret += snprintf(buf + ret, PAGE_SIZE - ret,
"cma info: [%pa-%pa] (0x%lx)\n",
&cma_base, &cma_end,
cma_get_size(cma));
ret += snprintf(buf + ret, PAGE_SIZE - ret,
"cma info: base %pa pfn [%lu-%lu] count %lu\n",
&cma_base,
__phys_to_pfn(cma_base), __phys_to_pfn(cma_end),
cma_get_size(cma) >> PAGE_SHIFT);
if (__MAX_NR_SSMR_FEATURES <= 0) {
ret += snprintf(buf + ret, PAGE_SIZE - ret,
"no SSMR user enable\n");
return ret;
}
for (; i < __MAX_NR_SSMR_FEATURES; i++) {
unsigned long region_pa;
region_pa = (unsigned long) page_to_phys(_ssmr_feats[i].page);
ret += snprintf(buf + ret, PAGE_SIZE - ret,
"%s base:0x%lx, count %lu, ",
_ssmr_feats[i].feat_name,
_ssmr_feats[i].page == NULL ? 0 : region_pa,
_ssmr_feats[i].count);
ret += snprintf(buf + ret, PAGE_SIZE - ret,
"alloc_pages %lu, state %s.%s\n",
_ssmr_feats[i].alloc_pages,
ssmr_state_text[_ssmr_feats[i].state],
_ssmr_feats[i].use_cache_memory ?
" (cache memory)" : "");
}
ret += snprintf(buf + ret, PAGE_SIZE - ret,
"cma usage: %lu pages\n", ssmr_usage_count);
ret += snprintf(buf + ret, PAGE_SIZE - ret, "[CONFIG]:\n");
ret += snprintf(buf + ret, PAGE_SIZE - ret,
"ssmr_upper_limit: 0x%llx\n",
ssmr_upper_limit);
return ret;
}
static struct kobj_attribute ssmr_attribute = {
.attr = {
.name = "ssmr_state",
.mode = 0644,
},
.show = ssmr_show,
.store = ssmr_store,
};
static struct kobject *ssmr_kobject;
static int memory_ssmr_sysfs_init(void)
{
int error = 0;
ssmr_kobject = kobject_create_and_add("memory_ssmr", kernel_kobj);
if (ssmr_kobject) {
error = sysfs_create_file(ssmr_kobject, &ssmr_attribute.attr);
if (error) {
pr_info("SSMR: sysfs create failed\n");
return -ENOMEM;
}
} else {
pr_notice("SSMR: Cannot find module %s object\n",
KBUILD_MODNAME);
return -EINVAL;
}
return 0;
}
#endif /* end of CONFIG_SYSFS */
static int __init ssmr_sanity(void)
{
phys_addr_t start = 0;
unsigned long size = 0;
u64 final_target_size = 0;
char *err_msg = NULL;
if (has_dedicate_resvmem_region())
return 0;
if (!cma) {
/*
* If total size is 0, which will imply cma will NOT be
* initialized by ZMC.
*/
pr_err("[INIT FAIL]: cma is not inited\n");
err_msg = "SSMR sanity: CMA init fail\n";
return -1;
}
final_target_size = get_final_target_size();
size = cma_get_size(cma);
if (final_target_size > size) {
start = cma_get_base(cma);
pr_info("Total target size: %pa, cma start: %pa, size: %pa\n",
&final_target_size, &start, &size);
err_msg = "SSMR sanity: not enough reserved memory\n";
goto out;
}
return 0;
out:
aee_kernel_warning_api(__FILE__, __LINE__,
DB_OPT_DEFAULT
| DB_OPT_DUMPSYS_ACTIVITY
| DB_OPT_PID_MEMORY_INFO /* smaps and hprof */
| DB_OPT_DUMPSYS_PROCSTATS,
"SSMR Sanity fail.\nCRDISPATCH_KEY:SVP_SS1\n",
err_msg);
return -1;
}
static int __init memory_ssmr_init_feature(char *name, u64 size,
struct SSMR_Feature *feature, const struct file_operations *entry_fops)
{
struct proc_dir_entry *procfs_entry;
bool has_dedicate_memory = (feature->use_cache_memory);
bool has_region = (has_dedicate_memory || size > 0);
if (!has_region) {
feature->state = SSMR_STATE_DISABLED;
return -1;
}
if (entry_fops) {
procfs_entry = proc_create(name, 0444, NULL, entry_fops);
if (!procfs_entry) {
pr_info("Failed to create procfs ssmr_region file\n");
return -1;
}
}
if (has_dedicate_memory) {
pr_info("[%s]:Use dedicate memory as cached memory\n", name);
size = ((u64) feature->count) * PAGE_SIZE;
goto region_init_done;
}
feature->count = (unsigned long)(size >> PAGE_SHIFT);
if (is_pre_reserve_memory) {
int ret_map;
struct page *page;
unsigned long region_cache_page_va =
(unsigned long)__va(page_to_phys(feature->cache_page));
page = zmc_cma_alloc(cma, feature->count,
SSMR_CMA_ALIGN_PAGE_ORDER,
&memory_ssmr_registration);
feature->use_cache_memory = true;
feature->cache_page = page;
ssmr_usage_count += feature->count;
ret_map = memory_unmapping(region_cache_page_va,
feature->count << PAGE_SHIFT);
if (ret_map < 0) {
pr_err("[unmapping fail]: virt:0x%lx, size:0x%lx",
(unsigned long)__va((page_to_phys(page))),
feature->count << PAGE_SHIFT);
aee_kernel_warning_api(__FILE__, __LINE__,
DB_OPT_DEFAULT
| DB_OPT_DUMPSYS_ACTIVITY
| DB_OPT_LOW_MEMORY_KILLER
| DB_OPT_PID_MEMORY_INFO /* smaps and hprof */
| DB_OPT_PROCESS_COREDUMP
| DB_OPT_PAGETYPE_INFO
| DB_OPT_DUMPSYS_PROCSTATS,
"offline unmap fail\nCRDISPATCH_KEY:SVP_SS1\n",
"[unmapping fail]: virt:0x%lx, size:0x%lx",
(unsigned long)__va((page_to_phys(page))),
feature->count << PAGE_SHIFT);
feature->is_unmapping = false;
} else
feature->is_unmapping = true;
}
region_init_done:
feature->state = SSMR_STATE_ON;
pr_info("%s: %s is enable with size: %pa\n",
__func__, name, &size);
return 0;
}
static int __init memory_ssmr_debug_init(void)
{
int i = 0;
if (ssmr_sanity() < 0) {
pr_err("SSMR sanity fail\n");
return 1;
}
if (reserved_for_unmap_ready)
pr_info("%s: reserved mem for unmap is ready\n", __func__);
pr_info("[PASS]: SSMR sanity.\n");
/* ssmr sys file init */
#if CONFIG_SYSFS
memory_ssmr_sysfs_init();
#endif
/*
* TODO: integrate into _svpregs[] initialization
*/
#ifdef CONFIG_MTK_SEC_VIDEO_PATH_SUPPORT
_ssmr_feats[i].proc_entry_fops = &ssmr_cma_fops;
#endif
if (__MAX_NR_SSMR_FEATURES <= 0)
return 0;
for (; i < __MAX_NR_SSMR_FEATURES; i++) {
memory_ssmr_init_feature(_ssmr_feats[i].feat_name,
_ssmr_feats[i].req_size,
&_ssmr_feats[i],
_ssmr_feats[i].proc_entry_fops);
}
return 0;
}
late_initcall(memory_ssmr_debug_init);