ddr: add dmc monitor driver tool [1/2]

PD#172256

1. Add dts config for dmc monitor of each chip;
2. rename ddr_band_port_desc.c to ddr_port_desc.c, to reuse
   name description of each DMC port and chip;
3. Implement driver code for dmc monitor.

Change-Id: I645ded2af519b858118365d359b1d47fa455039c
Signed-off-by: tao zeng <tao.zeng@amlogic.com>
This commit is contained in:
tao zeng
2018-08-22 14:34:44 +08:00
committed by Jianxin Pan
parent dc428cbf3c
commit 1310ef5ef2
18 changed files with 968 additions and 18 deletions

View File

@@ -13502,7 +13502,15 @@ F: drivers/amlogic/thermal/*
M: Tao Zeng <tao.zeng@amlogic.com>
F: drivers/amlogic/ddr_tool/*
F: drivers/amlogic/ddr_tool/ddr_band_port_desc.c
F: drivers/amlogic/ddr_tool/ddr_port_desc.c
AMLOGIC driver for dmc protection
M: Tao Zeng <tao.zeng@amlogic.com>
F: drivers/amlogic/ddr_tool/dmc_g12.c
F: drivers/amlogic/ddr_tool/dmc_gx.c
F: drivers/amlogic/ddr_tool/dmc_monitor.c
F: include/linux/amlogic/dmc_monitor.h
F: include/linux/amlogic/ddr_port.h
AMLOGIC driver for cpufreq
M: Tao Zeng <tao.zeng@amlogic.com>

View File

@@ -800,6 +800,12 @@
interrupts = <0 52 1>;
interrupt-names = "ddr_bandwidth";
};
dmc_monitor {
compatible = "amlogic, dmc_monitor";
status = "okay";
reg_base = <0xff638800>;
interrupts = <0 51 1>;
};
cpu_ver_name{
compatible = "amlogic, cpu-major-id-axg";

View File

@@ -1782,6 +1782,12 @@
interrupts = <0 52 1>;
interrupt-names = "ddr_bandwidth";
};
dmc_monitor {
compatible = "amlogic, dmc_monitor";
status = "okay";
reg_base = <0xff639000>;
interrupts = <0 51 1>;
};
defendkey: defendkey {
compatible = "amlogic, defendkey";

View File

@@ -1826,6 +1826,12 @@
interrupts = <0 52 1>;
interrupt-names = "ddr_bandwidth";
};
dmc_monitor {
compatible = "amlogic, dmc_monitor";
status = "okay";
reg_base = <0xff639000>;
interrupts = <0 51 1>;
};
isp_sc: isp-sc@ff655400 {
compatible = "amlogic, isp-sc";

View File

@@ -1347,6 +1347,12 @@
interrupts = <0 52 1>;
interrupt-names = "ddr_bandwidth";
};
dmc_monitor {
compatible = "amlogic, dmc_monitor";
status = "okay";
reg_base = <0xda838400>;
interrupts = <0 51 1>;
};
};
&gpu{

View File

@@ -1430,5 +1430,11 @@
interrupts = <0 52 1>;
interrupt-names = "ddr_bandwidth";
};
dmc_monitor {
compatible = "amlogic, dmc_monitor";
status = "okay";
reg_base = <0xda838400>;
interrupts = <0 51 1>;
};
};

View File

@@ -1035,6 +1035,20 @@
status = "okay";
};
ddr_bandwidth {
compatible = "amlogic, ddr-bandwidth";
status = "okay";
reg = <0x0 0xc8838000 0x0 0x100
0x0 0xc8837000 0x0 0x100>;
interrupts = <0 52 1>;
interrupt-names = "ddr_bandwidth";
};
dmc_monitor {
compatible = "amlogic, dmc_monitor";
status = "okay";
reg_base = <0xda838400>;
interrupts = <0 51 1>;
};
}; /* end of / */
&gpu{

View File

@@ -928,6 +928,12 @@
interrupts = <0 52 1>;
interrupt-names = "ddr_bandwidth";
};
dmc_monitor {
compatible = "amlogic, dmc_monitor";
status = "okay";
reg_base = <0xff638800>;
interrupts = <0 51 1>;
};
cpu_ver_name {
compatible = "amlogic, cpu-major-id-txlx";

View File

@@ -26,3 +26,13 @@ config AMLOGIC_DDR_BANDWIDTH
This config enables ddr bandwidth measure.
If open it, this driver will export interface to get ddr total
bandwidth, it can be used for ddr DVFS/devfreq system.
config AMLOGIC_DMC_MONITOR
bool "Amlogic dmc monitor"
depends on AMLOGIC_DDR_TOOL
default y
help
DMC monitor for hardware connected to DMC master. Using it can
set up an address range and assign R/W permission for each
hardware. If hardware violated R/W permission, then interrupt
will generated and can help to debug memory pollution.

View File

@@ -1,5 +1,8 @@
obj-y += ddr_port_desc.o
obj-$(CONFIG_AMLOGIC_DDR_WINDOW_TOOL) += ddr_window.o
obj-$(CONFIG_AMLOGIC_DDR_BANDWIDTH) += ddr_bandwidth.o ddr_band_op_gxl.o \
ddr_band_op_gx.o ddr_band_op_g12.o \
ddr_band_port_desc.o
ddr_band_op_gx.o ddr_band_op_g12.o
obj-$(CONFIG_AMLOGIC_DMC_MONITOR) += dmc_monitor.o \
dmc_g12.o dmc_gx.o

View File

@@ -281,11 +281,7 @@ static int __ref ddr_bandwidth_probe(struct platform_device *pdev)
if (pcnt < 0)
pr_err("can't find port descriptor,cpu:%d\n", aml_db->cpu_type);
else {
aml_db->port_desc = kcalloc(pcnt, sizeof(*desc), GFP_KERNEL);
if (!aml_db->port_desc)
goto inval;
pr_info("port count:%d, desc:%p\n", pcnt, aml_db->port_desc);
memcpy(aml_db->port_desc, desc, sizeof(*desc) * pcnt);
aml_db->port_desc = desc;
aml_db->real_ports = pcnt;
}

View File

@@ -1,5 +1,5 @@
/*
* drivers/amlogic/ddr_tool/ddr_band_port_desc.c
* drivers/amlogic/ddr_tool/ddr_port_desc.c
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
@@ -31,6 +31,25 @@
#include <linux/io.h>
#include <linux/slab.h>
/*
* NOTE:
* Port "DEVICE" is total name for small bandwidth device.
* There are many small bandwidth devices such as nand/arb/parser
* connected to dmc under port "device", for better configure of
* these devices, re-number them with start ID of 32
*
* EXAMPLE:
*
* DMC CONTROLLER
* |
* ---------------------------------
* | | | ..... | | ... |
* arm mali vpu device vdec hevc
* |
* ------------------------
* | | | ... | |
* emmc ge2d usb audio spicc
*/
static struct ddr_port_desc ddr_port_desc_m8b[] __initdata = {
{ .port_id = 0, .port_name = "ARM" },
{ .port_id = 1, .port_name = "MALI0" },
@@ -42,6 +61,7 @@ static struct ddr_port_desc ddr_port_desc_m8b[] __initdata = {
{ .port_id = 11, .port_name = "VDEC" },
{ .port_id = 12, .port_name = "HCODEC" },
{ .port_id = 14, .port_name = "AUDIO" },
{ .port_id = 15, .port_name = "DEVICE" },
/* start of each device */
{ .port_id = 33, .port_name = "NAND" },
{ .port_id = 34, .port_name = "BLKMV" },
@@ -64,6 +84,7 @@ static struct ddr_port_desc ddr_port_desc_gxbb[] __initdata = {
{ .port_id = 2, .port_name = "MALI1" },
{ .port_id = 3, .port_name = "HDCP" },
{ .port_id = 4, .port_name = "HEVC" },
{ .port_id = 7, .port_name = "DEVICE" },
{ .port_id = 8, .port_name = "VPU READ1" },
{ .port_id = 9, .port_name = "VPU READ2" },
{ .port_id = 10, .port_name = "VPU READ3" },
@@ -98,6 +119,7 @@ static struct ddr_port_desc ddr_port_desc_gxl[] __initdata = {
{ .port_id = 4, .port_name = "HEVC" },
{ .port_id = 5, .port_name = "TEST" },
{ .port_id = 6, .port_name = "USB3.0" },
{ .port_id = 7, .port_name = "DEVICE" },
{ .port_id = 8, .port_name = "VPU READ1" },
{ .port_id = 9, .port_name = "VPU READ2" },
{ .port_id = 10, .port_name = "VPU READ3" },
@@ -132,6 +154,7 @@ static struct ddr_port_desc ddr_port_desc_gxm[] __initdata = {
{ .port_id = 4, .port_name = "HEVC" },
{ .port_id = 5, .port_name = "TEST" },
{ .port_id = 6, .port_name = "USB3.0" },
{ .port_id = 7, .port_name = "DEVICE" },
{ .port_id = 8, .port_name = "VPU READ1" },
{ .port_id = 9, .port_name = "VPU READ2" },
{ .port_id = 10, .port_name = "VPU READ3" },
@@ -166,6 +189,7 @@ static struct ddr_port_desc ddr_port_desc_gxlx[] __initdata = {
{ .port_id = 4, .port_name = "HEVC" },
{ .port_id = 5, .port_name = "H265ENC/TEST" },
{ .port_id = 6, .port_name = "USB3.0" },
{ .port_id = 7, .port_name = "DEVICE" },
{ .port_id = 8, .port_name = "VPU READ1" },
{ .port_id = 9, .port_name = "VPU READ2" },
{ .port_id = 10, .port_name = "VPU READ3" },
@@ -200,6 +224,7 @@ static struct ddr_port_desc ddr_port_desc_g12a[] __initdata = {
{ .port_id = 4, .port_name = "HEVC FRONT" },
{ .port_id = 5, .port_name = "TEST" },
{ .port_id = 6, .port_name = "USB3.0" },
{ .port_id = 7, .port_name = "DEVICE" },
{ .port_id = 8, .port_name = "HEVC BACK" },
{ .port_id = 9, .port_name = "H265ENC" },
{ .port_id = 16, .port_name = "VPU READ1" },
@@ -235,6 +260,7 @@ static struct ddr_port_desc ddr_port_desc_g12b[] __initdata = {
{ .port_id = 4, .port_name = "HEVC FRONT" },
{ .port_id = 5, .port_name = "TEST" },
{ .port_id = 6, .port_name = "USB3.0" },
{ .port_id = 7, .port_name = "DEVICE" },
{ .port_id = 8, .port_name = "HEVC BACK" },
{ .port_id = 9, .port_name = "H265ENC" },
{ .port_id = 10, .port_name = "NNA" },
@@ -275,6 +301,7 @@ static struct ddr_port_desc ddr_port_desc_axg[] __initdata = {
{ .port_id = 4, .port_name = "AUDIO" },
{ .port_id = 5, .port_name = "TEST" },
{ .port_id = 6, .port_name = "USB3.0" },
{ .port_id = 7, .port_name = "DEVICE" },
{ .port_id = 8, .port_name = "VPU READ0" },
/* start of each device */
{ .port_id = 34, .port_name = "DMA" },
@@ -296,6 +323,7 @@ static struct ddr_port_desc ddr_port_desc_txl[] __initdata = {
{ .port_id = 4, .port_name = "HEVC" },
{ .port_id = 5, .port_name = "TEST" },
{ .port_id = 6, .port_name = "USB3.0" },
{ .port_id = 7, .port_name = "DEVICE" },
{ .port_id = 8, .port_name = "VPU READ1" },
{ .port_id = 9, .port_name = "VPU READ2" },
{ .port_id = 10, .port_name = "VPU READ3" },
@@ -331,6 +359,7 @@ static struct ddr_port_desc ddr_port_desc_txlx[] __initdata = {
{ .port_id = 4, .port_name = "HEVC" },
{ .port_id = 5, .port_name = "TEST" },
{ .port_id = 6, .port_name = "USB3.0" },
{ .port_id = 7, .port_name = "DEVICE" },
{ .port_id = 8, .port_name = "VPU READ1" },
{ .port_id = 9, .port_name = "VPU READ2" },
{ .port_id = 10, .port_name = "VPU READ3" },
@@ -365,6 +394,7 @@ static struct ddr_port_desc ddr_port_desc_txhd[] __initdata = {
{ .port_id = 4, .port_name = "HEVC" },
{ .port_id = 5, .port_name = "TEST" },
{ .port_id = 6, .port_name = "USB3.0" },
{ .port_id = 7, .port_name = "DEVICE" },
{ .port_id = 8, .port_name = "VPU READ1" },
{ .port_id = 9, .port_name = "VPU READ2" },
{ .port_id = 10, .port_name = "VPU READ3" },
@@ -391,10 +421,18 @@ static struct ddr_port_desc ddr_port_desc_txhd[] __initdata = {
{ .port_id = 47, .port_name = "DEMOD" }
};
static struct ddr_port_desc *chip_ddr_port;
static unsigned char chip_ddr_port_num;
int __init ddr_find_port_desc(int cpu_type, struct ddr_port_desc **desc)
{
int desc_size = -EINVAL;
if (chip_ddr_port) {
*desc = chip_ddr_port;
return chip_ddr_port_num;
}
switch (cpu_type) {
case MESON_CPU_MAJOR_ID_M8B:
*desc = ddr_port_desc_m8b;
@@ -459,5 +497,13 @@ int __init ddr_find_port_desc(int cpu_type, struct ddr_port_desc **desc)
break;
}
/* using once */
chip_ddr_port = kcalloc(desc_size, sizeof(*chip_ddr_port), GFP_KERNEL);
if (!chip_ddr_port)
return -ENOMEM;
memcpy(chip_ddr_port, *desc, sizeof(*chip_ddr_port) * desc_size);
chip_ddr_port_num = desc_size;
*desc = chip_ddr_port;
return desc_size;
}

View File

@@ -0,0 +1,171 @@
/*
* drivers/amlogic/ddr_tool/dmc_g12.c
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 the GNU General Public License for
* more details.
*
*/
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/sched.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_fdt.h>
#include <linux/irqreturn.h>
#include <linux/module.h>
#include <linux/mm.h>
#include <linux/cpu.h>
#include <linux/smp.h>
#include <linux/kallsyms.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/amlogic/cpu_version.h>
#include <linux/amlogic/page_trace.h>
#include <linux/amlogic/dmc_monitor.h>
#include <linux/amlogic/ddr_port.h>
#define DMC_PROT0_RANGE ((0x00a0 << 2))
#define DMC_PROT0_CTRL ((0x00a1 << 2))
#define DMC_PROT1_RANGE ((0x00a2 << 2))
#define DMC_PROT1_CTRL ((0x00a3 << 2))
#define DMC_SEC_STATUS ((0x00b8 << 2))
#define DMC_VIO_ADDR0 ((0x00b9 << 2))
#define DMC_VIO_ADDR1 ((0x00ba << 2))
#define DMC_VIO_ADDR2 ((0x00bb << 2))
#define DMC_VIO_ADDR3 ((0x00bc << 2))
#define DMC_VIO_PROT_RANGE0 (1 << 21)
#define DMC_VIO_PROT_RANGE1 (1 << 22)
static size_t g12_dmc_dump_reg(char *buf)
{
size_t sz = 0, i;
unsigned long val;
val = dmc_rw(DMC_PROT0_RANGE, 0, DMC_READ);
sz += sprintf(buf + sz, "DMC_PROT0_RANGE:%lx\n", val);
val = dmc_rw(DMC_PROT0_CTRL, 0, DMC_READ);
sz += sprintf(buf + sz, "DMC_PROT0_CTRL:%lx\n", val);
val = dmc_rw(DMC_PROT1_RANGE, 0, DMC_READ);
sz += sprintf(buf + sz, "DMC_PROT1_RANGE:%lx\n", val);
val = dmc_rw(DMC_PROT1_CTRL, 0, DMC_READ);
sz += sprintf(buf + sz, "DMC_PROT1_CTRL:%lx\n", val);
val = dmc_rw(DMC_SEC_STATUS, 0, DMC_READ);
sz += sprintf(buf + sz, "DMC_SEC_STATUS:%lx\n", val);
for (i = 0; i < 4; i++) {
val = dmc_rw(DMC_VIO_ADDR0 + (i << 2), 0, DMC_READ);
sz += sprintf(buf + sz, "DMC_VIO_ADDR%ld:%lx\n", i, val);
}
return sz;
}
static void check_violation(struct dmc_monitor *mon)
{
int i, port, subport;
unsigned long addr, status;
struct page *page;
unsigned long *p;
char id_str[4];
for (i = 1; i < 4; i += 2) {
status = dmc_rw(DMC_VIO_ADDR0 + (i << 2), 0, DMC_READ);
if (!(status & DMC_VIO_PROT_RANGE0))
continue;
addr = dmc_rw(DMC_VIO_ADDR0 + ((i - 1) << 2), 0,
DMC_READ);
if (addr > mon->addr_end)
continue;
/* ignore violation on same page/same port */
if ((addr & PAGE_MASK) == mon->last_addr &&
status == mon->last_status) {
mon->same_page++;
continue;
}
port = (status >> 13) & 0x1f;
subport = (status >> 6) & 0xf;
pr_info(DMC_TAG", addr:%08lx, s:%08lx, ID:%s, sub:%s, c:%ld\n",
addr, status, to_ports(port),
to_sub_ports(port, subport, id_str), mon->same_page);
if (pfn_valid(__phys_to_pfn(addr))) {
page = phys_to_page(addr);
p = (page_address(page) + (addr & (PAGE_SIZE - 1)));
pr_info(DMC_TAG" [%08lx]:%016lx, f:%8lx, m:%p, a:%pf\n",
addr, *p, page->flags & 0xffffffff,
page->mapping,
(void *)get_page_trace(page));
}
if (!port) /* dump stack for CPU write */
dump_stack();
mon->same_page = 0;
mon->last_addr = addr & PAGE_MASK;
mon->last_status = status;
}
}
static void g12_dmc_mon_irq(struct dmc_monitor *mon)
{
unsigned long value;
value = dmc_rw(DMC_SEC_STATUS, 0, DMC_READ);
if (value & DMC_WRITE_VIOLATION)
check_violation(mon);
/* check irq flags just after IRQ handler */
if (in_interrupt())
mod_delayed_work(system_wq, &mon->work, 0);
/* clear irq */
dmc_rw(DMC_SEC_STATUS, value, DMC_WRITE);
}
static int g12_dmc_mon_set(struct dmc_monitor *mon)
{
unsigned long value, end;
/* aligned to 64KB */
end = ALIGN(mon->addr_end, DMC_ADDR_SIZE);
value = (mon->addr_start >> 16) | ((end >> 16) << 16);
dmc_rw(DMC_PROT0_RANGE, value, DMC_WRITE);
value = (1 << 24) | mon->device;
dmc_rw(DMC_PROT0_CTRL, value, DMC_WRITE);
pr_info("range:%08lx - %08lx, device:%x\n",
mon->addr_start, mon->addr_end, mon->device);
return 0;
}
void g12_dmc_mon_disable(struct dmc_monitor *mon)
{
dmc_rw(DMC_PROT0_RANGE, 0, DMC_WRITE);
dmc_rw(DMC_PROT0_CTRL, 0, DMC_WRITE);
mon->device = 0;
mon->addr_start = 0;
mon->addr_end = 0;
}
struct dmc_mon_ops g12_dmc_mon_ops = {
.handle_irq = g12_dmc_mon_irq,
.set_montor = g12_dmc_mon_set,
.disable = g12_dmc_mon_disable,
.dump_reg = g12_dmc_dump_reg,
};

View File

@@ -0,0 +1,175 @@
/*
* drivers/amlogic/ddr_tool/dmc_gx.c
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 the GNU General Public License for
* more details.
*
*/
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/sched.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_fdt.h>
#include <linux/irqreturn.h>
#include <linux/module.h>
#include <linux/mm.h>
#include <linux/cpu.h>
#include <linux/smp.h>
#include <linux/kallsyms.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/amlogic/cpu_version.h>
#include <linux/amlogic/page_trace.h>
#include <linux/amlogic/dmc_monitor.h>
#include <linux/amlogic/ddr_port.h>
#define DMC_PROT0_RANGE ((0x00a0 << 2))
#define DMC_PROT0_CTRL ((0x00a1 << 2))
#define DMC_PROT1_RANGE ((0x00a2 << 2))
#define DMC_PROT1_CTRL ((0x00a3 << 2))
#define DMC_SEC_STATUS ((0x00b6 << 2))
#define DMC_VIO_ADDR0 ((0x00b7 << 2))
#define DMC_VIO_ADDR1 ((0x00b8 << 2))
#define DMC_VIO_ADDR2 ((0x00b9 << 2))
#define DMC_VIO_ADDR3 ((0x00ba << 2))
#define DMC_VIO_ADDR4 ((0x00bb << 2))
#define DMC_VIO_ADDR5 ((0x00bc << 2))
#define DMC_VIO_ADDR6 ((0x00bd << 2))
#define DMC_VIO_ADDR7 ((0x00be << 2))
#define DMC_VIO_PROT_RANGE0 (1 << 20)
#define DMC_VIO_PROT_RANGE1 (1 << 21)
static size_t gx_dmc_dump_reg(char *buf)
{
size_t sz = 0, i;
unsigned long val;
val = dmc_rw(DMC_PROT0_RANGE, 0, DMC_READ);
sz += sprintf(buf + sz, "DMC_PROT0_RANGE:%lx\n", val);
val = dmc_rw(DMC_PROT0_CTRL, 0, DMC_READ);
sz += sprintf(buf + sz, "DMC_PROT0_CTRL:%lx\n", val);
val = dmc_rw(DMC_PROT1_RANGE, 0, DMC_READ);
sz += sprintf(buf + sz, "DMC_PROT1_RANGE:%lx\n", val);
val = dmc_rw(DMC_PROT1_CTRL, 0, DMC_READ);
sz += sprintf(buf + sz, "DMC_PROT1_CTRL:%lx\n", val);
val = dmc_rw(DMC_SEC_STATUS, 0, DMC_READ);
sz += sprintf(buf + sz, "DMC_SEC_STATUS:%lx\n", val);
for (i = 0; i < 8; i++) {
val = dmc_rw(DMC_VIO_ADDR0 + (i << 2), 0, DMC_READ);
sz += sprintf(buf + sz, "DMC_VIO_ADDR%ld:%lx\n", i, val);
}
return sz;
}
static void check_violation(struct dmc_monitor *mon)
{
int i, port, subport;
unsigned long addr, status;
struct page *page;
unsigned long *p;
char id_str[4];
for (i = 1; i < 8; i += 2) {
status = dmc_rw(DMC_VIO_ADDR0 + (i << 2), 0, DMC_READ);
if (!(status & DMC_VIO_PROT_RANGE0))
continue;
addr = dmc_rw(DMC_VIO_ADDR0 + ((i - 1) << 2), 0,
DMC_READ);
if (addr > mon->addr_end)
continue;
/* ignore violation on same page/same port */
if ((addr & PAGE_MASK) == mon->last_addr &&
status == mon->last_status) {
mon->same_page++;
continue;
}
port = (status >> 10) & 0xf;
subport = (status >> 6) & 0xf;
pr_info(DMC_TAG", addr:%08lx, s:%08lx, ID:%s, sub:%s, c:%ld\n",
addr, status, to_ports(port),
to_sub_ports(port, subport, id_str), mon->same_page);
if (pfn_valid(__phys_to_pfn(addr))) {
page = phys_to_page(addr);
p = (page_address(page) + (addr & (PAGE_SIZE - 1)));
pr_info(DMC_TAG" [%08lx]:%016lx, f:%8lx, m:%p, a:%pf\n",
addr, *p, page->flags & 0xffffffff,
page->mapping,
(void *)get_page_trace(page));
}
if (!port) /* dump stack for CPU write */
dump_stack();
mon->same_page = 0;
mon->last_addr = addr & PAGE_MASK;
mon->last_status = status;
}
}
static void gx_dmc_mon_irq(struct dmc_monitor *mon)
{
unsigned long value;
value = dmc_rw(DMC_SEC_STATUS, 0, DMC_READ);
if (value & DMC_WRITE_VIOLATION)
check_violation(mon);
/* check irq flags just after IRQ handler */
if (in_interrupt())
mod_delayed_work(system_wq, &mon->work, 0);
/* clear irq */
dmc_rw(DMC_SEC_STATUS, value, DMC_WRITE);
}
static int gx_dmc_mon_set(struct dmc_monitor *mon)
{
unsigned long value, end;
/* aligned to 64KB */
end = ALIGN(mon->addr_end, DMC_ADDR_SIZE);
value = (mon->addr_start >> 16) | ((end >> 16) << 16);
dmc_rw(DMC_PROT0_RANGE, value, DMC_WRITE);
value = (1 << 19) | mon->device;
dmc_rw(DMC_PROT0_CTRL, value, DMC_WRITE);
pr_info("range:%08lx - %08lx, device:%x\n",
mon->addr_start, mon->addr_end, mon->device);
return 0;
}
void gx_dmc_mon_disable(struct dmc_monitor *mon)
{
dmc_rw(DMC_PROT0_RANGE, 0, DMC_WRITE);
dmc_rw(DMC_PROT0_CTRL, 0, DMC_WRITE);
mon->device = 0;
mon->addr_start = 0;
mon->addr_end = 0;
}
struct dmc_mon_ops gx_dmc_mon_ops = {
.handle_irq = gx_dmc_mon_irq,
.set_montor = gx_dmc_mon_set,
.disable = gx_dmc_mon_disable,
.dump_reg = gx_dmc_dump_reg,
};

View File

@@ -0,0 +1,373 @@
/*
* drivers/amlogic/ddr_tool/dmc_monitor.c
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 the GNU General Public License for
* more details.
*
*/
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/sched.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_fdt.h>
#include <linux/irqreturn.h>
#include <linux/module.h>
#include <linux/mm.h>
#include <linux/cpu.h>
#include <linux/smp.h>
#include <linux/kallsyms.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/amlogic/cpu_version.h>
#include <linux/amlogic/page_trace.h>
#include <linux/arm-smccc.h>
#include <linux/amlogic/dmc_monitor.h>
#include <linux/amlogic/ddr_port.h>
static struct dmc_monitor *dmc_mon;
unsigned long dmc_rw(unsigned long addr, unsigned long value, int rw)
{
struct arm_smccc_res smccc;
arm_smccc_smc(DMC_MON_RW, addr + dmc_mon->io_base,
value, rw, 0, 0, 0, 0, &smccc);
return smccc.a0;
}
static int dev_name_to_id(const char *dev_name)
{
int i, len;
for (i = 0; i < dmc_mon->port_num; i++) {
if (dmc_mon->port[i].port_id >= PORT_MAJOR)
return -1;
len = strlen(dmc_mon->port[i].port_name);
if (!strncmp(dmc_mon->port[i].port_name, dev_name, len))
break;
}
if (i >= dmc_mon->port_num)
return -1;
return i;
}
char *to_ports(int id)
{
int i;
for (i = 0; i < dmc_mon->port_num; i++) {
if (dmc_mon->port[i].port_id == id)
return dmc_mon->port[i].port_name;
}
return NULL;
}
char *to_sub_ports(int mid, int sid, char *id_str)
{
int i;
/* 7 is device port id */
if (mid == 7) {
for (i = 0; i < dmc_mon->port_num; i++) {
if (dmc_mon->port[i].port_id == sid + PORT_MAJOR)
return dmc_mon->port[i].port_name;
}
}
sprintf(id_str, "%2d", sid);
return id_str;
}
unsigned int get_all_dev_mask(void)
{
unsigned int ret = 0;
int i;
for (i = 0; i < PORT_MAJOR; i++) {
if (dmc_mon->port[i].port_id >= PORT_MAJOR)
break;
ret |= (1 << dmc_mon->port[i].port_id);
}
return ret;
}
static size_t dump_reg(char *buf)
{
size_t sz = 0, i;
if (dmc_mon->ops && dmc_mon->ops->dump_reg)
sz += dmc_mon->ops->dump_reg(buf);
sz += sprintf(buf + sz, "IO_BASE:%lx\n", dmc_mon->io_base);
sz += sprintf(buf + sz, "RANGE:%lx - %lx\n",
dmc_mon->addr_start, dmc_mon->addr_end);
sz += sprintf(buf + sz, "MONITOR DEVICE:\n");
for (i = 0; i < sizeof(dmc_mon->device) * 8; i++) {
if (dmc_mon->device & (1 << i))
sz += sprintf(buf + sz, " %s\n", to_ports(i));
}
return sz;
}
static irqreturn_t dmc_monitor_irq_handler(int irq, void *dev_instance)
{
if (dmc_mon->ops && dmc_mon->ops->handle_irq)
dmc_mon->ops->handle_irq(dmc_mon);
return IRQ_HANDLED;
}
static void clear_irq_work(struct work_struct *work)
{
/*
* DMC VIOLATION may happen very quickly and irq re-generated
* again before CPU leave IRQ mode, once this scenario happened,
* DMC protection would not generate IRQ again until we cleared
* it manually.
* Since no parameters used for irq handler, so we just call IRQ
* handler again to save code size.
*/
dmc_monitor_irq_handler(0, NULL);
schedule_delayed_work(&dmc_mon->work, HZ);
}
int dmc_set_monitor(unsigned long start, unsigned long end,
unsigned long dev_mask, int en)
{
if (!dmc_mon)
return -EINVAL;
dmc_mon->addr_start = start;
dmc_mon->addr_end = end;
if (en)
dmc_mon->device |= dev_mask;
else
dmc_mon->device &= ~(dev_mask);
if (start < end && dmc_mon->ops && dmc_mon->ops->set_montor)
return dmc_mon->ops->set_montor(dmc_mon);
return -EINVAL;
}
EXPORT_SYMBOL(dmc_set_monitor);
int dmc_set_monitor_by_name(unsigned long start, unsigned long end,
const char *port_name, int en)
{
long id;
id = dev_name_to_id(port_name);
if (id < 0 || id >= BITS_PER_LONG)
return -EINVAL;
return dmc_set_monitor(start, end, 1 << id, en);
}
EXPORT_SYMBOL(dmc_set_monitor_by_name);
void dmc_monitor_disable(void)
{
if (dmc_mon->ops && dmc_mon->ops->disable)
return dmc_mon->ops->disable(dmc_mon);
}
EXPORT_SYMBOL(dmc_monitor_disable);
static ssize_t protect_range_show(struct class *cla,
struct class_attribute *attr, char *buf)
{
return sprintf(buf, "%08lx - %08lx\n",
dmc_mon->addr_start, dmc_mon->addr_end);
}
static ssize_t protect_range_store(struct class *cla,
struct class_attribute *attr, const char *buf, size_t count)
{
int ret;
unsigned long start, end;
ret = sscanf(buf, "%lx %lx", &start, &end);
if (ret != 2) {
pr_info("%s, bad input:%s\n", __func__, buf);
return count;
}
dmc_set_monitor(start, end, dmc_mon->device, 1);
return count;
}
static ssize_t dev_store(struct class *cla,
struct class_attribute *attr, const char *buf, size_t count)
{
int i;
if (!strncmp(buf, "none", 4)) {
dmc_monitor_disable();
return count;
}
if (!strncmp(buf, "all", 3))
dmc_mon->device = get_all_dev_mask();
else {
i = dev_name_to_id(buf);
if (i < 0) {
pr_info("bad device:%s\n", buf);
return -EINVAL;
}
dmc_mon->device |= (1 << i);
}
dmc_set_monitor(dmc_mon->addr_start, dmc_mon->addr_end,
dmc_mon->device, 1);
return count;
}
static ssize_t dev_show(struct class *cla,
struct class_attribute *attr, char *buf)
{
int i, s = 0;
s += sprintf(buf + s, "supported device:\n");
for (i = 0; i < dmc_mon->port_num; i++) {
if (dmc_mon->port[i].port_id >= PORT_MAJOR)
break;
s += sprintf(buf + s, "%2d:%s\n",
dmc_mon->port[i].port_id,
dmc_mon->port[i].port_name);
}
return s;
}
static ssize_t dump_show(struct class *cla,
struct class_attribute *attr, char *buf)
{
return dump_reg(buf);
}
static struct class_attribute dmc_monitor_attr[] = {
__ATTR(range, 0664, protect_range_show, protect_range_store),
__ATTR(device, 0664, dev_show, dev_store),
__ATTR_RO(dump),
__ATTR_NULL
};
static struct class dmc_monitor_class = {
.name = "dmc_monitor",
.class_attrs = dmc_monitor_attr,
};
static int dmc_monitor_probe(struct platform_device *pdev)
{
int r = 0, irq, ports;
unsigned int io;
struct device_node *node = pdev->dev.of_node;
struct ddr_port_desc *desc;
pr_info("%s\n", __func__);
r = get_cpu_type();
dmc_mon = kzalloc(sizeof(struct dmc_monitor), GFP_KERNEL);
if (!dmc_mon)
return -ENOMEM;
ports = ddr_find_port_desc(r, &desc);
if (ports < 0) {
pr_info("can't get port desc\n");
goto inval;
}
dmc_mon->chip = r;
dmc_mon->port_num = ports;
dmc_mon->port = desc;
if (dmc_mon->chip >= MESON_CPU_MAJOR_ID_G12A)
dmc_mon->ops = &g12_dmc_mon_ops;
else
dmc_mon->ops = &gx_dmc_mon_ops;
r = of_property_read_u32(node, "reg_base", &io);
if (r < 0) {
pr_info("can't find iobase\n");
goto inval;
}
dmc_mon->io_base = io;
irq = of_irq_get(node, 0);
r = request_irq(irq, dmc_monitor_irq_handler,
IRQF_SHARED, "dmc_monitor", pdev);
if (r < 0) {
pr_info("request irq failed:%d, r:%d\n", irq, r);
goto inval;
}
r = class_register(&dmc_monitor_class);
if (r) {
pr_err("regist dmc_monitor_class failed\n");
goto inval;
}
INIT_DELAYED_WORK(&dmc_mon->work, clear_irq_work);
schedule_delayed_work(&dmc_mon->work, HZ);
return 0;
inval:
kfree(dmc_mon);
dmc_mon = NULL;
return -EINVAL;
}
static int dmc_monitor_remove(struct platform_device *pdev)
{
cancel_delayed_work_sync(&dmc_mon->work);
class_unregister(&dmc_monitor_class);
kfree(dmc_mon);
dmc_mon = NULL;
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id dmc_monitor_match[] = {
{
.compatible = "amlogic, dmc_monitor",
},
{}
};
#endif
static struct platform_driver dmc_monitor_driver = {
.driver = {
.name = "dmc_monitor",
.owner = THIS_MODULE,
#ifdef CONFIG_OF
.of_match_table = dmc_monitor_match,
#endif
},
.probe = dmc_monitor_probe,
.remove = dmc_monitor_remove,
};
static int __init dmc_monitor_init(void)
{
int ret;
ret = platform_driver_register(&dmc_monitor_driver);
return ret;
}
static void __exit dmc_monitor_exit(void)
{
platform_driver_unregister(&dmc_monitor_driver);
}
module_init(dmc_monitor_init);
module_exit(dmc_monitor_exit);
MODULE_DESCRIPTION("amlogic dmc monitor driver");
MODULE_LICENSE("GPL");

View File

@@ -24,10 +24,8 @@
#define DMC_QOS_IRQ (1 << 30)
#define MAX_CHANNEL 4
#define MAX_PORTS 256
#define MAX_NAME 15
#define PORT_MAJOR 32
#include <linux/amlogic/ddr_port.h>
/*
* register offset for chips before g12
*/
@@ -87,11 +85,6 @@ struct ddr_bandwidth_ops {
#endif
};
struct ddr_port_desc {
char port_name[MAX_NAME];
unsigned char port_id;
};
struct ddr_bandwidth {
void __iomem *ddr_reg;
void __iomem *pll_reg;
@@ -114,7 +107,6 @@ extern unsigned int aml_get_ddr_usage(void);
extern struct ddr_bandwidth_ops g12_ddr_bw_ops;
extern struct ddr_bandwidth_ops gx_ddr_bw_ops;
extern struct ddr_bandwidth_ops gxl_ddr_bw_ops;
extern int ddr_find_port_desc(int cpu_type, struct ddr_port_desc **desc);
#else
static inline unsigned int aml_get_ddr_usage(void)
{

View File

@@ -0,0 +1,32 @@
/*
* include/linux/amlogic/ddr_port.h
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 the GNU General Public License for
* more details.
*
*/
#ifndef __DDR_PORT_DESC_H__
#define __DDR_PORT_DESC_H__
#define MAX_PORTS 256
#define MAX_NAME 15
#define PORT_MAJOR 32
struct ddr_port_desc {
char port_name[MAX_NAME];
unsigned char port_id;
};
extern int ddr_find_port_desc(int cpu_type, struct ddr_port_desc **desc);
#endif /* __DDR_PORT_DESC_H__ */

View File

@@ -0,0 +1,94 @@
/*
* include/linux/amlogic/dmc_monitor.h
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 the GNU General Public License for
* more details.
*
*/
#ifndef __DMC_MONITOR_H__
#define __DMC_MONITOR_H__
#define PROTECT_READ (1 << 0)
#define PROTECT_WRITE (1 << 1)
#define DMC_MON_RW 0x8200004A
#define DMC_READ 0
#define DMC_WRITE 1
#define DMC_WRITE_VIOLATION (1 << 1)
/*
* Address is aligned to 64 KB
*/
#define DMC_ADDR_SIZE (0x10000)
#define DMC_TAG "DMC VIOLATION"
struct dmc_monitor;
struct dmc_mon_ops {
void (*handle_irq)(struct dmc_monitor *mon);
int (*set_montor)(struct dmc_monitor *mon);
void (*disable)(struct dmc_monitor *mon);
size_t (*dump_reg)(char *buf);
};
struct dmc_monitor {
unsigned long io_base;
unsigned long addr_start;
unsigned long addr_end;
unsigned int device;
unsigned short port_num;
unsigned short chip;
unsigned long last_addr;
unsigned long same_page;
unsigned long last_status;
struct ddr_port_desc *port;
struct dmc_mon_ops *ops;
struct delayed_work work;
};
extern void dmc_monitor_disable(void);
/*
* start: physical start address, aligned to 64KB
* end: physical end address, aligned to 64KB
* dev_mask: device bit to set
* en: 0: close monitor, 1: enable monitor
*/
extern int dmc_set_monitor(unsigned long start, unsigned long end,
unsigned long dev_mask, int en);
/*
* start: physical start address, aligned to 64KB
* end: physical end address, aligned to 64KB
* port_name: name of port to set, see ddr_port_desc for each chip in
* drivers/amlogic/ddr_tool/ddr_port_desc.c
* en: 0: close monitor, 1: enable monitor
*/
extern int dmc_set_monitor_by_name(unsigned long start, unsigned long end,
const char *port_name, int en);
extern unsigned int get_all_dev_mask(void);
/*
* Following functions are internal used only
*/
extern unsigned long dmc_rw(unsigned long addr, unsigned long value, int rw);
extern char *to_ports(int id);
extern char *to_sub_ports(int mid, int sid, char *id_str);
extern struct dmc_mon_ops gx_dmc_mon_ops;
extern struct dmc_mon_ops g12_dmc_mon_ops;
#endif /* __DMC_MONITOR_H__ */