/*
 * 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/base/recoveryJournalEntry.h#1 $
 */

#ifndef RECOVERY_JOURNAL_ENTRY_H
#define RECOVERY_JOURNAL_ENTRY_H

#include "numeric.h"

#include "blockMapEntry.h"
#include "journalPoint.h"
#include "types.h"

/**
 * A recovery journal entry stores two physical locations: a data location
 * that is the value of a single mapping in the block map tree, and the
 * location of the block map page and and slot that is either acquiring or
 * releasing a reference to the data location. The journal entry also stores
 * an operation code that says whether the reference is being acquired (an
 * increment) or released (a decrement), and whether the mapping is for a
 * logical block or for the block map tree itself.
 **/
typedef struct {
  BlockMapSlot     slot;
  DataLocation     mapping;
  JournalOperation operation;
} RecoveryJournalEntry;

/** The packed, on-disk representation of a recovery journal entry. */
typedef union __attribute__((packed)) {
  struct __attribute__((packed)) {
    /**
     * In little-endian bit order:
     * Bits 15..12:  The four highest bits of the 36-bit physical block number
     *               of the block map tree page
     * Bits 11..2:   The 10-bit block map page slot number
     * Bits 1..0:    The 2-bit JournalOperation of the entry
     **/
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
    unsigned operation     : 2;
    unsigned slotLow       : 6;
    unsigned slotHigh      : 4;
    unsigned pbnHighNibble : 4;
#else
    unsigned slotLow       : 6;
    unsigned operation     : 2;
    unsigned pbnHighNibble : 4;
    unsigned slotHigh      : 4;
#endif

    /**
     * Bits 47..16:  The 32 low-order bits of the block map page PBN,
     *               in little-endian byte order
     **/
    byte pbnLowWord[4];

    /**
     * Bits 87..48:  The five-byte block map entry encoding the location that
     *               was or will be stored in the block map page slot
     **/
    BlockMapEntry blockMapEntry;
  } fields;

  // A raw view of the packed encoding.
  uint8_t raw[11];

#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
  // This view is only valid on little-endian machines and is only present for
  // ease of directly examining packed entries in GDB.
  struct __attribute__((packed)) {
    unsigned      operation     : 2;
    unsigned      slot          : 10;
    unsigned      pbnHighNibble : 4;
    uint32_t      pbnLowWord;
    BlockMapEntry blockMapEntry;
  } littleEndian;
#endif
} PackedRecoveryJournalEntry;

/**
 * Return the packed, on-disk representation of a recovery journal entry.
 *
 * @param entry   The journal entry to pack
 *
 * @return  The packed representation of the journal entry
 **/
static inline PackedRecoveryJournalEntry
packRecoveryJournalEntry(const RecoveryJournalEntry *entry)
{
  PackedRecoveryJournalEntry packed = {
    .fields = {
      .operation     = entry->operation,
      .slotLow       = entry->slot.slot & 0x3F,
      .slotHigh      = (entry->slot.slot >> 6) & 0x0F,
      .pbnHighNibble = (entry->slot.pbn >> 32) & 0x0F,
      .blockMapEntry = packPBN(entry->mapping.pbn, entry->mapping.state),
    }
  };
  storeUInt32LE(packed.fields.pbnLowWord, entry->slot.pbn & UINT_MAX);
  return packed;
}

/**
 * Unpack the on-disk representation of a recovery journal entry.
 *
 * @param entry  The recovery journal entry to unpack
 *
 * @return  The unpacked entry
 **/
static inline RecoveryJournalEntry
unpackRecoveryJournalEntry(const PackedRecoveryJournalEntry *entry)
{
  PhysicalBlockNumber low32 = getUInt32LE(entry->fields.pbnLowWord);
  PhysicalBlockNumber high4 = entry->fields.pbnHighNibble;
  return (RecoveryJournalEntry) {
    .operation = entry->fields.operation,
    .slot      = {
      .pbn  = ((high4 << 32) | low32),
      .slot = (entry->fields.slotLow | (entry->fields.slotHigh << 6)),
    },
    .mapping = unpackBlockMapEntry(&entry->fields.blockMapEntry),
  };
}

#endif // RECOVERY_JOURNAL_ENTRY_H
