kernel_samsung_a34x-permissive/drivers/misc/mediatek/dramc/mtk_memtest.c

850 lines
19 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2019 MediaTek Inc.
* Author: Sagy Shih <sagy.shih@mediatek.com>
*/
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/kallsyms.h>
#include <linux/cpu.h>
#include <linux/smp.h>
#include <linux/vmalloc.h>
#include <linux/memblock.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <asm/cacheflush.h>
/* #include <mach/mtk_clkmgr.h> */
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_fdt.h>
#include <linux/debugfs.h>
#include <linux/mutex.h>
#include <linux/uaccess.h>
#include <linux/highmem.h>
#include <asm/setup.h>
#include <mt-plat/sync_write.h>
#include <mt-plat/mtk_chip.h>
#ifdef CONFIG_MTK_AEE_FEATURE
#include <mt-plat/aee.h>
#endif
#include <dramc_io.h>
#include "mtk_dramc.h"
#include "dramc.h"
#ifdef CONFIG_OF_RESERVED_MEM
#define DRAM_R0_MEMTEST_RESERVED_KEY "reserve-memory-dram_r0_memtest"
#define DRAM_R1_MEMTEST_RESERVED_KEY "reserve-memory-dram_r1_memtest"
#include <linux/of_reserved_mem.h>
#endif
#ifdef DRAMC_MEMTEST_DEBUG_SUPPORT
#define MAX_TEST_CLIENT 20
enum {
FAIL_REGION_RANK0,
FAIL_REGION_RANK1,
FAIL_REGION_VIRT,
FAIL_REGION_NUM,
};
enum {
TEST_RESULT_INIT,
TEST_RESULT_ONGOING,
TEST_RESULT_FAILED,
TEST_RESULT_PASS,
};
struct memtest_client {
struct list_head node;
pid_t pid;
unsigned int rank;
};
static void __iomem *(*get_emi_base)(void);
static phys_addr_t dram_addr;
static int result;
static DEFINE_MUTEX(test_result_mutex);
static DEFINE_MUTEX(test_mem0_mutex);
static DEFINE_MUTEX(test_mem1_mutex);
static DEFINE_MUTEX(test_client_mutex);
static struct dentry *memtest_dir;
static struct dentry *read_mr4, *read_mr5, *read_dram_addr;
static struct dentry *memtest_result, *memtest_v2p;
static struct dentry *memtest_mem0, *memtest_mem1;
static phys_addr_t memtest_rank0_addr, memtest_rank1_addr;
static unsigned int memtest_rank0_size, memtest_rank1_size;
static LIST_HEAD(test_client);
static unsigned int test_client_num;
static unsigned int test_fail_region[FAIL_REGION_NUM];
#define Reg_Sync_Writel(addr, val) writel(val, IOMEM(addr))
#define Reg_Readl(addr) readl(IOMEM(addr))
#ifdef CONFIG_OF_RESERVED_MEM
int dram_memtest_reserve_mem_of_init(struct reserved_mem *rmem)
{
phys_addr_t rptr = 0;
unsigned int rsize = 0;
rptr = rmem->base;
rsize = (unsigned int)rmem->size;
if (strstr(DRAM_R0_MEMTEST_RESERVED_KEY, rmem->name)) {
memtest_rank0_addr = rptr;
memtest_rank0_size = rsize;
}
if (strstr(DRAM_R1_MEMTEST_RESERVED_KEY, rmem->name)) {
memtest_rank1_addr = rptr;
memtest_rank1_size = rsize;
}
return 0;
}
RESERVEDMEM_OF_DECLARE(dram_reserve_r0_memtest_init,
DRAM_R0_MEMTEST_RESERVED_KEY,
dram_memtest_reserve_mem_of_init);
RESERVEDMEM_OF_DECLARE(dram_reserve_r1_memtest_init,
DRAM_R1_MEMTEST_RESERVED_KEY,
dram_memtest_reserve_mem_of_init);
#endif
static ssize_t read_mr4_read(struct file *file,
char __user *user_buf, size_t len, loff_t *offset)
{
void __iomem *emi_base;
void __iomem *dramc_nao_base;
unsigned int rank, channel, rank_max, channel_num;
unsigned int emi_cona, mr4;
unsigned char buf[64];
ssize_t ret;
ret = 0;
emi_base = get_emi_base();
if (emi_base == NULL) {
pr_info("[DRAMC] can't find EMI base\n");
return -1;
}
emi_cona = Reg_Readl(emi_base+0x000);
channel_num = mt_dramc_chn_get(emi_cona);
rank_max = mt_dramc_ta_support_ranks();
ret += snprintf(buf + ret, sizeof(buf) - ret, "MR4:");
for (rank = 0; rank < rank_max; rank++) {
for (channel = 0; channel < channel_num; channel++) {
dramc_nao_base = mt_dramc_nao_chn_base_get(channel);
if (!dramc_nao_base)
continue;
mr4 = Reg_Readl(dramc_nao_base + 0x90) & 0xFFFF;
ret += snprintf(buf + ret, sizeof(buf) - ret,
" R%uCH%c=0x%x,", rank, 'A' + channel, mr4);
}
}
ret += snprintf(buf + ret, sizeof(buf) - ret, "\n");
return simple_read_from_buffer(user_buf, len, offset, buf, ret);
}
static const struct file_operations read_mr4_fops = {
.owner = THIS_MODULE,
.read = read_mr4_read,
};
__weak unsigned char get_ddr_mr(unsigned int index)
{
return 0;
}
static ssize_t read_mr5_read(struct file *file,
char __user *user_buf, size_t len, loff_t *offset)
{
unsigned char buf[64];
ssize_t ret;
ret = 0;
ret += snprintf(buf + ret, sizeof(buf) - ret,
"MR5:0x%x\n", get_ddr_mr(5));
return simple_read_from_buffer(user_buf, len, offset, buf, ret);
}
static const struct file_operations read_mr5_fops = {
.owner = THIS_MODULE,
.read = read_mr5_read,
};
__weak unsigned int mt_dramc_col_size_get(unsigned int emi_cona,
unsigned int rank)
{
unsigned int col;
if (rank == 0)
col = (emi_cona >> 4) & 0x3;
else
col = (emi_cona >> 6) & 0x3;
return col + 9;
}
__weak unsigned int mt_dramc_row_size_get(unsigned int emi_cona,
unsigned int rank)
{
unsigned int row;
if (rank == 0)
row = ((emi_cona >> 22) & 0x4) | ((emi_cona >> 12) & 0x3);
else
row = ((emi_cona >> 23) & 0x4) | ((emi_cona >> 14) & 0x3);
return row + 13;
}
void dramc_addr_descramble(phys_addr_t *addr, unsigned int emi_conf)
{
unsigned int bit_scramble, bit_xor, bit_shift;
/* calculate DRAM base address (addr) */
for (bit_scramble = 11; bit_scramble < 17; bit_scramble++) {
bit_xor = (emi_conf >> (4*(bit_scramble-11))) & 0xf;
bit_xor &= *addr >> 16;
for (bit_shift = 0; bit_shift < 4; bit_shift++)
*addr ^= ((bit_xor>>bit_shift)&0x1) << bit_scramble;
}
}
int dramc_dram_address_get(phys_addr_t phys_addr,
unsigned int *rank, unsigned int *row,
unsigned int *bank, unsigned int *col, unsigned int *ch)
{
void __iomem *emi_base;
unsigned int emi_cona, emi_conf;
unsigned int ch_pos, channel_num;
unsigned int ch_width, col_width, row_width;
unsigned int bit_shift;
phys_addr_t temp;
unsigned int rank_max;
unsigned int ddr_type;
int r;
phys_addr_t rank_base;
emi_base = get_emi_base();
if (emi_base == NULL) {
pr_info("[DRAMC] can't find EMI base\n");
return -1;
}
emi_cona = Reg_Readl(emi_base+0x000);
emi_conf = Reg_Readl(emi_base+0x028)>>8;
channel_num = mt_dramc_chn_get(emi_cona);
rank_max = mt_dramc_ta_support_ranks();
if (rank_max > 2) {
pr_info("[DRAMC] invalid rank num (rank_max = %u)\n", rank_max);
return -1;
}
ddr_type = get_ddr_type();
for (r = rank_max - 1; r >= 0; r--) {
rank_base = mt_dramc_rankbase_get(r);
if (rank_base <= phys_addr) {
*rank = r;
phys_addr -= rank_base;
break;
}
}
if (r < 0) {
pr_info("[DRAMC] invalid rank\n");
return -1;
}
phys_addr &= 0xFFFFFFFF;
dramc_addr_descramble(&phys_addr, emi_conf);
/*
* pr_info("[LastDRAMC] reserved address after emi: %llx\n", phys_addr);
*/
*ch = 0;
if (channel_num > 1) {
ch_pos = mt_dramc_chp_get(emi_cona);
*ch = (phys_addr >> ch_pos) & 0x1;
for (ch_width = bit_shift = 0; bit_shift < 4;
bit_shift++) {
if ((1 << bit_shift) >= channel_num)
break;
ch_width++;
}
temp = (phys_addr &
~(((((phys_addr_t)0x1 << ch_width) - 1) << ch_pos) - 1))
>> ch_width;
phys_addr = temp |
(phys_addr & (((phys_addr_t)0x1 << ch_pos) - 1));
}
col_width = mt_dramc_col_size_get(emi_cona, *rank);
row_width = mt_dramc_row_size_get(emi_cona, *rank);
if ((ddr_type == TYPE_LPDDR4) || (ddr_type == TYPE_LPDDR4X))
phys_addr = phys_addr >> 1;
else if (ddr_type == TYPE_LPDDR3)
phys_addr = phys_addr >> 2;
else {
pr_info("[DRAMC] undefined DRAM type\n");
return -1;
}
/* now here is row.bank.col */
*col = phys_addr & ((1 << col_width) - 1);
*bank = (phys_addr >> col_width) & 0x7;
*row = (phys_addr >> (col_width + 3)) & ((1 << row_width) - 1);
return 0;
}
static int dramc_format_dram_addr(phys_addr_t addr, char *buf, unsigned int len)
{
unsigned int rank, row, bank, col, ch;
int sz;
sz = 0;
if (dramc_dram_address_get(addr, &rank, &row, &bank, &col, &ch))
return 0;
#ifdef CONFIG_PHYS_ADDR_T_64BIT
sz = snprintf(buf, len, "addr: 0x%llx ", addr);
#else
sz = snprintf(buf, len, "addr: 0x%x ", addr);
#endif
sz += snprintf(buf + sz, len - sz,
"(rank=0x%x, row=0x%x, bank=0x%x, col=0x%x, ch=0x%x)\n",
rank, row, bank, col, ch);
return sz;
}
struct read_dram_addr_ioctl_s {
unsigned long long pa;
unsigned int buf_sz;
unsigned char __user *buf;
};
static ssize_t read_dram_addr_read(struct file *file,
char __user *user_buf, size_t len, loff_t *offset)
{
unsigned char buf[128];
ssize_t sz;
sz = dramc_format_dram_addr(dram_addr, buf, sizeof(buf));
if (sz > 0)
return simple_read_from_buffer(user_buf, len, offset, buf, sz);
return 0;
}
static ssize_t read_dram_addr_write(struct file *file,
const char __user *user_buf, size_t len, loff_t *offset)
{
unsigned long long adr;
unsigned char buf[32];
int sz;
if (len) {
sz = simple_write_to_buffer(buf, sizeof(buf), offset,
user_buf, len);
if (sz != len)
return -EIO;
if (sz == sizeof(buf))
sz--;
buf[sz] = '\0';
if (kstrtoull(buf, 0, &adr) != 0)
return -EINVAL;
dram_addr = adr;
}
return len;
}
static long read_dram_addr_ioctl(struct file *file,
unsigned int cmd, unsigned long arg)
{
struct read_dram_addr_ioctl_s info;
unsigned char buf[128];
ssize_t sz;
if (copy_from_user(&info, (unsigned long *)arg, sizeof(info)))
return -EFAULT;
sz = dramc_format_dram_addr(info.pa, buf, sizeof(buf));
if (sz) {
if (sz > info.buf_sz)
sz = info.buf_sz;
if (copy_to_user(info.buf, &buf, sz))
return -EFAULT;
}
return 0;
}
static const struct file_operations read_dram_addr_fops = {
.owner = THIS_MODULE,
.read = read_dram_addr_read,
.write = read_dram_addr_write,
.unlocked_ioctl = read_dram_addr_ioctl,
};
static ssize_t test_result_read(struct file *file,
char __user *user_buf, size_t len, loff_t *offset)
{
char const *str[] = { "initial", "on-going", "fail", "pass" };
char buf[64];
int ret, sz, sz2, i;
ret = result;
sz = snprintf(buf, sizeof(buf), "%s\n", str[ret]);
if (!*offset && ret == TEST_RESULT_FAILED) {
sz2 = sz;
sz2 += snprintf(buf + sz2, sizeof(buf) - sz2, "Fail region: ");
for (i = 0; i < FAIL_REGION_VIRT; i++) {
if (test_fail_region[i])
sz2 += snprintf(buf + sz2, sizeof(buf) - sz2,
"rank %d, ", i);
}
if (test_fail_region[FAIL_REGION_VIRT])
sz2 += snprintf(buf + sz2, sizeof(buf) - sz2,
"virtual\n");
#ifdef CONFIG_MTK_AEE_FEATURE
aee_kernel_warning("DRAM_MEMTEST", buf);
#endif
}
return simple_read_from_buffer(user_buf, len, offset, buf, sz);
}
static ssize_t test_result_write(struct file *file,
const char __user *user_buf, size_t len, loff_t *offset)
{
struct memtest_client *client;
struct list_head *p;
char buf[32];
int ret, sz, region;
sz = simple_write_to_buffer(buf, sizeof(buf), offset, user_buf, len);
if (sz != len)
return -EIO;
if (sz == sizeof(buf))
sz--;
buf[sz] = '\0';
if (!strncmp(buf, "fail", 4))
ret = TEST_RESULT_FAILED;
else if (!strncmp(buf, "pass", 4))
ret = TEST_RESULT_PASS;
else
ret = TEST_RESULT_ONGOING;
mutex_lock(&test_result_mutex);
if (result != TEST_RESULT_FAILED && result != TEST_RESULT_PASS)
result = ret;
mutex_unlock(&test_result_mutex);
region = FAIL_REGION_VIRT;
mutex_lock(&test_client_mutex);
list_for_each(p, &test_client) {
client = list_entry(p, struct memtest_client, node);
if (client->pid == current->pid) {
region = client->rank;
break;
}
}
mutex_unlock(&test_client_mutex);
test_fail_region[region] = 1;
return sz;
}
static const struct file_operations test_result_fops = {
.owner = THIS_MODULE,
.read = test_result_read,
.write = test_result_write,
};
static phys_addr_t memtest_user_v2p(unsigned long va)
{
unsigned long pageOffset = (va & (PAGE_SIZE - 1));
pgd_t *pgd;
pud_t *pud;
pmd_t *pmd;
pte_t *pte;
phys_addr_t pa;
if (current == NULL) {
pr_info("warning: memorytest_user_v2p, current is NULL!\n");
return 0;
}
if (current->mm == NULL) {
pr_info("warning: memorytest_user_v2p, current->mm is NULL!\n");
pr_info("tgid=0x%x, name=%s\n", current->tgid, current->comm);
return 0;
}
pgd = pgd_offset(current->mm, va); /* what is tsk->mm */
if (pgd_none(*pgd) || pgd_bad(*pgd)) {
pr_info("memorytest_user_v2p, va=0x%lx, pgd invalid!\n", va);
return 0;
}
pud = pud_offset(pgd, va);
if (pud_none(*pud) || pud_bad(*pud)) {
pr_info("memorytest_user_v2p, va=0x%lx, pud invalid!\n", va);
return 0;
}
pmd = pmd_offset(pud, va);
if (pmd_none(*pmd) || pmd_bad(*pmd)) {
pr_info("(memorytest_user_v2p, va=0x%lx, pmd invalid!\n", va);
return 0;
}
pte = pte_offset_map(pmd, va);
if (pte_present(*pte)) {
/* pa=(pte_val(*pte) & (PAGE_MASK)) | pageOffset; */
pa = (pte_val(*pte) & (PHYS_MASK) & (~((phys_addr_t) 0xfff))) |
pageOffset;
pte_unmap(pte);
return pa;
}
pte_unmap(pte);
pr_info("memorytest_user_v2p, va=0x%lx, pte invalid!\n", va);
return 0;
}
static long test_v2p_ioctl(struct file *file,
unsigned int cmd, unsigned long arg)
{
unsigned long long va = 0x0L;
phys_addr_t pa = 0x0L;
if (copy_from_user(&va, (unsigned long *)arg, sizeof(va)))
return -EFAULT;
pa = memtest_user_v2p(va);
if (copy_to_user((unsigned long *)arg, &pa, sizeof(pa)))
return -EFAULT;
return 0;
}
static const struct file_operations test_v2p_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = test_v2p_ioctl,
};
static int test_mem_open(struct inode *inode, struct file *file)
{
file->private_data = inode->i_private;
return nonseekable_open(inode, file);
}
static void mmap_mem_open(struct vm_area_struct *vma)
{
unsigned long rank = (unsigned long) vma->vm_file->private_data;
struct memtest_client *client;
mutex_lock(&test_client_mutex);
if (test_client_num <= MAX_TEST_CLIENT) {
client = kmalloc(sizeof(*client), GFP_KERNEL);
if (client) {
client->pid = current->pid;
client->rank = rank;
list_add_tail(&client->node, &test_client);
test_client_num++;
}
}
mutex_unlock(&test_client_mutex);
}
static void mmap_mem_close(struct vm_area_struct *vma)
{
/*size_t size = vma->vm_end - vma->vm_start;*/
struct memtest_client *client;
struct list_head *p;
/*
*pr_info("%s(): 0x%lx~0x%lx pgoff=0x%lx %x\n", __func__,
* vma->vm_start, vma->vm_end, vma->vm_pgoff, size);
*/
mutex_lock(&test_client_mutex);
list_for_each(p, &test_client) {
client = list_entry(p, struct memtest_client, node);
if (client->pid == current->pid) {
list_del(&client->node);
test_client_num--;
kfree(client);
break;
}
}
mutex_unlock(&test_client_mutex);
}
static const struct vm_operations_struct mmap_mem_ops = {
.open = mmap_mem_open,
.close = mmap_mem_close,
#ifdef CONFIG_HAVE_IOREMAP_PROT
.access = generic_access_phys,
#endif
};
static ssize_t test_mem_read(struct file *file,
char __user *user_buf, size_t len, loff_t *offset)
{
static char buf[32];
unsigned long rank = (unsigned long) file->private_data;
phys_addr_t *addr;
unsigned int *sz;
int size;
if (rank == 0) {
addr = &memtest_rank0_addr;
sz = &memtest_rank0_size;
} else {
addr = &memtest_rank1_addr;
sz = &memtest_rank1_size;
}
size = snprintf(buf, sizeof(buf), "0x%lx 0x%x\n", (unsigned long) *addr,
(unsigned int) *sz);
return simple_read_from_buffer(user_buf, len, offset, buf, size);
}
static int test_mem_mmap(struct file *file, struct vm_area_struct *vma)
{
unsigned long pfn;
size_t size = vma->vm_end - vma->vm_start;
unsigned long rank = (unsigned long) file->private_data;
phys_addr_t *addr;
struct mutex *lock;
unsigned int *sz;
struct memtest_client *client;
int ret = 0;
if (rank == 0) {
addr = &memtest_rank0_addr;
sz = &memtest_rank0_size;
lock = &test_mem0_mutex;
} else {
addr = &memtest_rank1_addr;
sz = &memtest_rank1_size;
lock = &test_mem1_mutex;
}
mutex_lock(lock);
/* pr_info("%s(0): pgoff=0x%lx 0x%lx %lu\n", __func__, */
/* vma->vm_pgoff, size, (unsigned long) file->private_data); */
if (((vma->vm_pgoff << PAGE_SHIFT) + size) >= *sz) {
ret = -EINVAL;
goto end;
}
pfn = *addr >> PAGE_SHIFT;
vma->vm_pgoff += pfn;
/* pr_info("%s(1): pgoff=0x%lx 0x%lx %lu\n", __func__, */
/* vma->vm_pgoff, size, (unsigned long) file->private_data); */
if (!valid_mmap_phys_addr_range(vma->vm_pgoff, size)) {
ret = -EINVAL;
goto end;
}
if (!(vma->vm_flags & VM_MAYSHARE)) {
ret = -EINVAL;
goto end;
}
vma->vm_page_prot = phys_mem_access_prot(file, vma->vm_pgoff,
size,
vma->vm_page_prot);
vma->vm_ops = &mmap_mem_ops;
/* Remap-pfn-range will mark the range VM_IO */
if (remap_pfn_range(vma,
vma->vm_start,
vma->vm_pgoff,
size,
vma->vm_page_prot)) {
ret = -EAGAIN;
goto end;
}
end:
mutex_unlock(lock);
if (!ret) {
mutex_lock(&test_client_mutex);
if (test_client_num <= MAX_TEST_CLIENT) {
client = kmalloc(sizeof(*client), GFP_KERNEL);
if (client) {
client->pid = current->pid;
client->rank = rank;
list_add_tail(&client->node, &test_client);
test_client_num++;
}
}
mutex_unlock(&test_client_mutex);
}
return ret;
}
static const struct file_operations test_mem_fops = {
.owner = THIS_MODULE,
.open = test_mem_open,
.read = test_mem_read,
.mmap = test_mem_mmap,
};
static int __init dram_memtest_interface_init(void)
{
void __iomem *emi_base;
result = TEST_RESULT_INIT;
get_emi_base = (void __iomem *)symbol_get(mt_emi_base_get);
if (get_emi_base == NULL) {
pr_info("%s: mt_emi_base_get is NULL\n", __func__);
return -1;
}
emi_base = get_emi_base();
if (emi_base == NULL) {
pr_info("%s: can't find EMI base\n", __func__);
return -1;
}
memtest_dir = debugfs_create_dir("memtest", NULL);
if (!memtest_dir) {
pr_info("%s: create dir fail\n", __func__);
return -1;
}
read_mr4 = debugfs_create_file("read_mr4", 0444,
memtest_dir, NULL, &read_mr4_fops);
if (!read_mr4) {
pr_info("%s: create read_mr4 interface fail\n", __func__);
return -1;
}
read_mr5 = debugfs_create_file("read_mr5", 0444,
memtest_dir, NULL, &read_mr5_fops);
if (!read_mr5) {
pr_info("%s: create read_mr5 interface fail\n", __func__);
return -1;
}
read_dram_addr = debugfs_create_file("read_dram_addr",
0666,
memtest_dir, NULL, &read_dram_addr_fops);
if (!read_dram_addr) {
pr_info("%s: create read_dram_addr interface fail\n", __func__);
return -1;
}
memtest_result = debugfs_create_file("result",
0666,
memtest_dir, NULL, &test_result_fops);
if (!memtest_result) {
pr_info("%s: create result interface fail\n", __func__);
return -1;
}
memtest_v2p = debugfs_create_file("v2p", 0444,
memtest_dir, NULL, &test_v2p_fops);
if (!memtest_v2p) {
pr_info("%s: create v2p interface fail\n", __func__);
return -1;
}
if (memtest_rank0_addr) {
memtest_mem0 = debugfs_create_file("mem0",
0664,
memtest_dir, (void *) 0, &test_mem_fops);
if (!memtest_mem0) {
pr_info("%s: create mem0 interface fail\n", __func__);
return -1;
}
}
if (memtest_rank1_addr) {
memtest_mem1 = debugfs_create_file("mem1",
0664,
memtest_dir, (void *) 1, &test_mem_fops);
if (!memtest_mem1) {
pr_info("%s: create mem1 interface fail\n", __func__);
return -1;
}
}
return 0;
}
late_initcall(dram_memtest_interface_init);
#endif