aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fs/f2fs/f2fs.h6
-rw-r--r--fs/f2fs/gc.c145
-rw-r--r--fs/f2fs/gc.h3
-rw-r--r--fs/f2fs/super.c5
4 files changed, 151 insertions, 8 deletions
diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h
index a44a589ec..8d297b554 100644
--- a/fs/f2fs/f2fs.h
+++ b/fs/f2fs/f2fs.h
@@ -1256,6 +1256,7 @@ struct f2fs_sb_info {
char *s_qf_names[F2FS_MAXQUOTAS];
int s_jquota_fmt; /* Format of quota to use */
#endif
+ struct list_head list;
};
#ifdef CONFIG_F2FS_FAULT_INJECTION
@@ -2806,6 +2807,11 @@ int f2fs_migrate_page(struct address_space *mapping, struct page *newpage,
*/
int start_gc_thread(struct f2fs_sb_info *sbi);
void stop_gc_thread(struct f2fs_sb_info *sbi);
+void start_all_gc_threads(void);
+void stop_all_gc_threads(void);
+void f2fs_sbi_list_add(struct f2fs_sb_info *sbi);
+void f2fs_sbi_list_del(struct f2fs_sb_info *sbi);
+
block_t start_bidx_of_node(unsigned int node_ofs, struct inode *inode);
int f2fs_gc(struct f2fs_sb_info *sbi, bool sync, bool background,
unsigned int segno);
diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c
index 2ad858dd6..917bff037 100644
--- a/fs/f2fs/gc.c
+++ b/fs/f2fs/gc.c
@@ -16,6 +16,9 @@
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/freezer.h>
+#include <linux/fb.h>
+#include <linux/power_supply.h>
+#include <linux/wakelock.h>
#include "f2fs.h"
#include "node.h"
@@ -23,14 +26,33 @@
#include "gc.h"
#include <trace/events/f2fs.h>
+#define TRIGGER_SOFF (!screen_on && power_supply_is_system_supplied())
+static bool screen_on = true;
+// Use 1 instead of 0 to allow thread interrupts
+static unsigned int soff_wait_ms = 1;
+
+static inline void gc_set_wakelock(struct f2fs_gc_kthread *gc_th, bool val)
+{
+ if (val) {
+ if (!wake_lock_active(&gc_th->gc_wakelock)) {
+ pr_info("f2fs: catching wakelock for GC\n");
+ wake_lock(&gc_th->gc_wakelock);
+ }
+ } else {
+ if (wake_lock_active(&gc_th->gc_wakelock)) {
+ pr_info("f2fs: unlocking wakelock for GC\n");
+ wake_unlock(&gc_th->gc_wakelock);
+ }
+ }
+}
+
static int gc_thread_func(void *data)
{
struct f2fs_sb_info *sbi = data;
struct f2fs_gc_kthread *gc_th = sbi->gc_thread;
wait_queue_head_t *wq = &sbi->gc_thread->gc_wait_queue_head;
- unsigned int wait_ms;
-
- wait_ms = gc_th->min_sleep_time;
+ unsigned int wait_ms = gc_th->min_sleep_time;
+ bool force_gc;
set_freezable();
do {
@@ -39,6 +61,17 @@ static int gc_thread_func(void *data)
gc_th->gc_wake,
msecs_to_jiffies(wait_ms));
+ force_gc = TRIGGER_SOFF;
+ if (force_gc) {
+ gc_set_wakelock(gc_th, true);
+ wait_ms = soff_wait_ms;
+ gc_th->gc_urgent = 1;
+ } else {
+ gc_set_wakelock(gc_th, false);
+ wait_ms = gc_th->min_sleep_time;
+ gc_th->gc_urgent = 0;
+ }
+
/* give it a try one time */
if (gc_th->gc_wake)
gc_th->gc_wake = 0;
@@ -49,7 +82,8 @@ static int gc_thread_func(void *data)
break;
if (sbi->sb->s_writers.frozen >= SB_FREEZE_WRITE) {
- increase_sleep_time(gc_th, &wait_ms);
+ if (!force_gc)
+ increase_sleep_time(gc_th, &wait_ms);
continue;
}
@@ -79,6 +113,9 @@ static int gc_thread_func(void *data)
if (!mutex_trylock(&sbi->gc_mutex))
goto next;
+ if (force_gc)
+ goto do_gc;
+
if (gc_th->gc_urgent) {
wait_ms = gc_th->urgent_sleep_time;
goto do_gc;
@@ -98,8 +135,11 @@ do_gc:
stat_inc_bggc_count(sbi);
/* if return value is not zero, no victim was selected */
- if (f2fs_gc(sbi, test_opt(sbi, FORCE_FG_GC), true, NULL_SEGNO))
+ if (f2fs_gc(sbi, test_opt(sbi, FORCE_FG_GC), true, NULL_SEGNO)) {
wait_ms = gc_th->no_gc_sleep_time;
+ gc_set_wakelock(gc_th, false);
+ pr_info("f2fs: no more GC victim found, sleeping for %u ms\n", wait_ms);
+ }
trace_f2fs_background_gc(sbi->sb, wait_ms,
prefree_segments(sbi), free_segments(sbi));
@@ -118,6 +158,10 @@ int start_gc_thread(struct f2fs_sb_info *sbi)
struct f2fs_gc_kthread *gc_th;
dev_t dev = sbi->sb->s_bdev->bd_dev;
int err = 0;
+ char buf[25];
+
+ if (sbi->gc_thread != NULL)
+ goto out;
gc_th = f2fs_kmalloc(sbi, sizeof(struct f2fs_gc_kthread), GFP_KERNEL);
if (!gc_th) {
@@ -134,10 +178,13 @@ int start_gc_thread(struct f2fs_sb_info *sbi)
gc_th->gc_urgent = 0;
gc_th->gc_wake= 0;
+ snprintf(buf, sizeof(buf), "f2fs_gc-%u:%u", MAJOR(dev), MINOR(dev));
+
+ wake_lock_init(&gc_th->gc_wakelock, WAKE_LOCK_SUSPEND, buf);
+
sbi->gc_thread = gc_th;
init_waitqueue_head(&sbi->gc_thread->gc_wait_queue_head);
- sbi->gc_thread->f2fs_gc_task = kthread_run(gc_thread_func, sbi,
- "f2fs_gc-%u:%u", MAJOR(dev), MINOR(dev));
+ sbi->gc_thread->f2fs_gc_task = kthread_run(gc_thread_func, sbi, buf);
if (IS_ERR(gc_th->f2fs_gc_task)) {
err = PTR_ERR(gc_th->f2fs_gc_task);
kfree(gc_th);
@@ -155,10 +202,94 @@ void stop_gc_thread(struct f2fs_sb_info *sbi)
if (!gc_th)
return;
kthread_stop(gc_th->f2fs_gc_task);
+ wake_lock_destroy(&gc_th->gc_wakelock);
kfree(gc_th);
sbi->gc_thread = NULL;
}
+static LIST_HEAD(f2fs_sbi_list);
+static DEFINE_MUTEX(f2fs_sbi_mutex);
+
+void start_all_gc_threads(void)
+{
+ struct f2fs_sb_info *sbi;
+
+ mutex_lock(&f2fs_sbi_mutex);
+ list_for_each_entry(sbi, &f2fs_sbi_list, list) {
+ start_gc_thread(sbi);
+ sbi->gc_thread->gc_wake = 1;
+ wake_up_interruptible_all(&sbi->gc_thread->gc_wait_queue_head);
+ }
+ mutex_unlock(&f2fs_sbi_mutex);
+}
+
+void stop_all_gc_threads(void)
+{
+ struct f2fs_sb_info *sbi;
+
+ mutex_lock(&f2fs_sbi_mutex);
+ list_for_each_entry(sbi, &f2fs_sbi_list, list) {
+ stop_gc_thread(sbi);
+ }
+ mutex_unlock(&f2fs_sbi_mutex);
+}
+
+void f2fs_sbi_list_add(struct f2fs_sb_info *sbi)
+{
+ mutex_lock(&f2fs_sbi_mutex);
+ list_add_tail(&sbi->list, &f2fs_sbi_list);
+ mutex_unlock(&f2fs_sbi_mutex);
+}
+
+void f2fs_sbi_list_del(struct f2fs_sb_info *sbi)
+{
+ mutex_lock(&f2fs_sbi_mutex);
+ list_del(&sbi->list);
+ mutex_unlock(&f2fs_sbi_mutex);
+}
+
+static int fb_notifier_callback(struct notifier_block *self,
+ unsigned long event, void *data)
+{
+ struct fb_event *evdata = data;
+ int *blank;
+
+ if ((event == FB_EVENT_BLANK) && evdata && evdata->data) {
+ blank = evdata->data;
+
+ switch (*blank) {
+ case FB_BLANK_POWERDOWN:
+ screen_on = false;
+ /*
+ * Start all GC threads exclusively from here
+ * since the phone screen would turn on when
+ * a charger is connected
+ */
+ if (TRIGGER_SOFF)
+ start_all_gc_threads();
+ break;
+ case FB_BLANK_UNBLANK:
+ screen_on = true;
+ stop_all_gc_threads();
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static struct notifier_block fb_notifier_block = {
+ .notifier_call = fb_notifier_callback,
+};
+
+static int __init f2fs_gc_register_fb(void)
+{
+ fb_register_client(&fb_notifier_block);
+
+ return 0;
+}
+late_initcall(f2fs_gc_register_fb);
+
static int select_gc_type(struct f2fs_gc_kthread *gc_th, int gc_type)
{
int gc_mode = (gc_type == BG_GC) ? GC_CB : GC_GREEDY;
diff --git a/fs/f2fs/gc.h b/fs/f2fs/gc.h
index 9325191fa..adecfcd07 100644
--- a/fs/f2fs/gc.h
+++ b/fs/f2fs/gc.h
@@ -8,6 +8,8 @@
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
+#include <linux/wakelock.h>
+
#define GC_THREAD_MIN_WB_PAGES 1 /*
* a threshold to determine
* whether IO subsystem is idle
@@ -26,6 +28,7 @@
struct f2fs_gc_kthread {
struct task_struct *f2fs_gc_task;
wait_queue_head_t gc_wait_queue_head;
+ struct wake_lock gc_wakelock;
/* for gc sleep time */
unsigned int urgent_sleep_time;
diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c
index c6c3161d4..a310c85e2 100644
--- a/fs/f2fs/super.c
+++ b/fs/f2fs/super.c
@@ -826,6 +826,7 @@ static void f2fs_put_super(struct super_block *sb)
/* write_checkpoint can update stat informaion */
f2fs_destroy_stats(sbi);
+ f2fs_sbi_list_del(sbi);
/*
* normally superblock is clean, so we need to release this.
@@ -1124,7 +1125,6 @@ static void default_options(struct f2fs_sb_info *sbi)
/* init some FS parameters */
sbi->active_logs = NR_CURSEG_TYPE;
- set_opt(sbi, BG_GC);
set_opt(sbi, INLINE_XATTR);
set_opt(sbi, INLINE_DATA);
set_opt(sbi, INLINE_DENTRY);
@@ -2482,6 +2482,8 @@ try_onemore:
if (err)
goto free_nm;
+ f2fs_sbi_list_add(sbi);
+
/* read root inode and dentry */
root = f2fs_iget(sb, F2FS_ROOT_INO(sbi));
if (IS_ERR(root)) {
@@ -2596,6 +2598,7 @@ free_node_inode:
f2fs_leave_shrinker(sbi);
iput(sbi->node_inode);
mutex_unlock(&sbi->umount_mutex);
+ f2fs_sbi_list_del(sbi);
f2fs_destroy_stats(sbi);
free_nm:
destroy_node_manager(sbi);