/*
 * Copyright (c) 2020 Red Hat, Inc.
 *
 * 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.
 * 
 * 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.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA. 
 *
 * $Id: //eng/vdo-releases/aluminum/src/c++/vdo/kernel/workQueue.h#2 $
 */

#ifndef ALBIREO_WORK_QUEUE_H
#define ALBIREO_WORK_QUEUE_H

#include <linux/kobject.h>
#include <linux/sched.h>        /* for TASK_COMM_LEN */

#include "kernelTypes.h"
#include "util/funnelQueue.h"

enum {
  MAX_QUEUE_NAME_LEN        = TASK_COMM_LEN,
  /** Maximum number of action definitions per work queue type */
  WORK_QUEUE_ACTION_COUNT   = 8,
  /** Number of priority values available */
  WORK_QUEUE_PRIORITY_COUNT = 4,
};

struct kvdoWorkItem {
  /** Entry link for lock-free work queue */
  FunnelQueueEntry  workQueueEntryLink;
  /** Function to be called */
  KvdoWorkFunction  work;
  /** Optional alternate function for display in queue stats */
  void             *statsFunction;
  /** An index into the statistics table; filled in by workQueueStats code */
  unsigned int      statTableIndex;
  /**
   * The action code given to setupWorkItem, from which a priority will be
   * determined.
   **/
  unsigned int      action;
  /** The work queue in which the item is enqueued, or NULL if not enqueued. */
  KvdoWorkQueue    *myQueue;
  /**
   * Time at which to execute in jiffies for a delayed work item, or zero to
   * queue for execution ASAP.
   **/
  Jiffies           executionTime;
  /** List management for delayed or expired work items */
  KvdoWorkItem     *next;
  /** Time of enqueueing, in ns, for recording queue (waiting) time stats */
  uint64_t          enqueueTime;
};

/**
 * Table entries defining an action.
 *
 * Actions are intended to distinguish general classes of activity for
 * prioritization purposes, but not necessarily to indicate specific work
 * functions. They are indicated to setupWorkItem numerically, using an
 * enumerator defined per kind of work queue -- bio submission work queue
 * actions use BioQAction, cpu actions use CPUQAction, etc. For example, for
 * the CPU work queues, data compression can be prioritized separately from
 * final cleanup processing of a KVIO or from dedupe verification; base code
 * threads prioritize all VIO callback invocation the same, but separate from
 * sync or heartbeat operations. The bio acknowledgement work queue, on the
 * other hand, only does one thing, so it only defines one action code.
 *
 * Action codes values must be small integers, 0 through
 * WORK_QUEUE_ACTION_COUNT-1, and should not be duplicated for a queue type.
 *
 * A table of KvdoWorkQueueAction entries embedded in KvdoWorkQueueType
 * specifies the name, code, and priority for each type of action in the work
 * queue. The table can have at most WORK_QUEUE_ACTION_COUNT entries, but a
 * NULL name indicates an earlier end to the table.
 *
 * Priorities may be specified as values from 0 through
 * WORK_QUEUE_PRIORITY_COUNT-1, higher values indicating higher priority.
 * Priorities are just strong suggestions; it's possible for a lower-priority
 * work item scheduled right after a high-priority one to be run first, if the
 * worker thread happens to be scanning its queues at just the wrong moment,
 * but the high-priority item will be picked up next.
 *
 * Internally, the priorities in this table are used to initialize another
 * table in the constructed work queue object, and in internal builds,
 * device-mapper messages can be sent to change the priority for an action,
 * identified by name, in a running VDO device. Doing so does not affect the
 * priorities for other devices, or for future VDO device creation.
 **/
typedef struct kvdoWorkQueueAction {
  /** Name of the action */
  char         *name;

  /** The action code (per-type enum) */
  unsigned int  code;

  /** The initial priority for this action */
  unsigned int  priority;
} KvdoWorkQueueAction;

typedef void (*KvdoWorkQueueFunction)(void *);

/**
 * Static attributes of a work queue that are fixed at compile time
 * for a given call site. (Attributes that may be computed at run time
 * are passed as separate arguments.)
 **/
typedef struct kvdoWorkQueueType {
  /** A function to call in the new thread before servicing requests */
  KvdoWorkQueueFunction start;

  /** A function to call in the new thread when shutting down */
  KvdoWorkQueueFunction finish;

  /** A function to call in the new thread after running out of work */
  KvdoWorkQueueFunction suspend;

  /** Table of actions for this work queue */
  KvdoWorkQueueAction   actionTable[WORK_QUEUE_ACTION_COUNT];
} KvdoWorkQueueType;

/**
 * Create a work queue.
 *
 * If multiple threads are requested, work items will be distributed to them in
 * round-robin fashion.
 *
 * @param [in]  threadNamePrefix The per-device prefix to use in thread names
 * @param [in]  name             The queue name
 * @param [in]  parentKobject    The parent sysfs node
 * @param [in]  owner            The kernel layer owning the work queue
 * @param [in]  private          Private data of the queue for use by work
 *                               items or other queue-specific functions
 * @param [in]  type             The work queue type defining the lifecycle
 *                               functions, queue actions, priorities, and
 *                               timeout behavior
 * @param [in]  threadCount      Number of service threads to set up
 * @param [out] queuePtr         Where to store the queue handle
 *
 * @return VDO_SUCCESS or an error code
 **/
int makeWorkQueue(const char               *threadNamePrefix,
                  const char               *name,
                  struct kobject           *parentKobject,
                  KernelLayer              *owner,
                  void                     *private,
                  const KvdoWorkQueueType  *type,
                  unsigned int              threadCount,
                  KvdoWorkQueue           **queuePtr);

/**
 * Set up the fields of a work queue item.
 *
 * Before the first setup call (setupWorkItem or setupWorkItemWithTimeout), the
 * work item must have been initialized to all-zero. Resetting a
 * previously-used work item does not require another memset.
 *
 * The action code is typically defined in a work-queue-type-specific
 * enumeration; see the description of KvdoWorkQueueAction.
 *
 * @param item           The work item to initialize
 * @param work           The function pointer to execute
 * @param statsFunction  A function pointer to record for stats, or NULL
 * @param action         Action code, for determination of priority
 **/
void setupWorkItem(KvdoWorkItem     *item,
                   KvdoWorkFunction  work,
                   void             *statsFunction,
                   unsigned int      action);

/**
 * Add a work item to a work queue.
 *
 * If the work item has a timeout that has already passed, the timeout
 * handler function may be invoked at this time.
 *
 * @param queue      The queue handle
 * @param item       The work item to be processed
 **/
void enqueueWorkQueue(KvdoWorkQueue *queue, KvdoWorkItem *item);

/**
 * Add a work item to a work queue, to be run at a later point in time.
 *
 * Currently delayed work items are used only in a very limited fashion -- at
 * most one at a time for any of the work queue types that use them -- and some
 * shortcuts have been taken that assume that that's the case. Multiple delayed
 * work items should work, but they will execute in the order they were
 * enqueued.
 *
 * @param queue           The queue handle
 * @param item            The work item to be processed
 * @param executionTime   When to run the work item (jiffies)
 **/
void enqueueWorkQueueDelayed(KvdoWorkQueue *queue,
                             KvdoWorkItem  *item,
                             Jiffies        executionTime);

/**
 * Shut down a work queue's worker thread.
 *
 * Alerts the worker thread that it should shut down, and then waits
 * for it to do so.
 *
 * There should not be any new enqueueing of work items done once this
 * function is called. Any pending delayed work items will be
 * processed, as scheduled, before the worker thread shuts down, but
 * they must not re-queue themselves to run again.
 *
 * @param queue  The work queue to shut down
 **/
void finishWorkQueue(KvdoWorkQueue *queue);

/**
 * Free a work queue and null out the reference to it.
 *
 * @param queuePtr  Where the queue handle is found
 **/
void freeWorkQueue(KvdoWorkQueue **queuePtr);

/**
 * Print work queue state and statistics to the kernel log.
 *
 * @param queue  The work queue to examine
 **/
void dumpWorkQueue(KvdoWorkQueue *queue);

/**
 * Write to the buffer some info about the work item, for logging.
 * Since the common use case is dumping info about a lot of work items
 * to syslog all at once, the format favors brevity over readability.
 *
 * @param item    The work item
 * @param buffer  The message buffer to fill in
 * @param length  The length of the message buffer
 **/
void dumpWorkItemToBuffer(KvdoWorkItem *item, char *buffer, size_t length);


/**
 * Initialize work queue internals at module load time.
 **/
void initWorkQueueOnce(void);

/**
 * Checks whether two work items have the same action codes
 *
 * @param item1 The first item
 * @param item2 The second item
 *
 * @return TRUE if the actions are the same, FALSE otherwise
 */
static inline bool areWorkItemActionsEqual(KvdoWorkItem *item1,
                                           KvdoWorkItem *item2)
{
  return item1->action == item2->action;
}

/**
 * Returns the private data for the current thread's work queue.
 *
 * @return  The private data pointer, or NULL if none or if the current
 *          thread is not a work queue thread.
 **/
void *getWorkQueuePrivateData(void);

/**
 * Updates the private data pointer for the current thread's work queue.
 *
 * @param newData  The new private data pointer
 **/
void setWorkQueuePrivateData(void *newData);

/**
 * Returns the work queue pointer for the current thread, if any.
 *
 * @return   The work queue pointer or NULL
 **/
KvdoWorkQueue *getCurrentWorkQueue(void);

/**
 * Returns the kernel layer that owns the work queue.
 *
 * @param queue  The work queue
 *
 * @return   The owner pointer supplied at work queue creation
 **/
KernelLayer *getWorkQueueOwner(KvdoWorkQueue *queue);

#endif /* ALBIREO_WORK_QUEUE_H */
