patch-2.2.17 linux/drivers/s390/block/dasd_diag.c

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

diff -u --recursive --new-file v2.2.16/drivers/s390/block/dasd_diag.c linux/drivers/s390/block/dasd_diag.c
@@ -0,0 +1,555 @@
+/* 
+ * File...........: linux/drivers/s390/block/dasd_diag.c
+ * Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
+ * Based on.......: linux/drivers/s390/block/mdisk.c
+ * ...............: by Hartmunt Penner <hpenner@de.ibm.com>
+ * Bugreports.to..: <Linux390@de.ibm.com>
+ * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999,2000
+ */
+
+#include <linux/stddef.h>
+#include <linux/kernel.h>
+#include <asm/debug.h>
+
+#include <linux/malloc.h>
+#include <linux/hdreg.h>	/* HDIO_GETGEO                      */
+#include <linux/blk.h>
+#include <asm/ccwcache.h>
+#include <asm/dasd.h>
+
+#include <asm/ebcdic.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+
+#include "dasd.h"
+#include "dasd_diag.h"
+
+
+#ifdef PRINTK_HEADER
+#undef PRINTK_HEADER
+#endif				/* PRINTK_HEADER */
+#define PRINTK_HEADER DASD_NAME"(diag):"
+
+dasd_discipline_t dasd_diag_discipline;
+
+
+
+typedef struct
+dasd_diag_private_t {
+        dasd_diag_characteristics_t rdc_data;
+	diag_rw_io_t iob;
+	diag_init_io_t iib;
+	unsigned long *label;
+} dasd_diag_private_t;
+
+static __inline__ int
+dia210 (void *devchar)
+{
+	int rc;
+
+	__asm__ __volatile__ ("    lr    2,%1\n"
+                              "    .long 0x83200210\n"
+                              "0:  ipm   %0\n"
+                              "    srl   %0,28"
+                              :"=d" (rc)
+                              :"d" ((void *) __pa (devchar))
+                              :"2");
+	return rc;
+}
+
+static __inline__ int
+dia250 (void *iob, int cmd)
+{
+	int rc;
+
+	__asm__ __volatile__ ("    lr    2,%1\n"
+                              "    lr    3,%2\n"
+                              "    .long 0x83230250\n"
+                              "    lr    %0,3"
+                              :"=d" (rc)
+                              :"d" ((void *) __pa (iob)), "d" (cmd)
+                              :"2", "3");
+	return rc;
+}
+
+static __inline__ int
+mdsk_init_io (dasd_device_t *device, int blocksize, int offset, int size)
+{
+        dasd_diag_private_t *private = (dasd_diag_private_t *)device->private;
+	diag_init_io_t *iib = &private->iib;
+	int rc;
+
+	memset (iib, 0, sizeof (diag_init_io_t));
+
+	iib->dev_nr = device->devinfo.devno;
+	iib->block_size = blocksize;
+	iib->offset = offset;
+	iib->start_block = 0;
+	iib->end_block = size;
+
+	rc = dia250 (iib, INIT_BIO);
+
+	return rc;
+}
+
+static __inline__ int
+mdsk_term_io (dasd_device_t *device)
+{
+        dasd_diag_private_t *private = (dasd_diag_private_t *)device->private;
+	diag_init_io_t *iib = &private->iib;
+	int rc;
+	
+	memset (iib, 0, sizeof (diag_init_io_t));
+	iib->dev_nr = device->devinfo.devno;
+	rc = dia250 (iib, TERM_BIO);
+	return rc;
+}
+
+int
+dasd_start_diag (ccw_req_t * cqr)
+{
+	int rc;
+	dasd_device_t *device = cqr->device;
+        dasd_diag_private_t *private;
+	diag_rw_io_t *iob;
+
+        private = (dasd_diag_private_t *)device->private;
+	iob = &private->iob;
+
+	iob->dev_nr = device->devinfo.devno;
+	iob->key = 0;
+	iob->flags = 2;
+	iob->block_count = cqr->cplength >> 1;
+	iob->interrupt_params = (u32) cqr;
+	iob->bio_list = __pa (cqr->cpaddr);
+
+	asm volatile ("STCK %0":"=m" (cqr->startclk));
+	rc = dia250 (iob, RW_BIO);
+	if (rc > 8) {
+		PRINT_WARN ("dia250 returned CC %d\n", rc);
+		atomic_compare_and_swap_debug(&cqr->status, 
+                                              CQR_STATUS_QUEUED, 
+                                              CQR_STATUS_ERROR);
+	} else {
+		if ( cqr->expires ) {
+			cqr->expires += cqr->startclk;
+		}
+		atomic_compare_and_swap_debug(&cqr->status, 
+                                              CQR_STATUS_QUEUED, 
+                                              CQR_STATUS_IN_IO);
+                rc = 0;
+	}
+	return rc;
+}
+
+void 
+dasd_ext_handler (struct pt_regs *regs, __u16 code)
+{
+	ccw_req_t *cqr;
+	int ip = S390_lowcore.ext_params;
+	char status = *((char *) S390_lowcore.ext_params + 5);
+        dasd_device_t *device;
+	int done_fast_io = 0;
+        int devno,devindex;
+
+	if (!ip) {		/* no intparm: unsolicited interrupt */
+		printk ( KERN_WARNING PRINTK_HEADER
+			 "caught unsolicited interrupt\n");
+		return;
+	}
+	if (ip & 0x80000001) {
+		printk ( KERN_WARNING PRINTK_HEADER
+			 "caught spurious interrupt with parm %08x\n",
+			 ip);
+		return;
+	}
+	cqr = (ccw_req_t *) ip;
+	device = (dasd_device_t *) cqr->device;
+	devno = device->devinfo.devno;
+	devindex = devindex_from_devno (devno);
+	if (device == NULL) {
+		printk (KERN_WARNING PRINTK_HEADER
+			" INT on devno 0x%04X  = /dev/%s (%d:%d)"
+			" belongs to NULL device\n",
+			devno, device->name, major_from_devindex (devindex), devindex << DASD_PARTN_BITS);
+	}
+	if (strncmp (device->discipline->ebcname, (char *) &cqr->magic, 4)) {
+		printk (KERN_WARNING PRINTK_HEADER
+			"0x%04X : /dev/%s (%d:%d)"
+			" magic number of ccw_req_t 0x%08lX doesn't match"
+			" discipline 0x%08X\n",
+			devno, device->name,
+			major_from_devindex (devindex),
+			devindex << DASD_PARTN_BITS,
+			cqr->magic, *(int *) (&device->discipline->name));
+		return;
+	}
+	asm volatile ("STCK %0":"=m" (cqr->stopclk));
+	switch (status) {
+	case 0x00:
+		atomic_compare_and_swap_debug (&cqr->status, 
+					       CQR_STATUS_IN_IO, 
+					       CQR_STATUS_DONE);
+		atomic_compare_and_swap (DASD_DEVICE_LEVEL_ANALYSIS_PENDING,
+					 DASD_DEVICE_LEVEL_ANALYSIS_PREPARED,
+					 &device->level);
+		if (cqr->next &&
+		    (atomic_read (&cqr->next->status) ==
+		     CQR_STATUS_QUEUED)) {
+			if (dasd_start_diag (cqr->next) == 0) {
+				done_fast_io = 1;
+			}
+		}
+
+		break;
+	case 0x01:
+	case 0x02:
+	case 0x03:
+	default:
+		atomic_compare_and_swap_debug (&cqr->status, 
+					       CQR_STATUS_IN_IO, 
+					       CQR_STATUS_FAILED);
+		atomic_inc (&device->queue.dirty_requests);
+		break;
+	}
+	if (done_fast_io == 0)
+		atomic_clear_mask (DASD_CHANQ_BUSY, &device->queue.flags);
+
+#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,3,98))
+        wake_up (&device->wait_q);
+#else
+	if (device->wait_q) {
+		wake_up (&device->wait_q);
+	}
+#endif /* LINUX_VERSION_CODE */
+	dasd_schedule_bh ();
+}
+
+static int
+dasd_diag_check_characteristics (struct dasd_device_t *device)
+{
+	int rc = 0;
+	int bsize;
+	int label_block;
+        dasd_diag_private_t *private;
+	dasd_diag_characteristics_t *rdc_data;
+        ccw_req_t *cqr;
+	long *label;
+        int sb;
+
+        if ( device == NULL ) {
+                printk ( KERN_WARNING PRINTK_HEADER
+                         "Null device pointer passed to characteristics checker\n");
+                return -ENODEV;
+        }
+        if ( device->private == NULL ) {
+		device->private = kmalloc(sizeof(dasd_diag_private_t),GFP_KERNEL);
+		if ( device->private == NULL ) {
+			printk ( KERN_WARNING PRINTK_HEADER
+				 "memory allocation failed for private data\n");
+			return -ENOMEM;
+		}
+        }
+        private = (dasd_diag_private_t *)device->private;
+        rdc_data = (void *)&(private->rdc_data);
+	
+	rdc_data->dev_nr = device->devinfo.devno;
+	rdc_data->rdc_len = sizeof (dasd_diag_characteristics_t);
+	
+	if (dia210 (rdc_data) != 0) {
+	  return -ENODEV;
+	}
+	if (rdc_data->vdev_class != DEV_CLASS_FBA &&
+	    rdc_data->vdev_class != DEV_CLASS_ECKD &&
+	    rdc_data->vdev_class != DEV_CLASS_CKD) {
+		return -ENODEV;
+	}
+#if 0
+	printk ( KERN_INFO PRINTK_HEADER 
+		 "%04X: %04X on real %04X/%02X\n",
+		 rdc_data->dev_nr,
+		 rdc_data->vdev_type,
+		 rdc_data->rdev_type,rdc_data->rdev_model);
+#endif
+	/* Figure out position of label block */
+	if (private->rdc_data.vdev_class == DEV_CLASS_FBA) {
+		label_block = 1;
+	} else if (private->rdc_data.vdev_class == DEV_CLASS_ECKD ||
+		   private->rdc_data.vdev_class == DEV_CLASS_CKD) {
+		label_block = 2;
+	} else {
+		return -ENODEV;
+	}
+	private->label = (long *) get_free_page (GFP_KERNEL);
+        label = private->label;	
+	mdsk_term_io (device); /* first terminate all outstanding operations */
+	/* figure out blocksize of device */
+	for (bsize = 512; bsize <= PAGE_SIZE; bsize <<= 1) {
+                diag_bio_t *bio;
+                diag_rw_io_t *iob = &private ->iob;
+        
+                rc = mdsk_init_io (device, bsize, 0, 64);
+                if (rc > 4) {
+                        continue;
+		}
+                cqr = ccw_alloc_request (dasd_diag_discipline.name, 2,0);
+                if ( cqr == NULL ) {
+                        printk ( KERN_WARNING PRINTK_HEADER
+                                 "No memory to allocate initialization request\n");
+                        return -ENOMEM;
+                }
+                bio = (diag_bio_t *) (cqr->cpaddr);
+                memset (bio, 0, sizeof (diag_bio_t));
+                bio->type = MDSK_READ_REQ;
+                bio->block_number = label_block + 1;
+                bio->buffer = __pa (private->label);
+                cqr->device = device;
+                atomic_set (&cqr->status, CQR_STATUS_FILLED);
+                memset (iob, 0, sizeof(diag_rw_io_t));
+                iob->dev_nr      = rdc_data->dev_nr;
+                iob->key         = 0;
+                iob->flags       = 0;
+                iob->block_count = 1;
+                iob->interrupt_params = cqr;
+                iob->bio_list     = virt_to_phys(bio);
+                rc = dia250(iob,RW_BIO);
+                if ( rc == 0 ) {
+                        if (label[3] != bsize) {
+/*                                printk ( KERN_INFO PRINTK_HEADER 
+                                         "%04X mismatching blocksizes disk %d, reserved file %d\n", 
+                                         device->devinfo.devno, bsize, label[3]);
+*/  
+                              return -EINVAL; 
+                        }
+                        if (label[0] != 0xc3d4e2f1) {	/* CMS1 */
+/*                                PRINT_WARN ("volume %04X is not CMS formatted, accessing anyway\n", 
+                                            device->devinfo.devno);
+*/
+                                return -ENODEV;
+                        }
+                        if (label[13] == 0) {
+/*                                PRINT_WARN ("%04X is not reserved, accessing anyway\n",  
+                                  device->devinfo.devno); */
+                                return -ENODEV;
+                        }
+                        
+                        break;
+                }
+		mdsk_term_io (device);
+	}
+	device->sizes.bp_block = bsize;
+        device->sizes.s2b_shift = 0;	/* bits to shift 512 to get a block */
+	for (sb = 512; sb < bsize; sb = sb << 1)
+		device->sizes.s2b_shift++;
+
+	return rc;
+}
+
+
+static int
+dasd_diag_do_analysis (struct dasd_device_t *device)
+{
+	int sb;
+        dasd_diag_private_t *private = (dasd_diag_private_t *)device->private;
+
+	long *label = private->label;
+	
+	/* real size of the volume */
+        device->sizes.blocks = label[7];
+	
+        printk ( KERN_INFO PRINTK_HEADER 
+                 "/dev/%s (%04X): capacity (%dkB blks): %dkB\n",
+                 device->name, device->devinfo.devno,
+                 (device->sizes.bp_block>>10),
+		 (device->sizes.blocks<<device->sizes.s2b_shift)>>1);
+	return 0;
+}
+
+
+static int
+dasd_diag_fill_geometry(struct dasd_device_t *device, struct hd_geometry *geo)
+{
+        int rc = 0;
+        dasd_diag_private_t *private = (dasd_diag_private_t *)device->private;
+	unsigned long sectors = device->sizes.blocks << device->sizes.s2b_shift;
+	unsigned long tracks = sectors >> 6;
+	unsigned long trk_rem = sectors & ((1<<6)-1); /* 64k per track */
+	unsigned long cyls = tracks >> 4;
+	unsigned long cyls_rem = tracks & ((1<<4)-1); /* 16 head per cyl */
+	
+        switch(device->sizes.bp_block) {
+        case 512:
+        case 1024:
+        case 2048:
+        case 4096:
+                break;
+        default:
+                return -EINVAL;
+        }
+	geo->cylinders = cyls;
+	geo->heads = 16;
+	geo->sectors = 128 >> device->sizes.s2b_shift;
+	if (private->rdc_data.vdev_class == DEV_CLASS_FBA) {
+		geo->start = 1;
+	} else if (private->rdc_data.vdev_class == DEV_CLASS_ECKD ||
+		   private->rdc_data.vdev_class == DEV_CLASS_CKD) {
+		geo->start = 2;
+	} else {
+		return -EINVAL;
+	}
+        return rc;
+}
+
+static dasd_era_t
+dasd_diag_examine_error  (ccw_req_t *cqr, devstat_t * stat)
+{
+	return dasd_era_fatal;
+}
+
+static dasd_erp_action_fn_t 
+dasd_diag_erp_action ( ccw_req_t * cqr ) 
+{
+        return default_erp_action;
+}
+
+static dasd_erp_postaction_fn_t
+dasd_diag_erp_postaction (ccw_req_t * cqr)
+{
+        if ( cqr -> function == default_erp_action)
+                return default_erp_postaction;
+        printk ( KERN_WARNING PRINTK_HEADER
+                 "unknown ERP action %p\n",
+                 cqr -> function);
+	return NULL;
+}
+
+static ccw_req_t *
+dasd_diag_build_cp_from_req (dasd_device_t *device, struct request *req)
+{
+	ccw_req_t *rw_cp = NULL;
+	struct buffer_head *bh;
+	int rw_cmd;
+	int noblk = req->nr_sectors >> device->sizes.s2b_shift;
+	int byt_per_blk = device->sizes.bp_block;
+	int block;
+	diag_bio_t *bio;
+	int bhct;
+	long size;
+
+	if (!noblk) {
+		PRINT_ERR ("No blocks to read/write...returning\n");
+		return NULL;
+	}
+	if (req->cmd == READ) {
+		rw_cmd = MDSK_READ_REQ;
+	} else
+	if (req->cmd == WRITE)
+	{
+		rw_cmd = MDSK_WRITE_REQ;
+	}
+	bhct = 0;
+	for (bh = req->bh; bh; bh = bh->b_reqnext) {
+		if (bh->b_size > byt_per_blk)
+			for (size = 0; size < bh->b_size; size += byt_per_blk)
+				bhct++;
+		else
+			bhct++;
+	}
+	/* Build the request */
+        rw_cp = dasd_alloc_request (dasd_diag_discipline.name, 2 * bhct, 0);
+	if (!rw_cp) {
+		return NULL;
+	}
+	bio = (diag_bio_t *) (rw_cp->cpaddr);
+	
+	block = req->sector >> device->sizes.s2b_shift;
+	for (bh = req->bh; bh; bh = bh->b_reqnext) {
+		if (bh->b_size >= byt_per_blk) {
+			memset (bio, 0, sizeof (diag_bio_t));
+			for (size = 0; size < bh->b_size; size += byt_per_blk) {
+				bio->type = rw_cmd;
+				bio->block_number = block + 1;
+				bio->buffer = __pa (bh->b_data + size);
+				bio++;
+				block++;
+			}
+		} else {
+			PRINT_WARN ("Cannot fulfill request smaller than block\n");
+			ccw_free_request (rw_cp);
+			return NULL;
+		}
+	}
+        rw_cp->device = device;
+        rw_cp->expires = 5 * 0xf424000; /* 5 seconds */
+        rw_cp->req = req;
+        atomic_compare_and_swap_debug(&rw_cp->status,CQR_STATUS_EMPTY,CQR_STATUS_FILLED);
+	return rw_cp;
+}
+
+static char *
+dasd_diag_dump_sense(struct dasd_device_t *device, ccw_req_t *req)
+{
+        char *page = (char *)get_free_page(GFP_KERNEL);
+        int len;
+        if ( page == NULL ) {
+                return NULL;
+        }
+
+        len = sprintf ( page, KERN_WARNING PRINTK_HEADER 
+                        "device %04X on irq %d: I/O status report:\n",
+                        device->devinfo.devno,device->devinfo.irq);
+                        
+
+        return page;
+}
+
+dasd_discipline_t dasd_diag_discipline = {
+	name :                          "DIAG",
+	ebcname :                       "DIAG",
+        check_characteristics:          dasd_diag_check_characteristics,
+        do_analysis:                    dasd_diag_do_analysis,          
+        fill_geometry:                  dasd_diag_fill_geometry,
+        start_IO:                       dasd_start_diag,           
+        examine_error:                  dasd_diag_examine_error,          
+        erp_action:                     dasd_diag_erp_action,             
+        erp_postaction:                 dasd_diag_erp_postaction,         
+        build_cp_from_req:              dasd_diag_build_cp_from_req,      
+        dump_sense:                     dasd_diag_dump_sense,            
+        int_handler:                    dasd_ext_handler            
+};
+
+int
+dasd_diag_init( void ) {
+        int rc = 0;
+        if ( MACHINE_IS_VM ) {
+          printk ( KERN_INFO PRINTK_HEADER
+                   "%s discipline initializing\n", dasd_diag_discipline.name);
+          ASCEBC(dasd_diag_discipline.ebcname,4);
+          ctl_set_bit (0, 9);
+          register_external_interrupt (0x2603, dasd_ext_handler);
+          dasd_discipline_enq(&dasd_diag_discipline);
+        } else {
+          printk ( KERN_INFO PRINTK_HEADER
+                   "Machine is not VM: %s discipline not initializing\n", dasd_diag_discipline.name);
+          rc = -EINVAL;
+        }
+        return rc;
+}
+/*
+ * Overrides for Emacs so that we follow Linus's tabbing style.
+ * Emacs will notice this stuff at the end of the file and automatically
+ * adjust the settings for this buffer only.  This must remain at the end
+ * of the file.
+ * ---------------------------------------------------------------------------
+ * Local variables:
+ * c-indent-level: 4 
+ * c-brace-imaginary-offset: 0
+ * c-brace-offset: -4
+ * c-argdecl-indent: 4
+ * c-label-offset: -4
+ * c-continued-statement-offset: 4
+ * c-continued-brace-offset: 0
+ * indent-tabs-mode: nil
+ * tab-width: 8
+ * End:
+ */

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