kernel_samsung_a34x-permissive/drivers/misc/mediatek/m4u/2.0/m4u_mva.c
2024-04-28 15:51:13 +02:00

596 lines
17 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2019 MediaTek Inc.
*/
#include <linux/spinlock.h>
#include "m4u_priv.h"
/* ((va&0xfff)+size+0xfff)>>12 */
#define mva_pageOffset(mva) ((mva)&0xfff)
#define MVA_BLOCK_SIZE_ORDER 20 /* 1M */
#define MVA_MAX_BLOCK_NR 4095 /* 4GB */
#define MVA_BLOCK_SIZE (1<<MVA_BLOCK_SIZE_ORDER) /* 0x40000 */
#define MVA_BLOCK_ALIGN_MASK (MVA_BLOCK_SIZE-1) /* 0x3ffff */
#define MVA_BLOCK_NR_MASK (MVA_MAX_BLOCK_NR) /* 0xfff */
#define MVA_BUSY_MASK (1<<15) /* 0x8000 */
#define MVA_IS_BUSY(index) ((mvaGraph[index]&MVA_BUSY_MASK) != 0)
#define MVA_SET_BUSY(index) (mvaGraph[index] |= MVA_BUSY_MASK)
#define MVA_SET_FREE(index) (mvaGraph[index] & (~MVA_BUSY_MASK))
#define MVA_GET_NR(index) (mvaGraph[index] & MVA_BLOCK_NR_MASK)
#define MVAGRAPH_INDEX(mva) (mva>>MVA_BLOCK_SIZE_ORDER)
static short mvaGraph[MVA_MAX_BLOCK_NR + 1];
static void *mvaInfoGraph[MVA_MAX_BLOCK_NR + 1];
static DEFINE_SPINLOCK(gMvaGraph_lock);
void m4u_mvaGraph_init(void *priv_reserve)
{
unsigned long irq_flags;
spin_lock_irqsave(&gMvaGraph_lock, irq_flags);
memset(mvaGraph, 0, sizeof(short) * (MVA_MAX_BLOCK_NR + 1));
memset(mvaInfoGraph, 0, sizeof(void *) * (MVA_MAX_BLOCK_NR + 1));
mvaGraph[0] = 1 | (short)MVA_BUSY_MASK;
mvaInfoGraph[0] = priv_reserve;
mvaGraph[1] = MVA_MAX_BLOCK_NR;
mvaInfoGraph[1] = priv_reserve;
mvaGraph[MVA_MAX_BLOCK_NR] = MVA_MAX_BLOCK_NR;
mvaInfoGraph[MVA_MAX_BLOCK_NR] = priv_reserve;
spin_unlock_irqrestore(&gMvaGraph_lock, irq_flags);
}
void m4u_mvaGraph_dump_raw(void)
{
int i;
unsigned long irq_flags;
spin_lock_irqsave(&gMvaGraph_lock, irq_flags);
m4u_info("[M4U_K] dump raw data of mvaGraph:============>\n");
for (i = 0; i < MVA_MAX_BLOCK_NR + 1; i++)
m4u_high_info("0x%4x: 0x%08x\n", i, mvaGraph[i]);
spin_unlock_irqrestore(&gMvaGraph_lock, irq_flags);
}
void m4u_mvaGraph_dump(void)
{
unsigned int addr = 0, size = 0;
unsigned short index = 1, nr = 0;
int i, max_bit, is_busy;
short frag[12] = { 0 };
short nr_free = 0, nr_alloc = 0;
unsigned long irq_flags;
m4u_info("[M4U_K] mva allocation info dump:====================>\n");
m4u_info("start size blocknum busy\n");
spin_lock_irqsave(&gMvaGraph_lock, irq_flags);
for (index = 1; index < MVA_MAX_BLOCK_NR + 1; index += nr) {
addr = index << MVA_BLOCK_SIZE_ORDER;
nr = MVA_GET_NR(index);
size = nr << MVA_BLOCK_SIZE_ORDER;
if (MVA_IS_BUSY(index)) {
is_busy = 1;
nr_alloc += nr;
} else { /* mva region is free */
is_busy = 0;
nr_free += nr;
max_bit = 0;
for (i = 0; i < 12; i++) {
if (nr & (1 << i))
max_bit = i;
}
frag[max_bit]++;
}
m4u_info("0x%08x 0x%08x %4d %d\n",
addr, size, nr, is_busy);
}
spin_unlock_irqrestore(&gMvaGraph_lock, irq_flags);
m4u_info("\n");
m4u_info("[M4U_K] mva alloc summary: (unit: blocks)========>\n");
m4u_info("free: %d , alloc: %d, total: %d\n",
nr_free, nr_alloc, nr_free + nr_alloc);
m4u_info("[M4U_K] free region fragments in 2^x blocks unit:=====\n");
m4u_info(" 0 1 2 3 4 5 6 7 8 9 10 11\n");
m4u_info("%4d %4d %4d %4d %4d %4d %4d %4d %4d %4d %4d %4d\n",
frag[0], frag[1], frag[2], frag[3], frag[4], frag[5], frag[6],
frag[7], frag[8], frag[9], frag[10], frag[11]);
m4u_info("[M4U_K] mva alloc dump done=========================<\n");
}
void *mva_get_priv_ext(unsigned int mva)
{
void *priv = NULL;
unsigned int index;
unsigned long irq_flags;
index = MVAGRAPH_INDEX(mva);
if (index == 0 || index > MVA_MAX_BLOCK_NR) {
m4u_err("mvaGraph index is 0. mva=0x%x\n", mva);
return NULL;
}
spin_lock_irqsave(&gMvaGraph_lock, irq_flags);
/* find prev head/tail of this region */
while (mvaGraph[index] == 0)
index--;
if (MVA_IS_BUSY(index))
priv = mvaInfoGraph[index];
spin_unlock_irqrestore(&gMvaGraph_lock, irq_flags);
return priv;
}
int mva_foreach_priv(mva_buf_fn_t *fn, void *data)
{
unsigned short index = 1, nr = 0;
unsigned int mva;
void *priv;
unsigned long irq_flags;
int ret;
spin_lock_irqsave(&gMvaGraph_lock, irq_flags);
for (index = 1; index < MVA_MAX_BLOCK_NR + 1; index += nr) {
mva = index << MVA_BLOCK_SIZE_ORDER;
nr = MVA_GET_NR(index);
if (MVA_IS_BUSY(index)) {
priv = mvaInfoGraph[index];
ret = fn(priv, mva, mva + nr * MVA_BLOCK_SIZE, data);
if (ret)
break;
}
}
spin_unlock_irqrestore(&gMvaGraph_lock, irq_flags);
return 0;
}
unsigned int get_first_valid_mva(void)
{
unsigned short index = 1, nr = 0;
unsigned int mva;
void *priv;
unsigned long irq_flags;
spin_lock_irqsave(&gMvaGraph_lock, irq_flags);
for (index = 1; index < MVA_MAX_BLOCK_NR + 1; index += nr) {
mva = index << MVA_BLOCK_SIZE_ORDER;
nr = MVA_GET_NR(index);
if (MVA_IS_BUSY(index)) {
priv = mvaInfoGraph[index];
break;
}
}
spin_unlock_irqrestore(&gMvaGraph_lock, irq_flags);
return mva;
}
void *mva_get_priv(unsigned int mva)
{
void *priv = NULL;
unsigned int index;
unsigned long irq_flags;
index = MVAGRAPH_INDEX(mva);
if (index == 0 || index > MVA_MAX_BLOCK_NR) {
m4u_err("mvaGraph index is 0. mva=0x%x\n", mva);
return NULL;
}
spin_lock_irqsave(&gMvaGraph_lock, irq_flags);
if (MVA_IS_BUSY(index))
priv = mvaInfoGraph[index];
spin_unlock_irqrestore(&gMvaGraph_lock, irq_flags);
return priv;
}
unsigned int m4u_do_mva_alloc(unsigned long va, unsigned int size, void *priv)
{
unsigned short s, end;
unsigned short new_start, new_end;
unsigned short nr = 0;
unsigned int mvaRegionStart;
unsigned long startRequire, endRequire, sizeRequire;
unsigned long irq_flags;
if (size == 0)
return 0;
/* ----------------------------------------------------- */
/* calculate mva block number */
startRequire = va & (~M4U_PAGE_MASK);
endRequire = (va + size - 1) | M4U_PAGE_MASK;
sizeRequire = endRequire - startRequire + 1;
nr = (sizeRequire + MVA_BLOCK_ALIGN_MASK) >> MVA_BLOCK_SIZE_ORDER;
/* (sizeRequire>>MVA_BLOCK_SIZE_ORDER) +
* ((sizeRequire&MVA_BLOCK_ALIGN_MASK)!=0);
*/
spin_lock_irqsave(&gMvaGraph_lock, irq_flags);
/* ----------------------------------------------- */
/* find first match free region */
for (s = 1; (s < (MVA_MAX_BLOCK_NR + 1)) && (mvaGraph[s] < nr);
s += (mvaGraph[s] & MVA_BLOCK_NR_MASK))
;
if (s > MVA_MAX_BLOCK_NR) {
spin_unlock_irqrestore(&gMvaGraph_lock, irq_flags);
m4u_err("%s: no available MVA region for %d blocks!\n",
__func__, nr);
#ifdef M4U_PROFILE
mmprofile_log_ex(M4U_MMP_Events[M4U_MMP_M4U_ERROR],
MMPROFILE_FLAG_PULSE, size, s);
#endif
return 0;
}
/* ----------------------------------------------- */
/* alloc a mva region */
end = s + mvaGraph[s] - 1;
if (unlikely(nr == mvaGraph[s])) {
MVA_SET_BUSY(s);
MVA_SET_BUSY(end);
mvaInfoGraph[s] = priv;
mvaInfoGraph[end] = priv;
} else {
new_end = s + nr - 1;
new_start = new_end + 1;
/* note: new_start may equals to end */
mvaGraph[new_start] = (mvaGraph[s] - nr);
mvaGraph[new_end] = nr | MVA_BUSY_MASK;
mvaGraph[s] = mvaGraph[new_end];
mvaGraph[end] = mvaGraph[new_start];
mvaInfoGraph[s] = priv;
mvaInfoGraph[new_end] = priv;
}
spin_unlock_irqrestore(&gMvaGraph_lock, irq_flags);
mvaRegionStart = (unsigned int)s;
return (mvaRegionStart << MVA_BLOCK_SIZE_ORDER) + mva_pageOffset(va);
}
unsigned int
m4u_do_mva_alloc_fix(unsigned long va, unsigned int mva,
unsigned int size, void *priv)
{
unsigned short nr = 0;
unsigned int startRequire, endRequire, sizeRequire;
unsigned long irq_flags;
unsigned short startIdx = mva >> MVA_BLOCK_SIZE_ORDER;
unsigned short endIdx;
unsigned short region_start, region_end;
if (size == 0)
return 0;
if (startIdx == 0 || startIdx > MVA_MAX_BLOCK_NR) {
m4u_err("mvaGraph index is 0. index=0x%x\n", startIdx);
return 0;
}
mva = mva | (va & M4U_PAGE_MASK);
/* ----------------------------------------------------- */
/* calculate mva block number */
startRequire = mva & (~MVA_BLOCK_ALIGN_MASK);
endRequire = (mva + size - 1) | MVA_BLOCK_ALIGN_MASK;
sizeRequire = endRequire - startRequire + 1;
nr = (sizeRequire + MVA_BLOCK_ALIGN_MASK) >> MVA_BLOCK_SIZE_ORDER;
/* (sizeRequire>>MVA_BLOCK_SIZE_ORDER) +
* ((sizeRequire&MVA_BLOCK_ALIGN_MASK)!=0);
*/
spin_lock_irqsave(&gMvaGraph_lock, irq_flags);
region_start = startIdx;
/* find prev head of this region */
while (mvaGraph[region_start] == 0)
region_start--;
if (MVA_IS_BUSY(region_start) ||
(MVA_GET_NR(region_start) < nr + startIdx - region_start)) {
m4u_err("mva is inuse index=0x%x, mvaGraph=0x%x\n",
region_start, mvaGraph[region_start]);
mva = 0;
goto out;
}
/* carveout startIdx~startIdx+nr-1 out of region_start */
endIdx = startIdx + nr - 1;
region_end = region_start + MVA_GET_NR(region_start) - 1;
if (startIdx == region_start && endIdx == region_end) {
MVA_SET_BUSY(startIdx);
MVA_SET_BUSY(endIdx);
} else if (startIdx == region_start) {
mvaGraph[startIdx] = nr | MVA_BUSY_MASK;
mvaGraph[endIdx] = mvaGraph[startIdx];
mvaGraph[endIdx + 1] = region_end - endIdx;
mvaGraph[region_end] = mvaGraph[endIdx + 1];
} else if (endIdx == region_end) {
mvaGraph[region_start] = startIdx - region_start;
mvaGraph[startIdx - 1] = mvaGraph[region_start];
mvaGraph[startIdx] = nr | MVA_BUSY_MASK;
mvaGraph[endIdx] = mvaGraph[startIdx];
} else {
mvaGraph[region_start] = startIdx - region_start;
mvaGraph[startIdx - 1] = mvaGraph[region_start];
mvaGraph[startIdx] = nr | MVA_BUSY_MASK;
mvaGraph[endIdx] = mvaGraph[startIdx];
mvaGraph[endIdx + 1] = region_end - endIdx;
mvaGraph[region_end] = mvaGraph[endIdx + 1];
}
mvaInfoGraph[startIdx] = priv;
mvaInfoGraph[endIdx] = priv;
out:
spin_unlock_irqrestore(&gMvaGraph_lock, irq_flags);
return mva;
}
unsigned int
m4u_do_mva_alloc_start_from(unsigned long va, unsigned int mva,
unsigned int size, void *priv)
{
unsigned short s = 0, end;
unsigned short new_start, new_end;
unsigned short nr = 0;
unsigned int mvaRegionStart;
unsigned long startRequire, endRequire, sizeRequire;
unsigned long irq_flags;
unsigned short startIdx = mva >> MVA_BLOCK_SIZE_ORDER;
short region_start, region_end, next_region_start = 0;
if (size == 0)
return 0;
startIdx = (mva + MVA_BLOCK_ALIGN_MASK) >> MVA_BLOCK_SIZE_ORDER;
/* calculate mva block number */
startRequire = va & (~M4U_PAGE_MASK);
endRequire = (va + size - 1) | M4U_PAGE_MASK;
sizeRequire = endRequire - startRequire + 1;
nr = (sizeRequire + MVA_BLOCK_ALIGN_MASK) >> MVA_BLOCK_SIZE_ORDER;
/* (sizeRequire>>MVA_BLOCK_SIZE_ORDER) +
* ((sizeRequire&MVA_BLOCK_ALIGN_MASK)!=0);
*/
m4u_mid_info("%s mva:0x%x, startIdx=%d, size = %d, nr= %d\n",
__func__, mva, startIdx, size, nr);
spin_lock_irqsave(&gMvaGraph_lock, irq_flags);
/* find this region */
for (region_start = 1; (region_start < (MVA_MAX_BLOCK_NR + 1));
region_start += (MVA_GET_NR(region_start) & MVA_BLOCK_NR_MASK)) {
if ((mvaGraph[region_start] & MVA_BLOCK_NR_MASK) == 0) {
m4u_mvaGraph_dump();
m4u_aee_err("%s: s=%d, 0x%x\n",
__func__, s, mvaGraph[region_start]);
}
if ((region_start + MVA_GET_NR(region_start)) > startIdx) {
next_region_start = region_start +
MVA_GET_NR(region_start);
break;
}
}
if (region_start > MVA_MAX_BLOCK_NR) {
m4u_err("%s:alloc mva fail,no available MVA for %d blocks\n",
__func__, nr);
spin_unlock_irqrestore(&gMvaGraph_lock, irq_flags);
return 0;
}
region_end = region_start + MVA_GET_NR(region_start) - 1;
if (next_region_start == 0) {
m4u_aee_err("%s: region start-end:%d-%d, region=%d\n",
__func__, region_start, region_end,
MVA_GET_NR(region_start));
}
if (MVA_IS_BUSY(region_start)) {
m4u_err("mva is inuse index=%d, mvaGraph=0x%x\n",
region_start, mvaGraph[region_start]);
s = region_start;
} else {
if ((region_end - startIdx + 1) < nr)
s = next_region_start;
else
m4u_err("mva is free region_start=%d, s=%d\n",
region_start, s);
}
m4u_info("region start-end:%d-%d, region= %d, next_region_start= %d, search start: %d\n",
region_start, region_end, MVA_GET_NR(region_start),
next_region_start, s);
/* ----------------------------------------------- */
if (s != 0) {
/* find first match free region */
for (; (s < (MVA_MAX_BLOCK_NR + 1)) && (mvaGraph[s] < nr);
s += (mvaGraph[s] & MVA_BLOCK_NR_MASK)) {
if ((mvaGraph[s] & MVA_BLOCK_NR_MASK) == 0) {
m4u_aee_err("%s: s=%d, 0x%x\n",
__func__, s, mvaGraph[s]);
m4u_mvaGraph_dump();
}
}
}
if (s > MVA_MAX_BLOCK_NR) {
spin_unlock_irqrestore(&gMvaGraph_lock, irq_flags);
m4u_err("%s: no available MVA region for %d blocks!\n",
__func__, nr);
#ifdef M4U_PROFILE
mmprofile_log_ex(M4U_MMP_Events[M4U_MMP_M4U_ERROR],
MMPROFILE_FLAG_PULSE, size, s);
#endif
return 0;
}
/* ----------------------------------------------- */
if (s == 0) {
/* same as m4u_do_mva_alloc_fix */
short endIdx = startIdx + nr - 1;
region_end = region_start + MVA_GET_NR(region_start) - 1;
m4u_err("region_start: %d, region_end= %d, startIdx: %d, endIdx= %d\n",
region_start, region_end, startIdx, endIdx);
if (startIdx == region_start && endIdx == region_end) {
MVA_SET_BUSY(startIdx);
MVA_SET_BUSY(endIdx);
} else if (startIdx == region_start) {
mvaGraph[startIdx] = nr | MVA_BUSY_MASK;
mvaGraph[endIdx] = mvaGraph[startIdx];
mvaGraph[endIdx + 1] = region_end - endIdx;
mvaGraph[region_end] = mvaGraph[endIdx + 1];
} else if (endIdx == region_end) {
mvaGraph[region_start] = startIdx - region_start;
mvaGraph[startIdx - 1] = mvaGraph[region_start];
mvaGraph[startIdx] = nr | MVA_BUSY_MASK;
mvaGraph[endIdx] = mvaGraph[startIdx];
} else {
mvaGraph[region_start] = startIdx - region_start;
mvaGraph[startIdx - 1] = mvaGraph[region_start];
mvaGraph[startIdx] = nr | MVA_BUSY_MASK;
mvaGraph[endIdx] = mvaGraph[startIdx];
mvaGraph[endIdx + 1] = region_end - endIdx;
mvaGraph[region_end] = mvaGraph[endIdx + 1];
}
mvaInfoGraph[startIdx] = priv;
if (endIdx > 0)
mvaInfoGraph[endIdx] = priv;
s = startIdx;
} else {
/* alloc a mva region */
end = s + mvaGraph[s] - 1;
if (unlikely(nr == mvaGraph[s])) {
MVA_SET_BUSY(s);
MVA_SET_BUSY(end);
mvaInfoGraph[s] = priv;
mvaInfoGraph[end] = priv;
} else {
new_end = s + nr - 1;
new_start = new_end + 1;
/* note: new_start may equals to end */
mvaGraph[new_start] = (mvaGraph[s] - nr);
mvaGraph[new_end] = nr | MVA_BUSY_MASK;
mvaGraph[s] = mvaGraph[new_end];
mvaGraph[end] = mvaGraph[new_start];
mvaInfoGraph[s] = priv;
mvaInfoGraph[new_end] = priv;
}
}
spin_unlock_irqrestore(&gMvaGraph_lock, irq_flags);
mvaRegionStart = (unsigned int)s;
return (mvaRegionStart << MVA_BLOCK_SIZE_ORDER) + mva_pageOffset(va);
}
#define RightWrong(x) ((x) ? "correct" : "error")
int m4u_do_mva_free(unsigned int mva, unsigned int size)
{
unsigned short startIdx = mva >> MVA_BLOCK_SIZE_ORDER;
unsigned short nr;
unsigned short endIdx;
unsigned int startRequire, endRequire, sizeRequire;
short nrRequire;
unsigned long irq_flags;
spin_lock_irqsave(&gMvaGraph_lock, irq_flags);
if (startIdx == 0 || startIdx > MVA_MAX_BLOCK_NR) {
spin_unlock_irqrestore(&gMvaGraph_lock, irq_flags);
m4u_err("mvaGraph index is 0. mva=0x%x\n", mva);
return -1;
}
nr = mvaGraph[startIdx] & MVA_BLOCK_NR_MASK;
endIdx = startIdx + nr - 1;
/* --------------------------------
* check the input arguments
* right condition:
* startIdx is not NULL && region is busy && right module && right size
*/
startRequire = mva & (unsigned int)(~M4U_PAGE_MASK);
endRequire = (mva + size - 1) | (unsigned int)M4U_PAGE_MASK;
sizeRequire = endRequire - startRequire + 1;
nrRequire =
(sizeRequire + MVA_BLOCK_ALIGN_MASK) >> MVA_BLOCK_SIZE_ORDER;
/* (sizeRequire>>MVA_BLOCK_SIZE_ORDER) +
* ((sizeRequire&MVA_BLOCK_ALIGN_MASK)!=0);
*/
if (!(startIdx != 0 && MVA_IS_BUSY(startIdx) && (nr == nrRequire))) {
spin_unlock_irqrestore(&gMvaGraph_lock, irq_flags);
m4u_err("error to free mva========================>\n");
m4u_err("BufSize=%d(unit:0x%xBytes) (expect %d) [%s]\n",
nrRequire, MVA_BLOCK_SIZE, nr,
RightWrong(nrRequire == nr));
m4u_err("mva=0x%x, (IsBusy?)=%d (expect %d) [%s]\n",
mva, MVA_IS_BUSY(startIdx), 1,
RightWrong(MVA_IS_BUSY(startIdx)));
m4u_mvaGraph_dump();
/* m4u_mvaGraph_dump_raw(); */
return -1;
}
mvaInfoGraph[startIdx] = NULL;
mvaInfoGraph[endIdx] = NULL;
/* -------------------------------- */
/* merge with followed region */
if ((endIdx + 1 <= MVA_MAX_BLOCK_NR) && (!MVA_IS_BUSY(endIdx + 1))) {
nr += mvaGraph[endIdx + 1];
mvaGraph[endIdx] = 0;
mvaGraph[endIdx + 1] = 0;
}
/* -------------------------------- */
/* merge with previous region */
if ((startIdx - 1 > 0) && (!MVA_IS_BUSY(startIdx - 1))) {
int pre_nr = mvaGraph[startIdx - 1];
mvaGraph[startIdx] = 0;
mvaGraph[startIdx - 1] = 0;
startIdx -= pre_nr;
nr += pre_nr;
}
/* -------------------------------- */
/* set region flags */
mvaGraph[startIdx] = nr;
mvaGraph[startIdx + nr - 1] = nr;
spin_unlock_irqrestore(&gMvaGraph_lock, irq_flags);
return 0;
}