/*
 * Copyright Red Hat
 *
 * 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/base/slab.h#9 $
 */

#ifndef VDO_SLAB_H
#define VDO_SLAB_H

#include "permassert.h"

#include "adminState.h"
#include "fixedLayout.h"
#include "journalPoint.h"
#include "referenceOperation.h"
#include "ringNode.h"
#include "types.h"

typedef uint32_t SlabBlockNumber;

typedef enum {
  SLAB_REBUILT = 0,
  SLAB_REPLAYING,
  SLAB_REQUIRES_SCRUBBING,
  SLAB_REQUIRES_HIGH_PRIORITY_SCRUBBING,
  SLAB_REBUILDING,
} SlabRebuildStatus;

/**
 * This is the type declaration for the Slab type. (The struct tag is named
 * vdoSlab to avoid a conflict with the linux kernel type). A Slab currently
 * consists of a run of 2^23 data blocks, but that will soon change to
 * dedicate a small number of those blocks for metadata storage for the
 * reference counts and slab journal for the slab.
 **/
struct vdoSlab {
  /** A RingNode to queue this slab in a BlockAllocator ring */
  RingNode             ringNode;

  /** The BlockAllocator that owns this slab */
  BlockAllocator      *allocator;

  /** The reference counts for the data blocks in this slab */
  RefCounts           *referenceCounts;
  /** The journal for this slab */
  SlabJournal         *journal;

  /** The slab number of this slab */
  SlabCount            slabNumber;
  /** The offset in the allocator partition of the first block in this slab */
  PhysicalBlockNumber  start;
  /** The offset of the first block past the end of this slab */
  PhysicalBlockNumber  end;
  /** The starting translated PBN of the slab journal */
  PhysicalBlockNumber  journalOrigin;
  /** The starting translated PBN of the reference counts */
  PhysicalBlockNumber  refCountsOrigin;

  /** The administrative state of the slab */
  AdminState           state;
  /** The status of the slab */
  SlabRebuildStatus    status;
  /** Whether the slab was ever queued for scrubbing */
  bool                 wasQueuedForScrubbing;

  /** The priority at which this slab has been queued for allocation */
  uint8_t              priority;
};

/**
 * Measure and initialize the configuration to use for each slab.
 *
 * @param [in]  slabSize           The number of blocks per slab
 * @param [in]  slabJournalBlocks  The number of blocks for the slab journal
 * @param [out] slabConfig         The slab configuration to initialize
 *
 * @return VDO_SUCCESS or an error code
 **/
int configureSlab(BlockCount  slabSize,
                  BlockCount  slabJournalBlocks,
                  SlabConfig *slabConfig)
  __attribute__((warn_unused_result));

/**
 * Convert a Slab's RingNode back to the Slab.
 *
 * @param ringNode  The RingNode to convert
 *
 * @return  The RingNode as a Slab
 **/
static inline Slab *slabFromRingNode(RingNode *ringNode)
{
  STATIC_ASSERT(offsetof(Slab, ringNode) == 0);
  return (Slab *) ringNode;
}

/**
 * Get the physical block number of the start of the slab journal
 * relative to the start block allocator partition.
 *
 * @param slabConfig  The slab configuration of the VDO
 * @param origin      The first block of the slab
 **/
__attribute__((warn_unused_result))
PhysicalBlockNumber getSlabJournalStartBlock(const SlabConfig    *slabConfig,
                                             PhysicalBlockNumber  origin);

/**
 * Construct a new, empty slab.
 *
 * @param [in]  slabOrigin       The physical block number within the block
 *                               allocator partition of the first block in the
 *                               slab
 * @param [in]  allocator        The block allocator to which the slab belongs
 * @param [in]  translation      The translation from the depot's partition to
 *                               the physical storage
 * @param [in]  recoveryJournal  The recovery journal of the VDO
 * @param [in]  slabNumber       The slab number of the slab
 * @param [in]  isNew            <code>true</code> if this slab is being
 *                               allocated as part of a resize
 * @param [out] slabPtr          A pointer to receive the new slab
 *
 * @return VDO_SUCCESS or an error code
 **/
int makeSlab(PhysicalBlockNumber   slabOrigin,
             BlockAllocator       *allocator,
             PhysicalBlockNumber   translation,
             RecoveryJournal      *recoveryJournal,
             SlabCount             slabNumber,
             bool                  isNew,
             Slab                **slabPtr)
  __attribute__((warn_unused_result));

/**
 * Allocate the reference counts for a slab.
 *
 * @param slab  The slab whose reference counts need allocation.
 *
 * @return VDO_SUCCESS or an error code
 **/
int allocateRefCountsForSlab(Slab *slab)
  __attribute__((warn_unused_result));

/**
 * Destroy a slab and null out the reference to it.
 *
 * @param slabPtr  The reference to the slab to destroy
 **/
void freeSlab(Slab **slabPtr);

/**
 * Get the physical zone number of a slab.
 *
 * @param slab  The slab
 *
 * @return The number of the slab's physical zone
 **/
ZoneCount getSlabZoneNumber(Slab *slab)
  __attribute__((warn_unused_result));

/**
 * Check whether a slab is unrecovered.
 *
 * @param slab  The slab to check
 *
 * @return <code>true</code> if the slab is unrecovered
 **/
static inline bool isUnrecoveredSlab(const Slab *slab)
{
  return (slab->status != SLAB_REBUILT);
}

/**
 * Check whether a slab is being replayed into.
 *
 * @param slab  The slab to check
 *
 * @return <code>true</code> if the slab is replaying
 **/
static inline bool isReplayingSlab(const Slab *slab)
{
  return (slab->status == SLAB_REPLAYING);
}

/**
 * Check whether a slab is being rebuilt.
 *
 * @param slab  The slab to check
 *
 * @return <code>true</code> if the slab is being rebuilt
 **/
static inline bool slabIsRebuilding(const Slab *slab)
{
  return (slab->status == SLAB_REBUILDING);
}

/**
 * Mark a slab as replaying, during offline recovery.
 *
 * @param slab  The slab to mark
 **/
void markSlabReplaying(Slab *slab);

/**
 * Mark a slab as unrecovered, for online recovery.
 *
 * @param slab  The slab to mark
 **/
void markSlabUnrecovered(Slab *slab);

/**
 * Get the current number of free blocks in a slab.
 *
 * @param slab  The slab to query
 *
 * @return the number of free blocks in the slab
 **/
BlockCount getSlabFreeBlockCount(const Slab *slab)
  __attribute__((warn_unused_result));

/**
 * Increment or decrement the reference count of a block in a slab.
 *
 * @param slab          The slab containing the block (may be NULL when
 *                      referencing the zero block)
 * @param journalPoint  The slab journal entry corresponding to this change
 * @param operation     The operation to perform on the reference count
 *
 * @return VDO_SUCCESS or an error
 **/
int modifySlabReferenceCount(Slab               *slab,
                             const JournalPoint *journalPoint,
                             ReferenceOperation  operation)
  __attribute__((warn_unused_result));

/**
 * Acquire a provisional reference on behalf of a PBN lock if the block it
 * locks is unreferenced.
 *
 * @param slab  The slab which contains the block
 * @param pbn   The physical block to reference
 * @param lock  The lock
 *
 * @return VDO_SUCCESS or an error
 **/
int acquireProvisionalReference(Slab                *slab,
                                PhysicalBlockNumber  pbn,
                                PBNLock             *lock)
  __attribute__((warn_unused_result));

/**
 * Determine the index within the slab of a particular physical block number.
 *
 * @param [in]  slab                    The slab
 * @param [in]  physicalBlockNumber     The physical block number
 * @param [out] slabBlockNumberPtr      A pointer to the slab block number
 *
 * @return VDO_SUCCESS or an error code
 **/
int slabBlockNumberFromPBN(Slab                *slab,
                           PhysicalBlockNumber  physicalBlockNumber,
                           SlabBlockNumber     *slabBlockNumberPtr)
  __attribute__((warn_unused_result));

/**
 * Check whether the reference counts for a given rebuilt slab should be saved.
 * Implements SlabStatusChecker.
 *
 * @param slab  The slab to check
 *
 * @return true if the slab should be saved
 **/
bool shouldSaveFullyBuiltSlab(const Slab *slab)
  __attribute__((warn_unused_result));

/**
 * Start an administrative operation on a slab.
 *
 * @param slab       The slab to load
 * @param operation  The type of load to perform
 * @param parent     The object to notify when the operation is complete
 **/
void startSlabAction(Slab           *slab,
                     AdminStateCode  operation,
                     VDOCompletion  *parent);

/**
 * Inform a slab that its journal has been loaded.
 *
 * @param slab    The slab whose journal has been loaded
 * @param result  The result of the load operation
 **/
void notifySlabJournalIsLoaded(Slab *slab, int result);

/**
 * Check whether a slab is open, i.e. is neither quiescent nor quiescing.
 *
 * @param slab  The slab to check
 *
 * @return <code>true</code> if the slab is open
 **/
bool isSlabOpen(Slab *slab)
  __attribute__((warn_unused_result));

/**
 * Check whether a slab is currently draining.
 *
 * @param slab  The slab to check
 *
 * @return <code>true</code> if the slab is performing a drain operation
 **/
bool isSlabDraining(Slab *slab)
  __attribute__((warn_unused_result));

/**
 * Check whether a slab has drained, and if so, send a notification thereof.
 *
 * @param slab  The slab to check
 **/
void checkIfSlabDrained(Slab *slab);

/**
 * Inform a slab that its journal has finished draining.
 *
 * @param slab    The slab whose journal has been drained
 * @param result  The result of the drain operation
 **/
void notifySlabJournalIsDrained(Slab *slab, int result);

/**
 * Inform a slab that its RefCounts have finished draining.
 *
 * @param slab    The slab whose RefCounts has been drained
 * @param result  The result of the drain operation
 **/
void notifyRefCountsAreDrained(Slab *slab, int result);

/**
 * Check whether a slab is currently resuming.
 *
 * @param slab  The slab to check
 *
 * @return <code>true</code> if the slab is performing a resume operation
 **/
bool isSlabResuming(Slab *slab)
  __attribute__((warn_unused_result));

/**
 * Finish scrubbing a slab now that it has been rebuilt by updating its status,
 * queueing it for allocation, and reopening its journal.
 *
 * @param slab  The slab whose reference counts have been rebuilt from its
 *              journal
 **/
void finishScrubbingSlab(Slab *slab);

/**
 * Dump information about a slab to the log for debugging.
 *
 * @param slab   The slab to dump
 **/
void dumpSlab(const Slab *slab);

#endif // VDO_SLAB_H
