From 1310ef5ef2a9b950897171f09ff96e3cd2b01f14 Mon Sep 17 00:00:00 2001 From: tao zeng Date: Wed, 22 Aug 2018 14:34:44 +0800 Subject: [PATCH] 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 --- MAINTAINERS | 10 +- arch/arm64/boot/dts/amlogic/mesonaxg.dtsi | 6 + arch/arm64/boot/dts/amlogic/mesong12a.dtsi | 6 + arch/arm64/boot/dts/amlogic/mesong12b.dtsi | 6 + arch/arm64/boot/dts/amlogic/mesongxl.dtsi | 6 + arch/arm64/boot/dts/amlogic/mesongxm.dtsi | 6 + arch/arm64/boot/dts/amlogic/mesontxl.dtsi | 14 + arch/arm64/boot/dts/amlogic/mesontxlx.dtsi | 6 + drivers/amlogic/ddr_tool/Kconfig | 10 + drivers/amlogic/ddr_tool/Makefile | 7 +- drivers/amlogic/ddr_tool/ddr_bandwidth.c | 6 +- .../{ddr_band_port_desc.c => ddr_port_desc.c} | 48 ++- drivers/amlogic/ddr_tool/dmc_g12.c | 171 ++++++++ drivers/amlogic/ddr_tool/dmc_gx.c | 175 ++++++++ drivers/amlogic/ddr_tool/dmc_monitor.c | 373 ++++++++++++++++++ include/linux/amlogic/aml_ddr_bandwidth.h | 10 +- include/linux/amlogic/ddr_port.h | 32 ++ include/linux/amlogic/dmc_monitor.h | 94 +++++ 18 files changed, 968 insertions(+), 18 deletions(-) rename drivers/amlogic/ddr_tool/{ddr_band_port_desc.c => ddr_port_desc.c} (92%) create mode 100644 drivers/amlogic/ddr_tool/dmc_g12.c create mode 100644 drivers/amlogic/ddr_tool/dmc_gx.c create mode 100644 drivers/amlogic/ddr_tool/dmc_monitor.c create mode 100644 include/linux/amlogic/ddr_port.h create mode 100644 include/linux/amlogic/dmc_monitor.h diff --git a/MAINTAINERS b/MAINTAINERS index a84a9f00643b..4f707db2cd57 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13502,7 +13502,15 @@ F: drivers/amlogic/thermal/* M: Tao Zeng 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 +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 diff --git a/arch/arm64/boot/dts/amlogic/mesonaxg.dtsi b/arch/arm64/boot/dts/amlogic/mesonaxg.dtsi index cca0c31cbdce..49d84f32496b 100644 --- a/arch/arm64/boot/dts/amlogic/mesonaxg.dtsi +++ b/arch/arm64/boot/dts/amlogic/mesonaxg.dtsi @@ -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"; diff --git a/arch/arm64/boot/dts/amlogic/mesong12a.dtsi b/arch/arm64/boot/dts/amlogic/mesong12a.dtsi index 2b1e033f5c21..6e4424b1ec73 100644 --- a/arch/arm64/boot/dts/amlogic/mesong12a.dtsi +++ b/arch/arm64/boot/dts/amlogic/mesong12a.dtsi @@ -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"; diff --git a/arch/arm64/boot/dts/amlogic/mesong12b.dtsi b/arch/arm64/boot/dts/amlogic/mesong12b.dtsi index ddda2a670746..bdf231c8c9b6 100644 --- a/arch/arm64/boot/dts/amlogic/mesong12b.dtsi +++ b/arch/arm64/boot/dts/amlogic/mesong12b.dtsi @@ -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"; diff --git a/arch/arm64/boot/dts/amlogic/mesongxl.dtsi b/arch/arm64/boot/dts/amlogic/mesongxl.dtsi index 13a9861cbc0c..f40d05e53c85 100644 --- a/arch/arm64/boot/dts/amlogic/mesongxl.dtsi +++ b/arch/arm64/boot/dts/amlogic/mesongxl.dtsi @@ -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{ diff --git a/arch/arm64/boot/dts/amlogic/mesongxm.dtsi b/arch/arm64/boot/dts/amlogic/mesongxm.dtsi index d707e53de915..9c9232222189 100644 --- a/arch/arm64/boot/dts/amlogic/mesongxm.dtsi +++ b/arch/arm64/boot/dts/amlogic/mesongxm.dtsi @@ -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>; + }; }; diff --git a/arch/arm64/boot/dts/amlogic/mesontxl.dtsi b/arch/arm64/boot/dts/amlogic/mesontxl.dtsi index cf657a9ca28b..547e77443d58 100644 --- a/arch/arm64/boot/dts/amlogic/mesontxl.dtsi +++ b/arch/arm64/boot/dts/amlogic/mesontxl.dtsi @@ -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{ diff --git a/arch/arm64/boot/dts/amlogic/mesontxlx.dtsi b/arch/arm64/boot/dts/amlogic/mesontxlx.dtsi index c3bf130a37cc..220efeb40cd1 100644 --- a/arch/arm64/boot/dts/amlogic/mesontxlx.dtsi +++ b/arch/arm64/boot/dts/amlogic/mesontxlx.dtsi @@ -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"; diff --git a/drivers/amlogic/ddr_tool/Kconfig b/drivers/amlogic/ddr_tool/Kconfig index b6ffb87fe287..7e5f0f902a54 100644 --- a/drivers/amlogic/ddr_tool/Kconfig +++ b/drivers/amlogic/ddr_tool/Kconfig @@ -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. diff --git a/drivers/amlogic/ddr_tool/Makefile b/drivers/amlogic/ddr_tool/Makefile index a3a52b2033b9..af6c0942d584 100644 --- a/drivers/amlogic/ddr_tool/Makefile +++ b/drivers/amlogic/ddr_tool/Makefile @@ -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 diff --git a/drivers/amlogic/ddr_tool/ddr_bandwidth.c b/drivers/amlogic/ddr_tool/ddr_bandwidth.c index b161f376b7e2..e86286b3ba29 100644 --- a/drivers/amlogic/ddr_tool/ddr_bandwidth.c +++ b/drivers/amlogic/ddr_tool/ddr_bandwidth.c @@ -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; } diff --git a/drivers/amlogic/ddr_tool/ddr_band_port_desc.c b/drivers/amlogic/ddr_tool/ddr_port_desc.c similarity index 92% rename from drivers/amlogic/ddr_tool/ddr_band_port_desc.c rename to drivers/amlogic/ddr_tool/ddr_port_desc.c index 8dd9ea508462..124d679be56e 100644 --- a/drivers/amlogic/ddr_tool/ddr_band_port_desc.c +++ b/drivers/amlogic/ddr_tool/ddr_port_desc.c @@ -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 #include +/* + * 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; } diff --git a/drivers/amlogic/ddr_tool/dmc_g12.c b/drivers/amlogic/ddr_tool/dmc_g12.c new file mode 100644 index 000000000000..a8db41748438 --- /dev/null +++ b/drivers/amlogic/ddr_tool/dmc_g12.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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, +}; diff --git a/drivers/amlogic/ddr_tool/dmc_gx.c b/drivers/amlogic/ddr_tool/dmc_gx.c new file mode 100644 index 000000000000..c5d09414f372 --- /dev/null +++ b/drivers/amlogic/ddr_tool/dmc_gx.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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, +}; diff --git a/drivers/amlogic/ddr_tool/dmc_monitor.c b/drivers/amlogic/ddr_tool/dmc_monitor.c new file mode 100644 index 000000000000..0a3553888a9c --- /dev/null +++ b/drivers/amlogic/ddr_tool/dmc_monitor.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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"); diff --git a/include/linux/amlogic/aml_ddr_bandwidth.h b/include/linux/amlogic/aml_ddr_bandwidth.h index 7982156daa2a..5e3113b58775 100644 --- a/include/linux/amlogic/aml_ddr_bandwidth.h +++ b/include/linux/amlogic/aml_ddr_bandwidth.h @@ -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 /* * 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) { diff --git a/include/linux/amlogic/ddr_port.h b/include/linux/amlogic/ddr_port.h new file mode 100644 index 000000000000..a50755f189f5 --- /dev/null +++ b/include/linux/amlogic/ddr_port.h @@ -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__ */ diff --git a/include/linux/amlogic/dmc_monitor.h b/include/linux/amlogic/dmc_monitor.h new file mode 100644 index 000000000000..9fe2e3464d37 --- /dev/null +++ b/include/linux/amlogic/dmc_monitor.h @@ -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__ */