msm: ocmem: Add debugfs support

Add support for viewing ocmem allocations, zones,
zone specific statistics and power states via debugfs.

Change-Id: I7c3d4d4bccfcd7a1825023d860ce491ae2a7ef5f
Signed-off-by: Naveen Ramaraj <nramaraj@codeaurora.org>
diff --git a/arch/arm/mach-msm/include/mach/ocmem_priv.h b/arch/arm/mach-msm/include/mach/ocmem_priv.h
index 737db0b..47f9b10 100644
--- a/arch/arm/mach-msm/include/mach/ocmem_priv.h
+++ b/arch/arm/mach-msm/include/mach/ocmem_priv.h
@@ -10,8 +10,8 @@
  * GNU General Public License for more details.
  */
 
-#ifndef _ARCH_ARM_MACH_MSM_OCMEM_CORE_H
-#define _ARCH_ARM_MACH_MSM_OCMEM_CORE_H
+#ifndef _ARCH_ARM_MACH_MSM_OCMEM_PRIV_H
+#define _ARCH_ARM_MACH_MSM_OCMEM_PRIV_H
 
 /** All interfaces in this header should only be used by OCMEM driver
  *  Client drivers should use wrappers available in ocmem.h
@@ -36,6 +36,29 @@
 	int (*free) (struct ocmem_zone *, unsigned long, unsigned long);
 };
 
+/* OCMEM Zone specific counters */
+/* Must be in sync with zstat_names */
+enum ocmem_zstat_item {
+	NR_REQUESTS = 0x0,
+	NR_SYNC_ALLOCATIONS,
+	NR_RANGE_ALLOCATIONS,
+	NR_ASYNC_ALLOCATIONS,
+	NR_ALLOCATION_FAILS,
+	NR_GROWTHS,
+	NR_FREES,
+	NR_SHRINKS,
+	NR_MAPS,
+	NR_MAP_FAILS,
+	NR_UNMAPS,
+	NR_UNMAP_FAILS,
+	NR_TRANSFERS_TO_OCMEM,
+	NR_TRANSFERS_TO_DDR,
+	NR_TRANSFER_FAILS,
+	NR_EVICTIONS,
+	NR_RESTORES,
+	NR_OCMEM_ZSTAT_ITEMS,
+};
+
 struct ocmem_zone {
 	bool active;
 	int owner;
@@ -47,6 +70,7 @@
 	unsigned long z_head;
 	unsigned long z_tail;
 	unsigned long z_free;
+	atomic_long_t z_stat[NR_OCMEM_ZSTAT_ITEMS];
 	struct gen_pool *z_pool;
 	struct ocmem_zone_ops *z_ops;
 };
@@ -82,6 +106,7 @@
 	void __iomem *reg_base;
 	void __iomem *br_base;
 	void __iomem *dm_base;
+	struct dentry *debug_node;
 	unsigned nr_regions;
 	unsigned nr_macros;
 	unsigned nr_ports;
@@ -143,7 +168,9 @@
 struct ocmem_handle *req_to_handle(struct ocmem_req *);
 int ocmem_read(void *);
 int ocmem_write(unsigned long, void *);
-
+void inc_ocmem_stat(struct ocmem_zone *, enum ocmem_zstat_item);
+unsigned long get_ocmem_stat(struct ocmem_zone *z,
+				enum ocmem_zstat_item item);
 struct ocmem_zone *get_zone(unsigned);
 int zone_active(int);
 unsigned long offset_to_phys(unsigned long);
@@ -159,7 +186,7 @@
 int check_id(int);
 int dispatch_notification(int, enum ocmem_notif_type, struct ocmem_buf *);
 
-int ocmem_sched_init(void);
+int ocmem_sched_init(struct platform_device *);
 int ocmem_rdm_init(struct platform_device *);
 int ocmem_core_init(struct platform_device *);
 int process_allocate(int, struct ocmem_handle *, unsigned long, unsigned long,
diff --git a/arch/arm/mach-msm/ocmem.c b/arch/arm/mach-msm/ocmem.c
index 82fe2f8..51445aa 100644
--- a/arch/arm/mach-msm/ocmem.c
+++ b/arch/arm/mach-msm/ocmem.c
@@ -63,6 +63,27 @@
 	"other_os",
 };
 
+/* Must be in sync with enum ocmem_zstat_item */
+static const char *zstat_names[NR_OCMEM_ZSTAT_ITEMS] = {
+	"Allocation requests",
+	"Synchronous allocations",
+	"Ranged allocations",
+	"Asynchronous allocations",
+	"Allocation failures",
+	"Allocations grown",
+	"Allocations freed",
+	"Allocations shrunk",
+	"OCMEM maps",
+	"Map failures",
+	"OCMEM unmaps",
+	"Unmap failures",
+	"Transfers to OCMEM",
+	"Transfers to DDR",
+	"Transfer failures",
+	"Evictions",
+	"Restorations",
+};
+
 struct ocmem_quota_table {
 	const char *name;
 	int id;
@@ -135,6 +156,23 @@
 		return 0;
 }
 
+inline void inc_ocmem_stat(struct ocmem_zone *z,
+				enum ocmem_zstat_item item)
+{
+	if (!z)
+		return;
+	atomic_long_inc(&z->z_stat[item]);
+}
+
+inline unsigned long get_ocmem_stat(struct ocmem_zone *z,
+				enum ocmem_zstat_item item)
+{
+	if (!z)
+		return 0;
+	else
+		return atomic_long_read(&z->z_stat[item]);
+}
+
 static struct ocmem_plat_data *parse_static_config(struct platform_device *pdev)
 {
 	struct ocmem_plat_data *pdata = NULL;
@@ -473,6 +511,60 @@
 	return NULL;
 }
 
+static int ocmem_zones_show(struct seq_file *f, void *dummy)
+{
+	unsigned i = 0;
+	for (i = OCMEM_GRAPHICS; i < OCMEM_CLIENT_MAX; i++) {
+		struct ocmem_zone *z = get_zone(i);
+		if (z && z->active == true)
+			seq_printf(f, "zone %s\t:0x%08lx - 0x%08lx (%4ld KB)\n",
+				get_name(z->owner), z->z_start, z->z_end - 1,
+				(z->z_end - z->z_start)/SZ_1K);
+	}
+	return 0;
+}
+
+static int ocmem_zones_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, ocmem_zones_show, inode->i_private);
+}
+
+static const struct file_operations zones_show_fops = {
+	.open = ocmem_zones_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = seq_release,
+};
+
+static int ocmem_stats_show(struct seq_file *f, void *dummy)
+{
+	unsigned i = 0;
+	unsigned j = 0;
+	for (i = OCMEM_GRAPHICS; i < OCMEM_CLIENT_MAX; i++) {
+		struct ocmem_zone *z = get_zone(i);
+		if (z && z->active == true) {
+			seq_printf(f, "zone %s:\n", get_name(z->owner));
+			for (j = 0 ; j < ARRAY_SIZE(zstat_names); j++) {
+				seq_printf(f, "\t %s: %lu\n", zstat_names[j],
+					get_ocmem_stat(z, j));
+			}
+		}
+	}
+	return 0;
+}
+
+static int ocmem_stats_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, ocmem_stats_show, inode->i_private);
+}
+
+static const struct file_operations stats_show_fops = {
+	.open = ocmem_stats_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = seq_release,
+};
+
 static int ocmem_zone_init(struct platform_device *pdev)
 {
 
@@ -549,6 +641,8 @@
 			z_ops->allocate = allocate_head;
 			z_ops->free = free_head;
 		}
+		/* zap the counters */
+		memset(zone->z_stat, 0 , sizeof(zone->z_stat));
 		zone->active = true;
 		active_zones++;
 
@@ -557,7 +651,19 @@
 
 		pr_info(" zone %s\t: 0x%08lx - 0x%08lx (%4ld KB)\n",
 				client_names[part->id], zone->z_start,
-				zone->z_end, part->p_size/SZ_1K);
+				zone->z_end - 1, part->p_size/SZ_1K);
+	}
+
+	if (!debugfs_create_file("zones", S_IRUGO, pdata->debug_node,
+					NULL, &zones_show_fops)) {
+		dev_err(dev, "Unable to create debugfs node for zones\n");
+		return -EBUSY;
+	}
+
+	if (!debugfs_create_file("stats", S_IRUGO, pdata->debug_node,
+					NULL, &stats_show_fops)) {
+		dev_err(dev, "Unable to create debugfs node for stats\n");
+		return -EBUSY;
 	}
 
 	dev_dbg(dev, "Total active zones = %d\n", active_zones);
@@ -587,6 +693,27 @@
 	return 0;
 }
 
+static int __devinit ocmem_debugfs_init(struct platform_device *pdev)
+{
+	struct dentry *debug_dir = NULL;
+	struct ocmem_plat_data *pdata = platform_get_drvdata(pdev);
+
+	debug_dir = debugfs_create_dir("ocmem", NULL);
+	if (!debug_dir || IS_ERR(debug_dir)) {
+		pr_err("ocmem: Unable to create debugfs root\n");
+		return PTR_ERR(debug_dir);
+	}
+
+	pdata->debug_node =  debug_dir;
+	return 0;
+}
+
+static void __devexit ocmem_debugfs_exit(struct platform_device *pdev)
+{
+	struct ocmem_plat_data *pdata = platform_get_drvdata(pdev);
+	debugfs_remove_recursive(pdata->debug_node);
+}
+
 static int __devinit msm_ocmem_probe(struct platform_device *pdev)
 {
 	struct device   *dev = &pdev->dev;
@@ -635,6 +762,9 @@
 
 	platform_set_drvdata(pdev, ocmem_pdata);
 
+	if (ocmem_debugfs_init(pdev))
+		return -EBUSY;
+
 	if (ocmem_core_init(pdev))
 		return -EBUSY;
 
@@ -644,7 +774,7 @@
 	if (ocmem_notifier_init())
 		return -EBUSY;
 
-	if (ocmem_sched_init())
+	if (ocmem_sched_init(pdev))
 		return -EBUSY;
 
 	if (ocmem_rdm_init(pdev))
@@ -661,6 +791,7 @@
 
 static int __devexit msm_ocmem_remove(struct platform_device *pdev)
 {
+	ocmem_debugfs_exit(pdev);
 	return 0;
 }
 
diff --git a/arch/arm/mach-msm/ocmem_core.c b/arch/arm/mach-msm/ocmem_core.c
index d9d67a3..de9856d 100644
--- a/arch/arm/mach-msm/ocmem_core.c
+++ b/arch/arm/mach-msm/ocmem_core.c
@@ -665,6 +665,81 @@
 	return switch_power_state(id, offset, len, REGION_DEFAULT_RETENTION);
 }
 
+static int ocmem_power_show_sw_state(struct seq_file *f, void *dummy)
+{
+	unsigned i, j;
+	unsigned m_state;
+	mutex_lock(&region_ctrl_lock);
+
+	seq_printf(f, "OCMEM Aggregated Power States\n");
+	for (i = 0 ; i < num_regions; i++) {
+		struct ocmem_hw_region *region = &region_ctrl[i];
+		seq_printf(f, "Region %u mode %x\n", i, region->mode);
+		for (j = 0; j < num_banks; j++) {
+			m_state = read_macro_state(i, j);
+			if (m_state == MACRO_ON)
+				seq_printf(f, "M%u:%s\t", j, "ON");
+			else if (m_state == MACRO_SLEEP_RETENTION)
+				seq_printf(f, "M%u:%s\t", j, "RETENTION");
+			else
+				seq_printf(f, "M%u:%s\t", j, "OFF");
+		}
+		seq_printf(f, "\n");
+	}
+	mutex_unlock(&region_ctrl_lock);
+	return 0;
+}
+
+#ifdef CONFIG_MSM_OCMEM_POWER_DEBUG
+static int ocmem_power_show_hw_state(struct seq_file *f, void *dummy)
+{
+	unsigned i = 0;
+	unsigned r_state;
+
+	mutex_lock(&region_ctrl_lock);
+
+	seq_printf(f, "OCMEM Hardware Power States\n");
+	for (i = 0 ; i < num_regions; i++) {
+		struct ocmem_hw_region *region = &region_ctrl[i];
+		seq_printf(f, "Region %u mode %x ", i, region->mode);
+		r_state = read_hw_region_state(i);
+		if (r_state == REGION_DEFAULT_ON)
+			seq_printf(f, "state: %s\t", "REGION_ON");
+		else if (r_state == MACRO_SLEEP_RETENTION)
+			seq_printf(f, "state: %s\t", "REGION_RETENTION");
+		else
+			seq_printf(f, "state: %s\t", "REGION_OFF");
+		seq_printf(f, "\n");
+	}
+	mutex_unlock(&region_ctrl_lock);
+	return 0;
+}
+#else
+static int ocmem_power_show_hw_state(struct seq_file *f, void *dummy)
+{
+	return 0;
+}
+#endif
+
+static int ocmem_power_show(struct seq_file *f, void *dummy)
+{
+	ocmem_power_show_sw_state(f, dummy);
+	ocmem_power_show_hw_state(f, dummy);
+	return 0;
+}
+
+static int ocmem_power_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, ocmem_power_show, inode->i_private);
+}
+
+static const struct file_operations power_show_fops = {
+	.open = ocmem_power_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = seq_release,
+};
+
 int ocmem_core_init(struct platform_device *pdev)
 {
 	struct device   *dev = &pdev->dev;
@@ -785,8 +860,14 @@
 		return rc;
 
 	ocmem_disable_core_clock();
-	return 0;
 
+	if (!debugfs_create_file("power_state", S_IRUGO, pdata->debug_node,
+					NULL, &power_show_fops)) {
+		dev_err(dev, "Unable to create debugfs node for power state\n");
+		return -EBUSY;
+	}
+
+	return 0;
 err_no_mem:
 	pr_err("ocmem: Unable to allocate memory\n");
 region_init_error:
diff --git a/arch/arm/mach-msm/ocmem_sched.c b/arch/arm/mach-msm/ocmem_sched.c
index 54510c9..c95728e 100644
--- a/arch/arm/mach-msm/ocmem_sched.c
+++ b/arch/arm/mach-msm/ocmem_sched.c
@@ -244,6 +244,15 @@
 	return ret_addr;
 }
 
+static inline struct ocmem_zone *zone_of(struct ocmem_req *req)
+{
+	int owner;
+	if (!req)
+		return NULL;
+	owner = req->owner;
+	return get_zone(owner);
+}
+
 static int insert_region(struct ocmem_region *region)
 {
 
@@ -838,7 +847,6 @@
 	if (matched_req != req)
 		goto invalid_op_error;
 
-
 	ret = zone->z_ops->free(zone,
 		matched_req->req_start, matched_req->req_sz);
 
@@ -1317,6 +1325,8 @@
 	if (rc < 0)
 		return -EINVAL;
 
+	inc_ocmem_stat(zone_of(req), NR_FREES);
+
 	ocmem_destroy_req(req);
 	handle->req = NULL;
 
@@ -1393,14 +1403,16 @@
 		goto transfer_out_error;
 	}
 
+	inc_ocmem_stat(zone_of(req), NR_TRANSFERS_TO_DDR);
+
 	rc = queue_transfer(req, handle, list, TO_DDR);
 
 	if (rc < 0) {
 		pr_err("Failed to queue rdm transfer to DDR\n");
+		inc_ocmem_stat(zone_of(req), NR_TRANSFER_FAILS);
 		goto transfer_out_error;
 	}
 
-
 	return 0;
 
 transfer_out_error:
@@ -1429,10 +1441,13 @@
 		goto transfer_in_error;
 	}
 
+	inc_ocmem_stat(zone_of(req), NR_TRANSFERS_TO_OCMEM);
+
 	rc = queue_transfer(req, handle, list, TO_OCMEM);
 
 	if (rc < 0) {
 		pr_err("Failed to queue rdm transfer to OCMEM\n");
+		inc_ocmem_stat(zone_of(req), NR_TRANSFER_FAILS);
 		goto transfer_in_error;
 	}
 
@@ -1469,6 +1484,8 @@
 	if (is_tcm(req->owner))
 		do_unmap(req);
 
+	inc_ocmem_stat(zone_of(req), NR_SHRINKS);
+
 	if (size == 0) {
 		pr_info("req %p being shrunk to zero\n", req);
 		rc = do_free(req);
@@ -1551,6 +1568,8 @@
 					req->edata = edata;
 					buffer.addr = req->req_start;
 					buffer.len = 0x0;
+					inc_ocmem_stat(zone_of(req),
+								NR_EVICTIONS);
 					dispatch_notification(req->owner,
 						OCMEM_ALLOC_SHRINK, &buffer);
 				}
@@ -1580,8 +1599,10 @@
 	rc = __sched_allocate(req, can_block, can_wait);
 	mutex_unlock(&sched_mutex);
 
-	if (rc == OP_FAIL)
+	if (rc == OP_FAIL) {
+		inc_ocmem_stat(zone_of(req), NR_ALLOCATION_FAILS);
 		goto err_allocate_fail;
+	}
 
 	if (rc == OP_RESCHED) {
 		buffer->addr = 0x0;
@@ -1591,6 +1612,7 @@
 	} else if (rc == OP_PARTIAL) {
 		buffer->addr = device_address(req->owner, req->req_start);
 		buffer->len = req->req_sz;
+		inc_ocmem_stat(zone_of(req), NR_RANGE_ALLOCATIONS);
 		pr_debug("ocmem: Enqueuing req %p\n", req);
 		sched_enqueue(req);
 	} else if (rc == OP_COMPLETE) {
@@ -1622,6 +1644,7 @@
 			list_del(&req->sched_list);
 			req->op = SCHED_ALLOCATE;
 			sched_enqueue(req);
+			inc_ocmem_stat(zone_of(req), NR_RESTORES);
 		}
 	}
 	kfree(edata);
@@ -1668,11 +1691,15 @@
 	req->op = SCHED_ALLOCATE;
 	req->buffer = buffer;
 
+	inc_ocmem_stat(zone_of(req), NR_REQUESTS);
+
 	rc = do_allocate(req, can_block, can_wait);
 
 	if (rc < 0)
 		goto do_allocate_error;
 
+	inc_ocmem_stat(zone_of(req), NR_SYNC_ALLOCATIONS);
+
 	handle->req = req;
 
 	if (is_tcm(id)) {
@@ -1717,10 +1744,11 @@
 
 	rc = do_allocate(req, true, false);
 
-
 	if (rc < 0)
 		goto do_allocate_error;
 
+	inc_ocmem_stat(zone_of(req), NR_ASYNC_ALLOCATIONS);
+
 	if (is_tcm(id)) {
 		rc = process_map(req, req->req_start, req->req_end);
 		if (rc < 0)
@@ -1788,10 +1816,51 @@
 	return;
 }
 
-int ocmem_sched_init(void)
+static int ocmem_allocations_show(struct seq_file *f, void *dummy)
+{
+	struct rb_node *rb_node = NULL;
+	struct ocmem_req *req = NULL;
+	unsigned j;
+	mutex_lock(&sched_mutex);
+	for (rb_node = rb_first(&sched_tree); rb_node;
+				rb_node = rb_next(rb_node)) {
+		struct ocmem_region *tmp_region = NULL;
+		tmp_region = rb_entry(rb_node, struct ocmem_region, region_rb);
+		for (j = MAX_OCMEM_PRIO - 1; j > NO_PRIO; j--) {
+			req = find_req_match(j, tmp_region);
+			if (req) {
+				seq_printf(f,
+					"owner: %s 0x%lx -- 0x%lx size 0x%lx [state: %2lx]\n",
+					get_name(req->owner),
+					req->req_start, req->req_end,
+					req->req_sz, req->state);
+			}
+		}
+	}
+	mutex_unlock(&sched_mutex);
+	return 0;
+}
+
+static int ocmem_allocations_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, ocmem_allocations_show, inode->i_private);
+}
+
+static const struct file_operations allocations_show_fops = {
+	.open = ocmem_allocations_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = seq_release,
+};
+
+int ocmem_sched_init(struct platform_device *pdev)
 {
 	int i = 0;
+	struct ocmem_plat_data *pdata = NULL;
+	struct device   *dev = &pdev->dev;
+
 	sched_tree = RB_ROOT;
+	pdata = platform_get_drvdata(pdev);
 	mutex_init(&sched_mutex);
 	mutex_init(&sched_queue_mutex);
 	for (i = MIN_PRIO; i < MAX_OCMEM_PRIO; i++)
@@ -1805,5 +1874,11 @@
 	ocmem_eviction_wq = alloc_workqueue("ocmem_eviction_wq", 0, 0);
 	if (!ocmem_eviction_wq)
 		return -ENOMEM;
+
+	if (!debugfs_create_file("allocations", S_IRUGO, pdata->debug_node,
+					NULL, &allocations_show_fops)) {
+		dev_err(dev, "Unable to create debugfs node for scheduler\n");
+		return -EBUSY;
+	}
 	return 0;
 }