/*
 * JFFS2 -- Journalling Flash File System, Version 2.
 *
 * Copyright (C) 2001-2003 Red Hat, Inc.
 * Copyright (C) 2007 eCosCentric Limited.
 *
 * Created by David Woodhouse <dwmw2@redhat.com>
 *
 * For licensing information, see the file 'LICENCE' in this directory.
 *
 * $Id: gcthread.c,v 1.3 2005/01/22 16:01:12 lunn Exp $
 *
 */
#include <linux/kernel.h>
#include "nodelist.h"
#include <cyg/kernel/kapi.h>
#include <cyg/fileio/fileio.h>

#define GC_THREAD_FLAG_TRIG           (1<<0)
#define GC_THREAD_FLAG_STOP           (1<<1)
#define GC_THREAD_FLAG_HAS_EXIT       (1<<2)
#define GC_THREAD_FLAG_SUSPEND        (1<<3)
#define GC_THREAD_FLAG_HAS_SUSPENDED  (1<<4)

static cyg_thread_entry_t jffs2_garbage_collect_thread;

void jffs2_set_gc_thread_wait_ticks(struct jffs2_sb_info *c, cyg_tick_count_t ticks)
{
    struct super_block *sb=OFNI_BS_2SFFJ(c);
    cyg_bool_t ret;

    CYG_ASSERTC(c);

    ret = cyg_mutex_lock( &sb->s_gc_thread_mutex );
    CYG_ASSERT( ret, "GC thread mutex lock broken");
    sb->s_gc_thread_wait_ticks = ticks;
    cyg_mutex_unlock( &sb->s_gc_thread_mutex );
}

void jffs2_get_gc_thread_wait_ticks(struct jffs2_sb_info *c, cyg_tick_count_t *ticks)
{
    struct super_block *sb=OFNI_BS_2SFFJ(c);
    cyg_bool_t ret;

    CYG_ASSERTC(c);

    ret = cyg_mutex_lock( &sb->s_gc_thread_mutex );
    CYG_ASSERT( ret, "GC thread mutex lock broken");
    *ticks = sb->s_gc_thread_wait_ticks;
    cyg_mutex_unlock( &sb->s_gc_thread_mutex );
}

void jffs2_garbage_collect_trigger(struct jffs2_sb_info *c)
{
     struct super_block *sb=OFNI_BS_2SFFJ(c);
     cyg_bool_t ret;
     
     /* Wake up the thread */
     D1(printk("jffs2_garbage_collect_trigger\n"));

     ret = cyg_mutex_lock( &sb->s_gc_thread_mutex );
     CYG_ASSERT( ret, "GC thread mutex lock broken");

     sb->s_gc_thread_shared_data |= GC_THREAD_FLAG_TRIG;

     // Get GC thread to wake up and do something
     cyg_cond_signal( &sb->s_gc_thread_cv );
     cyg_mutex_unlock( &sb->s_gc_thread_mutex );
}


void 
jffs2_start_garbage_collect_thread(struct jffs2_sb_info *c)
{
     struct super_block *sb=OFNI_BS_2SFFJ(c);
     
     CYG_ASSERTC(c);
     CYG_ASSERTC(!sb->s_gc_thread_handle);

     cyg_mutex_init( &sb->s_gc_thread_mutex );
     cyg_cond_init( &sb->s_gc_thread_cv, &sb->s_gc_thread_mutex );
     sb->s_gc_thread_shared_data = 0;
     sb->s_gc_thread_wait_ticks = CYGNUM_JFFS2_GC_THREAD_TICKS;
     
     D1(printk("jffs2_start_garbage_collect_thread\n"));
     /* Start the thread. Doesn't matter if it fails -- it's only an
      * optimisation anyway */
     cyg_thread_create(CYGNUM_JFFS2_GC_THREAD_PRIORITY,
                       jffs2_garbage_collect_thread, 
                       (cyg_addrword_t)c,"jffs2 gc thread",
                       (void*)sb->s_gc_thread_stack,
                       sizeof(sb->s_gc_thread_stack),
                       &sb->s_gc_thread_handle, 
                       &sb->s_gc_thread);

     cyg_thread_resume(sb->s_gc_thread_handle);
}

// NB Doesn't actually suspend thread. Just whether the thread does GC.
void jffs2_suspend_garbage_collection_by_thread(struct jffs2_sb_info *c)
{
     struct super_block *sb=OFNI_BS_2SFFJ(c);
     cyg_bool_t ret;
     
     CYG_ASSERTC(c);
     CYG_ASSERTC(sb->s_gc_thread_handle);
     
     ret = cyg_mutex_lock( &sb->s_gc_thread_mutex );
     CYG_ASSERT( ret, "GC thread mutex lock broken");

     if ( 0 == (sb->s_gc_thread_shared_data & GC_THREAD_FLAG_HAS_SUSPENDED) )
     {
         sb->s_gc_thread_shared_data |= GC_THREAD_FLAG_SUSPEND;

         // Get GC thread to wake up and ack
         cyg_cond_signal( &sb->s_gc_thread_cv );

         do {
             ret = cyg_cond_wait( &sb->s_gc_thread_cv );
             CYG_ASSERT( ret, "Susp GC thread cond var wait broken");
         } while ( 0 == (sb->s_gc_thread_shared_data & GC_THREAD_FLAG_HAS_SUSPENDED) );
     }

     cyg_mutex_unlock( &sb->s_gc_thread_mutex );
}
     
void jffs2_resume_garbage_collection_by_thread(struct jffs2_sb_info *c)
{
     struct super_block *sb=OFNI_BS_2SFFJ(c);
     cyg_bool_t ret;
     
     CYG_ASSERTC(c);
     CYG_ASSERTC(sb->s_gc_thread_handle);
     
     ret = cyg_mutex_lock( &sb->s_gc_thread_mutex );
     CYG_ASSERT( ret, "GC thread mutex lock broken");

     sb->s_gc_thread_shared_data &= ~GC_THREAD_FLAG_HAS_SUSPENDED;

     cyg_mutex_unlock( &sb->s_gc_thread_mutex );
}
     

void 
jffs2_stop_garbage_collect_thread(struct jffs2_sb_info *c)
{
     struct super_block *sb=OFNI_BS_2SFFJ(c);
     cyg_bool_t ret;
     
     CYG_ASSERTC(c);
     CYG_ASSERTC(sb->s_gc_thread_handle);
     
     D1(printk("jffs2_stop_garbage_collect_thread\n"));
     /* Stop the thread and wait for it if necessary */
     
     ret = cyg_mutex_lock( &sb->s_gc_thread_mutex );
     CYG_ASSERT( ret, "GC thread mutex lock broken");

     sb->s_gc_thread_shared_data |= GC_THREAD_FLAG_STOP;

     // Get GC thread to wake up and exit
     cyg_cond_signal( &sb->s_gc_thread_cv );
     
     D1(printk("jffs2_stop_garbage_collect_thread wait\n"));

     while ( 0 == (sb->s_gc_thread_shared_data & GC_THREAD_FLAG_HAS_EXIT) ) {
         ret = cyg_cond_wait( &sb->s_gc_thread_cv );
         CYG_ASSERT( ret, "GC thread cond var wait broken");
     }

     cyg_mutex_unlock( &sb->s_gc_thread_mutex );
     
     // Kill and free the resources ...  this is safe due to the indication
     // from the thread.
     cyg_thread_kill(sb->s_gc_thread_handle);
     while (!cyg_thread_delete(sb->s_gc_thread_handle))
         CYG_EMPTY_STATEMENT; // Maybe this should assert instead?
     
     cyg_cond_destroy(&sb->s_gc_thread_cv);
     cyg_mutex_destroy(&sb->s_gc_thread_mutex);

     sb->s_gc_thread_handle = (cyg_handle_t)0;
}


static void
jffs2_garbage_collect_thread(cyg_addrword_t data)
{
     struct jffs2_sb_info *c=(struct jffs2_sb_info *)data;
     struct super_block *sb=OFNI_BS_2SFFJ(c);
     cyg_mtab_entry *mte=NULL;
     int ret;
     
     D1(printk("jffs2_garbage_collect_thread START\n"));

     // It would be nice to set up mte with cyg_fs_root_lookup now, but that
     // isn't possible.... if we did, there would be problems if the first
     // time this thread starts running is when unmounting. Instead it needs
     // to be done inside the loop.

     ret = cyg_mutex_lock( &sb->s_gc_thread_mutex );
     CYG_ASSERT( ret, "GC thread initial mutex wait broken");

     while (1) {
         // TRIG and SUSPEND are events which we should immediately handle
         // below, so don't wait.
         if (0 == (sb->s_gc_thread_shared_data & (GC_THREAD_FLAG_TRIG|GC_THREAD_FLAG_SUSPEND)))
             cyg_cond_timed_wait(&sb->s_gc_thread_cv,
                                 cyg_current_time() + sb->s_gc_thread_wait_ticks);

         // This is hairy. To avoid deadlock, before locking fs, we have to
         // release the mutex. Otherwise another thread may hold the fs lock and
         // contend with us for mutex. So we unlock and relock in the right
         // order.
         //
         // But it's even more complicated than that... unmounting is really
         // hard. So we take a two-step approach, and unmount will release its fs
         // lock and call the suspend above. Once acknowledged, this GC thread
         // will no longer be attempting to claim the FS lock, guaranteed.
         //
         // What a palaver.

         if (sb->s_gc_thread_shared_data & GC_THREAD_FLAG_STOP)
             break;

         if (sb->s_gc_thread_shared_data & GC_THREAD_FLAG_SUSPEND)
         {
             sb->s_gc_thread_shared_data |= GC_THREAD_FLAG_HAS_SUSPENDED;
             // We clear the trigger flag as well otherwise we won't do the cyg_cond_timed_wait.
             sb->s_gc_thread_shared_data &= ~(GC_THREAD_FLAG_SUSPEND|GC_THREAD_FLAG_TRIG);
             // Let waiting thread know
             cyg_cond_signal( &sb->s_gc_thread_cv );
             continue;
         }
         if (sb->s_gc_thread_shared_data & GC_THREAD_FLAG_HAS_SUSPENDED)
             continue;
         
         if (!mte)
         {
             mte=cyg_fs_root_lookup((cyg_dir *) sb->s_root);
             CYG_ASSERT(mte, "Bad mount point");
         }

         cyg_mutex_unlock( &sb->s_gc_thread_mutex );

         // Another thread can set trigger here. That's ok.
         // Another thread can call umount here. That's ok - it will unlock fs lock
         // and wait for us to ack the suspend before continuing. So we can still
         // go on, do one GC and loop, safely.
         // Another thread can call stop here? Bzzt, no. We must have been
         // suspended first.

         cyg_fs_lock(mte, mte->fs->syncmode);
         cyg_mutex_lock( &sb->s_gc_thread_mutex );

         // We're committed now.
         sb->s_gc_thread_shared_data &= ~GC_THREAD_FLAG_TRIG;

         D1(printk("jffs2: GC THREAD GC BEGIN\n"));

         ret = jffs2_garbage_collect_pass(c); // Do it.

         D1(printk("jffs2: GC THREAD GC END\n"));

#ifdef CYGSEM_JFFS2_GC_THREAD_CAN_ERASE
         // Erase a block if we can
         jffs2_erase_pending_blocks(c, 1);
#endif

         cyg_fs_unlock(mte, mte->fs->syncmode);
         
         if (ret == -ENOSPC) {
             printk(KERN_CRIT "No space for garbage collection. "
                    "Aborting JFFS2 GC thread\n");
             // Should assert instead? Probably overkill. Shouldn't happen though.
             break;
         }
     }

     D1(printk("jffs2_garbage_collect_thread EXIT\n"));

     sb->s_gc_thread_shared_data |= GC_THREAD_FLAG_HAS_EXIT;

     // Let waiting thread know we're done
     cyg_cond_signal( &sb->s_gc_thread_cv );

     // we will probably get killed inside the mutex unlock function, since
     // the waiting thread has been woken up and should have a higher priority.
     cyg_mutex_unlock( &sb->s_gc_thread_mutex );
}

// EOF gcthread.c
