/*
 * Copyright (c) 2018 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/magnesium/src/c++/vdo/kernel/dataKVIO.h#4 $
 */

#ifndef DATA_KVIO_H
#define DATA_KVIO_H

#include "dataVIO.h"
#include "kvio.h"
#include "uds-block.h"

typedef struct {
  /*
   * The BIO which was received from the device mapper to initiate an I/O
   * request. This field will be non-NULL only until the request is
   * acknowledged.
   */
  BIO           *bio;
  // Cached copies of fields from the bio which will need to be reset after
  // we're done.
  void          *private;
  void          *endIO;
  // This is a copy of the bi_rw field of the BIO which sadly is not just
  // a boolean read-write flag, but also includes other flag bits.
  unsigned long  rw;
} ExternalIORequest;

/* Dedupe support */
struct dedupeContext {
  UdsRequest          udsRequest;
  struct list_head    pendingList;
  Jiffies             submissionTime;
  Atomic32            requestState;
  int                 status;
  bool                isPending;
  /** Hash of the associated VIO (NULL if not calculated) */
  const UdsChunkName *chunkName;
};

/* Read cache support */
typedef struct {
  /**
   * A pointer to a block that holds the data from the last read operation.
   *
   * Note that this handle is counted as a reference on the read cache entry
   * associated with this block pointer, if the read cache exists.
   **/
  char                *data;
  /**
   * Temporary storage for doing reads from the underlying device when the
   * read cache does not exist. If the read cache exists, this will be NULL.
   **/
  char                *buffer;
  /**
   * A bio structure wrapping the buffer, if the read cache does not exist.
   * If the read cache exists, this will be NULL.
   **/
  BIO                 *bio;
  /**
   * Callback to invoke after completing the read I/O operation.
   **/
  DataKVIOCallback     callback;
  /**
   * Physical block number passed to kvdoReadBlock(). Used only if the read
   * cache exists.
   **/
  PhysicalBlockNumber  pbn;
  /**
   * Mapping state passed to kvdoReadBlock(), used to determine whether
   * the data must be uncompressed.
   **/
  BlockMappingState    mappingState;
  /**
   * The action code (BIO_Q_ACTION_* value) for the I/O work item, used if the
   * read request needs to read data from the underlying storage.
   *
   * Derived from the ReadBlockOperation passed to kvdoReadBlock().
   **/
  BioQAction           action;
  /**
   * The result code of the read attempt.
   **/
  int                  status;
  /**
   * The cache entry. If the read cache does not exist, this is always NULL.
   **/
  ReadCacheEntry      *cacheEntry;
} ReadBlock;

struct dataKVIO {
  /* The embedded base code's DataVIO */
  DataVIO            dataVIO;
  /* The embedded KVIO */
  KVIO               kvio;
  /* The BIO from the request which is being serviced by this KVIO. */
  ExternalIORequest  externalIORequest;
  /* Dedupe */
  DedupeContext      dedupeContext;
  /* Read cache */
  ReadBlock          readBlock;
  /* partial block support */
  BlockSize          offset;
  bool               isPartial;
  /* discard support */
  bool               hasDiscardPermit;
  DiscardSize        remainingDiscard;
  /**
   * A copy of user data written, so we can do additional processing
   * (dedupe, compression) after acknowledging the I/O operation and
   * thus losing access to the original data.
   *
   * Also used as buffer space for read-modify-write cycles when
   * emulating smaller-than-blockSize I/O operations.
   **/
  char              *dataBlock;
  /** A bio structure describing the #dataBlock buffer. */
  BIO               *dataBlockBio;
  /** A block used as output during compression or uncompression. */
  char              *scratchBlock;
};

/**
 * Convert a KVIO to a DataKVIO.
 *
 * @param kvio  The KVIO to convert
 *
 * @return The KVIO as a DataKVIO
 **/
static inline DataKVIO *kvioAsDataKVIO(KVIO *kvio)
{
  ASSERT_LOG_ONLY(isData(kvio), "KVIO is a DataKVIO");
  return container_of(kvio, DataKVIO, kvio);
}

/**
 * Convert a DataKVIO to a KVIO.
 *
 * @param dataKVIO  The DataKVIO to convert
 *
 * @return The DataKVIO as a KVIO
 **/
static inline KVIO *dataKVIOAsKVIO(DataKVIO *dataKVIO)
{
  return &dataKVIO->kvio;
}

/**
 * Returns a pointer to the DataKVIO wrapping a DataVIO.
 *
 * @param dataVIO  the DataVIO
 *
 * @return the DataKVIO
 **/
static inline DataKVIO *dataVIOAsDataKVIO(DataVIO *dataVIO)
{
  return container_of(dataVIO, DataKVIO, dataVIO);
}

/**
 * Returns a pointer to the KVIO associated with a DataVIO.
 *
 * @param dataVIO  the DataVIO
 *
 * @return the KVIO
 **/
static inline KVIO *dataVIOAsKVIO(DataVIO *dataVIO)
{
  return dataKVIOAsKVIO(dataVIOAsDataKVIO(dataVIO));
}

/**
 * Returns a pointer to the DataKVIO wrapping a work item.
 *
 * @param item  the work item
 *
 * @return the DataKVIO
 **/
static inline DataKVIO *workItemAsDataKVIO(KvdoWorkItem *item)
{
  return kvioAsDataKVIO(workItemAsKVIO(item));
}

/**
 * Get the WorkItem from a DataKVIO.
 *
 * @param dataKVIO  The DataKVIO
 *
 * @return the DataKVIO's work item
 **/
static inline KvdoWorkItem *workItemFromDataKVIO(DataKVIO *dataKVIO)
{
  return &dataKVIOAsKVIO(dataKVIO)->enqueueable.workItem;
}

/**
 * Get the BIO from a DataKVIO.
 *
 * @param dataKVIO  The DataKVIO from which to get the BIO
 *
 * @return The DataKVIO's BIO
 **/
static inline BIO *getBIOFromDataKVIO(DataKVIO *dataKVIO)
{
  return dataKVIOAsKVIO(dataKVIO)->bio;
}

/**
 * Get the KernelLayer from a DataKVIO.
 *
 * @param dataKVIO  The DataKVIO from which to get the KernelLayer
 *
 * @return The DataKVIO's KernelLayer
 **/
static inline KernelLayer *getLayerFromDataKVIO(DataKVIO *dataKVIO)
{
  return dataKVIOAsKVIO(dataKVIO)->layer;
}

/**
 * Set up and enqueue a DataKVIO's work item to be processed in the base code
 * context.
 *
 * @param dataKVIO       The DataKVIO with the work item to be run
 * @param work           The function pointer to execute
 * @param statsFunction  A function pointer to record for stats, or NULL
 * @param action         Action code, mapping to a relative priority
 **/
static inline void enqueueDataKVIO(DataKVIO         *dataKVIO,
                                   KvdoWorkFunction  work,
                                   void             *statsFunction,
                                   unsigned int      action)
{
  enqueueKVIO(dataKVIOAsKVIO(dataKVIO), work, statsFunction, action);
}

/**
 * Enqueue a DataKVIO on a work queue.
 *
 * @param queue     The queue
 * @param dataKVIO  The DataKVIO
 **/
static inline void enqueueDataKVIOWork(KvdoWorkQueue *queue,
                                       DataKVIO      *dataKVIO)
{
  enqueueKVIOWork(queue, dataKVIOAsKVIO(dataKVIO));
}

/**
 * Add a trace record for the current source location.
 *
 * @param dataKVIO  The DataKVIO structure to be updated
 * @param location  The source-location descriptor to be recorded
 **/
static inline void dataKVIOAddTraceRecord(DataKVIO      *dataKVIO,
                                          TraceLocation  location)
{
  dataVIOAddTraceRecord(&dataKVIO->dataVIO, location);
}

/**
 * Set up and enqueue a DataKVIO on the CPU queue.
 *
 * @param dataKVIO       The DataKVIO to set up
 * @param work           The function pointer to execute
 * @param statsFunction  A function pointer to record for stats, or NULL
 * @param action         Action code, mapping to a relative priority
 **/
static inline void launchDataKVIOOnCPUQueue(DataKVIO         *dataKVIO,
                                            KvdoWorkFunction  work,
                                            void             *statsFunction,
                                            unsigned int      action)
{
  KVIO *kvio = dataKVIOAsKVIO(dataKVIO);
  launchKVIO(kvio, work, statsFunction, action, kvio->layer->cpuQueue);
}

/**
 * Set up and enqueue a DataKVIO on the BIO Ack queue.
 *
 * @param dataKVIO       The DataKVIO to set up
 * @param work           The function pointer to execute
 * @param statsFunction  A function pointer to record for stats, or NULL
 * @param action         Action code, mapping to a relative priority
 **/
static inline void launchDataKVIOOnBIOAckQueue(DataKVIO         *dataKVIO,
                                               KvdoWorkFunction  work,
                                               void             *statsFunction,
                                               unsigned int      action)
{
  KVIO *kvio = dataKVIOAsKVIO(dataKVIO);
  launchKVIO(kvio, work, statsFunction, action, kvio->layer->bioAckQueue);
}

/**
 * Move a DataKVIO back to the base threads.
 *
 * @param dataKVIO The DataKVIO to enqueue
 **/
static inline void kvdoEnqueueDataVIOCallback(DataKVIO *dataKVIO)
{
  kvdoEnqueueVIOCallback(dataKVIOAsKVIO(dataKVIO));
}

/**
 * Check whether the external request bio had FUA set.
 *
 * @param dataKVIO  The DataKVIO to check
 *
 * @return <code>true</code> if the external request bio had FUA set
 **/
static inline bool requestorSetFUA(DataKVIO *dataKVIO)
{
  return ((dataKVIO->externalIORequest.rw & REQ_FUA) == REQ_FUA);
}

/**
 * Associate a KVIO with a BIO passed in from the block layer, and start
 * processing the KVIO.
 *
 * If setting up a KVIO fails, a message is logged, and the limiter permits
 * (request and maybe discard) released, but the caller is responsible for
 * disposing of the bio.
 *
 * @param layer                 The physical layer
 * @param bio                   The bio for which to create KVIO
 * @param arrivalTime           The time (in jiffies) when the external request
 *                              entered the device mapbio function
 * @param hasDiscardPermit      Whether we got a permit from the discardLimiter
 *                              of the kernel layer
 *
 * @return VDO_SUCCESS or a system error code
 **/
int kvdoLaunchDataKVIOFromBio(KernelLayer *layer,
                              BIO         *bio,
                              Jiffies      arrivalTime,
                              bool         hasDiscardPermit)
  __attribute__((warn_unused_result));

/**
 * Return a batch of DataKVIOs to the pool.
 *
 * <p>Implements BatchProcessorCallback.
 *
 * @param batch    The batch processor
 * @param closure  The kernal layer
 **/
void returnDataKVIOBatchToPool(BatchProcessor *batch, void *closure);

/**
 * Implements DataVIOZeroer.
 *
 * @param dataVIO  The DataVIO to zero
 **/
void kvdoZeroDataVIO(DataVIO *dataVIO);

/**
 * Implements DataCopier.
 *
 * @param source       The DataVIO to copy from
 * @param destination  The DataVIO to copy to
 **/
void kvdoCopyDataVIO(DataVIO *source, DataVIO *destination);

/**
 * Implements DataReader.
 *
 * @param dataVIO  The DataVIO to read
 **/
void kvdoReadDataVIO(DataVIO *dataVIO);

/**
 * Implements DataWriter.
 *
 * @param dataVIO  The DataVIO to write
 **/
void kvdoWriteDataVIO(DataVIO *dataVIO);

/**
 * Implements DataModifier.
 *
 * @param dataVIO  The DataVIO to modify
 **/
void kvdoModifyWriteDataVIO(DataVIO *dataVIO);

/**
 * Implements DataHasher.
 *
 * @param dataVIO  The DataVIO to hash
 **/
void kvdoHashDataVIO(DataVIO *dataVIO);

/**
 * Implements DuplicationChecker.
 *
 * @param dataVIO  The DataVIO containing the block to check
 **/
void kvdoCheckForDuplication(DataVIO *dataVIO);

/**
 * Implements DataAcknowledger.
 *
 * @param dataVIO  The DataVIO to acknowledge
 **/
void kvdoAcknowledgeDataVIO(DataVIO *dataVIO);

/**
 * Implements DataCompressor.
 *
 * @param dataVIO  The DataVIO to compress
 **/
void kvdoCompressDataVIO(DataVIO *dataVIO);

/**
 * Implements AlbireoUpdater.
 *
 * @param dataVIO  The DataVIO which needs to change the entry for its data
 **/
void kvdoUpdateDedupeAdvice(DataVIO *dataVIO);

/**
 * Allocate a buffer pool of DataKVIOs.
 *
 * @param [in]  layer          The layer in which the DataKVIOs will operate
 * @param [in]  poolSize       The number of DataKVIOs in the pool
 * @param [out] bufferPoolPtr  A pointer to hold the new buffer pool
 *
 * @return VDO_SUCCESS or an error
 **/
int makeDataKVIOBufferPool(KernelLayer  *layer,
                           uint32_t      poolSize,
                           BufferPool  **bufferPoolPtr)
  __attribute__((warn_unused_result));

/**
 * Get the state needed to generate UDS metadata from the DataKVIO
 * associated with a DedupeContext.
 *
 * @param context  The DedupeContext
 *
 * @return the advice to store in the UDS index
 **/
DataLocation getDedupeAdvice(const DedupeContext *context)
  __attribute__((warn_unused_result));

/**
 * Set the result of a dedupe query for the DataKVIO associated with a
 * DedupeContext.
 *
 * @param context  The context receiving advice
 * @param advice   A data location at which the chunk named in the context
 *                 might be stored (will be NULL if no advice was found)
 **/
void setDedupeAdvice(DedupeContext *context, const DataLocation *advice);

#endif /* DATA_KVIO_H */
