Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c409a1a3 authored by Imran Khan's avatar Imran Khan Committed by Runmin Wang
Browse files

soc: qcom: amon: Activity monitor driver for LLCC



Add activity monitor driver to detect deadlock in system
cache operations and dump the status of corresponding entries.

Change-Id: If8baae37a183beced49a5660afa16bce6eabb89b
Signed-off-by: default avatarImran Khan <kimran@codeaurora.org>
Signed-off-by: default avatarChannagoud Kadabi <ckadabi@codeaurora.org>
Signed-off-by: default avatarRunmin Wang <runminw@codeaurora.org>
parent 645a0798
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -65,6 +65,12 @@ Optional Properties:
	Value type: <string>
	Definition: Property to enable or disable the driver

== llcc amon device ==

Optional Properties:
-qcom,fg-cnt : The value of fine grained counter of activity monitor
	        block.

Example:

	qcom,llcc@01300000 {
@@ -85,6 +91,7 @@ Example:

		qcom,llcc-amon {
			compatible = "qcom,llcc-amon";
			qcom,fg-cnt = <0x7>;
		};
	};

+18 −0
Original line number Diff line number Diff line
@@ -31,6 +31,24 @@ config QCOM_LLCC
	  Level Cache. This provides interfaces to client's that use the LLCC.
	  Say yes here to enable LLCC slice driver.

config QCOM_LLCC_AMON
	tristate "Qualcomm Technologies, Inc. LLCC Activity Monitor(AMON) driver"
	depends on QCOM_LLCC
	help
	  This option enables a activity monitor driver for last level cache
	  controller. This driver configures the activity monitor as
	  deadlock detector and dumps the AMON registers upon detection of
	  deadlock.

config QCOM_LLCC_AMON_PANIC
	tristate "Panic on detecting LLCC Activity Monitor(AMON) error"
	depends on QCOM_LLCC_AMON
	help
	  This option enables panic upon detection of LLCC Activity Monitor(AMON)
	  errors. Say yes here to enable deadlock detection mode of AMON. In
	  deadlock detection mode AMON will trigger an interrupt if some LLCC request
	  ages out.

config QCOM_PM
	bool "Qualcomm Power Management"
	depends on ARCH_QCOM && !ARM64
+1 −0
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@ obj-$(CONFIG_QCOM_CPUSS_DUMP) += cpuss_dump.o
obj-$(CONFIG_QCOM_GSBI)	+=	qcom_gsbi.o
obj-$(CONFIG_QCOM_MDT_LOADER)	+= mdt_loader.o
obj-$(CONFIG_QCOM_LLCC) += llcc-core.o llcc-slice.o
obj-$(CONFIG_QCOM_LLCC_AMON) += llcc-amon.o
obj-$(CONFIG_QCOM_PM)	+=	spm.o
obj-$(CONFIG_QCOM_SMD_RPM)	+= smd-rpm.o
obj-$(CONFIG_QCOM_SMEM) +=	smem.o
+536 −0
Original line number Diff line number Diff line
/* Copyright (c) 2016-2017, The Linux Foundation. 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 version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sysfs.h>
#include <linux/kobject.h>
#include <linux/regmap.h>
#include <linux/mfd/syscon.h>
#include <linux/platform_device.h>

#define MODULE_NAME "LLCC AMON deadlock detector"
static bool qcom_llcc_amon_panic = IS_ENABLED(
			CONFIG_QCOM_LLCC_AMON_PANIC) ? true:false;
module_param(qcom_llcc_amon_panic, bool, 0644);
MODULE_PARM_DESC(qcom_llcc_amon_panic,
		"Enables deadlock detection by AMON");

static int amon_interrupt_mode;
module_param(amon_interrupt_mode, int, 0644);
MODULE_PARM_DESC(amon_interrupt_mode,
		"Controls whether to use interrupt or poll mode");

enum channel_type {
	AMON_READ,
	AMON_WRITE,
	AMON_EVICT,
};

#define LLCC_AMON_FG_CNT_MASK			0x70
#define LLCC_AMON_CG_CNT_MASK			0x380
#define LLCC_AMON_FG_CNT_SHIFT			0x4
#define LLCC_AMON_CG_CNT_SHIFT			0x7
#define LLCC_COMMON_IRQ_AMON			0x8
#define LLCC_AMON_STATUS0_MASK			0x3ff
#define LLCC_AMON_CHAN_SHIFT			0x4
#define LLCC_AMON_CNTR_SATURATED_SOURCE_TYPE(x) (0x1 << x)
#define LLCC_AMON_CFG0_DLDM_SHIFT		0x2

/* AMON register offsets */
#define LLCC_AMON_START			0x3d000
#define LLCC_AMON_CFG0			0x3d000
#define LLCC_AMON_EVICT_REGS		0x3d020
#define LLCC_AMON_WRITE_REGS		0x3d060
#define LLCC_AMON_READ_REGS		0x3d100
#define LLCC_AMON_IRQ_STATUS		0x3d200
#define LLCC_AMON_IRQ_CLEAR		0x3d208
#define LLCC_AMON_IRQ_ENABLE		0x3d20c
#define LLCC_AMON_STATUS_0		0x3d210

/* AMON configuration register bits */
#define LLCC_AMON_CFG0_ENABLE	0x0 /* Enable AMON */
#define LLCC_AMON_CFG0_DLDM	0x2 /* AMON deadlock detection mode */
#define LLCC_AMON_CFG0_SCRM	0x3 /* Enable SCID based deadlock detection */

/* Number of entries maintained by AMON */
#define NR_READ_ENTRIES		40
#define NR_WRITE_ENTRIES	16
#define NR_EVICT_ENTRIES	8
#define AMON_IRQ_BIT		0x0
#define CHAN_OFF(x)		(x << 2)

/**
 * llcc_amon_data: Data structure containing AMON driver data
 *
 * @llcc_amon_regmap: map of AMON register block
 * @dev: LLCC activiy monitor device
 * @amon_irq: LLCC activity monitor irq number
 * @amon_fg_cnt: LLCC activity monitor fine grained counter overflow bit
 * @amon_work: LLCC activity monitor work item to poll and
 * dump AMON CSRs
 * @amon_wq: LLCC activity monitor workqueue to poll and
 * dump AMON CSRs
 * @amon_lock: lock to access and change AMON driver data
 *
 **/
struct llcc_amon_data {
	struct regmap *llcc_amon_regmap;
	struct device *dev;
	u32 amon_irq;
	u32 amon_fg_cnt;
	struct work_struct amon_work;
	struct workqueue_struct *amon_wq;
	struct mutex amon_lock;
};

static void amon_dump_channel_status(struct device *dev, u32 chnum, u32 type)
{
	u32 llcc_amon_csr;
	struct llcc_amon_data *amon_data = dev_get_drvdata(dev);

	switch (type) {
	case AMON_READ:
		regmap_read(amon_data->llcc_amon_regmap,
			(LLCC_AMON_READ_REGS + CHAN_OFF(chnum)),
			&llcc_amon_csr);
		dev_err(dev, "READ entry %u : %8x\n",
					chnum, llcc_amon_csr);
		break;
	case AMON_WRITE:
		regmap_read(amon_data->llcc_amon_regmap,
			(LLCC_AMON_WRITE_REGS + CHAN_OFF(chnum)),
			&llcc_amon_csr);
		dev_err(dev, "WRITE entry %u : %8x\n",
					chnum, llcc_amon_csr);
		break;
	case AMON_EVICT:
		regmap_read(amon_data->llcc_amon_regmap,
			(LLCC_AMON_EVICT_REGS + CHAN_OFF(chnum)),
			&llcc_amon_csr);
		dev_err(dev, "EVICT entry %u : %8x\n",
					chnum, llcc_amon_csr);
		break;
	}
}

static void amon_dump_read_channel_status(struct device *dev, u32 chnum)
{
	amon_dump_channel_status(dev, chnum, AMON_READ);
}

static void amon_dump_write_channel_status(struct device *dev, u32 chnum)
{
	amon_dump_channel_status(dev, chnum, AMON_WRITE);
}

static void amon_dump_evict_channel_status(struct device *dev, u32 chnum)
{
	amon_dump_channel_status(dev, chnum, AMON_EVICT);
}

static ssize_t amon_fg_count_store(struct device *dev,
			struct device_attribute *attr,
			const char *buf, size_t n)
{
	struct llcc_amon_data *amon_data;
	int ret;
	u32 count;

	amon_data = dev_get_drvdata(dev);
	if (!amon_data)
		return -ENODEV;

	mutex_lock(&amon_data->amon_lock);
	ret = kstrtouint(buf, 0, &count);
	if (ret) {
		dev_err(amon_data->dev, "invalid user input\n");
		mutex_unlock(&amon_data->amon_lock);
		return ret;
	}

	/* Set fine grained counter */
	regmap_update_bits(amon_data->llcc_amon_regmap, LLCC_AMON_CFG0,
		LLCC_AMON_FG_CNT_MASK, (count << LLCC_AMON_FG_CNT_SHIFT));
	mutex_unlock(&amon_data->amon_lock);
	return n;
}

static ssize_t amon_fg_count_show(struct device *dev,
			struct device_attribute *attr,
			char *buf)
{
	struct llcc_amon_data *amon_data;
	int ret;
	u32 count, llcc_amon_cfg0;

	amon_data = dev_get_drvdata(dev);
	if (!amon_data)
		return -ENODEV;

	mutex_lock(&amon_data->amon_lock);
	/* Get fine grained counter */
	regmap_read(amon_data->llcc_amon_regmap, LLCC_AMON_CFG0,
		&llcc_amon_cfg0);
	count = (llcc_amon_cfg0 & LLCC_AMON_FG_CNT_MASK)
					>> LLCC_AMON_FG_CNT_SHIFT;
	ret = snprintf(buf, PAGE_SIZE, "%u\n", count);
	mutex_unlock(&amon_data->amon_lock);
	return ret;
}

static ssize_t amon_deadlock_mode_store(struct device *dev,
			struct device_attribute *attr,
			const char *buf, size_t n)
{
	struct llcc_amon_data *amon_data;
	int ret;
	u32 mode;

	amon_data = dev_get_drvdata(dev);
	if (!amon_data)
		return -ENODEV;

	mutex_lock(&amon_data->amon_lock);
	ret = kstrtouint(buf, 0, &mode);
	if (ret) {
		dev_err(amon_data->dev, "invalid user input\n");
		mutex_unlock(&amon_data->amon_lock);
		return ret;
	}

	/* Set deadlock detection mode */
	regmap_update_bits(amon_data->llcc_amon_regmap, LLCC_AMON_CFG0,
		BIT(LLCC_AMON_CFG0_DLDM), mode);
	mutex_unlock(&amon_data->amon_lock);
	return n;
}

static ssize_t amon_deadlock_mode_show(struct device *dev,
			struct device_attribute *attr,
			char *buf)
{
	struct llcc_amon_data *amon_data;
	int ret;
	u32 val, llcc_amon_cfg0;

	amon_data = dev_get_drvdata(dev);
	if (!amon_data)
		return -ENODEV;

	mutex_lock(&amon_data->amon_lock);

	/* Get deadlock detection mode */
	regmap_read(amon_data->llcc_amon_regmap, LLCC_AMON_CFG0,
		&llcc_amon_cfg0);
	val = llcc_amon_cfg0 & BIT(LLCC_AMON_CFG0_DLDM);
	ret = snprintf(buf, PAGE_SIZE, "%d\n",
			val >> LLCC_AMON_CFG0_DLDM_SHIFT);

	mutex_unlock(&amon_data->amon_lock);
	return ret;
}


static DEVICE_ATTR(amon_fg_count, 0644,
				amon_fg_count_show, amon_fg_count_store);
static DEVICE_ATTR(amon_deadlock_mode, 0644,
			amon_deadlock_mode_show, amon_deadlock_mode_store);

static const struct device_attribute *llcc_amon_attrs[] = {
	&dev_attr_amon_fg_count,
	&dev_attr_amon_deadlock_mode,
	NULL,
};

static int amon_create_sysfs_files(struct device *dev,
			const struct device_attribute **attrs)
{
	int ret = 0, i;

	for (i = 0; attrs[i] != NULL; i++) {
		ret = device_create_file(dev, attrs[i]);
		if (ret) {
			dev_err(dev, "AMON: Couldn't create sysfs entry: %s!\n",
				attrs[i]->attr.name);
			break;
		}
	}
	return ret;
}

static int amon_remove_sysfs_files(struct device *dev,
			const struct device_attribute **attrs)
{
	int ret = 0, i;

	for (i = 0; attrs[i] != NULL; i++)
		device_remove_file(dev, attrs[i]);

	return ret;
}

static void enable_qcom_amon_interrupt(struct llcc_amon_data *amon_data)
{

	regmap_update_bits(amon_data->llcc_amon_regmap, LLCC_AMON_IRQ_ENABLE,
		BIT(AMON_IRQ_BIT), BIT(AMON_IRQ_BIT));
}

void disable_qcom_amon_interrupt(struct llcc_amon_data *amon_data)
{
	regmap_update_bits(amon_data->llcc_amon_regmap, LLCC_AMON_IRQ_ENABLE,
		AMON_IRQ_BIT, AMON_IRQ_BIT);
}

static void clear_qcom_amon_interrupt(struct llcc_amon_data *amon_data)
{
	regmap_update_bits(amon_data->llcc_amon_regmap, LLCC_AMON_IRQ_CLEAR,
		BIT(AMON_IRQ_BIT), BIT(AMON_IRQ_BIT));
}

static void amon_poll_work(struct work_struct *work)
{
	u32 llcc_amon_status0, llcc_amon_irq_status, chnum;
	struct llcc_amon_data *amon_data = container_of(work,
				struct llcc_amon_data, amon_work);

	/* Check for deadlock */
	regmap_read(amon_data->llcc_amon_regmap, LLCC_AMON_IRQ_STATUS,
		&llcc_amon_irq_status);
	if (!(llcc_amon_irq_status & BIT(AMON_IRQ_BIT)))
		/* No deadlock interrupt */
		return;

	regmap_read(amon_data->llcc_amon_regmap, LLCC_AMON_STATUS_0,
		&llcc_amon_status0);
	if (!llcc_amon_status0)
		return;

	chnum = (llcc_amon_status0 & LLCC_AMON_STATUS0_MASK) >>
				 LLCC_AMON_CHAN_SHIFT;
	if (llcc_amon_status0 &
		LLCC_AMON_CNTR_SATURATED_SOURCE_TYPE(AMON_READ)) {
		/* Read channel error */
		amon_dump_read_channel_status(amon_data->dev, chnum);
	} else if (llcc_amon_status0 &
		LLCC_AMON_CNTR_SATURATED_SOURCE_TYPE(AMON_WRITE)) {
		/* Write channel error */
		amon_dump_write_channel_status(amon_data->dev, chnum);
	} else if (llcc_amon_status0 &
		LLCC_AMON_CNTR_SATURATED_SOURCE_TYPE(AMON_EVICT)) {
		/* Evict channel error */
		amon_dump_evict_channel_status(amon_data->dev, chnum);
	}

	clear_qcom_amon_interrupt(amon_data);

	if (qcom_llcc_amon_panic)
		panic("AMON deadlock detected");
}

static irqreturn_t llcc_amon_irq_handler(int irq, void *dev_data)
{
	u32 llcc_amon_status0, llcc_amon_irq_status;
	int chnum;
	struct llcc_amon_data *amon_data = dev_data;

	regmap_read(amon_data->llcc_amon_regmap, LLCC_AMON_IRQ_STATUS,
		&llcc_amon_irq_status);
	if (!(llcc_amon_irq_status & BIT(AMON_IRQ_BIT)))
		/* No deadlock interrupt */
		return IRQ_NONE;

	regmap_read(amon_data->llcc_amon_regmap, LLCC_AMON_STATUS_0,
		&llcc_amon_status0);
	if (unlikely(llcc_amon_status0 == 0))
		return IRQ_NONE;

	/*
	 * Check type of interrupt and channel number.
	 * Call corresponding handler with channel number.
	 */

	chnum = (llcc_amon_status0 & LLCC_AMON_STATUS0_MASK) >>
			 LLCC_AMON_CHAN_SHIFT;
	if (llcc_amon_status0 &
		LLCC_AMON_CNTR_SATURATED_SOURCE_TYPE(AMON_READ)) {
		/* Read channel error */
		amon_dump_read_channel_status(amon_data->dev, chnum);
	} else if (llcc_amon_status0 &
		LLCC_AMON_CNTR_SATURATED_SOURCE_TYPE(AMON_WRITE)) {
		/* Write channel error */
		amon_dump_write_channel_status(amon_data->dev, chnum);
	} else if (llcc_amon_status0 &
		LLCC_AMON_CNTR_SATURATED_SOURCE_TYPE(AMON_EVICT)) {
		/* Evict channel error */
		amon_dump_evict_channel_status(amon_data->dev, chnum);
	}

	clear_qcom_amon_interrupt(amon_data);

	if (qcom_llcc_amon_panic)
		panic("AMON deadlock detected");

	return IRQ_HANDLED;
}


static int qcom_llcc_amon_dt_to_pdata(struct platform_device *pdev,
					struct llcc_amon_data *pdata)
{
	struct device_node *node = pdev->dev.of_node;

	pdata->dev = &pdev->dev;

	pdata->llcc_amon_regmap = syscon_node_to_regmap(
				pdata->dev->parent->of_node);
	if (IS_ERR(pdata->llcc_amon_regmap)) {
		dev_err(pdata->dev, "No regmap for syscon amon parent\n");
		return -ENOMEM;
	}

	pdata->amon_irq = platform_get_irq(pdev, 0);
	if (!pdata->amon_irq)
		return -ENODEV;

	of_property_read_u32(node, "qcom,fg-cnt",
				&pdata->amon_fg_cnt);
	return 0;
}

static int qcom_llcc_amon_probe(struct platform_device *pdev)
{
	int ret;
	struct llcc_amon_data *amon_data;
	u32 cnt;

	if (!pdev->dev.of_node)
		return -ENODEV;

	amon_data = devm_kzalloc(&pdev->dev, sizeof(struct llcc_amon_data),
				GFP_KERNEL);
	if (!amon_data)
		return -ENOMEM;

	ret = qcom_llcc_amon_dt_to_pdata(pdev, amon_data);
	if (ret) {
		dev_err(amon_data->dev, "failed to get amon data\n");
		return ret;
	}

	amon_data->dev = &pdev->dev;


	platform_set_drvdata(pdev, amon_data);

	/* Enable Activity Monitor */
	regmap_update_bits(amon_data->llcc_amon_regmap, LLCC_AMON_CFG0,
		BIT(LLCC_AMON_CFG0_ENABLE), 0x1);

	/* Enable Activity Monitor (AMON) as deadlock detector */
	regmap_update_bits(amon_data->llcc_amon_regmap, LLCC_AMON_CFG0,
		BIT(LLCC_AMON_CFG0_DLDM), BIT(LLCC_AMON_CFG0_DLDM));

	/* Set fine grained counter */
	cnt = amon_data->amon_fg_cnt << LLCC_AMON_FG_CNT_SHIFT;
	if (cnt)
		regmap_update_bits(amon_data->llcc_amon_regmap,
			LLCC_AMON_CFG0,	LLCC_AMON_FG_CNT_MASK, cnt);

	mutex_init(&amon_data->amon_lock);
	ret = amon_create_sysfs_files(&pdev->dev, llcc_amon_attrs);
	if (ret) {
		dev_err(amon_data->dev,
			"failed to create sysfs entries\n");
		platform_set_drvdata(pdev, NULL);
		return ret;
	}

	if (amon_interrupt_mode) { /* Interrupt mode */
		ret = devm_request_irq(amon_data->dev, amon_data->amon_irq,
				llcc_amon_irq_handler, IRQF_TRIGGER_RISING,
				"amon_deadlock", amon_data);
		if (ret) {
			dev_err(amon_data->dev,
				"failed to request amon deadlock irq\n");
			platform_set_drvdata(pdev, NULL);
			return ret;
		}
		enable_qcom_amon_interrupt(amon_data);
	} else { /* Polling mode */
		amon_data->amon_wq = create_singlethread_workqueue(
						"amon_deadlock_detector");
		if (!amon_data->amon_wq) {
			dev_err(amon_data->dev,
				"failed to create polling work queue\n");
			platform_set_drvdata(pdev, NULL);
			return -ENOMEM;
		}
		INIT_WORK(&amon_data->amon_work, amon_poll_work);
		queue_work(amon_data->amon_wq, &amon_data->amon_work);
	}

	return 0;
}

static int qcom_llcc_amon_remove(struct platform_device *pdev)
{
	struct llcc_amon_data *amon_data = platform_get_drvdata(pdev);

	disable_qcom_amon_interrupt(amon_data);
	clear_qcom_amon_interrupt(amon_data);
	/* Disable Activity Monitor (AMON) as deadlock detector */
	regmap_update_bits(amon_data->llcc_amon_regmap, LLCC_AMON_CFG0,
		BIT(LLCC_AMON_CFG0_DLDM), 0x0);
	/* Disable Activity Monitor */
	regmap_update_bits(amon_data->llcc_amon_regmap, LLCC_AMON_CFG0,
		BIT(LLCC_AMON_CFG0_ENABLE), 0x0);

	amon_remove_sysfs_files(&pdev->dev, llcc_amon_attrs);
	destroy_workqueue(amon_data->amon_wq);
	platform_set_drvdata(pdev, NULL);
	return 0;
}

static const struct of_device_id qcom_llcc_amon_match_table[] = {
	{ .compatible = "qcom,llcc-amon" },
	{},
};

static struct platform_driver qcom_llcc_amon_driver = {
	.probe = qcom_llcc_amon_probe,
	.remove = qcom_llcc_amon_remove,
	.driver = {
		.name = MODULE_NAME,
		.owner = THIS_MODULE,
		.of_match_table = qcom_llcc_amon_match_table,
	},
};

static int __init init_qcom_llcc_amon(void)
{
	return platform_driver_register(&qcom_llcc_amon_driver);
}
module_init(init_qcom_llcc_amon);

static void __exit exit_qcom_llcc_amon(void)
{
	return platform_driver_unregister(&qcom_llcc_amon_driver);
}
module_exit(exit_qcom_llcc_amon);

MODULE_DESCRIPTION("QTI LLCC Activity Monitor Driver");
MODULE_LICENSE("GPL v2");