patch-2.2.14 linux/drivers/s390/block/mdisk.c

Next file: linux/drivers/s390/block/mdisk.h
Previous file: linux/drivers/s390/block/dasd_types.h
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.2.13/linux/drivers/s390/block/mdisk.c linux/drivers/s390/block/mdisk.c
@@ -0,0 +1,697 @@
+/*
+ *  drivers/s390/block/mdisk.c
+ *    VM minidisk device driver.
+ *
+ *  S390 version
+ *    Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ *    Author(s): Hartmut Penner (hp@de.ibm.com)
+ */
+
+
+#ifndef __KERNEL__
+#  define __KERNEL__
+#endif
+
+#define __NO_VERSION__
+#include <linux/version.h>
+
+char kernel_version [] = UTS_RELEASE;
+
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>              /* printk()                         */
+#include <linux/malloc.h>              /* kmalloc()                        */
+#include <linux/vmalloc.h>             /* vmalloc()                        */
+#include <linux/fs.h>                  /* everything...                    */
+#include <linux/errno.h>               /* error codes                      */
+#include <linux/timer.h>
+#include <linux/types.h>               /* size_t                           */
+#include <linux/fcntl.h>               /* O_ACCMODE                        */
+#include <linux/hdreg.h>               /* HDIO_GETGEO                      */
+#include <linux/init.h>                /* initfunc                         */
+#include <linux/interrupt.h>
+#include <linux/ctype.h>
+
+#include <asm/system.h>                /* cli(), *_flags                   */
+#include <asm/uaccess.h>               /* access_ok                        */
+#include <asm/io.h>                    /* virt_to_phys                     */
+
+	 /* Added statement HSM 12/03/99 */
+#include "../../../arch/s390/kernel/irq.h"
+
+#define MAJOR_NR MDISK_MAJOR /* force definitions on in blk.h */
+
+#include <linux/blk.h>
+
+
+#include "mdisk.h"        /* local definitions */
+
+/*
+ * structure for all device specific information
+ */
+
+typedef struct mdisk_Dev {
+	u32 vdev;   /* vdev of mindisk */
+	u32 size;   /* size in blocks */
+	u32 status; /* status of last io operation */
+	u32 nr_bhs; /* number of buffer of last io operation */
+        u32 blksize; /* blksize from minidisk */
+        u32 blkmult; /* multiplier between blksize and 512 HARDSECT */
+        u32 blkshift; /* loe2 of multiplier above */
+	/*
+	 * each device has own iob and bio,
+	 * it's possible to run io in parallel
+	 * not used yet due to only one CURRENT per MAJOR
+	 */
+	
+	mdisk_rw_io_t* iob;      /* each device has it own iob and bio */
+	mdisk_bio_t*   bio;
+	 /* Added statement HSM 12/03/99 */
+	devstat_t dev_status;    /* Here we hold the I/O status */
+
+	int usage;               /* usage counter */
+
+	struct tq_struct tqueue; /* per device task queue */
+} mdisk_Dev;
+
+
+/*
+ * appended to global structures in mdisk_init;
+ */
+
+static int mdisk_blksizes[MDISK_DEVS];
+static int mdisk_sizes[MDISK_DEVS] = { 0 };
+static int mdisk_hardsects[MDISK_DEVS];
+static int mdisk_maxsectors[MDISK_DEVS];
+
+/*
+ *  structure hold device specific information
+ */
+
+static mdisk_Dev mdisk_devices[MDISK_DEVS];
+static mdisk_rw_io_t mdisk_iob[MDISK_DEVS] __attribute__ ((aligned(8)));
+static mdisk_bio_t mdisk_bio[MDISK_DEVS][256]__attribute__ ((aligned(8)));
+
+
+/*
+ * Parameter parsing
+ */
+struct {
+        long vdev[MDISK_DEVS];
+	long size[MDISK_DEVS];
+	long offset[MDISK_DEVS];
+        long blksize[MDISK_DEVS];
+} mdisk_setup_data;
+
+/*
+ * Parameter parsing function, called from init/main.c
+ * vdev    : virtual device number
+ * size    : size in kbyte
+ * offset  : offset after which minidisk is available
+ * blksize : blocksize minidisk is formated
+ * Format is: mdisk=<vdev>:<size>:<offset>:<blksize>,<vdev>:<size>:<offset>...
+ * <vdev>:<size>:<offset>:<blksize> can be shortened to <vdev>:<size> with offset=0,blksize=512
+ */
+__initfunc(void mdisk_setup(char *str,int *ints))
+{
+	char *cur = str;
+	int vdev, size, offset=0,blksize;
+	static i = 0;
+	if (!i)
+	  memset(&mdisk_setup_data,0,sizeof(mdisk_setup_data));
+
+        while (*cur != 0) {
+	        blksize=MDISK_HARDSECT;
+		vdev = size = offset = 0;
+		if (!isxdigit(*cur)) goto syntax_error;
+		vdev = simple_strtoul(cur,&cur,16);
+		if (*cur++ != ':') goto syntax_error;
+		if (!isxdigit(*cur)) goto syntax_error;
+		size = simple_strtoul(cur,&cur,16);
+		if (*cur == ':') { /* another colon -> offset specified */
+			cur++;
+			if (!isxdigit(*cur)) goto syntax_error;
+			offset = simple_strtoul(cur,&cur,16);
+			if (*cur == ':') { /* another colon -> blksize */
+			  cur++;
+			  if (!isxdigit(*cur)) goto syntax_error;
+			  blksize = simple_strtoul(cur,&cur,16);
+			}
+		}
+		if (*cur != ',' && *cur != 0) goto syntax_error;
+		if (*cur == ',') cur++;
+		if (i >= MDISK_DEVS) {
+			printk(KERN_WARNING "mnd: too many devices\n");
+			return;
+		}
+		mdisk_setup_data.vdev[i] = vdev;
+		mdisk_setup_data.size[i] = size;
+		mdisk_setup_data.offset[i] = offset;
+		mdisk_setup_data.blksize[i] = blksize;
+
+		i++;
+	}
+	
+	return;
+
+syntax_error:
+        printk(KERN_WARNING "mnd: syntax error in parameter string: %s\n", str);
+	return;
+}
+
+/*
+ * Open and close
+ */
+
+static int mdisk_open (struct inode *inode, struct file *filp)
+{
+	mdisk_Dev *dev; /* device information */
+	int num = MINOR(inode->i_rdev);
+	
+	/* 
+	 * size 0 means device not installed
+	 */
+	if ((num >= MDISK_DEVS) || (mdisk_sizes[num] == 0))
+		return -ENODEV;
+	MOD_INC_USE_COUNT;
+	dev = &mdisk_devices[num];
+	dev->usage++;
+	return 0;          /* success */
+}
+
+static int mdisk_release (struct inode *inode, struct file *filp)
+{
+	mdisk_Dev *dev = &mdisk_devices[MINOR(inode->i_rdev)];
+	
+	/*
+	 * flush device
+	 */
+	
+	fsync_dev(inode->i_rdev);
+	dev->usage--;
+	MOD_DEC_USE_COUNT;
+	return 0;
+}
+
+
+/*
+ * The mdisk() implementation
+ */
+
+static int mdisk_ioctl (struct inode *inode, struct file *filp,
+                 unsigned int cmd, unsigned long arg)
+{
+	int err,rc, size=0;
+	struct hd_geometry *geo = (struct hd_geometry *)arg;
+	mdisk_Dev *dev = mdisk_devices + MINOR(inode->i_rdev);
+	
+	switch(cmd) {
+		
+	case BLKGETSIZE:
+	        rc = copy_to_user ((long *) arg, &dev->size, sizeof (long));
+		printk(KERN_WARNING "mnd: ioctl BLKGETSIZE %d\n",dev->size);
+		return rc;
+	case BLKFLSBUF: /* flush */
+		if (!suser()) return -EACCES; /* only root */
+		fsync_dev(inode->i_rdev);
+		invalidate_buffers(inode->i_rdev);
+		return 0;
+		
+	case BLKRAGET: /* return the readahead value */
+		if (!arg)  return -EINVAL;
+		err = access_ok(VERIFY_WRITE, (long *) arg, sizeof(long));
+		if (err) return err;
+		put_user(read_ahead[MAJOR(inode->i_rdev)],(long *) arg);
+		return 0;
+		
+	case BLKRASET: /* set the readahead value */
+		if (!suser()) return -EACCES;
+		if (arg > 0xff) return -EINVAL; /* limit it */
+		read_ahead[MAJOR(inode->i_rdev)] = arg;
+		return 0;
+		
+	case BLKRRPART: /* re-read partition table: can't do it */
+		return -EINVAL;
+		
+		RO_IOCTLS(inode->i_rdev, arg); /* the default RO operations */
+		
+	case HDIO_GETGEO:
+		/*
+		 * get geometry of device -> linear
+		 */
+		size = dev->size;
+		if (geo==NULL) return -EINVAL;
+		err = access_ok(VERIFY_WRITE, geo, sizeof(*geo));
+		if (err) return err;
+		put_user(1,    &geo->cylinders);
+		put_user(1,    &geo->heads);
+		put_user(size, &geo->sectors);
+		put_user(0,    &geo->start);
+		return 0;
+	}
+	
+	return -EINVAL; /* unknown command */
+}
+
+/*
+ * The file operations
+ */
+
+static struct file_operations mdisk_fops = {
+	NULL,          /* lseek: default */
+	block_read,
+	block_write,
+	NULL,          /* mdisk_readdir */
+	NULL,          /* mdisk_select */
+	mdisk_ioctl,
+	NULL,          /* mdisk_mmap */
+	mdisk_open,
+	NULL,          /* mdisk_flush */
+	mdisk_release,
+	block_fsync,
+	NULL,          /* mdisk_fasync */
+	NULL,          /* mdisk_check_change */
+	NULL,          /* mdisk_revalidate */
+	NULL,          /* mdisk_lock */
+};
+
+/*
+ * The 'low level' IO function
+ */
+
+
+static __inline__ int
+dia250(void* iob,int cmd)
+{
+	int rc;
+	
+	iob = (void*) virt_to_phys(iob);
+
+	asm volatile ("    lr    2,%1\n"
+		      "    lr    3,%2\n"
+		      "    .long 0x83230250\n"
+		      "    lr    %0,3"
+		      : "=d" (rc)
+		      : "d" (iob) , "d" (cmd)
+		      : "2", "3" );
+	return rc;
+}
+
+/*
+ * Init of minidisk device
+ */
+
+static __inline__ int
+mdisk_init_io(mdisk_Dev *dev,int blocksize,int offset,int size)
+{
+	mdisk_init_io_t *iob = (mdisk_init_io_t*) dev->iob;
+	int rc;
+
+	memset(iob,0,sizeof(mdisk_init_io_t));
+	
+	iob->dev_nr = dev->vdev;
+	iob->block_size = blocksize;
+	iob->offset     = offset;
+	iob->start_block= 0;
+	iob->end_block  = size;
+	
+	rc = dia250(iob,INIT_BIO);
+	
+	/*
+	 * clear for following io once
+	 */
+	
+	memset(iob,0,sizeof(mdisk_rw_io_t));
+	
+	return rc;
+}
+
+/*
+ * release of minidisk device
+ */
+
+static __inline__ int
+mdisk_term_io(mdisk_Dev *dev)
+{
+	mdisk_init_io_t *iob = (mdisk_init_io_t*) dev->iob;
+	
+	memset(iob,0,sizeof(mdisk_init_io_t));
+	
+	iob->dev_nr = dev->vdev;
+	
+	return dia250(iob,TERM_BIO);
+}
+
+/*
+ * setup and start of minidisk io request
+ */
+
+static __inline__ int
+mdisk_rw_io_clustered (mdisk_Dev *dev,
+                       mdisk_bio_t* bio_array,
+                       int length,
+                       int req,
+                       int sync)
+{
+	int rc;
+	mdisk_rw_io_t *iob = dev->iob;
+	
+	iob->dev_nr      = dev->vdev;
+	iob->key         = 0;
+	iob->flags       = sync;
+	
+	iob->block_count = length;
+	iob->interrupt_params = req;
+	iob->bio_list     = virt_to_phys(bio_array);
+	
+	rc = dia250(iob,RW_BIO);
+	rc = (rc == 8) ? 0 : rc;
+	return rc;
+}
+
+
+/*
+ * this handles a clustered request in success case
+ * all buffers are detach and marked uptodate to the kernel
+ * then CURRENT->bh is set to the last processed but not
+ * update buffer
+ */
+
+static __inline__ void
+mdisk_end_request(int nr_bhs)
+{
+	int i;
+	struct buffer_head *bh;
+	struct request *req;
+
+	if (nr_bhs != 1) {
+		req = CURRENT;
+		bh  = req->bh;
+		
+		for (i=0;i<nr_bhs-1;i++) {
+			req->bh = bh->b_reqnext;
+			bh->b_reqnext = NULL;
+			bh->b_end_io(bh,1);
+			bh = req->bh;			
+		}
+		
+		/*
+		 * set CURRENT to last processed, not marked buffer
+		 */
+		req->buffer = bh->b_data;
+		req->current_nr_sectors = bh->b_size >> 9;
+		CURRENT = req;
+	}
+	end_request(1);
+}
+
+
+
+/*
+ * Block-driver specific functions
+ */
+
+void mdisk_request(void)
+{
+	mdisk_Dev *dev;
+	mdisk_bio_t *bio;
+	struct buffer_head *bh;
+	unsigned int sector, nr, offset;
+	int rc,rw,i;
+
+	i = 0;
+	while(CURRENT) {
+		INIT_REQUEST;
+		
+		/* Check if the minor number is in range */
+		if (DEVICE_NR(CURRENT_DEV) > MDISK_DEVS) {
+			static int count = 0;
+			if (count++ < 5) /* print the message at most five times */
+				printk(KERN_WARNING "mnd: request for minor %d out of range\n",
+				       DEVICE_NR(CURRENT_DEV)  )       ;
+			end_request(0);
+			continue;
+		}
+		
+		/*
+		 * Pointer to device structure, from the static array
+		 */
+		dev = mdisk_devices + DEVICE_NR(CURRENT_DEV);
+		
+		/*
+		 * check, if operation is past end of devices
+		 */
+		if (CURRENT->nr_sectors + CURRENT->sector > dev->size) {
+			static int count = 0;
+			if (count++ < 5)
+				printk(KERN_WARNING "mnd%c: request past end of device\n",
+				       DEVICE_NR(CURRENT_DEV));
+			end_request(0);
+			continue;
+		}
+		
+		/*
+		 * do command (read or write)
+		 */
+		switch(CURRENT->cmd) {
+		case READ:
+			rw = MDISK_READ_REQ;
+			break;
+		case WRITE:
+			rw = MDISK_WRITE_REQ;
+			break;
+		default:
+			/* can't happen */
+			end_request(0);
+			continue;
+		}
+		
+		/*
+		 * put the clustered requests in mdisk_bio array
+		 * nr_sectors is checked against max_sectors in make_request
+		 * nr_sectors and sector are always blocks of 512 
+		 * but bh_size depends on the filesystems size
+		 */
+		sector = CURRENT->sector>>dev->blkshift;
+		bh     = CURRENT->bh;
+		bio    = dev->bio;
+		dev->nr_bhs = 0;
+		
+		/*
+		 * sector is translated to block in minidisk context
+		 * 
+		 */
+		offset = 0;
+
+
+		
+		for (nr = 0,i = 0; 
+		     nr < CURRENT->nr_sectors && bh;
+		     nr+=dev->blkmult, sector++,i++) {
+			memset(&bio[i], 0, sizeof(mdisk_bio_t));
+			bio[i].type   = rw;
+			bio[i].block_number = sector;
+			bio[i].buffer  = virt_to_phys(bh->b_data+offset);
+			offset += dev->blksize;
+			if (bh->b_size <= offset) {
+				offset = 0;
+				bh = bh->b_reqnext;
+				dev->nr_bhs++;
+			}
+		}
+
+		if (( rc = mdisk_rw_io_clustered(dev, &bio[0], i,
+						 (unsigned long) dev,
+#ifdef CONFIG_MDISK_SYNC
+						 MDISK_SYNC
+#else
+						 MDISK_ASYNC
+#endif
+			)) != 0 ) {
+			printk(KERN_WARNING "mnd%c: %s request failed rc %d"
+			       " sector %ld nr_sectors %ld \n",
+			       DEVICE_NR(CURRENT_DEV),
+			       rw == MDISK_READ_REQ ? "read" : "write",
+			       rc, CURRENT->sector, CURRENT->nr_sectors);
+			end_request(0);
+			continue;
+		}
+		i = 0;		
+		/*
+		 * Synchron: looping to end of request (INIT_REQUEST has return)
+		 * Asynchron: end_request done in bottom half
+		 */
+#ifdef CONFIG_MDISK_SYNC
+		mdisk_end_request(dev->nr_bhs);
+#else
+		return;
+#endif	
+	}
+}
+
+
+/*
+ * mdisk interrupt handler called when read/write request finished
+ * queues and marks a bottom half.
+ *
+ */
+void do_mdisk_interrupt(void)
+{
+        u16 code;
+	mdisk_Dev *dev;
+	
+	code = S390_lowcore.cpu_addr;
+
+	if ((code >> 8) != 0x03) {
+	  printk("mnd: wrong sub-interruption code %d",code>>8);
+	  return;
+	}
+
+	/*
+	 * pointer to devives structure given as external interruption
+	 * parameter
+	 */
+	dev = (mdisk_Dev*) S390_lowcore.ext_params;
+	dev->status = code & 0x00ff;
+
+	queue_task(&dev->tqueue, &tq_immediate);
+	mark_bh(IMMEDIATE_BH);
+}
+
+/*
+ * the bottom half checks the status of request
+ * on success it calls end_request and calls mdisk_request
+ * if more transfer to do
+ */
+
+static void
+do_mdisk_bh(void *data)
+{
+	mdisk_Dev *dev = (mdisk_Dev*) data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&io_request_lock, flags);
+	/*
+	 * check for status of asynchronous rw
+	 */
+	if (dev->status != 0x00) {
+		printk("mnd: status of async rw %d",dev->status);
+		end_request(0);
+	} else {
+		/*
+		 * end request for clustered requests
+		 */
+		mdisk_end_request(dev->nr_bhs);
+	}
+
+	/*
+	 * if more to do, call mdisk_request
+	 */
+	if (CURRENT)
+		mdisk_request();
+	spin_unlock_irqrestore(&io_request_lock, flags);
+}
+
+void /* Added fuction HSM 12/03/99 */
+mdisk_handler (int cpu, void *ds, struct pt_regs *regs)
+{
+	printk (KERN_ERR "mnd: received I/O interrupt... shouldn't happen\n");
+}
+
+__initfunc(int mdisk_init(void))
+{
+        int rc,i;
+        mdisk_Dev *dev;
+	
+	/*
+	 * register block device
+	 */
+	if (register_blkdev(MAJOR_NR,"mnd",&mdisk_fops) < 0) {
+		printk("mnd: unable to get major %d for mini disk\n"
+		       ,MAJOR_NR);
+		return MAJOR_NR;
+	}
+	
+	/*
+	 * setup global major dependend structures
+	 */
+	blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
+
+	/*
+	 * setup sizes for available devices
+	 */
+	read_ahead[MAJOR_NR] = MDISK_RAHEAD;	/* 8 sector (4kB) read-ahead */
+	blk_size[MAJOR_NR] = mdisk_sizes;       /* size of reserved mdisk    */
+	blksize_size[MAJOR_NR] = mdisk_blksizes;   /* blksize of device      */
+	hardsect_size[MAJOR_NR] = mdisk_hardsects;
+	max_sectors[MAJOR_NR]   = mdisk_maxsectors;
+	
+	for (i=0;i<MDISK_DEVS;i++) {
+		mdisk_sizes[i] = mdisk_setup_data.size[i];
+		if (mdisk_sizes[i] == 0) {
+			continue;
+		}
+		/* Added block HSM 12/03/99 */
+		if ( request_irq(get_irq_by_devno(mdisk_setup_data.vdev[i]),
+				 mdisk_handler, 0, "mnd",
+				 &(mdisk_devices[i].dev_status)) ){
+			printk ( KERN_WARNING "mnd: Cannot acquire I/O irq of"
+				 " %4X for paranoia reasons, skipping\n",
+				 mdisk_setup_data.vdev[i]);
+			continue;
+		}
+		/*
+		 * open VM minidisk low level device
+		 */
+		dev = &mdisk_devices[i];
+		dev->bio=mdisk_bio[i];
+		dev->iob=&mdisk_iob[i];
+		dev->vdev = mdisk_setup_data.vdev[i];
+		dev->size = mdisk_setup_data.size[i] * 2; /* buffer 512 b */
+		dev->blksize = mdisk_setup_data.blksize[i]; 
+		dev->tqueue.routine = do_mdisk_bh;
+		dev->tqueue.data = dev;
+		dev->blkmult = dev->blksize/512;
+		dev->blkshift = 
+		  dev->blkmult==1?0:
+		  dev->blkmult==2?1:
+		  dev->blkmult==4?2:
+		  dev->blkmult==8?3:-1;
+
+		mdisk_blksizes[i]  = mdisk_setup_data.blksize[i];
+		mdisk_hardsects[i] = mdisk_setup_data.blksize[i];
+
+		/*
+		 * max sectors for one clustered req  
+		 */
+		mdisk_maxsectors[i] = MDISK_MAXSECTORS*dev->blkmult;
+
+		rc = mdisk_init_io(dev,
+				   mdisk_setup_data.blksize[i],
+				   mdisk_setup_data.offset[i],/* offset in vdev*/
+				   dev->size>>dev->blkshift  /* size in blocks */
+			);
+		if (rc > 4) {
+			printk("mnd%c: init failed (rc: %d)\n",'a'+i,rc);
+			mdisk_sizes[i] = 0;
+			continue;
+		}
+		
+		/*
+		 * set vdev in device structure for further rw access
+		 * vdev and size given by linload
+		 */
+		printk("mnd%c: register device at major %X with %d blocks %d blksize \n",
+		       'a' + i, MAJOR_NR, dev->size>>dev->blkshift,dev->blkmult*512);
+	}
+	
+	/*
+	 * enable service-signal external interruptions,
+	 * Control Register 0 bit 22 := 1
+	 * (besides PSW bit 7 must be set to 1 somewhere for external
+	 * interruptions)
+	 */
+        ctl_set_bit(0, 9);
+	
+        return 0;
+}

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)