patch-2.1.23 linux/drivers/ap1000/ddv.c

Next file: linux/drivers/ap1000/ddv_util.c
Previous file: linux/drivers/ap1000/bif.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.1.22/linux/drivers/ap1000/ddv.c linux/drivers/ap1000/ddv.c
@@ -0,0 +1,1008 @@
+  /*
+   * Copyright 1996 The Australian National University.
+   * Copyright 1996 Fujitsu Laboratories Limited
+   * 
+   * This software may be distributed under the terms of the Gnu
+   * Public License version 2 or later
+  */
+/* 
+ * ddv.c - Single AP1000 block driver.
+ *
+ * This block driver performs io operations to the ddv option
+ * board. (Hopefully:)
+ *
+ */
+
+#include <linux/errno.h>
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/fs.h>
+#include <linux/ext2_fs.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/mm.h>
+#include <linux/malloc.h>
+#define __KERNEL_SYSCALLS__
+#include <linux/unistd.h>
+#include <linux/sched.h>
+#include <asm/pgtable.h>
+#include <asm/uaccess.h>
+#include <linux/module.h>
+#include <asm/ap1000/apreg.h>
+#include <asm/ap1000/DdvReqTable.h>
+
+#define MAJOR_NR DDV_MAJOR
+
+#include <linux/blk.h>
+#include <linux/genhd.h>
+#include <linux/hdreg.h> 
+
+#define DDV_DEBUG 0
+#define AIR_DISK 1
+
+#define SECTOR_SIZE 512
+
+/* we can have lots of partitions */
+#define PARTN_BITS 6
+#define NUM_DDVDEVS (1<<PARTN_BITS)
+
+#define PARDISK_BASE (1<<5) /* partitions above this number are 
+			       striped	across all the cells */
+#define STRIPE_SHIFT   6
+#define STRIPE_SECTORS (1<<STRIPE_SHIFT) /* number of sectors per stripe */
+
+#define MAX_BNUM 16
+#define MAX_REQUEST (TABLE_SIZE - 2)
+#define REQUEST_LOW 16
+#define REQUEST_HIGH 4
+
+
+/* we fake up a block size larger than the physical block size to try
+   to make things a bit more efficient */
+#define SECTOR_BLOCK_SHIFT 9
+
+/* try to read ahead a bit */
+#define DDV_READ_AHEAD 64
+
+static int have_ddv_board = 1;
+static unsigned num_options = 0;
+static unsigned this_option = 0;
+
+extern int ddv_get_mlist(unsigned mptr[],int bnum);
+extern int ddv_set_request(struct request *req,
+			   int request_type,int bnum,int mlist,int len,int offset);
+extern void ddv_load_kernel(char *opcodep);
+extern int ddv_restart_cpu(void);
+extern int ddv_mlist_available(void);
+static int ddv_revalidate(kdev_t dev, struct gendisk *gdev);
+static void ddv_geninit(struct gendisk *ignored);
+static void ddv_release(struct inode * inode, struct file * filp);
+static void ddv_request1(void);
+
+
+static char *ddv_opcodep = NULL;
+static struct request *next_request = NULL;
+
+static struct wait_queue * busy_wait = NULL;
+
+static int ddv_blocksizes[NUM_DDVDEVS]; /* in bytes */
+int ddv_sect_length[NUM_DDVDEVS]; /* in sectors */
+int ddv_blk_length[NUM_DDVDEVS]; /* in blocks */
+
+/* these are used by the ddv_daemon, which services remote disk requests */
+static struct remote_request *rem_queue = NULL;
+static struct remote_request *rem_queue_end;
+static struct wait_queue *ddv_daemon_wait = NULL;
+
+static int opiu_kernel_loaded = 0;
+
+static struct {
+	unsigned reads, writes, blocks, rq_started, rq_finished, errors;
+	unsigned sectors_read, sectors_written;
+} ddv_stats;
+
+static struct hd_struct partition_tables[NUM_DDVDEVS];
+
+static struct gendisk ddv_gendisk = {
+	MAJOR_NR,	/* Major number */
+	DEVICE_NAME,		/* Major name */
+	PARTN_BITS,		/* Bits to shift to get real from partition */
+	1 << PARTN_BITS,	/* Number of partitions per real */
+	1,	        /* maximum number of real */
+#ifdef MODULE
+	NULL,		/* called from init_module */
+#else
+        ddv_geninit,     /* init function */
+#endif
+	partition_tables,/* hd struct */
+	ddv_blk_length,	/* block sizes */
+	1,		/* number */
+	(void *) NULL,	/* internal */
+	NULL		/* next */
+};
+
+
+struct ddv_geometry {
+	unsigned char heads;
+	unsigned char sectors;
+	unsigned short cylinders;
+	unsigned long start;
+}; 
+
+static struct ddv_geometry ddv_geometry;
+
+
+struct remote_request {
+	union {
+		struct remote_request *next;
+		void (*fn)(void);
+	} u;
+	unsigned bnum; /* how many blocks does this contain */
+	struct request *reqp; /* pointer to the request on the original cell */
+	unsigned cell; /* what cell is the request from */
+	struct request req; /* details of the request */
+};
+
+
+static void ddv_set_optadr(void)
+{
+	unsigned addr = 0x11000000;
+	OPT_IO(OBASE) = addr;
+	MSC_IO(MSC_OPTADR) = 
+		((addr & 0xff000000)>>16) |
+			((OPTION_BASE & 0xf0000000)>>24) | 
+				((OPTION_BASE + 0x10000000)>>28); 
+	OPT_IO(PRST) = 0;
+}
+
+extern struct RequestTable *RTable;
+extern struct OPrintBufArray *PrintBufs;
+extern struct OAlignBufArray *AlignBufs;
+extern struct DiskInfo *DiskInfo;
+
+static void ddv_release(struct inode * inode, struct file * filp)
+{
+#if DEBUG
+	printk("ddv_release started\n");
+#endif
+	sync_dev(inode->i_rdev);
+#if DEBUG
+	printk("ddv_release done\n");
+#endif
+}
+
+
+static unsigned in_request = 0;
+static unsigned req_queued = 0;
+
+static void ddv_end_request(int uptodate,struct request *req) 
+{
+	struct buffer_head * bh;
+
+	ddv_stats.rq_finished++;
+
+/*	printk("ddv_end_request(%d,%p)\n",uptodate,req); */
+
+	req->errors = 0;
+	if (!uptodate) {
+		printk("end_request: I/O error, dev %s, sector %lu\n",
+		       kdevname(req->rq_dev), req->sector);
+		req->nr_sectors--;
+		req->nr_sectors &= ~SECTOR_MASK;
+		req->sector += (BLOCK_SIZE / SECTOR_SIZE);
+		req->sector &= ~SECTOR_MASK;		
+		ddv_stats.errors++;
+	}
+	
+	if ((bh = req->bh) != NULL) {
+		req->bh = bh->b_reqnext;
+		bh->b_reqnext = NULL;
+		mark_buffer_uptodate(bh, uptodate);
+		unlock_buffer(bh);
+		if ((bh = req->bh) != NULL) {
+			req->current_nr_sectors = bh->b_size >> 9;
+			if (req->nr_sectors < req->current_nr_sectors) {
+				req->nr_sectors = req->current_nr_sectors;
+				printk("end_request: buffer-list destroyed\n");
+			}
+			req->buffer = bh->b_data;
+			printk("WARNING: ddv: more sectors!\n");
+			ddv_stats.errors++;
+			return;
+		}
+	}
+	if (req->sem != NULL)
+		up(req->sem);
+	req->rq_status = RQ_INACTIVE;	
+	wake_up(&wait_for_request);
+}
+
+
+/* check that a request is all OK to process */
+static int request_ok(struct request *req)
+{
+	int minor;
+	if (!req) return 0;
+
+	if (MAJOR(req->rq_dev) != MAJOR_NR)
+		panic(DEVICE_NAME ": bad major number\n");
+	if (!buffer_locked(req->bh))
+		panic(DEVICE_NAME ": block not locked");
+	
+	minor = MINOR(req->rq_dev);
+	if (minor >= NUM_DDVDEVS) {
+		printk("ddv_request: Invalid minor (%d)\n", minor);
+		return 0;
+	}
+
+	if ((req->sector + req->current_nr_sectors) > ddv_sect_length[minor]) {
+		printk("ddv: out of range minor=%d offset=%d len=%d sect_length=%d\n",
+		       minor,(int)req->sector,(int)req->current_nr_sectors,
+		       ddv_sect_length[minor]);
+		return 0;
+	}
+
+	if (req->cmd != READ && req->cmd != WRITE) {
+		printk("unknown request type %d\n",req->cmd);
+		return 0;
+	}			
+
+	/* it seems to be OK */
+	return 1;
+}
+
+
+static void complete_request(struct request *req,int bnum)
+{
+	while (bnum--) {
+		ddv_end_request(1,req);
+		req = req->next;
+	}
+}
+
+
+static int completion_pointer = 0;
+
+static void check_completion(void)
+{
+	int i,bnum;
+	struct request *req;
+
+	if (!RTable) return;
+
+	for (;
+	     (i=completion_pointer) != RTable->ddv_pointer &&
+	     RTable->async_info[i].status == DDV_REQ_FREE;
+	     completion_pointer = INC_T(completion_pointer))	     
+	{
+		req = (struct request *)RTable->async_info[i].argv[7];
+		bnum = RTable->async_info[i].bnum;
+		if (!req || !bnum) {
+			printk("%s(%d)\n",__FILE__,__LINE__);
+			ddv_stats.errors++;
+			continue;
+		}
+			
+		RTable->async_info[i].status = 0;
+		RTable->async_info[i].argv[7] = 0;
+
+		complete_request(req,bnum);
+		in_request--;		
+	}
+}
+
+
+static struct request *get_request_queue(struct request *oldq)
+{
+	struct request *req,*req2;
+
+	/* skip any non-active or bad requests */
+ skip1:
+	if (!(req = CURRENT)) 
+		return oldq;
+
+	if (req->rq_status != RQ_ACTIVE) {
+		CURRENT = req->next;
+		goto skip1;
+	}
+
+	if (!request_ok(req)) {
+		ddv_end_request(0,req);
+		CURRENT = req->next;
+		goto skip1;
+	}
+
+	/* now grab as many as we can */
+	req_queued++;
+
+	for (req2 = req;
+	     req2->next && 
+	     req2->next->rq_status == RQ_ACTIVE &&
+	     request_ok(req2->next);
+	     req2 = req2->next) 
+		req_queued++;
+
+	/* leave CURRENT pointing at the bad ones */
+	CURRENT = req2->next;
+
+	/* chop our list at that point */
+	req2->next = NULL;
+
+	if (!oldq)
+		return req;
+
+	for (req2=oldq;req2->next;req2=req2->next) ;
+
+	req2->next = req;
+
+	return oldq;	
+}
+
+
+static void ddv_rem_complete(struct remote_request *rem)
+{
+	unsigned flags;
+	int bnum = rem->bnum;
+	struct request *req = rem->reqp;
+
+	complete_request(req,bnum);
+	in_request--;
+
+	save_flags(flags); cli();
+	ddv_request1();
+	restore_flags(flags);
+}
+
+
+/*
+ * The background ddv daemon. This receives remote disk requests
+ * and processes them via the normal block operations
+ */
+static int ddv_daemon(void *unused)
+{
+	current->session = 1;
+	current->pgrp = 1;
+	sprintf(current->comm, "ddv_daemon");
+	current->blocked = ~0UL; /* block all signals */
+  
+	/* Give it a realtime priority. */
+	current->policy = SCHED_FIFO;
+	current->priority = 32;  /* Fixme --- we need to standardise our
+				    namings for POSIX.4 realtime scheduling
+				    priorities.  */
+  
+	printk("Started ddv_daemon\n");
+  
+	while (1) {
+		struct remote_request *rem;
+		unsigned flags;
+		struct buffer_head *bhlist[MAX_BNUM*4];
+		int i,j,minor,len,shift,offset;
+
+		save_flags(flags); cli();
+
+		while (!rem_queue) {
+			current->signal = 0;
+			interruptible_sleep_on(&ddv_daemon_wait);
+		}
+
+		rem = rem_queue;
+		rem_queue = rem->u.next;
+		restore_flags(flags);
+			
+
+		minor = MINOR(rem->req.rq_dev);
+		len = rem->req.current_nr_sectors;
+		offset = rem->req.sector;
+
+		/* work out the conversion to the local block size from
+		   sectors */
+		for (shift=0;
+		     (SECTOR_SIZE<<shift) != ddv_blocksizes[minor];
+		     shift++) ;
+
+		/* do the request */
+		for (i=0; len; i++) {
+			bhlist[i] = getblk(rem->req.rq_dev, 
+					   offset >> shift,
+					   ddv_blocksizes[minor]);
+			if (!buffer_uptodate(bhlist[i]))
+				ll_rw_block(READ,1,&bhlist[i]);
+			offset += 1<<shift;
+			len -= 1<<shift;
+		}
+			    
+		for (j=0;j<i;j++)
+			if (!buffer_uptodate(bhlist[j]))
+				wait_on_buffer(bhlist[j]);
+		
+		
+		/* put() the data */
+		
+
+		/* release the buffers */
+		for (j=0;j<i;j++)
+			brelse(bhlist[j]);
+
+		/* tell the originator that its done */
+		rem->u.fn = ddv_rem_complete;
+		tnet_rpc(rem->cell,rem,sizeof(int)*3,1);
+	}
+}
+
+
+/* receive a remote disk request */
+static void ddv_rem_queue(char *data,unsigned size)
+{
+	unsigned flags;
+	struct remote_request *rem = (struct remote_request *)
+		kmalloc(size,GFP_ATOMIC);
+
+	if (!rem) {
+		/* oh bugger! */
+		ddv_stats.errors++;
+		return;
+	}
+
+	memcpy(rem,data,size);
+	rem->u.next = NULL;
+
+	save_flags(flags); cli();
+
+	/* add it to our remote request queue */
+	if (!rem_queue)
+		rem_queue = rem;
+	else
+		rem_queue_end->u.next = rem;
+	rem_queue_end = rem;
+
+	restore_flags(flags);
+
+	wake_up(&ddv_daemon_wait);  
+}
+
+
+/* which disk should this request go to */
+static inline unsigned pardisk_num(struct request *req)
+{
+	int minor = MINOR(req->rq_dev);
+	unsigned stripe;
+	unsigned cell;
+
+	if (minor < PARDISK_BASE)
+		return this_option;
+
+	stripe = req->sector >> STRIPE_SHIFT;
+	cell = stripe % num_options;
+
+	return cell;
+}
+
+
+/* check if a 2nd request can be tacked onto the first */
+static inline int contiguous(struct request *req1,struct request *req2)
+{
+	if (req2->cmd != req1->cmd ||
+	    req2->rq_dev != req1->rq_dev ||
+	    req2->sector != req1->sector + req1->current_nr_sectors ||
+	    req2->current_nr_sectors != req1->current_nr_sectors)
+		return 0;
+	if (pardisk_num(req1) != pardisk_num(req2))
+		return 0;
+	return 1;
+}
+
+static void ddv_request1(void)
+{
+	struct request *req,*req1,*req2;
+	unsigned offset,len,req_num,mlist,bnum,available=0;
+	static unsigned mptrs[MAX_BNUM];
+	unsigned cell;
+
+	if (in_request > REQUEST_HIGH)
+		return;
+
+	next_request = get_request_queue(next_request);
+
+	while ((req = next_request)) {
+		int minor;
+
+		if (in_request >= MAX_REQUEST)
+			return;
+
+		if (in_request>1 && req_queued<REQUEST_LOW)
+			return;
+	
+		/* make sure we have room for a request */
+		available = ddv_mlist_available();
+		if (available < 1) return;
+		if (available > MAX_BNUM)
+			available = MAX_BNUM;
+
+		offset = req->sector;
+		len = req->current_nr_sectors;
+		minor = MINOR(req->rq_dev);
+		  
+		mptrs[0] = (int)req->buffer;
+
+		for (bnum=1,req1=req,req2=req->next;
+		     req2 && bnum<available && contiguous(req1,req2);
+		     req1=req2,req2=req2->next) {
+			mptrs[bnum++] = (int)req2->buffer;
+		}
+
+		next_request = req2;
+
+
+		req_queued -= bnum;
+		ddv_stats.blocks += bnum;
+		ddv_stats.rq_started += bnum;
+
+		if (req->cmd == READ) {
+			ddv_stats.reads++;
+			ddv_stats.sectors_read += len*bnum;
+		} else {
+			ddv_stats.writes++;
+			ddv_stats.sectors_written += len*bnum;
+		}
+
+		if (minor >= PARDISK_BASE) {
+			/* translate the request to the normal partition */
+			unsigned stripe;
+			minor -= PARDISK_BASE;
+
+			stripe = offset >> STRIPE_SHIFT;
+			stripe /= num_options;
+			offset = (stripe << STRIPE_SHIFT) + 
+				(offset & ((1<<STRIPE_SHIFT)-1));
+#if AIR_DISK 
+			/* like an air-guitar :-) */
+			complete_request(req,bnum);
+			continue;
+#endif
+		}
+
+		if ((cell=pardisk_num(req)) != this_option) {
+			/* its a remote request */
+			struct remote_request *rem;
+			unsigned *remlist;
+			unsigned size = sizeof(*rem) + sizeof(int)*bnum;
+
+			rem = (struct remote_request *)kmalloc(size,GFP_ATOMIC);
+			if (!rem) {
+				/* hopefully we can get it on the next go */
+				return;
+			}
+			remlist = (unsigned *)(rem+1);
+			
+			rem->u.fn = ddv_rem_queue;
+			rem->cell = this_option;
+			rem->bnum = bnum;
+			rem->req = *req;
+			rem->reqp = req;
+			rem->req.rq_dev = MKDEV(MAJOR_NR,minor);
+			rem->req.sector = offset;
+			memcpy(remlist,mptrs,sizeof(mptrs[0])*bnum);
+			
+			if (tnet_rpc(cell,rem,size,1) != 0) {
+				kfree_s(rem,size);
+				return;
+			}
+		} else {
+			/* its a local request */
+			if ((mlist = ddv_get_mlist(mptrs,bnum)) == -1) {
+				ddv_stats.errors++;
+				panic("ddv: mlist corrupted");
+			}
+
+			req_num = RTable->cell_pointer;
+			RTable->async_info[req_num].status = 
+				req->cmd==READ?DDV_RAWREAD_REQ:DDV_RAWWRITE_REQ;
+			RTable->async_info[req_num].bnum = bnum;
+			RTable->async_info[req_num].argv[0] = mlist;
+			RTable->async_info[req_num].argv[1] = len;
+			RTable->async_info[req_num].argv[2] = offset + 
+				partition_tables[minor].start_sect;
+			RTable->async_info[req_num].argv[3] = bnum;
+			RTable->async_info[req_num].argv[7] = (unsigned)req;
+			RTable->cell_pointer = INC_T(RTable->cell_pointer);
+			
+		}
+
+		in_request++;	
+	}
+}
+
+
+static void ddv_request(void)
+{
+	cli();
+	ddv_request1();
+	sti();
+}
+
+
+static void check_printbufs(void)
+{  
+	int i;
+
+	if (!PrintBufs) return;
+
+	while (PrintBufs->option_counter != PrintBufs->cell_counter) {
+		i = PrintBufs->cell_counter;
+		printk("opiu (%d): ",i); 
+		if (((unsigned)PrintBufs->bufs[i].fmt) > 0x100000)
+			printk("Error: bad format in printk at %p\n", 
+			       PrintBufs->bufs[i].fmt);
+		else
+			printk(PrintBufs->bufs[i].fmt + OPIBUS_BASE,
+			       PrintBufs->bufs[i].args[0],
+			       PrintBufs->bufs[i].args[1],
+			       PrintBufs->bufs[i].args[2],
+			       PrintBufs->bufs[i].args[3],
+			       PrintBufs->bufs[i].args[4],
+			       PrintBufs->bufs[i].args[5]);
+		if (++PrintBufs->cell_counter == PRINT_BUFS)
+			PrintBufs->cell_counter = 0;
+	}
+}
+
+static void ddv_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	unsigned long flags;
+	save_flags(flags); cli();
+	OPT_IO(IRC1) = 0x80000000;
+
+	check_printbufs();
+	check_completion();
+
+	ddv_request1();
+	restore_flags(flags);
+}
+
+static int ddv_open(struct inode * inode, struct file * filp)
+{
+	int minor = MINOR(inode->i_rdev);
+
+	if (!have_ddv_board || minor >= NUM_DDVDEVS)
+		return -ENODEV;
+
+	if (minor >= PARDISK_BASE) {
+		ddv_sect_length[minor] = ddv_sect_length[minor - PARDISK_BASE];
+		ddv_blk_length[minor] = ddv_blk_length[minor - PARDISK_BASE];
+	}
+
+	return 0;
+}
+
+
+static void ddv_open_reply(struct cap_request *creq)
+{
+	int size = creq->size - sizeof(*creq);
+	ddv_opcodep = (char *)kmalloc(size,GFP_ATOMIC);
+	read_bif(ddv_opcodep, size);
+#if DEBUG
+	printk("received opiu kernel of size %d\n",size);
+#endif
+	if (size == 0)
+		have_ddv_board = 0;
+	wake_up(&busy_wait);	
+}
+
+
+static void ddv_load_opiu(void)
+{
+	int i;
+	struct cap_request creq;
+
+	/* if the opiu kernel is already loaded then we don't do anything */
+	if (!have_ddv_board || opiu_kernel_loaded)
+		return;
+  
+	bif_register_request(REQ_DDVOPEN,ddv_open_reply);
+
+	/* send the open request to the front end */
+	creq.cid = mpp_cid();
+	creq.type = REQ_DDVOPEN;
+	creq.header = 0;
+	creq.size = sizeof(creq);
+	
+	bif_queue(&creq,0,0);
+	
+	ddv_set_optadr();	
+
+	while (!ddv_opcodep)
+		sleep_on(&busy_wait);
+
+	if (!have_ddv_board)
+		return;
+
+	ddv_load_kernel(ddv_opcodep);
+	
+	kfree(ddv_opcodep);
+	ddv_opcodep = NULL;
+	
+	if (ddv_restart_cpu()) 
+		return;
+
+	ddv_sect_length[0] = DiskInfo->blocks;
+	ddv_blk_length[0] = DiskInfo->blocks >> 1;
+	ddv_blocksizes[0] = BLOCK_SIZE;
+
+	ddv_geometry.cylinders = ddv_sect_length[0] / 
+		(ddv_geometry.heads*ddv_geometry.sectors);
+
+	ddv_gendisk.part[0].start_sect = 0;
+	ddv_gendisk.part[0].nr_sects = ddv_sect_length[0];
+
+	resetup_one_dev(&ddv_gendisk, 0);
+	
+	for (i=0;i<PARDISK_BASE;i++) {
+		ddv_sect_length[i] = ddv_gendisk.part[i].nr_sects;
+		ddv_blk_length[i] = ddv_gendisk.part[i].nr_sects >> 1;
+	}
+
+	/* setup the parallel partitions by multiplying the normal
+	   partition by the number of options */
+	for (;i<NUM_DDVDEVS;i++) {
+		ddv_sect_length[i] = ddv_sect_length[i-PARDISK_BASE]*num_options;
+		ddv_blk_length[i] = ddv_blk_length[i-PARDISK_BASE]*num_options;
+		ddv_gendisk.part[i].start_sect = ddv_gendisk.part[i-PARDISK_BASE].start_sect;
+		ddv_gendisk.part[i].nr_sects = ddv_sect_length[i];
+	}
+
+
+	opiu_kernel_loaded = 1;       
+}
+
+
+/*
+ * This routine is called to flush all partitions and partition tables
+ * for a changed disk, and then re-read the new partition table.
+ */
+static int ddv_revalidate(kdev_t dev, struct gendisk *gdev)
+{
+	int target;
+	int max_p;
+	int start;
+	int i;
+
+	target = DEVICE_NR(dev);
+
+	max_p = gdev->max_p;
+	start = target << gdev->minor_shift;
+
+	printk("ddv_revalidate dev=%d target=%d max_p=%d start=%d\n",
+	       dev,target,max_p,start);
+
+	for (i=max_p - 1; i >=0 ; i--) {
+		int minor = start + i;
+		kdev_t devi = MKDEV(gdev->major, minor);
+		sync_dev(devi);
+		invalidate_inodes(devi);
+		invalidate_buffers(devi);
+		gdev->part[minor].start_sect = 0;
+		gdev->part[minor].nr_sects = 0;
+	};
+
+	ddv_sect_length[start] = DiskInfo->blocks;
+	ddv_blk_length[start] = DiskInfo->blocks >> 1;
+
+	gdev->part[start].nr_sects = ddv_sect_length[start];
+	resetup_one_dev(gdev, target);
+
+	printk("sect_length[%d]=%d blk_length[%d]=%d\n",
+	       start,ddv_sect_length[start],
+	       start,ddv_blk_length[start]);
+
+	for (i=0;i<max_p;i++) {
+		ddv_sect_length[start+i] = gdev->part[start+i].nr_sects;
+		ddv_blk_length[start+i] = gdev->part[start+i].nr_sects >> 1;
+		if (gdev->part[start+i].nr_sects)
+			printk("partition[%d] start=%d length=%d\n",i,
+			       (int)gdev->part[start+i].start_sect,
+			       (int)gdev->part[start+i].nr_sects);
+	}
+
+	return 0;
+}
+
+
+
+
+static int ddv_ioctl(struct inode *inode, struct file *file, 
+		    unsigned int cmd, unsigned long arg)
+{
+	int err;
+	struct ddv_geometry *loc = (struct ddv_geometry *) arg;
+	int dev;
+	int minor = MINOR(inode->i_rdev);
+  
+	if ((!inode) || !(inode->i_rdev))
+		return -EINVAL;
+	dev = DEVICE_NR(inode->i_rdev);
+#if DEBUG
+	printk("ddv_ioctl: cmd=%x dev=%x minor=%d\n", cmd, dev, minor);
+#endif
+	switch (cmd) {
+	case HDIO_GETGEO:
+		printk("\tHDIO_GETGEO\n");
+		if (!loc)  return -EINVAL;
+		if (put_user(ddv_geometry.heads, (char *) &loc->heads)) return -EFAULT;
+		if (put_user(ddv_geometry.sectors, (char *) &loc->sectors)) return -EFAULT;
+		if (put_user(ddv_geometry.cylinders, (short *) &loc->cylinders)) return -EFAULT;
+		if (put_user(ddv_geometry.start, (long *) &loc->start)) return -EFAULT;
+		return 0;
+  
+	case HDIO_GET_MULTCOUNT :
+		printk("\tHDIO_GET_MULTCOUNT\n");
+		return -EINVAL;
+
+	case HDIO_GET_IDENTITY :
+		printk("\tHDIO_GET_IDENTITY\n");
+		return -EINVAL;
+
+	case HDIO_GET_NOWERR :
+		printk("\tHDIO_GET_NOWERR\n");
+		return -EINVAL;
+
+	case HDIO_SET_NOWERR :
+		printk("\tHDIO_SET_NOWERR\n");
+		return -EINVAL;
+
+	case BLKRRPART:
+		printk("\tBLKRRPART\n");
+		return ddv_revalidate(inode->i_rdev,&ddv_gendisk);
+
+	case BLKGETSIZE:   /* Return device size */
+		if (put_user(ddv_sect_length[minor],(long *) arg)) return -EFAULT;
+#if DEBUG
+		printk("BLKGETSIZE gave %d\n",ddv_sect_length[minor]);
+#endif
+		return 0;
+    
+	default:
+		printk("ddv_ioctl: Invalid cmd=%d(0x%x)\n", cmd, cmd);
+		return -EINVAL;
+	};
+}
+
+static struct file_operations ddv_fops = {
+        NULL,                   /* lseek - default */
+        block_read,         /* read */
+        block_write,        /* write */
+        NULL,                   /* readdir - bad */
+        NULL,                   /* poll */
+        ddv_ioctl,               /* ioctl */
+        NULL,                   /* mmap */
+        ddv_open,                /* open */
+	ddv_release,
+        block_fsync          /* fsync */
+};
+
+
+static void ddv_status(void)
+{
+	if (!have_ddv_board) {
+		printk("no ddv board\n");
+		return;
+	}
+
+	printk("
+in_request %u   req_queued %u
+MTable:    start=%u end=%u
+Requests:  started=%u finished=%u
+Requests:  completion_pointer=%u ddv_pointer=%u cell_pointer=%u
+PrintBufs: option_counter=%u cell_counter=%u
+ddv_stats: reads=%u writes=%u blocks=%u
+ddv_stats: sectors_read=%u sectors_written=%u
+CURRENT=%p next_request=%p errors=%u
+",
+	       in_request,req_queued,
+	       RTable->start_mtable,RTable->end_mtable,
+	       ddv_stats.rq_started,ddv_stats.rq_finished,
+	       completion_pointer,RTable->ddv_pointer,RTable->cell_pointer,
+	       PrintBufs->option_counter,PrintBufs->cell_counter,
+	       ddv_stats.reads,ddv_stats.writes,ddv_stats.blocks,
+	       ddv_stats.sectors_read,ddv_stats.sectors_written,
+	       CURRENT,next_request,
+	       ddv_stats.errors);	
+}
+
+
+int ddv_init(void)
+{
+	int cid;
+	
+	cid = mpp_cid();
+	
+	if (register_blkdev(MAJOR_NR,DEVICE_NAME,&ddv_fops)) {
+		printk("ap: unable to get major %d for ap block dev\n",
+		       MAJOR_NR);
+		return -1;
+	}
+
+	printk("ddv_init: register dev %d\n", MAJOR_NR);
+	blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
+	read_ahead[MAJOR_NR] = DDV_READ_AHEAD;
+	
+	bif_add_debug_key('d',ddv_status,"DDV status");
+	ddv_gendisk.next = gendisk_head;
+	gendisk_head = &ddv_gendisk;
+
+	num_options = mpp_num_cells();
+	this_option = mpp_cid();
+
+	kernel_thread(ddv_daemon, NULL, 0);
+
+	return(0);
+}
+
+
+static void ddv_geninit(struct gendisk *ignored)
+{
+	int i;
+	static int done = 0;
+
+	if (done)
+		printk("ddv_geninit already done!\n");
+
+	done = 1;
+
+	printk("ddv_geninit\n");
+
+	/* request interrupt line 2 */
+	if (request_irq(APOPT0_IRQ,ddv_interrupt,SA_INTERRUPT,"apddv",NULL)) {
+		printk("Failed to install ddv interrupt handler\n");
+	}
+	
+	for (i=0;i<NUM_DDVDEVS;i++) {
+		ddv_blocksizes[i] = BLOCK_SIZE;
+		ddv_sect_length[i] = 0;
+		ddv_blk_length[i] = 0;
+	}
+
+	ddv_geometry.heads = 32;
+	ddv_geometry.sectors = 32;
+	ddv_geometry.cylinders = 1;
+	ddv_geometry.start = 0;
+	
+	blksize_size[MAJOR_NR] = ddv_blocksizes;
+
+	ddv_load_opiu();
+}
+
+
+/* loadable module support */
+
+#ifdef MODULE
+
+int init_module(void)
+{
+	int error = ddv_init();
+	ddv_geninit(&(struct gendisk) { 0,0,0,0,0,0,0,0,0,0,0 });
+	if (!error)
+		printk(KERN_INFO "DDV: Loaded as module.\n");
+	return error;
+}
+
+/* Before freeing the module, invalidate all of the protected buffers! */
+void cleanup_module(void)
+{
+	int i;
+
+	for (i = 0 ; i < NUM_DDVDEVS; i++)
+		invalidate_buffers(MKDEV(MAJOR_NR, i));
+
+	/* reset the opiu */
+	OPT_IO(OPIU_OP) = OPIU_RESET; 
+	OPT_IO(PRST) = PRST_IRST; 
+
+	unregister_blkdev( MAJOR_NR, DEVICE_NAME );
+	free_irq(APOPT0_IRQ, NULL);
+	blk_dev[MAJOR_NR].request_fn = 0;
+}
+
+#endif  /* MODULE */
+
+

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov