/*****************************************************************************
 *
 * Generic engine definition.
 * Copyright (C) 2001  Bart Trojanowski <bart@jukie.net>.
 * 
 * This file attempts to implement a generic algorithm database, which can be
 * used for a transparent storage of functions which can take a pice of data
 * and mutate it to another form.
 *
 * Each algorithm is split into three constructs: engine, context, and job.
 *   ENGINE is a factory of instances (contexts); it represents the algorithm.
 *   CONTEXT is an instance for processing; it can execute an alg. operation.
 *   JOB is a process in which the algorithm is executein; can be async.
 *
 * Example:
 *   - If the algorithm we consider is the RLE algorithm then the ENGINE 
 *     embodies the knowledge of RLE, an CONTEXT is one image to be encoded, 
 *     and a JOB holds one burst (ex: one line) of pixels to encode.
 *   - A better example is the LZW algorithm as it stores a lot more state 
 *     information between each chunk that is processed; this information
 *     would be stored in the CONTEXT while a JOB would embody the single
 *     compression operation.
 *
 * Naming:
 *   Each algorithm is named according to its type (ex: comp for compress),
 *   specific algorithm name, and implementation method.  An example of an
 *   algorighm engine name is "comp-lzw-sw" which describes an engine which
 *   implements a compression LZW algorithm in software.  It is possible to
 *   request any implementation by not speciing the last portion of a name;
 *   ex: if only one implementation of LZW was present then the same engine
 *   would be returned from get_engine_by_name when either "comp-lzw-sw" or
 *   "comp-lzw" were passed.
 *
 * Why?
 *   There are two main reasons.  First, to reduce the duplication of code
 *   for operations.  If there is an accepted interface for commonly use
 *   algorithms developers will be more enclined to use the existing code.
 *   Second, to allow for these operations to be done in a hardware 
 *   accelerator for that algorithm - i.e. asynchronously.
 *
 * How to use?
 *   1. locate the engine in question:
 *     engine = get_engine_by_name("comp-lzw-sw");
 *   2. generate a context:
 *     // defn is the init data for the context
 *     create_context( engine, &defn, GFP_ATOMIC, &context );
 *   3. use context:
 *     create_job( context, GFP_ATOMIC, &job );
 *     job->data.in_ptr = (void*)...;   // buffer with data
 *     job->data.in_len = (int)...;             
 *     job->data.out_ptr = (void*)...;  // buffer for result
 *     job->data.out_len = (int)...;
 *     job->callback = (int)(*)(void*); // async callback(job)
 *     job->opaque = (void*)...;        // anything
 *     execute_job( job );
 *   4. job finished...
 *     job->result;                     // stores the result of the op
 *     release_job( &job );
 *   5. done with context...
 *     release_context( &context );
 *   6. done with engine...
 *     put_engine( engine );
 *
 * License:
 *   This program is free software; you can redistribute it and/or modify it
 *   under the terms of the GNU General Public License as published by the
 *   Free Software Foundation; either version 2 of the License, or (at your
 *   option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
 * 
 *   This program is distributed in the hope that it will be useful, but
 *   WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 *   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 *   for more details.
 *
 *****************************************************************************/
#ifndef ENGINE_H
#define ENGINE_H

#define GENERIC_ENGINE_NAME_MAX_LEN 128

#ifdef __KERNEL__

#include <linux/config.h>
#include <linux/list.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <asm/types.h>
#include <asm/softirq.h>

#define MAX(a,b) (((a)>(b))?(a):(b))
#define MIN(a,b) (((a)<(b))?(a):(b))

#include "engine-debug.h"

struct generic_engine;		/* defines the algorithm */
struct generic_context;		/* a single instance of an algorithm */
struct generic_job;		/* a single operation context */

/* ---------- database access---------------------------------------------- */

/* add and remove engines from the database */
int register_generic_engine(struct generic_engine*);
int unregister_generic_engine(struct generic_engine*);

/* find an engine given a name, null if not present */
struct generic_engine* get_engine_by_name(const char*);


/* ---------- inline declarations and typedefs----------------------------- */

static inline int create_context(struct generic_engine *eng, void *defn,
				 int gfp_flag, struct generic_context ** pcon);
static inline void release_context(struct generic_context ** pcon);
static inline int create_job(struct generic_context * con, int gfp_flag, 
			     struct generic_job ** pjob);
static inline void release_job(struct generic_job ** pjob);

static inline int execute_job(struct generic_job *job);
static inline void finalize_job(struct generic_job *job);

static inline int get_engine(struct generic_engine *ge);
static inline void put_engine(struct generic_engine *ge);
static inline int get_context(struct generic_context *gi);
static inline void put_context(struct generic_context *gi);
static inline int get_job(struct generic_job *gj);
static inline void put_job(struct generic_job *gj);

/* callback generated by an async job */
typedef void (*generic_job_cb_fn)( struct generic_job * );

/* vaues returned when on inbound/outbound calls */
enum {
  ENGINE_OK = 0,		/* finished operation */
  ENGINE_DETACHED = 1,		/* performing in async mode */
  ENGINE_JOB_FAULT = -1,	/* error caused by job processing */
  ENGINE_CONTEXT_FAULT = -2,	/* context used may be unreliable */
  ENGINE_ENGINE_FAULT = -3	/* engine used may be unreliable */
};


/* ---------- ops structs ------------------------------------------------- */


/* function pointers to things that a generic engine can do
 *  NOT TO BE CALLED DIRECTLY; SEE COMMENTS IN FILE HEADER ABOVE */
struct generic_eng_ops {
  
  /* return size of memory needed for an context */
  int (*context_size) ( struct generic_engine *, void * defn );

  /* other things can go here like get_stats, etc
   * proc is probably a good interface for reporting stas */
};

/* function pointers to things that a generic context can do
 *  NOT TO BE CALLED DIRECTLY; SEE COMMENTS IN FILE HEADER ABOVE */
struct generic_con_ops {
  
  /* init function */
  int (*init) ( struct generic_context*, int gfp_flag );
  int (*cleanup) ( struct generic_context* );
  
  /* for job creation */
  int (*job_size) ( struct generic_context* );
  
  /* processing function */
  int (*process) ( struct generic_job* ); /* assigned at init time */

  /* other things can go here like get_stats, etc
   * proc is probably a good interface for reporting stas */
};

/* function pointers to things that a generic job can do
 *  NOT TO BE CALLED DIRECTLY; SEE COMMENTS IN FILE HEADER ABOVE */
struct generic_job_ops {
  void (*cancel_async) ( struct generic_job *); /* cancel if possible */
};


/* ---------- engine/context/job structs --------------------------------- */

/* a protocol engine is capable of generating contexts */
struct generic_engine {
  /* definitions */
  struct list_head eng_elem;	/* element on list of all engines */
  struct list_head con_list;	/* head of the contexts list */
  char *name;			/* name of protocol engine */
  
  /* function lists for all three incarnations */
  struct generic_eng_ops ops;
  struct generic_con_ops con_ops;
  struct generic_job_ops job_ops;
  
  /* synchronization */
  rwlock_t lock;		/* protects this structure only */
  atomic_t del_flag;		/* not available for new usage when set */
  struct module *owner;		/* needed to INC/DEC module ref count */
};

/* an context is algorithm specific but contains at least these fileds and
 * is of size returned by engine->context_size(engine) */
struct generic_context {
  /* management */
  struct list_head con_elem;	/* element on list of all contexts */
  struct list_head job_list;	/* head of the jobs list */
  struct generic_engine *eng;	/* protocol engine pointer */

  /* function list */
  struct generic_con_ops ops;
  
  /* context definition passed by user */
  void * defn;			/* engine-type specific */
  
  /* synchronization */
  rwlock_t lock;		/* protects this structure only */
  atomic_t use_count;		/* usage count, 1 when created */
  atomic_t del_flag;		/* not available for new usage when set */
};

/* an job keeps the variables used to process a chunk of data for an algorithm;
 * in a synchronous or asynchronous mode; it is algorithm specific but contains
 * at least the following fileds and is of size returned by 
 * context->job_size(context) */
struct generic_job {
  /* !!! list element must be first in the structure of the job !!! */
  struct list_head job_elem;	/* element on list of all jobs */
  struct generic_context *con;	/* protocol context pointer */
  
  /* function list */
  struct generic_job_ops ops;
  
  /* job definition passed by user */
  struct {
    void *in_ptr, *out_ptr, *extra_ptr;
    int in_len, out_len, extra_len;
  } data;
  
  /* call back mechanism */
  generic_job_cb_fn callback;	/* call back function */
  void* opaque;			/* anything to associate with this job */
  int result;			/* status of operation */

  /* synchronization */
  rwlock_t lock;		/* protects this structure only */
  atomic_t use_count;		/* usage count, 1 when created */
  atomic_t del_flag;		/* not available for new usage when set */
};

/* ---------- structure init macros --------------------------------------- */

#define INIT_GENERIC_CONTEXT( gc, ge ) \
do{ \
  INIT_LIST_HEAD( &(gc)->con_elem ); \
  INIT_LIST_HEAD( &(gc)->job_list ); \
  (gc)->eng = (ge); \
  (gc)->ops = (ge)->con_ops; \
  rwlock_init( &(gc)->lock ); \
  GE_DBG(GE_DBG_OBJREF, __PRETTY_FUNCTION__ ": gc->use_count 1\n"); \
  atomic_set( &(gc)->use_count, 1 ); \
  atomic_set( &(gc)->del_flag, 0 ); \
} while( 0 )

#define INIT_GENERIC_JOB( gj, gc ) \
do{ \
  INIT_LIST_HEAD( &(gj)->job_elem ); \
  (gj)->con = (gc); \
  rwlock_init( &(gj)->lock ); \
  GE_DBG(GE_DBG_OBJREF, __PRETTY_FUNCTION__ ": gj->use_count 1\n"); \
  atomic_set( &(gj)->use_count, 1 ); \
  atomic_set( &(gj)->del_flag, 0 ); \
} while( 0 )

/* ---------- create/release functions ------------------------------------ */

/* inline function used to create an CONTEXT */
static inline int
create_context(struct generic_engine *eng, void *defn,
	       int gfp_flags, struct generic_context ** pcon) 
{
  int ret = 0;
  register struct generic_context * gc;
  
  FIN();
  
  *pcon = NULL;
  
  ret = -EINVAL;
  if( !defn )
    goto done;

  /* make sure the engine is not released too early */
  ret = -ENODEV; /* XXX - there has to be a better return for this */
  if( get_engine( eng ) )
    goto done;
  
  ret = -ENOMEM;
  gc = (void*)kmalloc( eng->ops.context_size(eng,defn), gfp_flags );
  if ( !gc )
    goto bail_has_engine;
  
  GE_DBG(GE_DBG_OBJREF, "+con @ %p created\n",gc);

  /* initialize key components */
  INIT_GENERIC_CONTEXT( gc, eng );
  
  /* use the passed in definition to configure the context */
  gc->defn = defn;
  ret = gc->ops.init( gc, gfp_flags );
  if( ret )
    goto bail_has_context;

  /* now append this context to the list of all contexts */
  list_add_tail( &gc->con_elem, &gc->eng->con_list );
  
  ret = 0;
  *pcon = gc;
  goto done;

 bail_has_context:
  kfree( gc );
  
 bail_has_engine:
  put_engine( eng );
  
 done:
  FEXIT(ret==0);
  return ret;
}

/* inline function used to release an CONTEXT */
static inline void
release_context(struct generic_context ** pcon)
{
  register struct generic_context * gi = *pcon;
  FIN();
  if( gi ) {
    atomic_set( &gi->del_flag, 1 );
    put_context( gi );
    *pcon = NULL;		/* prevent anyone else from using this */
  }
  FOUT();
}

/* inline function used to create a JOB */
static inline int 
create_job(struct generic_context * con, int gfp_flags, 
	   struct generic_job ** pjob) 
{
  int ret = 0;
  register struct generic_job * gj;
  
  FIN();
  
  *pjob = NULL;
  
  ret = -ENODEV; /* XXX - there has to be a better return for this */
  if( get_context( con ) )
    goto bail;
  
  ret = -ENOMEM;
  gj = (void*)kmalloc( con->ops.job_size(con), gfp_flags );
  if ( !gj )
    goto bail;

  GE_DBG(GE_DBG_OBJREF, "+job @ %p created\n",gj);

  /* initialize key components */
  INIT_GENERIC_JOB( gj, con );
  
  ret = 0;
  *pjob = gj;
  goto done;

 bail:
  put_context( con );
  
 done:
  FEXIT(ret==0);
  return ret;
}

/* inline function used to release a JOB */
static inline void
release_job(struct generic_job ** pjob)
{
  register struct generic_job * gj = *pjob;
  FIN();
  atomic_set( &gj->del_flag, 1 );
  put_job( gj );
  *pjob = NULL;			/* prevent anyone else from using this */
  FOUT();
}


/* ---------- execution inlines ------------------------------------------- */

static inline int
execute_job(struct generic_job *job)
{
  int ret, first;
  struct generic_context * con = job->con;

  FIN();

  /* set to failure by default */
  job->result = -1;
  
  /* NOTE: only one job can be processed concurrently on a context; thus
   * any subsequent jobs are enqueued on job_list; then are then executed
   * after a job already running is finalized */
  write_lock_bh( &con->lock );
  first = list_empty( &con->job_list );
  list_add_tail( &job->job_elem, &con->job_list );
  write_unlock_bh( &con->lock );
  
  ret = ENGINE_DETACHED;
  if( !first ) goto done;
  
  ret = con->ops.process( job );
  if( ret>=0 ) goto done; /* success */
  
  /* error processing - must be done properly through finalize */
  job->result = ret;
  finalize_job( job );
  
 done:
  FEXIT(ret==ENGINE_OK || ret==ENGINE_DETACHED);
  return ret;
}

static inline void 
finalize_job(struct generic_job *job)
{
  struct generic_context * con = job->con;
  struct generic_job *job2 = NULL;

  FIN();
  
  /* ... */
  write_lock_bh( &con->lock );
  
  list_del( &job->job_elem );

  if( !list_empty( &con->job_list ) )
    job2 = (void*)con->job_list.next;
  
  write_unlock_bh( &con->lock );
  
  job->callback( job );
  
  if( job2 )
    con->ops.process( job2 );

  FOUT();
}


/* ---------- get/put inlines --------------------------------------------- */

/* inline function used to increment usage on an engine, 0 == success */
static inline int
get_engine(struct generic_engine *ge)
{
  int ret = 1;
  FIN();
  read_lock_bh( &ge->lock );
  if( atomic_read( &ge->del_flag ) )
    goto done;
  __MOD_INC_USE_COUNT(ge->owner);
  ret = 0;
 done:
  read_unlock_bh( &ge->lock );
  FEXIT(ret==0);
  return ret;
}

/* inline function used to decrement usage on an engine */
static inline void
put_engine(struct generic_engine *ge)
{
  FIN();
  __MOD_DEC_USE_COUNT(ge->owner);
  FOUT();
}

/* inline function used to increment usage on an context, 0 == success */
static inline int
get_context(struct generic_context *gc)
{
  int ret = 1;
  FIN();
  read_lock_bh( &gc->lock );
  if( atomic_read( &gc->del_flag ) )
    goto done;
  atomic_inc( &gc->use_count );
  GE_DBG(GE_DBG_OBJREF, "get_context: gc->use_count %d\n",
	 atomic_read( &gc->use_count ) );
  ret = 0;
 done:
  read_unlock_bh( &gc->lock );
  FEXIT(ret==0);
  return ret;  
}

/* inline function used to decrement usage on an context */
static inline void
put_context(struct generic_context *gc)
{
  FIN();
  GE_DBG(GE_DBG_OBJREF, "put_context: gc->use_count %d\n",
	 atomic_read( &gc->use_count ) );
  if( atomic_dec_and_test( &gc->use_count ) ) {
    write_lock_bh( &gc->eng->lock );
    list_del( &gc->con_elem );
    write_unlock_bh( &gc->eng->lock );
    gc->ops.cleanup( gc );
    put_engine( gc->eng );
    GE_DBG(GE_DBG_OBJREF, "-con @ %p deleted\n",gc);
    kfree( gc );
  }
  FOUT();
}

/* inline function used to increment usage on a job, 0 == success */
static inline int
get_job(struct generic_job *gj)
{
  int ret = 1;
  FIN();
  read_lock_bh( &gj->lock );
  if( atomic_read( &gj->del_flag ) )
    goto done;
  atomic_inc( &gj->use_count );
  GE_DBG(GE_DBG_OBJREF, "get_job: gj->use_count %d\n",
	 atomic_read( &gj->use_count ) );
  ret = 0;
 done:
  read_unlock_bh( &gj->lock );
  FEXIT(ret==0);
  return ret;  
}

/* inline function used to decrement usage on a job */
static inline void
put_job(struct generic_job *gj)
{
  FIN();
  GE_DBG(GE_DBG_OBJREF, "put_job: gj->use_count %d\n",
	 atomic_read( &gj->use_count ) );
  if( atomic_dec_and_test( &gj->use_count ) ) {
    write_lock_bh( &gj->con->lock );
    list_del( &gj->job_elem );	/* XXX just in case */
    write_unlock_bh( &gj->con->lock );
    put_context( gj->con );
    GE_DBG(GE_DBG_OBJREF, "-job @ %p deleted\n",gj);
    kfree( gj );
  }
  FOUT();
}


/* ---------- quick and dirty init macros --------------------------------- */

#define INIT_GENERIC_ENG_OPS(name)  \
  context_size: name##_eng_context_size

#define INIT_GENERIC_CON_OPS(name) \
  init: name##_con_init, \
  cleanup: name##_con_cleanup, \
  job_size: name##_con_job_size \

#define INIT_GENERIC_JOB_OPS(name) \
  cancel_async: name##_job_cancel_async 

#define DEFINE_GENERIC_ENGINE(_name_,_prefix_) \
  struct generic_engine _prefix_ = { \
    owner: THIS_MODULE, \
    name: _name_, \
    lock: RW_LOCK_UNLOCKED, \
    del_flag: ATOMIC_INIT(0), \
    ops: {INIT_GENERIC_ENG_OPS(_prefix_)}, \
    con_ops: {INIT_GENERIC_CON_OPS(_prefix_)}, \
    job_ops: {INIT_GENERIC_JOB_OPS(_prefix_)}, \
  };

#endif /* __KERNEL__ */

#endif /* ENGINE_H */

/*****************************************************************************
 *
 * $Log: engine.h,v $
 * Revision 1.17  2001/06/08 23:29:10  bart
 * major refining
 *
 * Revision 1.16  2001/06/04 02:12:36  bart
 * added execute_job/finalize_job as wrappers to con->ops.process; in addtion
 * these functions take care of queueing successive command on a context
 *
 * Revision 1.15  2001/06/03 15:22:18  bart
 * added lots of debug code to inlined functions - this is temporary
 *
 * Revision 1.14  2001/06/03 13:17:43  bart
 * moved get_engine from con_init to create_context.
 *
 * Revision 1.13  2001/06/03 02:16:32  bart
 * extended the DEFINE_GENERIC_ENGINE macro to take different prefix and name
 *
 * Revision 1.12  2001/06/01 01:28:48  bart
 * updates for context_size function; added exptr ptr into data for a job
 *
 * Revision 1.11  2001/05/28 00:54:33  bart
 * added INIT_GENERIC_CONTEXT & INIT_GENERIC_JOB macros
 *
 * Revision 1.10  2001/05/27 21:50:38  bart
 * corrected where the context is appended-to/removed-from the eng->con_list
 *
 * Revision 1.9  2001/05/27 21:44:03  bart
 * cleaned up the activation and cleanup of contexts by using init/cleanup
 * functions.
 *
 * Revision 1.8  2001/05/26 17:25:45  bart
 * cosmetic changes: renamed context->definition to context->defn
 *
 * Revision 1.7  2001/05/26 00:49:12  bart
 * updated job function definitions
 *
 * Revision 1.6  2001/05/25 23:53:05  bart
 * s/find_engine_by_name/get_engine_by_name/ substitution applied.
 *
 * Revision 1.5  2001/05/24 02:09:36  bart
 * renamed instance to context to be consistent with kerneli naming.
 *
 * Revision 1.4  2001/05/15 11:39:38  bart
 * added some documentation
 *
 * Revision 1.3  2001/05/15 01:10:00  bart
 * initial compiling state
 *
 * Revision 1.2  2001/05/15 00:31:14  bart
 * updated stubs
 *
 * Revision 1.1.1.1  2001/05/14 20:12:01  bart
 * initial import
 *
 *
 *****************************************************************************/

