mm/zswap.c: add allocation hysteresis if pool limit is hit
[linux-2.6-microblaze.git] / mm / zswap.c
index 46a3223..7ec8bd9 100644 (file)
@@ -32,6 +32,7 @@
 #include <linux/swapops.h>
 #include <linux/writeback.h>
 #include <linux/pagemap.h>
+#include <linux/workqueue.h>
 
 /*********************************
 * statistics
@@ -65,6 +66,11 @@ static u64 zswap_reject_kmemcache_fail;
 /* Duplicate store was encountered (rare) */
 static u64 zswap_duplicate_entry;
 
+/* Shrinker work queue */
+static struct workqueue_struct *shrink_wq;
+/* Pool limit was hit, we need to calm down */
+static bool zswap_pool_reached_full;
+
 /*********************************
 * tunables
 **********************************/
@@ -109,6 +115,11 @@ module_param_cb(zpool, &zswap_zpool_param_ops, &zswap_zpool_type, 0644);
 static unsigned int zswap_max_pool_percent = 20;
 module_param_named(max_pool_percent, zswap_max_pool_percent, uint, 0644);
 
+/* The threshold for accepting new pages after the max_pool_percent was hit */
+static unsigned int zswap_accept_thr_percent = 90; /* of max pool size */
+module_param_named(accept_threshold_percent, zswap_accept_thr_percent,
+                  uint, 0644);
+
 /* Enable/disable handling same-value filled pages (enabled by default) */
 static bool zswap_same_filled_pages_enabled = true;
 module_param_named(same_filled_pages_enabled, zswap_same_filled_pages_enabled,
@@ -123,7 +134,8 @@ struct zswap_pool {
        struct crypto_comp * __percpu *tfm;
        struct kref kref;
        struct list_head list;
-       struct work_struct work;
+       struct work_struct release_work;
+       struct work_struct shrink_work;
        struct hlist_node node;
        char tfm_name[CRYPTO_MAX_ALG_NAME];
 };
@@ -214,6 +226,13 @@ static bool zswap_is_full(void)
                        DIV_ROUND_UP(zswap_pool_total_size, PAGE_SIZE);
 }
 
+static bool zswap_can_accept(void)
+{
+       return totalram_pages() * zswap_accept_thr_percent / 100 *
+                               zswap_max_pool_percent / 100 >
+                       DIV_ROUND_UP(zswap_pool_total_size, PAGE_SIZE);
+}
+
 static void zswap_update_total_size(void)
 {
        struct zswap_pool *pool;
@@ -501,6 +520,16 @@ static struct zswap_pool *zswap_pool_find_get(char *type, char *compressor)
        return NULL;
 }
 
+static void shrink_worker(struct work_struct *w)
+{
+       struct zswap_pool *pool = container_of(w, typeof(*pool),
+                                               shrink_work);
+
+       if (zpool_shrink(pool->zpool, 1, NULL))
+               zswap_reject_reclaim_fail++;
+       zswap_pool_put(pool);
+}
+
 static struct zswap_pool *zswap_pool_create(char *type, char *compressor)
 {
        struct zswap_pool *pool;
@@ -551,6 +580,7 @@ static struct zswap_pool *zswap_pool_create(char *type, char *compressor)
         */
        kref_init(&pool->kref);
        INIT_LIST_HEAD(&pool->list);
+       INIT_WORK(&pool->shrink_work, shrink_worker);
 
        zswap_pool_debug("created", pool);
 
@@ -624,7 +654,8 @@ static int __must_check zswap_pool_get(struct zswap_pool *pool)
 
 static void __zswap_pool_release(struct work_struct *work)
 {
-       struct zswap_pool *pool = container_of(work, typeof(*pool), work);
+       struct zswap_pool *pool = container_of(work, typeof(*pool),
+                                               release_work);
 
        synchronize_rcu();
 
@@ -647,8 +678,8 @@ static void __zswap_pool_empty(struct kref *kref)
 
        list_del_rcu(&pool->list);
 
-       INIT_WORK(&pool->work, __zswap_pool_release);
-       schedule_work(&pool->work);
+       INIT_WORK(&pool->release_work, __zswap_pool_release);
+       schedule_work(&pool->release_work);
 
        spin_unlock(&zswap_pools_lock);
 }
@@ -942,22 +973,6 @@ end:
        return ret;
 }
 
-static int zswap_shrink(void)
-{
-       struct zswap_pool *pool;
-       int ret;
-
-       pool = zswap_pool_last_get();
-       if (!pool)
-               return -ENOENT;
-
-       ret = zpool_shrink(pool->zpool, 1, NULL);
-
-       zswap_pool_put(pool);
-
-       return ret;
-}
-
 static int zswap_is_page_same_filled(void *ptr, unsigned long *value)
 {
        unsigned int pos;
@@ -1011,21 +1026,23 @@ static int zswap_frontswap_store(unsigned type, pgoff_t offset,
 
        /* reclaim space if needed */
        if (zswap_is_full()) {
+               struct zswap_pool *pool;
+
                zswap_pool_limit_hit++;
-               if (zswap_shrink()) {
-                       zswap_reject_reclaim_fail++;
-                       ret = -ENOMEM;
-                       goto reject;
-               }
+               zswap_pool_reached_full = true;
+               pool = zswap_pool_last_get();
+               if (pool)
+                       queue_work(shrink_wq, &pool->shrink_work);
+               ret = -ENOMEM;
+               goto reject;
+       }
 
-               /* A second zswap_is_full() check after
-                * zswap_shrink() to make sure it's now
-                * under the max_pool_percent
-                */
-               if (zswap_is_full()) {
+       if (zswap_pool_reached_full) {
+              if (!zswap_can_accept()) {
                        ret = -ENOMEM;
                        goto reject;
-               }
+               } else
+                       zswap_pool_reached_full = false;
        }
 
        /* allocate entry */
@@ -1332,11 +1349,17 @@ static int __init init_zswap(void)
                zswap_enabled = false;
        }
 
+       shrink_wq = create_workqueue("zswap-shrink");
+       if (!shrink_wq)
+               goto fallback_fail;
+
        frontswap_register_ops(&zswap_frontswap_ops);
        if (zswap_debugfs_init())
                pr_warn("debugfs initialization failed\n");
        return 0;
 
+fallback_fail:
+       zswap_pool_destroy(pool);
 hp_fail:
        cpuhp_remove_state(CPUHP_MM_ZSWP_MEM_PREPARE);
 dstmem_fail: