/*
 * nasd_cheops_mapacc.c
 *
 * RAID mapping module for the CHEOPS client clerk
 *
 * Author: Khalil Amiri, CMU SCS/ECE, July 18 1997
 */
/*
 * Copyright (c) of Carnegie Mellon University, 1996,1997,1998,1999.
 *
 * Permission to reproduce, use, and prepare derivative works of
 * this software for internal use is granted provided the copyright
 * and "No Warranty" statements are included with all reproductions
 * and derivative works. This software may also be redistributed
 * without charge provided that the copyright and "No Warranty"
 * statements are included in all redistributions.
 *
 * NO WARRANTY. THIS SOFTWARE IS FURNISHED ON AN "AS IS" BASIS.
 * CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER
 * EXPRESSED OR IMPLIED AS TO THE MATTER INCLUDING, BUT NOT LIMITED
 * TO: WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY, EXCLUSIVITY
 * OF RESULTS OR RESULTS OBTAINED FROM USE OF THIS SOFTWARE. CARNEGIE
 * MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND WITH RESPECT
 * TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.
 */


#include <nasd/nasd_options.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

#include <nasd/nasd_types.h>
#include <nasd/nasd_cheops_common.h>
#include <nasd/nasd_cheops_types.h>
#include <nasd/nasd_cheops_raidmap.h> 

/* static layout mapping switch table              */
/* new layouts must define the mapping             */
/* and nasd interface operations and add them here */

static NASD_layout_switch_t layoutsw[] = {
  { RAID0,
    "raid level 0",
    _nasd_cheops_raid0_MapDataAccess,
    _nasd_cheops_raid0_MapParityAccess,
    _nasd_cheops_raid0_IdentifyStripe,
    _nasd_cheops_raid0_read,
    _nasd_cheops_raid0_write},

  { NORAID,
    "plain storage",
    _nasd_cheops_raid0_MapDataAccess,
    _nasd_cheops_raid0_MapParityAccess,
    _nasd_cheops_raid0_IdentifyStripe,
    _nasd_cheops_raid0_read,
    _nasd_cheops_raid0_write},
	
  { RAID1,
    "raid level 1",
    _nasd_cheops_raid1_MapDataAccess,
    _nasd_cheops_raid1_MapParityAccess,
    _nasd_cheops_raid1_IdentifyStripe,
    _nasd_cheops_raid1_read,
    _nasd_cheops_raid1_write},

  { RAID5,
    "raid level 5",
    _nasd_cheops_raid5_MapDataAccess,
    _nasd_cheops_raid5_MapParityAccess,
    _nasd_cheops_raid5_IdentifyStripe,
    _nasd_cheops_raid5_read,
    _nasd_cheops_raid5_write},
	
  { LAST_DEFINED_LAYOUT,
    "undefined layout",
    NULL,
    NULL,
    NULL},
};

/* Prototypes */
void print_pda(NASD_pda_t *pda);
void _nasd_cheops_print_asm(nasd_asm_t *a);

/* map logical access to a set of access stripe maps */
int
_nasd_cheops_map_access(
  NASD_layout_t  *LayoutPtr, /* pointer to layout */
  nasd_offset_t   saddr,     /* start address (offset) */
  nasd_len_t      len,       /* length of access */
  nasd_byte_t     *buf,      /* buffer of data */
  nasd_asm_t      *a)        /* access stripe map */ 
{
  NASD_pda_t *npda;
  nasd_len_t access_size_within_su;
  nasd_offset_t stripe_eaddr;
  nasd_offset_t st_laddr;               /* first logical addr in cur s.u. */
  nasd_offset_t laddr;                  /* first logical addr in cur stripe */
  int boff;				/* offset into buffer data */
  nasd_offset_t eaddr;                  /* logical address of last byte */
  nasd_offset_t offset, poffset;
  int col, pcol;
  int first_sid, last_sid;
  int suid, first_suid, last_suid;
  int i, j, si, sui;
  NASD_stripe_access_t *sac, *psac;
  nasd_host_tag_t host_tag;
  struct timeval tv;

  gettimeofday (&tv, (struct timezone *) NULL);
  host_tag.timestamp.tv_sec = tv.tv_sec;
  host_tag.timestamp.tv_usec = tv.tv_usec;
  host_tag.timestamp.client_id = 1; /* get client id */
  host_tag.start_block = saddr / NASD_CHEOPS_BLOCK_SIZE;  /* start block */
  host_tag.len = len /NASD_CHEOPS_BLOCK_SIZE; /* number of blocks */
  a->host_tag = host_tag;

  /* verify args */
  if ( (len <=0) || (saddr <0) ) {
    (cheops_Msg "CHEOPS error: len=%d, start_addr=%" NASD_64s_FMT 
     " ?? bailing out\n",
     len, saddr);
    exit(1);
  } else
    eaddr = saddr + len - 1;

  /* fast path for single drive objects */
  if (LayoutPtr->numCol == 1) {
    NASD_Malloc(sac, sizeof(NASD_stripe_access_t), (NASD_stripe_access_t *));
    if (sac == NULL) {
      (cheops_Msg "malloc failed..\n");
      return ENOMEM;
    } else {
      bzero((char *)sac, sizeof(NASD_stripe_access_t));	  
      sac->next = NULL;
    }
    a->first_stripe_access = sac;	 	 
    npda = &sac->pda_list[0];
    npda->data_obj_id.nasd_identifier =
      LayoutPtr->identifier_list[0].nasd_identifier;
    npda->data_obj_id.disk_identifier =
      LayoutPtr->identifier_list[0].disk_identifier;
    npda->parity_obj_id.nasd_identifier =
      LayoutPtr->identifier_list[0].nasd_identifier;
    npda->parity_obj_id.disk_identifier =
      LayoutPtr->identifier_list[0].disk_identifier;
    npda->col_id = 0;
    npda->pcol_id = 0;
    npda->start_address = saddr;
    npda->len = len;
    npda->buf = buf;
    npda->databuf_offset = 0;	  	  
    return 0;
  }

  /* map the logical access into a set of stripe accesses. Each stripe access
     is in turn converted to physical disk accesses. If the access touches the
     whole stripe, set a flag so we do the full-write optimization */

  first_sid  =  _nasd_cheops_LogicalAddressToStripeID(LayoutPtr, saddr);
  last_sid   =  _nasd_cheops_LogicalAddressToStripeID(LayoutPtr, eaddr);
  first_suid =  _nasd_cheops_LogicalAddressToStripeUnitID(LayoutPtr, saddr);
  last_suid  =  _nasd_cheops_LogicalAddressToStripeUnitID(LayoutPtr, eaddr);

  a->num_su_accessed = 0;
  a->datalen = len;
  a->buf = buf;

  si = 0;	
  boff = 0; 

  /* one stripe at a time */
  for(laddr = saddr; laddr <= eaddr; 
      laddr = _nasd_cheops_LogicalAddressOfNextStripeBoundary(LayoutPtr,
                                                              laddr)) {

    NASD_Malloc(sac, sizeof(NASD_stripe_access_t), (NASD_stripe_access_t *));
    if (sac == NULL) {
      (cheops_Msg "malloc failed ...\n");
      return ENOMEM;
    } else 
      bzero((char *)sac, sizeof(NASD_stripe_access_t));
	  
    sac->next = NULL;
    /* init first stripe access ptr */
    if (si==0)
      a->first_stripe_access = sac;	 	  
    else
      psac->next = sac;

    stripe_eaddr =
      _nasd_cheops_LogicalAddressOfNextStripeBoundary(LayoutPtr, laddr)-1;
    if (eaddr <= stripe_eaddr) 
      stripe_eaddr = eaddr;
    if (stripe_eaddr-laddr+1 == LayoutPtr->dataStripeSize)
      sac->isfull = NASD_TRUE;
    else 
      sac->isfull = NASD_FALSE;

    sui = 0;

    /* one stripe unit at a time */
    for (st_laddr = laddr; st_laddr <= stripe_eaddr; 
         st_laddr =
           _nasd_cheops_LogicalAddressOfNextStripeUnitBoundary(LayoutPtr,
                                                               st_laddr)) {
	  
      suid =  _nasd_cheops_LogicalAddressToStripeUnitID(LayoutPtr, st_laddr);	  
      /* compute the length of the access within the stripe unit (check if
         first or last su)*/
      if ( (suid==first_suid) && (suid==last_suid) )
        access_size_within_su = eaddr - saddr +1;
      else if (suid==first_suid) 
        access_size_within_su = (LayoutPtr->stripeUnitSize -
                                 (st_laddr%LayoutPtr->stripeUnitSize));
      else if (suid==last_suid)
        access_size_within_su = eaddr%LayoutPtr->stripeUnitSize + 1;
      else
        access_size_within_su = LayoutPtr->stripeUnitSize;
	    
      LayoutPtr->map->MapParityAccess(LayoutPtr, st_laddr, &pcol, &poffset);
      LayoutPtr->map->MapDataAccess(LayoutPtr, st_laddr, &col, &offset);

      npda = &sac->pda_list[col];
      npda->data_obj_id.nasd_identifier =
        LayoutPtr->identifier_list[col].nasd_identifier;
      npda->data_obj_id.disk_identifier =
        LayoutPtr->identifier_list[col].disk_identifier;
      npda->parity_obj_id.nasd_identifier =
        LayoutPtr->identifier_list[pcol].nasd_identifier;
      npda->parity_obj_id.disk_identifier =
        LayoutPtr->identifier_list[pcol].disk_identifier;
      npda->col_id = col;
      npda->pcol_id = pcol;
      npda->start_address = offset;
      npda->len = access_size_within_su;
      npda->buf = buf;
      npda->databuf_offset = boff;
      boff += npda->len;
      NASD_ASSERT( (col>=0) && (col < NASD_CHEOPS_MAX_STRIPE_SIZE));
      a->num_su_accessed ++;
      sui++; /* go to next stripe unit */
    }

    psac = sac;
    si++; /* go to next stripe */
  }

  return 0;
}

/* RAID 0 mapping routines */

/* map access from a logical (offset,len) to a set of physical access
   descriptors */
void
_nasd_cheops_raid0_MapDataAccess(
  NASD_layout_t     *LayoutPtr, 
  nasd_offset_t      laddr,
  int               *col,
  nasd_offset_t     *offset)
{
  int SUID;

  SUID = laddr / LayoutPtr->stripeUnitSize;
  *col = SUID % LayoutPtr->numCol;
  *offset = (SUID / LayoutPtr->numCol) * LayoutPtr->stripeUnitSize +
    (laddr % LayoutPtr->stripeUnitSize);
}

void
_nasd_cheops_raid0_MapParityAccess(
  NASD_layout_t    *LayoutPtr, 
  nasd_offset_t     laddr,
  int              *col,
  nasd_offset_t    *offset)
{
  *col = *offset = 0;
}

void
_nasd_cheops_raid0_IdentifyStripe(
  NASD_layout_t      *LayoutPtr, 
  nasd_offset_t       offset,
  nasd_disk_ident_t **diskids, 
  int                *outRow)
{
  return;
}

/* RAID 1 mapping routines */

void
_nasd_cheops_raid1_MapDataAccess(
  NASD_layout_t     *LayoutPtr, 
  nasd_offset_t      laddr,
  int               *col,
  nasd_offset_t     *offset)
{ 
  int SUID = laddr / LayoutPtr->stripeUnitSize;
  int mirrorPair = SUID % (LayoutPtr->numCol / 2);
	
  NASD_ASSERT ( LayoutPtr->numCol % 2 == 0 );
  *col = 2 * mirrorPair;
  *offset = ((SUID / (LayoutPtr->numCol / 2)) * LayoutPtr->stripeUnitSize) 
    + (laddr % LayoutPtr->stripeUnitSize);
}

void
_nasd_cheops_raid1_MapParityAccess(
  NASD_layout_t    *LayoutPtr, 
  nasd_offset_t     laddr,
  int              *col,
  nasd_offset_t    *offset)
{
  int SUID = laddr / LayoutPtr->stripeUnitSize;
  int mirrorPair = SUID % (LayoutPtr->numCol / 2);
	
  *col = 2 * mirrorPair+1;
  *offset = ((SUID / (LayoutPtr->numCol / 2)) * LayoutPtr->stripeUnitSize) 
    + (laddr % LayoutPtr->stripeUnitSize);
}

void
_nasd_cheops_raid1_IdentifyStripe(
  NASD_layout_t      *LayoutPtr, 
  nasd_offset_t       offset,
  nasd_disk_ident_t **diskids,
  int                *outRow)
{
  return;
}

/* RAID level 5 mapping routines */

void
_nasd_cheops_raid5_MapDataAccess(
  NASD_layout_t    *LayoutPtr, 
  nasd_offset_t     laddr,
  int              *col,
  nasd_offset_t    *offset)
{ 
  int SUID = laddr / LayoutPtr->stripeUnitSize;
  *col = (SUID % LayoutPtr->numDataCol);
  *offset = (SUID / (LayoutPtr->numDataCol)) * LayoutPtr->stripeUnitSize+
    (laddr % LayoutPtr->stripeUnitSize);

}

void
_nasd_cheops_raid5_MapParityAccess(
  NASD_layout_t    *LayoutPtr, 
  nasd_offset_t     laddr,
  int              *col,
  nasd_offset_t    *offset)
{
  int SUID = laddr / LayoutPtr->stripeUnitSize;

  /**col = LayoutPtr->numDataCol-(SUID/LayoutPtr->numDataCol)%LayoutPtr->numCol; */
  *col = LayoutPtr->numDataCol;

  *offset =(SUID / (LayoutPtr->numDataCol)) * LayoutPtr->stripeUnitSize +
    (laddr% LayoutPtr->stripeUnitSize);
}

void
_nasd_cheops_raid5_IdentifyStripe(
  NASD_layout_t      *LayoutPtr, 
  nasd_offset_t       offset,
  nasd_disk_ident_t **diskids,
  int                *outRow)
{
  return;
}

/* Utility routines for layout descriptor manipulation */

NASD_layout_switch_t *
_nasd_cheops_get_layout(
  nasd_cheops_raid_level_t      layoutType)
{

  NASD_layout_switch_t *p;

  /* look up the specific layout */
  for (p=&layoutsw[0]; NASD_TRUE; p++)  {
    if (p->layoutType == layoutType)
      break;
    if (p->layoutType == LAST_DEFINED_LAYOUT)
      return(NULL);
  }
  return(p);
}

void
_nasd_cheops_print_asm(
  nasd_asm_t    *a)
{
  NASD_pda_t *p;
  NASD_stripe_access_t *sac;
  int i=0, j=0;

  if (a==NULL) {
    (cheops_Msg "null asm pointer\n");
    return;
  }	

  sac = a->first_stripe_access;
  while (sac) {
    if (sac->isfull)
      (cheops_Msg "asm: full access \n"); 
    else 
      (cheops_Msg "asm:  partial access \n");

    (cheops_Msg "asm host tag: start_block = %d\n", a->host_tag.start_block);
    (cheops_Msg "asm host tag: number of blocks = %d\n", a->host_tag.len);
    (cheops_Msg "num sus: %d\n", a->num_su_accessed);
    for (j=0; j< NASD_CHEOPS_MAX_STRIPE_SIZE; j++) {
      p = &sac->pda_list[j];
      if (p->buf != NULL)
	print_pda(p);
    }
    sac = sac->next;
  }
}

void
print_pda(                              /* XXX namespace purity */
  NASD_pda_t    *p)
{
  (cheops_Msg "pda: \t(ni,di)= 0x%" NASD_ID_FMT ",%d",
   p->data_obj_id.nasd_identifier, p->data_obj_id.disk_identifier);
  (cheops_Msg "\toffset: %d", p->start_address);
  (cheops_Msg "\tsize: %d",p->len);
  (cheops_Msg "\tboff: %d",p->databuf_offset);
  (cheops_Msg "\t pcol %d, po = 0x%"NASD_ID_FMT",%d\n",
   p->pcol_id, p->parity_obj_id.nasd_identifier, 
   p->parity_obj_id.disk_identifier);
}

/* Local Variables:  */
/* indent-tabs-mode: nil */
/* tab-width: 2 */
/* End: */
