/* * 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 #include char kernel_version [] = UTS_RELEASE; #include #include #include /* printk() */ #include /* kmalloc() */ #include /* vmalloc() */ #include /* everything... */ #include /* error codes */ #include #include /* size_t */ #include /* O_ACCMODE */ #include /* HDIO_GETGEO */ #include /* initfunc */ #include #include #include /* cli(), *_flags */ #include /* access_ok */ #include /* virt_to_phys */ /* Added statement HSM 12/03/99 */ #include #define MAJOR_NR MDISK_MAJOR /* force definitions on in blk.h */ #include #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=:::,::... * ::: can be shortened to : with offset=0,blksize=512 */ int __init mdisk_setup(char *str) { char *cur = str; int vdev, size, offset=0,blksize; static int 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 != 0 && *cur != ',') { 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 1; } 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 1; syntax_error: printk(KERN_WARNING "mnd: syntax error in parameter string: %s\n", str); return 0; } __setup("mdisk=", mdisk_setup); /* * 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; 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 block_device_operations mdisk_fops = { ioctl: mdisk_ioctl, open: mdisk_open, release: mdisk_release, }; /* * 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); return rc; } /* * The device characteristics function */ static __inline__ int dia210(void* devchar) { int rc; devchar = (void*) virt_to_phys(devchar); asm volatile (" lr 2,%1\n" " .long 0x83200210\n" " ipm %0\n" " srl %0,28" : "=d" (rc) : "d" (devchar) : "2" ); return rc; } /* * read the label of a minidisk and extract its characteristics */ static __inline__ int mdisk_read_label (mdisk_Dev *dev, int i) { static mdisk_dev_char_t devchar; static long label[1024]; int block, b; int rc; mdisk_bio_t *bio; devchar.dev_nr = dev -> vdev; devchar.rdc_len = sizeof(mdisk_dev_char_t); if (dia210(&devchar) == 0) { if (devchar.vdev_class == DEV_CLASS_FBA) { block = 2; } else { block = 3; } bio = dev->bio; for (b=512;b<4097;b=b*2) { rc = mdisk_init_io(dev, b, 0, 64); if (rc > 4) { continue; } memset(&bio[0], 0, sizeof(mdisk_bio_t)); bio[0].type = MDISK_READ_REQ; bio[0].block_number = block; bio[0].buffer = virt_to_phys(&label); dev->nr_bhs = 1; if (mdisk_rw_io_clustered(dev, &bio[0], 1, (unsigned long) dev, MDISK_SYNC) == 0 ) { if (label[0] != 0xc3d4e2f1) { /* CMS1 */ printk ( KERN_WARNING "mnd: %4lX " "is not CMS format\n", mdisk_setup_data.vdev[i]); rc = mdisk_term_io(dev); return 1; } if (label[13] == 0) { printk ( KERN_WARNING "mnd: %4lX " "is not reserved\n", mdisk_setup_data.vdev[i]); rc = mdisk_term_io(dev); return 2; } mdisk_setup_data.size[i] = (label[7] - 1 - label[13]) * (label[3] >> 9) >> 1; mdisk_setup_data.blksize[i] = label[3]; mdisk_setup_data.offset[i] = label[13] + 1; rc = mdisk_term_io(dev); return rc; } rc = mdisk_term_io(dev); } printk ( KERN_WARNING "mnd: Cannot read label of %4lX " "- is it formatted?\n", mdisk_setup_data.vdev[i]); return 3; } return 4; } /* * 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(request_queue_t *queue) { 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 )) > 8 ) { 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 if (rc == 0) 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 */ if (CURRENT) mdisk_end_request(dev->nr_bhs); } /* * if more to do, call mdisk_request */ if (CURRENT) mdisk_request(NULL); 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"); } int __init mdisk_init(void) { int rc,i; mdisk_Dev *dev; request_queue_t *q; /* * 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; } q = BLK_DEFAULT_QUEUE(MAJOR_NR); blk_init_queue(q, mdisk_request); blk_queue_headactive(BLK_DEFAULT_QUEUE(major), 0); /* * 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; blk_init_queue(BLK_DEFAULT_QUEUE(MAJOR_NR), DEVICE_REQUEST); for (i=0;ibio=mdisk_bio[i]; dev->iob=&mdisk_iob[i]; dev->vdev = mdisk_setup_data.vdev[i]; if ( mdisk_setup_data.size[i] == 0 ) rc = mdisk_read_label(dev, 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_sizes[i] = mdisk_setup_data.size[i]; 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; }