msm: kgsl: Fixup per-process memory statistics
Make the framework for reporting per-process memory statistics a little bit more generic. This should make it easier to keep track of more external memory sources as they are added.
This commit is contained in:
		| @@ -121,18 +121,20 @@ kgsl_mem_entry_destroy(struct kref *kref) | ||||
| 	struct kgsl_mem_entry *entry = container_of(kref, | ||||
| 						    struct kgsl_mem_entry, | ||||
| 						    refcount); | ||||
| 	size_t size = entry->memdesc.size; | ||||
|  | ||||
| 	entry->priv->stats[entry->memtype].cur -= entry->memdesc.size; | ||||
|  | ||||
| 	if (entry->memtype != KGSL_MEM_ENTRY_KERNEL) | ||||
| 		kgsl_driver.stats.mapped -= entry->memdesc.size; | ||||
|  | ||||
| 	kgsl_sharedmem_free(&entry->memdesc); | ||||
|  | ||||
| 	if (entry->memtype == KGSL_USER_MEMORY) | ||||
| 		entry->priv->stats.user -= size; | ||||
| 	else if (entry->memtype == KGSL_MAPPED_MEMORY) { | ||||
| 		if (entry->file_ptr) | ||||
| 			fput(entry->file_ptr); | ||||
|  | ||||
| 		kgsl_driver.stats.mapped -= size; | ||||
| 		entry->priv->stats.mapped -= size; | ||||
| 	switch (entry->memtype) { | ||||
| 	case KGSL_MEM_ENTRY_PMEM: | ||||
| 	case KGSL_MEM_ENTRY_ASHMEM: | ||||
| 		if (entry->priv_data) | ||||
| 			fput(entry->priv_data); | ||||
| 		break; | ||||
| 	} | ||||
|  | ||||
| 	kfree(entry); | ||||
| @@ -585,11 +587,6 @@ kgsl_put_process_private(struct kgsl_device *device, | ||||
| 	if (--private->refcnt) | ||||
| 		goto unlock; | ||||
|  | ||||
| 	KGSL_MEM_INFO(device, | ||||
| 			"Memory usage: user (%d/%d) mapped (%d/%d)\n", | ||||
| 			private->stats.user, private->stats.user_max, | ||||
| 			private->stats.mapped, private->stats.mapped_max); | ||||
|  | ||||
| 	kgsl_process_uninit_sysfs(private); | ||||
|  | ||||
| 	list_del(&private->list); | ||||
| @@ -1203,13 +1200,12 @@ kgsl_ioctl_sharedmem_from_vmalloc(struct kgsl_device_private *dev_priv, | ||||
|  | ||||
| 	param->gpuaddr = entry->memdesc.gpuaddr; | ||||
|  | ||||
| 	entry->memtype = KGSL_USER_MEMORY; | ||||
| 	entry->memtype = KGSL_MEM_ENTRY_KERNEL; | ||||
|  | ||||
| 	kgsl_mem_entry_attach_process(entry, private); | ||||
|  | ||||
| 	/* Process specific statistics */ | ||||
| 	KGSL_STATS_ADD(len, private->stats.user, | ||||
| 		       private->stats.user_max); | ||||
| 	kgsl_process_add_stats(private, entry->memtype, len); | ||||
|  | ||||
| 	kgsl_check_idle(dev_priv->device); | ||||
| 	return 0; | ||||
| @@ -1310,7 +1306,7 @@ static int kgsl_setup_phys_file(struct kgsl_mem_entry *entry, | ||||
|  | ||||
| 	} | ||||
|  | ||||
| 	entry->file_ptr = filep; | ||||
| 	entry->priv_data = filep; | ||||
|  | ||||
| 	entry->memdesc.pagetable = pagetable; | ||||
| 	entry->memdesc.size = size; | ||||
| @@ -1482,7 +1478,7 @@ static int kgsl_setup_ashmem(struct kgsl_mem_entry *entry, | ||||
| 		goto err; | ||||
| 	} | ||||
|  | ||||
| 	entry->file_ptr = filep; | ||||
| 	entry->priv_data = filep; | ||||
| 	entry->memdesc.pagetable = pagetable; | ||||
| 	entry->memdesc.size = ALIGN(size, PAGE_SIZE); | ||||
| 	entry->memdesc.hostptr = hostptr; | ||||
| @@ -1533,6 +1529,7 @@ static long kgsl_ioctl_map_user_mem(struct kgsl_device_private *dev_priv, | ||||
| 		result = kgsl_setup_phys_file(entry, private->pagetable, | ||||
| 					      param->fd, param->offset, | ||||
| 					      param->len); | ||||
| 		entry->memtype = KGSL_MEM_ENTRY_PMEM; | ||||
| 		break; | ||||
|  | ||||
| 	case KGSL_USER_MEM_TYPE_ADDR: | ||||
| @@ -1549,6 +1546,7 @@ static long kgsl_ioctl_map_user_mem(struct kgsl_device_private *dev_priv, | ||||
| 		result = kgsl_setup_hostptr(entry, private->pagetable, | ||||
| 					    (void *) param->hostptr, | ||||
| 					    param->offset, param->len); | ||||
| 		entry->memtype = KGSL_MEM_ENTRY_USER; | ||||
| 		break; | ||||
|  | ||||
| 	case KGSL_USER_MEM_TYPE_ASHMEM: | ||||
| @@ -1565,6 +1563,8 @@ static long kgsl_ioctl_map_user_mem(struct kgsl_device_private *dev_priv, | ||||
| 		result = kgsl_setup_ashmem(entry, private->pagetable, | ||||
| 					   param->fd, (void *) param->hostptr, | ||||
| 					   param->len); | ||||
|  | ||||
| 		entry->memtype = KGSL_MEM_ENTRY_ASHMEM; | ||||
| 		break; | ||||
| 	default: | ||||
| 		KGSL_CORE_ERR("Invalid memory type: %x\n", memtype); | ||||
| @@ -1584,14 +1584,10 @@ static long kgsl_ioctl_map_user_mem(struct kgsl_device_private *dev_priv, | ||||
| 	/* Adjust the returned value for a non 4k aligned offset */ | ||||
| 	param->gpuaddr = entry->memdesc.gpuaddr + (param->offset & ~PAGE_MASK); | ||||
|  | ||||
| 	entry->memtype = KGSL_MAPPED_MEMORY; | ||||
|  | ||||
| 	KGSL_STATS_ADD(param->len, kgsl_driver.stats.mapped, | ||||
| 		       kgsl_driver.stats.mapped_max); | ||||
|  | ||||
| 	/* Statistics */ | ||||
| 	KGSL_STATS_ADD(param->len, private->stats.mapped, | ||||
| 		       private->stats.mapped_max); | ||||
| 	kgsl_process_add_stats(private, entry->memtype, param->len); | ||||
|  | ||||
| 	kgsl_mem_entry_attach_process(entry, private); | ||||
|  | ||||
| @@ -1599,8 +1595,8 @@ static long kgsl_ioctl_map_user_mem(struct kgsl_device_private *dev_priv, | ||||
| 	return result; | ||||
|  | ||||
|  error_put_file_ptr: | ||||
| 	if (entry->file_ptr) | ||||
| 		fput(entry->file_ptr); | ||||
| 	if (entry->priv_data) | ||||
| 		fput(entry->priv_data); | ||||
|  | ||||
| error: | ||||
| 	kfree(entry); | ||||
| @@ -1626,6 +1622,7 @@ kgsl_ioctl_sharedmem_flush_cache(struct kgsl_device_private *dev_priv, | ||||
| 		result = -EINVAL; | ||||
| 		goto done; | ||||
| 	} | ||||
|  | ||||
| 	if (!entry->memdesc.hostptr) { | ||||
| 		KGSL_CORE_ERR("invalid hostptr with gpuaddr %08x\n", | ||||
| 			param->gpuaddr); | ||||
| @@ -1633,9 +1630,6 @@ kgsl_ioctl_sharedmem_flush_cache(struct kgsl_device_private *dev_priv, | ||||
| 	} | ||||
|  | ||||
| 	kgsl_cache_range_op(&entry->memdesc, KGSL_CACHE_OP_CLEAN); | ||||
|  | ||||
| 	/* Statistics - keep track of how many flushes each process does */ | ||||
| 	private->stats.flushes++; | ||||
| done: | ||||
| 	spin_unlock(&private->mem_lock); | ||||
| 	return result; | ||||
| @@ -1658,12 +1652,11 @@ kgsl_ioctl_gpumem_alloc(struct kgsl_device_private *dev_priv, | ||||
| 		param->size, param->flags); | ||||
|  | ||||
| 	if (result == 0) { | ||||
| 		entry->memtype = KGSL_USER_MEMORY; | ||||
| 		entry->memtype = KGSL_MEM_ENTRY_KERNEL; | ||||
| 		kgsl_mem_entry_attach_process(entry, private); | ||||
| 		param->gpuaddr = entry->memdesc.gpuaddr; | ||||
|  | ||||
| 		KGSL_STATS_ADD(entry->memdesc.size, private->stats.user, | ||||
| 		       private->stats.user_max); | ||||
| 		kgsl_process_add_stats(private, entry->memtype, param->size); | ||||
| 	} else | ||||
| 		kfree(entry); | ||||
|  | ||||
|   | ||||
| @@ -104,9 +104,6 @@ struct kgsl_driver { | ||||
|  | ||||
| extern struct kgsl_driver kgsl_driver; | ||||
|  | ||||
| #define KGSL_USER_MEMORY 1 | ||||
| #define KGSL_MAPPED_MEMORY 2 | ||||
|  | ||||
| struct kgsl_pagetable; | ||||
| struct kgsl_memdesc_ops; | ||||
|  | ||||
| @@ -123,11 +120,19 @@ struct kgsl_memdesc { | ||||
| 	struct kgsl_memdesc_ops *ops; | ||||
| }; | ||||
|  | ||||
| /* List of different memory entry types */ | ||||
|  | ||||
| #define KGSL_MEM_ENTRY_KERNEL 0 | ||||
| #define KGSL_MEM_ENTRY_PMEM   1 | ||||
| #define KGSL_MEM_ENTRY_ASHMEM 2 | ||||
| #define KGSL_MEM_ENTRY_USER   3 | ||||
| #define KGSL_MEM_ENTRY_MAX    4 | ||||
|  | ||||
| struct kgsl_mem_entry { | ||||
| 	struct kref refcount; | ||||
| 	struct kgsl_memdesc memdesc; | ||||
| 	int memtype; | ||||
| 	struct file *file_ptr; | ||||
| 	void *priv_data; | ||||
| 	struct list_head list; | ||||
| 	uint32_t free_timestamp; | ||||
| 	/* back pointer to private structure under whose context this | ||||
|   | ||||
| @@ -199,15 +199,12 @@ struct kgsl_process_private { | ||||
| 	struct list_head mem_list; | ||||
| 	struct kgsl_pagetable *pagetable; | ||||
| 	struct list_head list; | ||||
| 	struct kobject *kobj; | ||||
| 	struct kobject kobj; | ||||
|  | ||||
| 	struct { | ||||
| 		unsigned int user; | ||||
| 		unsigned int user_max; | ||||
| 		unsigned int mapped; | ||||
| 		unsigned int mapped_max; | ||||
| 		unsigned int flushes; | ||||
| 	} stats; | ||||
| 		unsigned int cur; | ||||
| 		unsigned int max; | ||||
| 	} stats[KGSL_MEM_ENTRY_MAX]; | ||||
| }; | ||||
|  | ||||
| struct kgsl_device_private { | ||||
| @@ -222,6 +219,14 @@ struct kgsl_power_stats { | ||||
|  | ||||
| struct kgsl_device *kgsl_get_device(int dev_idx); | ||||
|  | ||||
| static inline void kgsl_process_add_stats(struct kgsl_process_private *priv, | ||||
| 	unsigned int type, size_t size) | ||||
| { | ||||
| 	priv->stats[type].cur += size; | ||||
| 	if (priv->stats[type].max < priv->stats[type].cur) | ||||
| 		priv->stats[type].max = priv->stats[type].cur; | ||||
| } | ||||
|  | ||||
| static inline void kgsl_regread(struct kgsl_device *device, | ||||
| 				unsigned int offsetwords, | ||||
| 				unsigned int *value) | ||||
|   | ||||
| @@ -23,6 +23,52 @@ | ||||
| #include "kgsl_device.h" | ||||
| #include "adreno_ringbuffer.h" | ||||
|  | ||||
| /* An attribute for showing per-process memory statistics */ | ||||
| struct kgsl_mem_entry_attribute { | ||||
| 	struct attribute attr; | ||||
| 	int memtype; | ||||
| 	ssize_t (*show)(struct kgsl_process_private *priv, | ||||
| 		int type, char *buf); | ||||
| }; | ||||
|  | ||||
| #define to_mem_entry_attr(a) \ | ||||
| container_of(a, struct kgsl_mem_entry_attribute, attr) | ||||
|  | ||||
| #define __MEM_ENTRY_ATTR(_type, _name, _show) \ | ||||
| { \ | ||||
| 	.attr = { .name = __stringify(_name), .mode = 0444 }, \ | ||||
| 	.memtype = _type, \ | ||||
| 	.show = _show, \ | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * A structure to hold the attributes for a particular memory type. | ||||
|  * For each memory type in each process we store the current and maximum | ||||
|  * memory usage and display the counts in sysfs.  This structure and | ||||
|  * the following macro allow us to simplify the definition for those | ||||
|  * adding new memory types | ||||
|  */ | ||||
|  | ||||
| struct mem_entry_stats { | ||||
| 	int memtype; | ||||
| 	struct kgsl_mem_entry_attribute attr; | ||||
| 	struct kgsl_mem_entry_attribute max_attr; | ||||
| }; | ||||
|  | ||||
|  | ||||
| #define MEM_ENTRY_STAT(_type, _name) \ | ||||
| { \ | ||||
| 	.memtype = _type, \ | ||||
| 	.attr = __MEM_ENTRY_ATTR(_type, _name, mem_entry_show), \ | ||||
| 	.max_attr = __MEM_ENTRY_ATTR(_type, _name##_max, \ | ||||
| 		mem_entry_max_show), \ | ||||
| } | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Given a kobj, find the process structure attached to it | ||||
|  */ | ||||
|  | ||||
| static struct kgsl_process_private * | ||||
| _get_priv_from_kobj(struct kobject *kobj) | ||||
| { | ||||
| @@ -43,87 +89,106 @@ _get_priv_from_kobj(struct kobject *kobj) | ||||
| 	return NULL; | ||||
| } | ||||
|  | ||||
| /* sharedmem / memory sysfs files */ | ||||
| /** | ||||
|  * Show the current amount of memory allocated for the given memtype | ||||
|  */ | ||||
|  | ||||
| static ssize_t | ||||
| process_show(struct kobject *kobj, | ||||
| 	     struct kobj_attribute *attr, | ||||
| 	     char *buf) | ||||
| mem_entry_show(struct kgsl_process_private *priv, int type, char *buf) | ||||
| { | ||||
| 	return snprintf(buf, PAGE_SIZE, "%d\n", priv->stats[type].cur); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Show the maximum memory allocated for the given memtype through the life of | ||||
|  * the process | ||||
|  */ | ||||
|  | ||||
| static ssize_t | ||||
| mem_entry_max_show(struct kgsl_process_private *priv, int type, char *buf) | ||||
| { | ||||
| 	return snprintf(buf, PAGE_SIZE, "%d\n", priv->stats[type].max); | ||||
| } | ||||
|  | ||||
|  | ||||
| static void mem_entry_sysfs_release(struct kobject *kobj) | ||||
| { | ||||
| } | ||||
|  | ||||
| static ssize_t mem_entry_sysfs_show(struct kobject *kobj, | ||||
| 	struct attribute *attr, char *buf) | ||||
| { | ||||
| 	struct kgsl_mem_entry_attribute *pattr = to_mem_entry_attr(attr); | ||||
| 	struct kgsl_process_private *priv; | ||||
| 	unsigned int val = 0; | ||||
| 	ssize_t ret; | ||||
|  | ||||
| 	mutex_lock(&kgsl_driver.process_mutex); | ||||
| 	priv = _get_priv_from_kobj(kobj); | ||||
|  | ||||
| 	if (priv == NULL) { | ||||
| 		mutex_unlock(&kgsl_driver.process_mutex); | ||||
| 		return 0; | ||||
| 	} | ||||
|  | ||||
| 	if (!strncmp(attr->attr.name, "user", 4)) | ||||
| 		val = priv->stats.user; | ||||
| 	if (!strncmp(attr->attr.name, "user_max", 8)) | ||||
| 		val = priv->stats.user_max; | ||||
| 	if (!strncmp(attr->attr.name, "mapped", 6)) | ||||
| 		val = priv->stats.mapped; | ||||
| 	if (!strncmp(attr->attr.name, "mapped_max", 10)) | ||||
| 		val = priv->stats.mapped_max; | ||||
| 	if (!strncmp(attr->attr.name, "flushes", 7)) | ||||
| 		val = priv->stats.flushes; | ||||
| 	if (priv && pattr->show) | ||||
| 		ret = pattr->show(priv, pattr->memtype, buf); | ||||
| 	else | ||||
| 		ret = -EIO; | ||||
|  | ||||
| 	mutex_unlock(&kgsl_driver.process_mutex); | ||||
| 	return snprintf(buf, PAGE_SIZE, "%u\n", val); | ||||
| 	return ret; | ||||
| } | ||||
|  | ||||
| #define KGSL_MEMSTAT_ATTR(_name, _show) \ | ||||
| 	static struct kobj_attribute attr_##_name = \ | ||||
| 	__ATTR(_name, 0444, _show, NULL) | ||||
|  | ||||
| KGSL_MEMSTAT_ATTR(user, process_show); | ||||
| KGSL_MEMSTAT_ATTR(user_max, process_show); | ||||
| KGSL_MEMSTAT_ATTR(mapped, process_show); | ||||
| KGSL_MEMSTAT_ATTR(mapped_max, process_show); | ||||
| KGSL_MEMSTAT_ATTR(flushes, process_show); | ||||
|  | ||||
| static struct attribute *process_attrs[] = { | ||||
| 	&attr_user.attr, | ||||
| 	&attr_user_max.attr, | ||||
| 	&attr_mapped.attr, | ||||
| 	&attr_mapped_max.attr, | ||||
| 	&attr_flushes.attr, | ||||
| 	NULL | ||||
| static const struct sysfs_ops mem_entry_sysfs_ops = { | ||||
| 	.show = mem_entry_sysfs_show, | ||||
| }; | ||||
|  | ||||
| static struct attribute_group process_attr_group = { | ||||
| 	.attrs = process_attrs, | ||||
| static struct kobj_type ktype_mem_entry = { | ||||
| 	.sysfs_ops = &mem_entry_sysfs_ops, | ||||
| 	.default_attrs = NULL, | ||||
| 	.release = mem_entry_sysfs_release | ||||
| }; | ||||
|  | ||||
| static struct mem_entry_stats mem_stats[] = { | ||||
| 	MEM_ENTRY_STAT(KGSL_MEM_ENTRY_KERNEL, kernel), | ||||
| #ifdef CONFIG_ANDROID_PMEM | ||||
| 	MEM_ENTRY_STAT(KGSL_MEM_ENTRY_PMEM, pmem), | ||||
| #endif | ||||
| #ifdef CONFIG_ASHMEM | ||||
| 	MEM_ENTRY_STAT(KGSL_MEM_ENTRY_ASHMEM, ashmem), | ||||
| #endif | ||||
| 	MEM_ENTRY_STAT(KGSL_MEM_ENTRY_USER, user), | ||||
| }; | ||||
|  | ||||
| void | ||||
| kgsl_process_uninit_sysfs(struct kgsl_process_private *private) | ||||
| { | ||||
| 	/* Remove the sysfs entry */ | ||||
| 	if (private->kobj) { | ||||
| 		sysfs_remove_group(private->kobj, &process_attr_group); | ||||
| 		kobject_put(private->kobj); | ||||
| 	int i; | ||||
|  | ||||
| 	for (i = 0; i < ARRAY_SIZE(mem_stats); i++) { | ||||
| 		sysfs_remove_file(&private->kobj, &mem_stats[i].attr.attr); | ||||
| 		sysfs_remove_file(&private->kobj, | ||||
| 			&mem_stats[i].max_attr.attr); | ||||
| 	} | ||||
|  | ||||
| 	kobject_put(&private->kobj); | ||||
| } | ||||
|  | ||||
| void | ||||
| kgsl_process_init_sysfs(struct kgsl_process_private *private) | ||||
| { | ||||
| 	unsigned char name[16]; | ||||
| 	int i, ret; | ||||
|  | ||||
| 	/* Add a entry to the sysfs device */ | ||||
| 	snprintf(name, sizeof(name), "%d", private->pid); | ||||
| 	private->kobj = kobject_create_and_add(name, kgsl_driver.prockobj); | ||||
|  | ||||
| 	/* sysfs failure isn't fatal, just annoying */ | ||||
| 	if (private->kobj != NULL) { | ||||
| 		if (sysfs_create_group(private->kobj, &process_attr_group)) { | ||||
| 			kobject_put(private->kobj); | ||||
| 			private->kobj = NULL; | ||||
| 		} | ||||
| 	if (kobject_init_and_add(&private->kobj, &ktype_mem_entry, | ||||
| 		kgsl_driver.prockobj, name)) | ||||
| 		return; | ||||
|  | ||||
| 	for (i = 0; i < ARRAY_SIZE(mem_stats); i++) { | ||||
| 		/* We need to check the value of sysfs_create_file, but we | ||||
| 		 * don't really care if it passed or not */ | ||||
|  | ||||
| 		ret = sysfs_create_file(&private->kobj, | ||||
| 			&mem_stats[i].attr.attr); | ||||
| 		ret = sysfs_create_file(&private->kobj, | ||||
| 			&mem_stats[i].max_attr.attr); | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user