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(®ion_ctrl_lock);
+
+ seq_printf(f, "OCMEM Aggregated Power States\n");
+ for (i = 0 ; i < num_regions; i++) {
+ struct ocmem_hw_region *region = ®ion_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(®ion_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(®ion_ctrl_lock);
+
+ seq_printf(f, "OCMEM Hardware Power States\n");
+ for (i = 0 ; i < num_regions; i++) {
+ struct ocmem_hw_region *region = ®ion_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(®ion_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;
}