diff -u --recursive --new-file /usr/src/linux/MAGIC linux/MAGIC
--- /usr/src/linux/MAGIC	Mon Feb 27 09:35:08 1995
+++ linux/MAGIC	Mon Mar  6 07:31:07 1995
@@ -53,6 +53,7 @@
 0x03	hdreg.h
 0x06	lp.h
 0x12	fs.h
+0x1e	md.h
 'C'	soundcard.h
 'K'	kd.h
 'M'	soundcard.h
diff -u --recursive --new-file /usr/src/linux/arch/i386/config.in linux/arch/i386/config.in
--- /usr/src/linux/arch/i386/config.in	Mon Feb 27 09:42:01 1995
+++ linux/arch/i386/config.in	Mon Mar  6 07:31:07 1995
@@ -22,6 +22,7 @@
 fi
 
 bool 'XT harddisk support' CONFIG_BLK_DEV_XD n
+bool 'Multiple devices driver support' CONFIG_BLK_DEV_MD n
 bool 'Networking support' CONFIG_NET y
 bool 'Limit memory to low 16MB' CONFIG_MAX_16M n
 bool 'PCI bios support' CONFIG_PCI n
diff -u --recursive --new-file /usr/src/linux/drivers/block/Makefile linux/drivers/block/Makefile
--- /usr/src/linux/drivers/block/Makefile	Mon Feb 27 09:41:43 1995
+++ linux/drivers/block/Makefile	Mon Mar  6 07:31:07 1995
@@ -84,6 +84,11 @@
 SRCS := $(SRCS) sonycd535.c
 endif
 
+ifdef CONFIG_BLK_DEV_MD
+OBJS := $(OBJS) md.o
+SRCS := $(SRCS) md.c
+endif
+
 all: block.a
 
 block.a: $(OBJS)
diff -u --recursive --new-file /usr/src/linux/drivers/block/blk.h linux/drivers/block/blk.h
--- /usr/src/linux/drivers/block/blk.h	Mon Feb 27 09:41:44 1995
+++ linux/drivers/block/blk.h	Mon Mar  6 07:31:07 1995
@@ -59,6 +59,9 @@
 #ifdef CONFIG_SBPCD
 extern unsigned long sbpcd_init(unsigned long, unsigned long);
 #endif CONFIG_SBPCD
+#ifdef CONFIG_BLK_DEV_MD
+extern unsigned long md_init(unsigned long mem_start, unsigned long mem_end);
+#endif
 extern void set_device_ro(int dev,int flag);
 
 extern void floppy_init(void);
@@ -221,6 +224,14 @@
 
 #define DEVICE_NAME "Matsushita CD-ROM controller #4"
 #define DEVICE_REQUEST do_sbpcd4_request
+#define DEVICE_NR(device) (MINOR(device))
+#define DEVICE_ON(device)
+#define DEVICE_OFF(device)
+
+#elif  (MAJOR_NR == MD_MAJOR)
+
+#define DEVICE_NAME "Multiple devices driver"
+#define DEVICE_REQUEST do_md_request
 #define DEVICE_NR(device) (MINOR(device))
 #define DEVICE_ON(device)
 #define DEVICE_OFF(device)
diff -u --recursive --new-file /usr/src/linux/drivers/block/ll_rw_blk.c linux/drivers/block/ll_rw_blk.c
--- /usr/src/linux/drivers/block/ll_rw_blk.c	Mon Feb 27 09:42:02 1995
+++ linux/drivers/block/ll_rw_blk.c	Wed Mar  8 19:59:15 1995
@@ -21,6 +21,10 @@
 #include <asm/io.h>
 #include "blk.h"
 
+#ifdef CONFIG_BLK_DEV_MD
+#include <linux/md.h>
+#endif
+
 /*
  * The request-struct contains all necessary data
  * to load a nr of sectors into memory
@@ -224,10 +228,26 @@
 	unsigned int sector, count;
 	struct request * req;
 	int rw_ahead, max_req;
+	dev_t rdev;
 
 /* WRITEA/READA is special case - it is not really needed, so if the */
 /* buffer is locked, we just forget about it, else it's a normal read */
 	rw_ahead = (rw == READA || rw == WRITEA);
+
+	count = bh->b_size >> 9;
+#ifdef CONFIG_BLK_DEV_MD
+	if (MAJOR(bh->b_dev)==MD_MAJOR)
+	  md_to_real (bh, 0, 0, &rdev, &sector);
+	else
+	{
+	  rdev=bh->b_dev;
+	  sector = bh->b_blocknr * count;
+	}
+#else
+	rdev=bh->b_dev;
+	sector = bh->b_blocknr * count;
+#endif
+
 	if (rw_ahead) {
 		if (bh->b_lock)
 			return;
@@ -240,10 +260,9 @@
 		printk("Bad block dev command, must be R/W/RA/WA\n");
 		return;
 	}
-	count = bh->b_size >> 9;
-	sector = bh->b_blocknr * count;
+
 	if (blk_size[major])
-		if (blk_size[major][MINOR(bh->b_dev)] < (sector + count)>>1) {
+		if (blk_size[major][MINOR(rdev)] < (sector + count)>>1) {
 			bh->b_dirt = bh->b_uptodate = 0;
 			bh->b_req = 0;
 			return;
@@ -287,7 +306,7 @@
 #endif CONFIG_BLK_DEV_HD
 			req = req->next;
 		while (req) {
-			if (req->dev == bh->b_dev &&
+			if (req->dev == rdev &&
 			    !req->sem &&
 			    req->cmd == rw &&
 			    req->sector + req->nr_sectors == sector &&
@@ -301,7 +320,7 @@
 				return;
 			}
 
-			if (req->dev == bh->b_dev &&
+			if (req->dev == rdev &&
 			    !req->sem &&
 			    req->cmd == rw &&
 			    req->sector - count == sector &&
@@ -323,7 +342,7 @@
 	}
 
 /* find an unused request. */
-	req = get_request(max_req, bh->b_dev);
+	req = get_request(max_req, rdev);
 
 /* if no request available: if rw_ahead, forget it; otherwise try again. */
 	if (! req) {
@@ -359,24 +378,38 @@
 	struct request * req;
 	unsigned int major = MAJOR(dev);
 	struct semaphore sem = MUTEX_LOCKED;
+	int sector = page<<3;
+	dev_t rdev=(dev_t) dev;
+
+#ifdef CONFIG_BLK_DEV_MD
+	if (major==MD_MAJOR)
+	{
+	  if (md_to_real (NULL, rdev, sector, &rdev, &sector))
+	  {
+	    printk ("Oops ! Bad locate_dev in ll_rw_page. rdev=%04x, page=%d\n", rdev, page);
+	    return;
+	  }
+	  major=MAJOR(rdev);
+	}
+#endif
 
 	if (major >= MAX_BLKDEV || !(blk_dev[major].request_fn)) {
-		printk("Trying to read nonexistent block-device %04x (%d)\n",dev,page*8);
+		printk("Trying to read nonexistent block-device %04x (%d)\n",rdev,page*8);
 		return;
 	}
 	if (rw!=READ && rw!=WRITE)
 		panic("Bad block dev command, must be R/W");
-	if (rw == WRITE && is_read_only(dev)) {
-		printk("Can't page to read-only device 0x%X\n",dev);
+	if (rw == WRITE && is_read_only(rdev)) {
+		printk("Can't page to read-only device 0x%X\n",rdev);
 		return;
 	}
 	cli();
-	req = get_request_wait(NR_REQUEST, dev);
+	req = get_request_wait(NR_REQUEST, rdev);
 	sti();
 /* fill up the request-info, and add it to the queue */
 	req->cmd = rw;
 	req->errors = 0;
-	req->sector = page<<3;
+	req->sector = sector;
 	req->nr_sectors = 8;
 	req->current_nr_sectors = 8;
 	req->buffer = buffer;
@@ -394,8 +427,15 @@
 void ll_rw_block(int rw, int nr, struct buffer_head * bh[])
 {
 	unsigned int major;
+#ifdef CONFIG_BLK_DEV_MD
+	int nr_plugged=0;
+	struct blk_dev_struct *plugged[MAX_REAL];
+	struct request plug[MAX_REAL];
+	dev_t rdev;
+#else
 	struct request plug;
 	int plugged;
+#endif CONFIG_BLK_DEV_MD
 	int correct_size;
 	struct blk_dev_struct * dev;
 	int i;
@@ -445,6 +485,53 @@
 	   from starting until we have shoved all of the blocks into the
 	   queue, and then we let it rip.  */
 
+#ifdef CONFIG_BLK_DEV_MD
+	for (i=0; i<nr; i++)
+	{
+	  int maj;
+	  
+	  if (major==MD_MAJOR)
+	  {
+	    if (md_to_real (bh[i], 0, 0, &rdev, NULL))
+	    {
+	      printk ("device md%d not ready, givin' up !\n", MINOR(bh[i]->b_dev));
+	      goto sorry;
+	    }
+	  }
+	  else
+	    rdev=bh[i]->b_dev;
+	  maj=MAJOR(rdev);
+
+	  cli();
+	  if (!(blk_dev+maj)->current_request && nr>1)
+	  {
+	    plugged[nr_plugged]=blk_dev+maj;
+	    plugged[nr_plugged]->current_request=plug+nr_plugged;
+	    plug[nr_plugged].dev=-1;
+	    plug[nr_plugged++].next=NULL;
+	  }
+	  sti();
+
+	  if (bh[i])
+	  {
+	    bh[i]->b_req=1;
+	    make_request (maj, rw, bh[i]);
+	    if (rw == READ || rw == READA)
+	      kstat.pgpgin++;
+	    else
+	      kstat.pgpgout++;
+	  }
+	}
+
+	for (i=0; i<nr_plugged; i++)
+	{
+	  cli();
+	  plugged[i]->current_request=plug[i].next;
+	  (plugged[i]->request_fn)();
+	  sti();
+	}
+	return;
+#else
 	plugged = 0;
 	cli();
 	if (!dev->current_request && nr > 1) {
@@ -471,6 +558,7 @@
 		sti();
 	}
 	return;
+#endif CONFIG_BLK_DEV_MD
 
       sorry:
 	for (i = 0; i < nr; i++) {
@@ -482,8 +570,9 @@
 
 void ll_rw_swap_file(int rw, int dev, unsigned int *b, int nb, char *buf)
 {
-	int i;
-	int buffersize;
+	int i, major_is_md;
+	dev_t rdev;
+	int buffersize, sector;
 	struct request * req;
 	unsigned int major = MAJOR(dev);
 	struct semaphore sem = MUTEX_LOCKED;
@@ -503,15 +592,29 @@
 	}
 	
 	buffersize = PAGE_SIZE / nb;
+	major_is_md=(major==MD_MAJOR);
+	rdev=dev;
 
 	for (i=0; i<nb; i++, buf += buffersize)
 	{
+	        sector=(b[i]*buffersize)>>9;
+#ifdef CONFIG_BLK_DEV_MD
+	        if (major_is_md)
+		{
+		  if (md_to_real (NULL, dev, sector, &rdev, &sector))
+		  {
+		    printk ("Oops ! Bad locate_dev in ll_rw_swap_file. dev=%04x, sector=%d\n", dev, sector);
+		    return;
+		  }
+		  major=MAJOR(rdev);
+		}
+#endif
 		cli();
-		req = get_request_wait(NR_REQUEST, dev);
+		req = get_request_wait(NR_REQUEST, rdev);
 		sti();
 		req->cmd = rw;
 		req->errors = 0;
-		req->sector = (b[i] * buffersize) >> 9;
+		req->sector = sector;
 		req->nr_sectors = buffersize >> 9;
 		req->current_nr_sectors = buffersize >> 9;
 		req->buffer = buf;
@@ -562,6 +665,9 @@
 #ifdef CONFIG_SBPCD
 	mem_start = sbpcd_init(mem_start, mem_end);
 #endif CONFIG_SBPCD
+#ifdef CONFIG_BLK_DEV_MD
+	mem_start = md_init(mem_start, mem_end);
+#endif CONFIG_BLK_DEV_MD
 	if (ramdisk_size)
 		mem_start += rd_init(mem_start, ramdisk_size*1024);
 	return mem_start;
diff -u --recursive --new-file /usr/src/linux/drivers/block/md.c linux/drivers/block/md.c
--- /usr/src/linux/drivers/block/md.c
+++ linux/drivers/block/md.c	Thu Mar  9 13:43:58 1995
@@ -0,0 +1,602 @@
+
+/*
+   md.c : Multiple Devices driver for Linux
+          Copyright (C) 1994, 1995 Marc ZYNGIER
+	  <zyngier@amertume.ufr-info-p7.ibp.fr> or
+	  <maz@gloups.fdn.fr>
+
+   A lot of inspiration came from hd.c ...
+
+   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.  
+*/
+
+#define MD_VERSION "v0.14"
+
+#include <linux/malloc.h>
+#include <linux/mm.h>
+#include <linux/md.h>
+#include <linux/hdreg.h>
+#include <linux/fs.h>
+#include <errno.h>
+
+#define MAJOR_NR MD_MAJOR
+
+#include "blk.h"
+
+static int nb_dev[MAX_MD_DEV]={0, };  /* Real devices count */
+static int md_size[MAX_MD_DEV]={0, }; /* Size of md device (in blocks) */
+
+static int busy[MAX_MD_DEV]={0, };
+static struct hd_struct md_hd_struct[MAX_MD_DEV];
+static int md_blocksizes[MAX_MD_DEV];
+
+static void md_geninit (void);
+
+static struct gendisk md_gendisk=
+{
+  MD_MAJOR,
+  "md",
+  0,
+  1,
+  MAX_MD_DEV,
+  md_geninit,
+  md_hd_struct,
+  md_size,
+  MAX_MD_DEV,
+  NULL,
+  NULL
+};
+
+static int nr_zones[MAX_MD_DEV]={0, };
+static struct real_dev devices[MAX_MD_DEV][MAX_REAL];
+static struct strip_zone *strip_zone[MAX_MD_DEV]; /* Only used whed STRIPPED */
+static int nr_strip_zones[MAX_MD_DEV]={0, };
+
+/* Those are global, since functions defined in md.h need them. */
+
+int repartition[MAX_MD_DEV]={0, }; /* Default is linear, factor=0 */
+struct md_hash *md_hash_table[MAX_MD_DEV]={NULL, };
+union md_union md_smallest[MAX_MD_DEV]={{NULL}, };
+
+static void create_strip_zones (int minor)
+{
+  int i, j, c=0;
+  int current_offset=0;
+  struct real_dev *smallest_by_zone;
+  
+  nr_strip_zones[minor]=1;
+  
+  for (i=1; i<nb_dev[minor]; i++)
+  {
+    for (j=0; j<i; j++)
+      if (devices[minor][i].size==devices[minor][j].size)
+      {
+	c=1;
+	break;
+      }
+
+    if (!c)
+      nr_strip_zones[minor]++;
+
+    c=0;
+  }
+
+  strip_zone[minor]=kmalloc (sizeof (struct strip_zone)*nr_strip_zones[minor],
+			     GFP_KERNEL);
+
+  md_smallest[minor].zone=NULL;
+  
+  for (i=0; i<nr_strip_zones[minor]; i++)
+  {
+    strip_zone[minor][i].dev_offset=current_offset;
+    smallest_by_zone=NULL;
+    c=0;
+
+    for (j=0; j<nb_dev[minor]; j++)
+      if (devices[minor][j].size>current_offset)
+      {
+	strip_zone[minor][i].dev[c++]=devices[minor]+j;
+	if (!smallest_by_zone ||
+	    smallest_by_zone->size > devices[minor][j].size)
+	  smallest_by_zone=devices[minor]+j;
+      }
+
+    strip_zone[minor][i].nb_dev=c;
+    strip_zone[minor][i].size=(smallest_by_zone->size-current_offset)*c;
+
+    if (!md_smallest[minor].zone ||
+	md_smallest[minor].zone->size > strip_zone[minor][i].size)
+      md_smallest[minor].zone=strip_zone[minor]+i;
+
+    strip_zone[minor][i].zone_offset=i ? (strip_zone[minor][i-1].zone_offset +
+					  strip_zone[minor][i-1].size) : 0;
+    current_offset=smallest_by_zone->size;
+  }
+}
+
+static void create_stripped_hash_table (int minor)
+{
+  int current=0, i=0, size, zone0_size, nb_zone;
+
+  create_strip_zones (minor);
+  
+  nb_zone=nr_zones[minor]=
+    md_size[minor]/md_smallest[minor].zone->size +
+    (md_size[minor]%md_smallest[minor].zone->size ? 1 : 0);
+  
+  md_hash_table[minor]=kmalloc (sizeof (struct md_hash)*nb_zone, GFP_KERNEL);
+
+  size=strip_zone[minor][current].size;
+
+  while (current<nr_strip_zones[minor])
+  {
+    md_hash_table[minor][i].unit0.zone=strip_zone[minor]+current;
+
+    if (size>=md_smallest[minor].zone->size)/* If we completly fill the slot */
+    {
+      md_hash_table[minor][i++].unit1.zone=NULL;
+      size-=md_smallest[minor].zone->size;
+
+      if (!size)
+      {
+	if (++current==nr_strip_zones[minor]) continue;
+	size=strip_zone[minor][current].size;
+      }
+
+      continue;
+    }
+
+    if (++current==nr_strip_zones[minor]) /* Last dev, set unit1 as NULL */
+    {
+      md_hash_table[minor][i].unit1.zone=NULL;
+      continue;
+    }
+
+    zone0_size=size;		/* Here, we use a 2nd dev to fill the slot */
+    size=strip_zone[minor][current].size;
+    md_hash_table[minor][i++].unit1.zone=strip_zone[minor]+current;
+    size-=(md_smallest[minor].zone->size - zone0_size);
+  }
+}
+
+
+static void create_linear_hash_table (int minor)
+{
+  int current=0, i=0, size, dev0_size, nb_zone;
+
+  nb_zone=nr_zones[minor]=
+    md_size[minor]/md_smallest[minor].dev->size +
+    (md_size[minor]%md_smallest[minor].dev->size ? 1 : 0);
+  
+  md_hash_table[minor]=kmalloc (sizeof (struct md_hash)*nb_zone, GFP_KERNEL);
+
+  size=devices[minor][current].size;
+
+  while (current<nb_dev[minor])
+  {
+    md_hash_table[minor][i].unit0.dev=devices[minor]+current;
+
+    if (size>=md_smallest[minor].dev->size) /* If we completly fill the slot */
+    {
+      md_hash_table[minor][i++].unit1.dev=NULL;
+      size-=md_smallest[minor].dev->size;
+
+      if (!size)
+      {
+	if (++current==nb_dev[minor]) continue;
+	size=devices[minor][current].size;
+      }
+
+      continue;
+    }
+
+    if (++current==nb_dev[minor]) /* Last dev, set dev1 as NULL */
+    {
+      md_hash_table[minor][i].unit1.dev=NULL;
+      continue;
+    }
+
+    dev0_size=size;		/* Here, we use a 2nd dev to fill the slot */
+    size=devices[minor][current].size;
+    md_hash_table[minor][i++].unit1.dev=devices[minor]+current;
+    size-=(md_smallest[minor].dev->size - dev0_size);
+  }
+}
+
+
+static int create_md_hash_table (int minor)
+{
+  int i;
+  
+  switch (PERSONALITY(minor))
+  {
+    case LINEAR:
+    create_linear_hash_table (minor);
+    break;
+
+    case STRIPPED:
+    /* Resize devices according to the factor */
+    md_size[minor]=0;
+
+    for (i=0; i<nb_dev[minor]; i++)
+    {
+      devices[minor][i].size &= ~((1<<(PAGE_SHIFT + FACTOR(minor) - 10)) - 1);
+      md_size[minor] += devices[minor][i].size;
+    }
+      
+    create_stripped_hash_table (minor);
+    break;
+
+    default:
+    printk ("Unknown personality %x\n", PERSONALITY(minor));
+    return -EINVAL;
+  }
+
+  return 0;
+}
+
+
+static struct gendisk *find_gendisk (dev_t dev)
+{
+  struct gendisk *tmp=gendisk_head;
+
+  while (tmp != NULL)
+  {
+    if (tmp->major==MAJOR(dev))
+      return (tmp);
+    
+    tmp=tmp->next;
+  }
+
+  return (NULL);
+}
+
+
+/* Picked up from genhd.c */
+static char *partition_name (dev_t dev)
+{
+  static char name[10];		/* This should be long
+				   enough for a device name ! */
+  struct gendisk *hd=find_gendisk (dev);
+  char base_name = (hd->major == IDE1_MAJOR) ? 'c' : 'a';
+  int minor=MINOR(dev);
+  
+  sprintf(name, "%s%c%d",
+	  hd->major_name,
+	  base_name + (minor >> hd->minor_shift),
+	  minor & ((1 << hd->minor_shift) - 1));
+  return (name);
+}
+
+
+static int md_ioctl (struct inode *inode, struct file *file,
+                     unsigned int cmd, unsigned long arg)
+{
+  int minor, index, err;
+  struct gendisk *gen_real;
+  struct hd_geometry *loc = (struct hd_geometry *) arg;
+
+  if (!suser())
+    return -EACCES;
+
+  if (((minor=MINOR(inode->i_rdev))>=MAX_MD_DEV))
+    return -EINVAL;
+
+  switch (cmd)
+  {
+    case REGISTER_DEV:
+    if (MAJOR((dev_t) arg)==MD_MAJOR || nb_dev[minor]==MAX_REAL)
+      return -EINVAL;
+
+    if (!fs_may_mount ((dev_t) arg) || md_hash_table[minor])
+      return -EBUSY;
+
+    if (!(gen_real=find_gendisk ((dev_t) arg)))
+      return -ENOENT;
+
+    index=nb_dev[minor]++;
+    devices[minor][index].dev=(dev_t) arg;
+
+    /* Lock the device by inserting a dummy inode. This doesn't
+       smeel very good, but I need to be consistent with the
+       mount stuff, specially with fs_may_mount. If someone have
+       a better idea, please help ! */
+    
+    devices[minor][index].inode=get_empty_inode ();
+    devices[minor][index].inode->i_dev=(dev_t) arg; /* don't care about
+						       other fields */
+    insert_inode_hash (devices[minor][index].inode);
+    
+    /* Devices sizes are rounded to a multiple of page (needed for
+       paging). It should be already done by fdisk when
+       partitionning, but I would NOT trust such a DOS thing... */
+    
+    devices[minor][index].size=gen_real->sizes[MINOR((dev_t) arg)] & (PAGE_MASK>>10);
+    devices[minor][index].offset=index ?
+      (devices[minor][index-1].offset + devices[minor][index-1].size) : 0;
+
+    if (!index || md_smallest[minor].dev->size > devices[minor][index].size)
+      md_smallest[minor].dev=devices[minor]+index;
+
+    if (!index)
+      md_size[minor]=devices[minor][index].size;
+    else
+      md_size[minor]+=devices[minor][index].size;
+
+    printk("REGISTER_DEV %s to md%x done\n", partition_name((dev_t) arg), minor);
+    break;
+
+    case START_MD:
+    if (!nb_dev[minor])
+      return -EINVAL;
+
+    if (md_hash_table[minor])
+      return -EBUSY;
+
+    repartition[minor]=(int) arg;
+    
+    if (PERSONALITY(minor)==STRIPPED)
+    {
+      int i, min=(1 << (PAGE_SHIFT + FACTOR(minor) - 10));
+
+      for (i=0; i<nb_dev[minor]; i++)
+	if (devices[minor][i].size<min)
+	{
+	  printk ("Cannot use %dk clusters on dev %s\n", min,
+		  partition_name (devices[minor][i].dev));
+	  return -EINVAL;
+	}
+    }
+    
+    if ((err=create_md_hash_table (minor)))
+      return err;
+
+    /* FIXME : We assume here we have blocks
+       that are twice as large as sectors.
+       THIS MAY NOT BE TRUE !!! */
+    md_hd_struct[minor].start_sect=0;
+    md_hd_struct[minor].nr_sects=md_size[minor]<<1;
+
+    printk ("START_DEV md%x ", minor);
+    switch (PERSONALITY(minor))
+    {
+      case LINEAR:
+      printk ("linear\n");
+      break;
+
+      case STRIPPED:
+      printk ("stripped, factor %d\n", FACTOR (minor));
+      break;
+    }
+    
+    break;
+
+    case STOP_MD:
+    /* FIXME: We must check that the device is not
+       mounted nor opened BEFORE trying to stop it !! */
+    if (inode->i_count>1 || busy[minor]>1) /* ioctl : one open channel */
+    {
+      printk ("STOP_MD md%x failed : i_count=%d, busy=%d\n", minor, inode->i_count, busy[minor]);
+      return -EBUSY;
+    }
+
+    if (md_hash_table[minor])
+    {
+      /*  The device won't exist anymore -> flush it now */
+      fsync_dev (inode->i_rdev);
+      invalidate_buffers (inode->i_rdev);
+      if (PERSONALITY(minor)==STRIPPED)
+	kfree (strip_zone[minor]);
+      kfree (md_hash_table[minor]);
+    }
+
+    /* Remove locks. */
+    for (index=0; index<nb_dev[minor]; index++)
+      clear_inode (devices[minor][index].inode);
+    
+    nb_dev[minor]=md_size[minor]=0;
+    md_hash_table[minor]=NULL;
+    md_smallest[minor].dev=NULL;
+    printk ("STOP_DEV md%x\n", minor);
+    break;
+
+    case BLKGETSIZE:   /* Return device size */
+    if  (!arg)  return -EINVAL;
+    err=verify_area (VERIFY_WRITE, (long *) arg, sizeof(long));
+    if (err)
+      return err;
+    put_fs_long (md_hd_struct[MINOR(inode->i_rdev)].nr_sects, (long *) arg);
+    break;
+
+    case BLKFLSBUF:
+    fsync_dev (inode->i_rdev);
+    invalidate_buffers (inode->i_rdev);
+    break;
+
+    case HDIO_GETGEO:
+    if (!loc)  return -EINVAL;
+    err = verify_area(VERIFY_WRITE, loc, sizeof(*loc));
+    if (err)
+      return err;
+    put_fs_byte(2, (char *) &loc->heads);
+    put_fs_byte(4, (char *) &loc->sectors);
+    put_fs_word(md_hd_struct[minor].nr_sects/8, (short *) &loc->cylinders);
+    put_fs_long(md_hd_struct[MINOR(inode->i_rdev)].start_sect,
+		(long *) &loc->start);
+    break;
+    
+    default:
+    printk ("Unknown md_ioctl %d\n", cmd);
+    return -EINVAL;
+  }
+
+  return (0);
+}
+
+
+static int md_open (struct inode *inode, struct file *file)
+{
+  int minor=MINOR(inode->i_rdev);
+
+  busy[minor]++;
+  /* printk ("md_open called. busy[%d] is now %d\n", minor, busy[minor]);*/
+  return (0);			/* Always succed */
+}
+
+
+static void md_release (struct inode *inode, struct file *file)
+{
+  int minor=MINOR(inode->i_rdev);
+
+  sync_dev (inode->i_rdev);
+  busy[minor]--;
+}
+
+
+static struct file_operations md_fops=
+{
+  NULL,
+  block_read,
+  block_write,
+  NULL,
+  NULL,
+  md_ioctl,
+  NULL,
+  md_open,
+  md_release,
+  block_fsync
+};
+
+
+static void do_md_request (void)
+{
+  INIT_REQUEST;
+  printk ("Oops ! Got unexpected md request on dev md%x\n", CURRENT->dev);
+  end_request (0);
+}
+
+
+static void md_geninit (void)
+{
+  int i;
+  
+  for(i=0;i<MAX_MD_DEV;i++)
+  {
+    md_blocksizes[i] = 1024;
+    md_gendisk.part[i].start_sect=-1;
+  }
+
+  blksize_size[MAJOR_NR] = md_blocksizes;
+}
+
+
+unsigned long md_init (unsigned long mem_start,
+		       unsigned long mem_long)
+{
+  printk ("md driver %s MAX_MD_DEV=%d, MAX_REAL=%d\n", MD_VERSION, MAX_MD_DEV, MAX_REAL);
+
+  if (register_blkdev (MD_MAJOR, "md", &md_fops))
+  {
+    printk ("Unable to get major %d for md\n", MD_MAJOR);
+    return (mem_start);
+  }
+
+  blk_dev[MD_MAJOR].request_fn=DEVICE_REQUEST;
+  read_ahead[MD_MAJOR]=0;
+  md_gendisk.next=gendisk_head;
+
+  gendisk_head=&md_gendisk;
+  
+  return (mem_start);
+}
+
+int get_md_status (char *page)
+{
+  int sz=0, i, j, k;
+
+  for (i=0; i<MAX_MD_DEV; i++)
+  {
+    sz+=sprintf (page+sz, "md%d : %sactive", i, md_hash_table[i] ? "" : "in");
+
+    if (md_hash_table[i])
+      switch (PERSONALITY(i))
+      {
+	case LINEAR:
+	sz+=sprintf (page+sz, " linear");
+	break;
+	
+	case STRIPPED:
+	sz+=sprintf (page+sz, " stripped(%d)", FACTOR(i));
+	break;
+	
+	default:
+	sz+=sprintf (page+sz, " unknown");
+      }
+    
+    for (j=0; j<nb_dev[i]; j++)
+      sz+=sprintf (page+sz, " %s", partition_name(devices[i][j].dev));
+
+    if (nb_dev[i])
+      sz+=sprintf (page+sz, " %d blocks\n", md_size[i]);
+    else
+      sz+=sprintf (page+sz, "\n");
+
+    if (!md_hash_table[i])
+      continue;
+    
+    switch (PERSONALITY(i))
+    {
+      case LINEAR:
+      sz+=sprintf (page+sz, "      ");
+      for (j=0; j<nr_zones[i]; j++)
+      {
+	sz+=sprintf (page+sz, "[%s",
+		     partition_name (md_hash_table[i][j].unit0.dev->dev));
+	if (md_hash_table[i][j].unit1.dev)
+	  sz+=sprintf (page+sz, "/%s] ",
+		       partition_name(md_hash_table[i][j].unit1.dev->dev));
+	else
+	  sz+=sprintf (page+sz, "] ");
+      }
+      sz+=sprintf (page+sz, "\n");
+      break;
+      
+      case STRIPPED:
+      sz+=sprintf (page+sz, "      ");
+      for (j=0; j<nr_zones[i]; j++)
+      {
+	sz+=sprintf (page+sz, "[z%d",
+		     md_hash_table[i][j].unit0.zone-strip_zone[i]);
+	if (md_hash_table[i][j].unit1.zone)
+	  sz+=sprintf (page+sz, "/z%d] ",
+		       md_hash_table[i][j].unit1.zone-strip_zone[i]);
+	else
+	  sz+=sprintf (page+sz, "] ");
+      }
+
+      sz+=sprintf (page+sz, "\n");
+      
+      for (j=0; j<nr_strip_zones[i]; j++)
+      {
+	sz+=sprintf (page+sz, "      z%d=[", j);
+	for (k=0; k<strip_zone[i][j].nb_dev; k++)
+	  sz+=sprintf (page+sz, "%s/",
+		       partition_name(strip_zone[i][j].dev[k]->dev));
+	sz--;
+	sz+=sprintf (page+sz, "] zo=%d do=%d s=%d\n",
+		     strip_zone[i][j].zone_offset,
+		     strip_zone[i][j].dev_offset,
+		     strip_zone[i][j].size);
+      }
+    }
+  }
+
+  return (sz);
+}
diff -u --recursive --new-file /usr/src/linux/fs/proc/array.c linux/fs/proc/array.c
--- /usr/src/linux/fs/proc/array.c	Mon Mar  6 07:19:26 1995
+++ linux/fs/proc/array.c	Mon Mar  6 07:31:08 1995
@@ -44,6 +44,10 @@
 #include <linux/delay.h>
 #include <linux/mm.h>
 
+#ifdef CONFIG_BLK_DEV_MD
+#include <linux/md.h>
+#endif
+
 #include <asm/segment.h>
 #include <asm/pgtable.h>
 #include <asm/io.h>
@@ -804,6 +808,11 @@
 
 		case PROC_IOPORTS:
 			return get_ioport_list(page);
+
+#ifdef CONFIG_BLK_DEV_MD
+		case PROC_MD:
+			return get_md_status(page);
+#endif
 	}
 	return -EBADF;
 }
diff -u --recursive --new-file /usr/src/linux/fs/proc/root.c linux/fs/proc/root.c
--- /usr/src/linux/fs/proc/root.c	Mon Feb 27 09:35:26 1995
+++ linux/fs/proc/root.c	Mon Mar  6 07:31:08 1995
@@ -80,6 +80,9 @@
 #ifdef CONFIG_PROFILE
 	{ PROC_PROFILE,		7, "profile"},
 #endif
+#ifdef CONFIG_BLK_DEV_MD
+	{ PROC_MD,              6, "mdstat"},
+#endif
 };
 
 #define NR_ROOT_DIRENTRY ((sizeof (root_dir))/(sizeof (root_dir[0])))
diff -u --recursive --new-file /usr/src/linux/include/linux/major.h linux/include/linux/major.h
--- /usr/src/linux/include/linux/major.h	Mon Mar  6 07:19:32 1995
+++ linux/include/linux/major.h	Mon Mar  6 07:31:08 1995
@@ -47,6 +47,7 @@
  * 27 - qic117 tape            matsushita cdrom 3     minors 0..3
  * 28 -                        matsushita cdrom 4     minors 0..3
  * 29 -                        aztech/orchid/okano/wearnes cdrom
+ * 30 -                        multiple devices driver (md)
  */
 
 #define UNNAMED_MAJOR	0
@@ -81,6 +82,7 @@
 #define MATSUSHITA_CDROM4_MAJOR 28
 #define QIC117_TAPE_MAJOR 27
 #define AZTECH_CDROM_MAJOR 29
+#define MD_MAJOR 30
 
 /*
  * Tests for SCSI devices.
diff -u --recursive --new-file /usr/src/linux/include/linux/md.h linux/include/linux/md.h
--- /usr/src/linux/include/linux/md.h
+++ linux/include/linux/md.h	Wed Mar  8 19:56:16 1995
@@ -0,0 +1,166 @@
+
+/*
+   md.h : Multiple Devices driver for Linux
+          Copyright (C) 1994, 1995 Marc ZYNGIER
+	  <zyngier@amertume.ufr-info-p7.ibp.fr> or
+	  <maz@gloups.fdn.fr>
+	  
+   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.  
+*/
+
+#ifndef _MD_H
+#define _MD_H
+
+#include <sys/types.h>
+#include <linux/fs.h>
+#include <linux/major.h>
+
+/* ioctls */
+#define REGISTER_DEV (MD_MAJOR<<8 | 1)
+#define START_MD     (MD_MAJOR<<8 | 2)
+#define STOP_MD      (MD_MAJOR<<8 | 3)
+
+/*
+   personalities are made of the personality itself in the upper 16 bits
+   and the cluster factor in the lower 16 bits.
+ */
+#define PERSONALITY_SHIFT 16
+#define FACTOR_MASK       ((1U << PERSONALITY_SHIFT) -1)
+#define PERSONALITY_MASK  (~FACTOR_MASK)
+#define FACTOR(a)         (repartition[a] & FACTOR_MASK)
+#define PERSONALITY(a)    (repartition[a] & PERSONALITY_MASK)
+
+#define LINEAR            (1UL << PERSONALITY_SHIFT)
+#define STRIPPED           (2UL << PERSONALITY_SHIFT)
+
+#ifdef __KERNEL__
+
+#define MAX_REAL     8
+#define MAX_MD_DEV   4
+
+struct real_dev
+{
+  dev_t dev;			/* Device number */
+  int size;			/* device size (in blocks) */
+  int offset;			/* real device offset (in blocks) in md dev
+				   (only used in linear mode) */
+  struct inode *inode;		/* lock inode */
+};
+
+struct strip_zone
+{
+  int zone_offset;		/* Zone offset in md_dev */
+  int dev_offset;		/* Zone offset in real dev */
+  int size;			/* Zone size */
+  int nb_dev;			/* Number of devices attached to the zone */
+  struct real_dev *dev[MAX_REAL]; /* Devices attached to the zone */
+};
+
+union md_union
+{
+  struct real_dev *dev;
+  struct strip_zone *zone;
+};
+
+struct md_hash
+{
+  union md_union unit0, unit1;
+};
+
+extern struct md_hash *md_hash_table[MAX_MD_DEV];
+extern union md_union md_smallest[MAX_MD_DEV];
+extern int repartition[MAX_MD_DEV];
+
+extern inline int md_to_real (struct buffer_head *bh, dev_t log_dev,
+			      int log_sector, dev_t *dev, int *sector)
+{
+  static struct md_hash *hash;
+  struct real_dev *tmp_dev;
+  struct strip_zone *zone;
+  int mask, blk_in_cluster, cluster, factor;
+  unsigned long block=bh ? bh->b_blocknr : log_sector>>1;
+  dev_t md_dev=bh ? bh->b_dev : log_dev;
+    
+  if (!md_hash_table[MINOR(md_dev)])
+  {
+    printk ("Oops ! md_hash_table[%04x]=NULL\n", MINOR(md_dev));
+    return -1;
+  }
+
+  switch (PERSONALITY(MINOR(md_dev)))
+  {
+    case LINEAR:
+    
+    hash=md_hash_table[MINOR(md_dev)]+(block/md_smallest[MINOR(md_dev)].dev->size);
+    
+    if (block >= (hash->unit0.dev->size + hash->unit0.dev->offset))
+    {
+      if (!hash->unit1.dev)
+	printk ("md_bh_to_real : hash->unit1.dev==NULL for block %ld\n", block);
+      tmp_dev=hash->unit1.dev;
+    }
+    else
+      tmp_dev=hash->unit0.dev;
+    
+    if (dev)
+      *dev=tmp_dev->dev;
+    
+    if (!sector)
+      return 0;
+    
+    if (block >= (tmp_dev->size + tmp_dev->offset) || block < tmp_dev->offset)
+      printk ("Block %ld out of bounds on dev %04x size %d offset %d\n", block, tmp_dev->dev, tmp_dev->size, tmp_dev->offset);
+    
+    *sector=(block-(tmp_dev->offset))*(bh ? (bh->b_size>>9) : 2);
+    
+    return 0;
+
+    case STRIPPED:
+    factor=FACTOR(MINOR(md_dev));
+    
+    hash=md_hash_table[MINOR(md_dev)]+(block/md_smallest[MINOR(md_dev)].zone->size);
+        
+    if (block >= (hash->unit0.zone->size +
+		  hash->unit0.zone->zone_offset))
+    {
+      if (!hash->unit1.zone)
+	printk ("md_bh_to_real : hash->unit1.dev==NULL for block %ld\n", block);
+      zone=hash->unit1.zone;
+    }
+    else
+      zone=hash->unit0.zone;
+
+    mask=(1UL << (PAGE_SHIFT + factor - 10)) - 1;
+    blk_in_cluster=block & mask;
+    cluster=block & ~mask;
+    tmp_dev=zone->dev[((cluster >> (PAGE_SHIFT + factor -10)) % zone->nb_dev)];
+    
+    if (dev)
+      *dev=tmp_dev->dev;
+    
+    if (!sector)
+      return 0;
+    
+    *sector=((cluster - zone->zone_offset) / zone->nb_dev +
+	     blk_in_cluster + zone->dev_offset)*(bh ? (bh->b_size>>9) : 2);
+    
+    return 0;
+
+    default:
+    printk ("Unknown personality %04x for md%d\n", PERSONALITY(MINOR(md_dev)), MINOR(md_dev));
+    return (-1);
+  }
+}
+    
+
+extern int get_md_status (char *page);
+
+#endif __KERNEL__
+#endif _MD_H
diff -u --recursive --new-file /usr/src/linux/include/linux/proc_fs.h linux/include/linux/proc_fs.h
--- /usr/src/linux/include/linux/proc_fs.h	Mon Feb 27 09:41:56 1995
+++ linux/include/linux/proc_fs.h	Mon Mar  6 07:31:09 1995
@@ -30,7 +30,10 @@
 	PROC_KSYMS,
 	PROC_DMA,	
 	PROC_IOPORTS,
-	PROC_PROFILE /* whether enabled or not */
+	PROC_PROFILE, /* whether enabled or not */
+#ifdef CONFIG_BLK_DEV_MD
+	PROC_MD
+#endif
 };
 
 enum pid_directory_inos {
