diff --git a/drivers/staging/android/ashmem.c b/drivers/staging/android/ashmem.c index 8e566279ce75..64772bb9c437 100644 --- a/drivers/staging/android/ashmem.c +++ b/drivers/staging/android/ashmem.c @@ -104,6 +104,9 @@ static struct kmem_cache *ashmem_range_cachep __read_mostly; */ static struct lock_class_key backing_shmem_inode_class; +/* Enable unpinning feature by default to retain compatibility with existing behavior. */ +static bool unpinning_enable = true; + static inline unsigned long range_size(struct ashmem_range *range) { return range->pgend - range->pgstart + 1; @@ -479,6 +482,9 @@ ashmem_shrink_scan(struct shrinker *shrink, struct shrink_control *sc) { unsigned long freed = 0; + if (!unpinning_enable) + return 0; + /* We might recurse into filesystem code, so bail out if necessary */ if (!(sc->gfp_mask & __GFP_FS)) return SHRINK_STOP; @@ -524,7 +530,7 @@ ashmem_shrink_count(struct shrinker *shrink, struct shrink_control *sc) * objects on the list. This means the scan function needs to return the * number of pages freed, not the number of objects scanned. */ - return lru_count; + return unpinning_enable ? lru_count : SHRINK_EMPTY; } static struct shrinker ashmem_shrinker = { @@ -802,7 +808,7 @@ static int ashmem_pin_unpin(struct ashmem_area *asma, unsigned long cmd, ret = ashmem_pin(asma, pgstart, pgend, &range); break; case ASHMEM_UNPIN: - ret = ashmem_unpin(asma, pgstart, pgend, &range); + ret = unpinning_enable ? ashmem_unpin(asma, pgstart, pgend, &range) : 0; break; case ASHMEM_GET_PIN_STATUS: ret = ashmem_get_pin_status(asma, pgstart, pgend); @@ -921,6 +927,40 @@ static void ashmem_show_fdinfo(struct seq_file *m, struct file *file) mutex_unlock(&ashmem_mutex); } #endif + +static int unpinning_enable_open(struct inode *inode, struct file *file) +{ + file->private_data = &unpinning_enable; + return 0; +} + +static ssize_t attr_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) +{ + bool *attrp = file->private_data; + char val_str[2]; + + val_str[0] = *attrp ? '1' : '0'; + val_str[1] = '\n'; + return simple_read_from_buffer(buf, count, ppos, val_str, sizeof(val_str)); +} + +static ssize_t attr_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) +{ + bool *attrp = file->private_data; + bool enable; + ssize_t ret = count; + + if (!kstrtobool_from_user(buf, count, &enable)) { + mutex_lock(&ashmem_mutex); + *attrp = enable; + mutex_unlock(&ashmem_mutex); + } else { + ret = -EINVAL; + } + + return ret; +} + static const struct file_operations ashmem_fops = { .owner = THIS_MODULE, .open = ashmem_open, @@ -946,15 +986,30 @@ int is_ashmem_file(struct file *file) } EXPORT_SYMBOL_GPL(is_ashmem_file); -static struct miscdevice ashmem_misc = { - .minor = MISC_DYNAMIC_MINOR, - .name = "ashmem", - .fops = &ashmem_fops, +static const struct file_operations unpinning_enable_fops = { + .owner = THIS_MODULE, + .open = unpinning_enable_open, + .read = attr_read, + .write = attr_write, +}; + +static struct miscdevice ashmem_miscs[] = { + { + .minor = MISC_DYNAMIC_MINOR, + .name = "ashmem", + .fops = &ashmem_fops, + }, + { + .minor = MISC_DYNAMIC_MINOR, + .name = "ashmem_unpinning_enable", + .fops = &unpinning_enable_fops, + }, }; static int __init ashmem_init(void) { int ret = -ENOMEM; + int i; ashmem_area_cachep = kmem_cache_create("ashmem_area_cache", sizeof(struct ashmem_area), @@ -972,10 +1027,12 @@ static int __init ashmem_init(void) goto out_free1; } - ret = misc_register(&ashmem_misc); - if (ret) { - pr_err("failed to register misc device!\n"); - goto out_free2; + for (i = 0; i < ARRAY_SIZE(ashmem_miscs); i++) { + ret = misc_register(&ashmem_miscs[i]); + if (ret) { + pr_err("failed to register %s misc device!\n", ashmem_miscs[i].name); + goto out_demisc; + } } ret = register_shrinker(&ashmem_shrinker, "android-ashmem"); @@ -989,8 +1046,8 @@ static int __init ashmem_init(void) return 0; out_demisc: - misc_deregister(&ashmem_misc); -out_free2: + while (--i >= 0) + misc_deregister(&ashmem_miscs[i]); kmem_cache_destroy(ashmem_range_cachep); out_free1: kmem_cache_destroy(ashmem_area_cachep);