
/*
   raid0.c : Multiple Devices driver for Linux
             Copyright (C) 1994-96 Marc ZYNGIER
	     <zyngier@ufr-info-p7.ibp.fr> or
	     <maz@gloups.fdn.fr>

   RAID-0 management functions.

   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, or (at your option)
   any later version.
   
   You should have received a copy of the GNU General Public License
   (for example /usr/src/linux/COPYING); if not, write to the Free
   Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  
*/

#include <linux/module.h>
#include <linux/md.h>
#include <linux/raid0.h>
#include <linux/malloc.h>

#define MAJOR_NR MD_MAJOR
#define MD_DRIVER
#define MD_PERSONALITY

static void create_strip_zones (int minor, struct md_dev *mddev)
{
  int i, j, c=0;
  int current_offset=0;
  struct real_dev *smallest_by_zone;
  struct raid0_data *data=(struct raid0_data *) mddev->private;
  
  data->nr_strip_zones=1;
  
  for (i=1; i<mddev->nb_dev; i++)
  {
    for (j=0; j<i; j++)
      if (devices[minor][i].size==devices[minor][j].size)
      {
	c=1;
	break;
      }

    if (!c)
      data->nr_strip_zones++;

    c=0;
  }

  data->strip_zone=kmalloc (sizeof(struct strip_zone)*data->nr_strip_zones,
			      GFP_KERNEL);

  data->smallest=NULL;
  
  for (i=0; i<data->nr_strip_zones; i++)
  {
    data->strip_zone[i].dev_offset=current_offset;
    smallest_by_zone=NULL;
    c=0;

    for (j=0; j<mddev->nb_dev; j++)
      if (devices[minor][j].size>current_offset)
      {
	data->strip_zone[i].dev[c++]=devices[minor]+j;
	if (!smallest_by_zone ||
	    smallest_by_zone->size > devices[minor][j].size)
	  smallest_by_zone=devices[minor]+j;
      }

    data->strip_zone[i].nb_dev=c;
    data->strip_zone[i].size=(smallest_by_zone->size-current_offset)*c;

    if (!data->smallest ||
	data->smallest->size > data->strip_zone[i].size)
      data->smallest=data->strip_zone+i;

    data->strip_zone[i].zone_offset=i ? (data->strip_zone[i-1].zone_offset+
					   data->strip_zone[i-1].size) : 0;
    current_offset=smallest_by_zone->size;
  }
}

static int raid0_run (int minor, struct md_dev *mddev)
{
  int cur=0, i=0, size, zone0_size, nb_zone, min;
  struct raid0_data *data;

  min=1 << FACTOR_SHIFT(FACTOR(mddev));

  for (i=0; i<mddev->nb_dev; i++)
    if (devices[minor][i].size<min)
    {
      printk ("Cannot use %dk chunks on dev %s\n", min,
	      partition_name (devices[minor][i].dev));
      return -EINVAL;
    }
  
  MOD_INC_USE_COUNT;
  
  /* Resize devices according to the factor */
  md_size[minor]=0;
  
  for (i=0; i<mddev->nb_dev; i++)
  {
    devices[minor][i].size &= ~((1 << FACTOR_SHIFT(FACTOR(mddev))) - 1);
    md_size[minor] += devices[minor][i].size;
  }

  mddev->private=kmalloc (sizeof (struct raid0_data), GFP_KERNEL);
  data=(struct raid0_data *) mddev->private;
  
  create_strip_zones (minor, mddev);

  nb_zone=data->nr_zones=
    md_size[minor]/data->smallest->size +
    (md_size[minor]%data->smallest->size ? 1 : 0);
  
  data->hash_table=kmalloc (sizeof (struct raid0_hash)*nb_zone, GFP_KERNEL);

  size=data->strip_zone[cur].size;

  i=0;
  while (cur<data->nr_strip_zones)
  {
    data->hash_table[i].zone0=data->strip_zone+cur;

    if (size>=data->smallest->size)/* If we completely fill the slot */
    {
      data->hash_table[i++].zone1=NULL;
      size-=data->smallest->size;

      if (!size)
      {
	if (++cur==data->nr_strip_zones) continue;
	size=data->strip_zone[cur].size;
      }

      continue;
    }

    if (++cur==data->nr_strip_zones) /* Last dev, set unit1 as NULL */
    {
      data->hash_table[i].zone1=NULL;
      continue;
    }

    zone0_size=size;		/* Here, we use a 2nd dev to fill the slot */
    size=data->strip_zone[cur].size;
    data->hash_table[i++].zone1=data->strip_zone+cur;
    size-=(data->smallest->size - zone0_size);
  }

  return (0);
}


static int raid0_stop (int minor, struct md_dev *mddev)
{
  struct raid0_data *data=(struct raid0_data *) mddev->private;

  kfree (data->hash_table);
  kfree (data->strip_zone);
  kfree (data);

  MOD_DEC_USE_COUNT;
  return 0;
}

/*
 * FIXME - We assume some things here :
 * - requested buffers NEVER bigger than chunk size,
 * - requested buffers NEVER cross stripes limits.
 * Of course, those facts may not be valid anymore (and surely won't...)
 * Hey guys, there's some work out there ;-)
 */
static int raid0_map (struct md_dev *mddev, kdev_t *rdev,
		      unsigned long *rsector, unsigned long size)
{
  struct raid0_data *data=(struct raid0_data *) mddev->private;
  static struct raid0_hash *hash;
  struct strip_zone *zone;
  struct real_dev *tmp_dev;
  int blk_in_chunk, factor, chunk, chunk_size;
  long block, rblock;

  factor=FACTOR(mddev);
  chunk_size=(1UL << FACTOR_SHIFT(factor));
  block=*rsector >> 1;
  hash=data->hash_table+(block/data->smallest->size);

  /* Sanity check */
  if ((chunk_size*2)<(*rsector % (chunk_size*2))+size)
  {
    printk ("raid0_convert : can't convert block across chunks or bigger than %dk %ld %ld\n", chunk_size, *rsector, size);
    return (-1);
  }
  
  if (block >= (hash->zone0->size +
		hash->zone0->zone_offset))
  {
    if (!hash->zone1)
    {
      printk ("raid0_convert : hash->zone1==NULL for block %ld\n", block);
      return (-1);
    }
    
    zone=hash->zone1;
  }
  else
    zone=hash->zone0;
    
  blk_in_chunk=block & (chunk_size -1);
  chunk=(block - zone->zone_offset) / (zone->nb_dev<<FACTOR_SHIFT(factor));
  tmp_dev=zone->dev[(block >> FACTOR_SHIFT(factor)) % zone->nb_dev];
  rblock=(chunk << FACTOR_SHIFT(factor)) + blk_in_chunk + zone->dev_offset;
  
  *rdev=tmp_dev->dev;
  *rsector=rblock<<1;

  return (0);
}

			   
static int raid0_status (char *page, int minor, struct md_dev *mddev)
{
  int sz=0;
#undef MD_DEBUG
#ifdef MD_DEBUG
  int j, k;
  struct raid0_data *data=(struct raid0_data *) mddev->private;
  
  sz+=sprintf (page+sz, "      ");
  for (j=0; j<data->nr_zones; j++)
  {
    sz+=sprintf (page+sz, "[z%d",
		 data->hash_table[j].zone0-data->strip_zone);
    if (data->hash_table[j].zone1)
      sz+=sprintf (page+sz, "/z%d] ",
		   data->hash_table[j].zone1-data->strip_zone);
    else
      sz+=sprintf (page+sz, "] ");
  }
  
  sz+=sprintf (page+sz, "\n");
  
  for (j=0; j<data->nr_strip_zones; j++)
  {
    sz+=sprintf (page+sz, "      z%d=[", j);
    for (k=0; k<data->strip_zone[j].nb_dev; k++)
      sz+=sprintf (page+sz, "%s/",
		   partition_name(data->strip_zone[j].dev[k]->dev));
    sz--;
    sz+=sprintf (page+sz, "] zo=%d do=%d s=%d\n",
		 data->strip_zone[j].zone_offset,
		 data->strip_zone[j].dev_offset,
		 data->strip_zone[j].size);
  }
#endif
  return sz;
}


static struct md_personality raid0_personality=
{
  "raid0",
  raid0_map,
  raid0_run,
  raid0_stop,
  raid0_status,
  NULL,				/* no ioctls */
  0
};


#ifndef MODULE

void raid0_init (void)
{
  register_md_personality (RAID0, &raid0_personality);
}

#else

int init_module (void)
{
  return (register_md_personality (RAID0, &raid0_personality));
}

void cleanup_module (void)
{
  unregister_md_personality (RAID0);
}

#endif
