/*************************************************************************** * * drivers/s390/char/tapechar.c * character device frontend for tape device driver * * S390 and zSeries version * Copyright (C) 2001 IBM Corporation * Author(s): Carsten Otte * Tuan Ngo-Anh * * **************************************************************************** */ #include "tapedefs.h" #include #include #include #include #include /* CCW allocations */ #include #include #include #include #include #ifdef MODULE #define __NO_VERSION__ #include #endif #include "tape.h" #include "tapechar.h" #define PRINTK_HEADER "TCHAR:" /* * file operation structure for tape devices */ static struct file_operations tape_fops = { // owner : THIS_MODULE, llseek:NULL, /* lseek - default */ read:tape_read, /* read */ write:tape_write, /* write */ readdir:NULL, /* readdir - bad */ poll:NULL, /* poll */ ioctl:tape_ioctl, /* ioctl */ mmap:NULL, /* mmap */ open:tape_open, /* open */ flush:NULL, /* flush */ release:tape_release, /* release */ fsync:NULL, /* fsync */ fasync:NULL, /* fasync */ lock:NULL, }; int tape_major = TAPE_MAJOR; #ifdef CONFIG_DEVFS_FS void tapechar_mkdevfstree (tape_info_t* ti) { ti->devfs_char_dir=devfs_mk_dir (ti->devfs_dir, "char", ti); ti->devfs_nonrewinding=devfs_register(ti->devfs_char_dir, "nonrewinding", DEVFS_FL_DEFAULT,tape_major, ti->nor_minor, TAPECHAR_DEFAULTMODE, &tape_fops, ti); ti->devfs_rewinding=devfs_register(ti->devfs_char_dir, "rewinding", DEVFS_FL_DEFAULT, tape_major, ti->rew_minor, TAPECHAR_DEFAULTMODE, &tape_fops, ti); } void tapechar_rmdevfstree (tape_info_t* ti) { devfs_unregister(ti->devfs_nonrewinding); devfs_unregister(ti->devfs_rewinding); devfs_unregister(ti->devfs_char_dir); } #endif void tapechar_setup (tape_info_t * ti) { #ifdef CONFIG_DEVFS_FS tapechar_mkdevfstree(ti); #endif } void tapechar_init (void) { int result; tape_frontend_t *charfront,*temp; tape_info_t* ti; tape_init(); /* Register the tape major number to the kernel */ #ifdef CONFIG_DEVFS_FS result = devfs_register_chrdev (tape_major, "tape", &tape_fops); #else result = register_chrdev (tape_major, "tape", &tape_fops); #endif if (result < 0) { PRINT_WARN (KERN_ERR "tape: can't get major %d\n", tape_major); #ifdef TAPE_DEBUG debug_text_event (tape_debug_area,3,"c:initfail"); debug_text_event (tape_debug_area,3,"regchrfail"); #endif /* TAPE_DEBUG */ panic ("no major number available for tape char device"); } if (tape_major == 0) tape_major = result; /* accept dynamic major number */ PRINT_WARN (KERN_ERR " tape gets major %d for character device\n", result); charfront = kmalloc (sizeof (tape_frontend_t), GFP_KERNEL); if (charfront == NULL) { #ifdef TAPE_DEBUG debug_text_event (tape_debug_area,3,"c:initfail"); debug_text_event (tape_debug_area,3,"no mem"); #endif /* TAPE_DEBUG */ panic ("no major number available for tape char device"); } charfront->device_setup = tapechar_setup; #ifdef CONFIG_DEVFS_FS charfront->mkdevfstree = tapechar_mkdevfstree; charfront->rmdevfstree = tapechar_rmdevfstree; #endif #ifdef TAPE_DEBUG debug_text_event (tape_debug_area,3,"c:init ok"); #endif /* TAPE_DEBUG */ charfront->next=NULL; if (first_frontend==NULL) { first_frontend=charfront; } else { temp=first_frontend; while (temp->next!=NULL) temp=temp->next; temp->next=charfront; } ti=first_tape_info; while (ti!=NULL) { tapechar_setup(ti); ti=ti->next; } } void tapechar_uninit (void) { unregister_chrdev (tape_major, "tape"); } /* * Tape device read function */ ssize_t tape_read (struct file *filp, char *data, size_t count, loff_t * ppos) { long lockflags; tape_info_t *ti; size_t block_size; ccw_req_t *cqr; int rc; #ifdef TAPE_DEBUG debug_text_event (tape_debug_area,6,"c:read"); #endif /* TAPE_DEBUG */ ti = first_tape_info; while ((ti != NULL) && (ti->rew_filp != filp) && (ti->nor_filp != filp)) ti = (tape_info_t *) ti->next; if (ti == NULL) { #ifdef TAPE_DEBUG debug_text_event (tape_debug_area,6,"c:nodev"); #endif /* TAPE_DEBUG */ return -ENODEV; } if (ppos != &filp->f_pos) { /* "A request was outside the capabilities of the device." */ #ifdef TAPE_DEBUG debug_text_event (tape_debug_area,6,"c:ppos wrong"); #endif /* TAPE_DEBUG */ return -EOVERFLOW; /* errno=75 Value too large for def. data type */ } if (ti->block_size == 0) { block_size = count; } else { block_size = ti->block_size; } #ifdef TAPE_DEBUG debug_text_event (tape_debug_area,6,"c:nbytes:"); debug_int_event (tape_debug_area,6,block_size); #endif cqr = ti->discipline->read_block (data, block_size, ti); if (!cqr) { return -ENOBUFS; } s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags); ti->cqr = cqr; ti->wanna_wakeup=0; rc = do_IO (ti->devinfo.irq, cqr->cpaddr, (unsigned long) cqr, 0x00, cqr->options); if (rc) { tapestate_set(ti,TS_IDLE); kfree (cqr); s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags); return rc; } s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags); wait_event (ti->wq,ti->wanna_wakeup); ti->cqr = NULL; ti->discipline->free_read_block (cqr, ti); s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags); if (tapestate_get (ti) == TS_FAILED) { tapestate_set (ti, TS_IDLE); s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags); return ti->rc; } if (tapestate_get (ti) == TS_NOT_OPER) { ti->blk_minor=ti->rew_minor=ti->nor_minor=-1; ti->devinfo.irq=-1; s390irq_spin_unlock_irqrestore (ti->devinfo.irq,lockflags); return -ENODEV; } if (tapestate_get (ti) != TS_DONE) { tapestate_set (ti, TS_IDLE); s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags); return -EIO; } tapestate_set (ti, TS_IDLE); s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags); #ifdef TAPE_DEBUG debug_text_event (tape_debug_area,6,"c:rbytes:"); debug_int_event (tape_debug_area,6,block_size - ti->devstat.rescnt); #endif /* TAPE_DEBUG */ filp->f_pos += block_size - ti->devstat.rescnt; return block_size - ti->devstat.rescnt; } /* * Tape device write function */ ssize_t tape_write (struct file *filp, const char *data, size_t count, loff_t * ppos) { long lockflags; tape_info_t *ti; size_t block_size; ccw_req_t *cqr; int nblocks, i, rc; size_t written = 0; #ifdef TAPE_DEBUG debug_text_event (tape_debug_area,6,"c:write"); #endif ti = first_tape_info; while ((ti != NULL) && (ti->nor_filp != filp) && (ti->rew_filp != filp)) ti = (tape_info_t *) ti->next; if (ti == NULL) return -ENODEV; if (ppos != &filp->f_pos) { /* "A request was outside the capabilities of the device." */ #ifdef TAPE_DEBUG debug_text_event (tape_debug_area,6,"c:ppos wrong"); #endif return -EOVERFLOW; /* errno=75 Value too large for def. data type */ } if ((ti->block_size != 0) && (count % ti->block_size != 0)) return -EIO; if (ti->block_size == 0) { block_size = count; nblocks = 1; } else { block_size = ti->block_size; nblocks = count / (ti->block_size); } #ifdef TAPE_DEBUG debug_text_event (tape_debug_area,6,"c:nbytes:"); debug_int_event (tape_debug_area,6,block_size); debug_text_event (tape_debug_area,6,"c:nblocks:"); debug_int_event (tape_debug_area,6,nblocks); #endif for (i = 0; i < nblocks; i++) { cqr = ti->discipline->write_block (data + i * block_size, block_size, ti); if (!cqr) { return -ENOBUFS; } s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags); ti->cqr = cqr; ti->wanna_wakeup=0; rc = do_IO (ti->devinfo.irq, cqr->cpaddr, (unsigned long) cqr, 0x00, cqr->options); s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags); wait_event_interruptible (ti->wq,ti->wanna_wakeup); ti->cqr = NULL; ti->discipline->free_write_block (cqr, ti); if (signal_pending (current)) { tapestate_set (ti, TS_IDLE); return -ERESTARTSYS; } s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags); if (tapestate_get (ti) == TS_FAILED) { tapestate_set (ti, TS_IDLE); s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags); if ((ti->rc==-ENOSPC) && (i!=0)) return i*block_size; return ti->rc; } if (tapestate_get (ti) == TS_NOT_OPER) { ti->blk_minor=ti->rew_minor=ti->nor_minor=-1; ti->devinfo.irq=-1; s390irq_spin_unlock_irqrestore (ti->devinfo.irq,lockflags); return -ENODEV; } if (tapestate_get (ti) != TS_DONE) { tapestate_set (ti, TS_IDLE); s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags); return -EIO; } tapestate_set (ti, TS_IDLE); s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags); #ifdef TAPE_DEBUG debug_text_event (tape_debug_area,6,"c:wbytes:"); debug_int_event (tape_debug_area,6,block_size - ti->devstat.rescnt); #endif filp->f_pos += block_size - ti->devstat.rescnt; written += block_size - ti->devstat.rescnt; if (ti->devstat.rescnt > 0) return written; } #ifdef TAPE_DEBUG debug_text_event (tape_debug_area,6,"c:wtotal:"); debug_int_event (tape_debug_area,6,written); #endif return written; } static int tape_mtioctop (struct file *filp, short mt_op, int mt_count) { tape_info_t *ti; ccw_req_t *cqr = NULL; int rc; long lockflags; #ifdef TAPE_DEBUG debug_text_event (tape_debug_area,6,"c:mtio"); debug_text_event (tape_debug_area,6,"c:ioop:"); debug_int_event (tape_debug_area,6,mt_op); debug_text_event (tape_debug_area,6,"c:arg:"); debug_int_event (tape_debug_area,6,mt_count); #endif ti = first_tape_info; while ((ti != NULL) && (ti->rew_filp != filp) && (ti->nor_filp != filp)) ti = (tape_info_t *) ti->next; if (ti == NULL) return -ENODEV; switch (mt_op) { case MTREW: // rewind cqr = ti->discipline->mtrew (ti, mt_count); break; case MTOFFL: // put drive offline cqr = ti->discipline->mtoffl (ti, mt_count); break; case MTUNLOAD: // unload the tape cqr = ti->discipline->mtunload (ti, mt_count); break; case MTWEOF: // write tapemark cqr = ti->discipline->mtweof (ti, mt_count); break; case MTFSF: // forward space file cqr = ti->discipline->mtfsf (ti, mt_count); break; case MTBSF: // backward space file cqr = ti->discipline->mtbsf (ti, mt_count); break; case MTFSFM: // forward space file, stop at BOT side cqr = ti->discipline->mtfsfm (ti, mt_count); break; case MTBSFM: // backward space file, stop at BOT side cqr = ti->discipline->mtbsfm (ti, mt_count); break; case MTFSR: // forward space file cqr = ti->discipline->mtfsr (ti, mt_count); break; case MTBSR: // backward space file cqr = ti->discipline->mtbsr (ti, mt_count); break; case MTNOP: cqr = ti->discipline->mtnop (ti, mt_count); break; case MTEOM: // postion at the end of portion case MTRETEN: // retension the tape cqr = ti->discipline->mteom (ti, mt_count); break; case MTERASE: cqr = ti->discipline->mterase (ti, mt_count); break; case MTSETDENSITY: cqr = ti->discipline->mtsetdensity (ti, mt_count); break; case MTSEEK: cqr = ti->discipline->mtseek (ti, mt_count); break; case MTSETDRVBUFFER: cqr = ti->discipline->mtsetdrvbuffer (ti, mt_count); break; case MTLOCK: cqr = ti->discipline->mtsetdrvbuffer (ti, mt_count); break; case MTUNLOCK: cqr = ti->discipline->mtsetdrvbuffer (ti, mt_count); break; case MTLOAD: cqr = ti->discipline->mtload (ti, mt_count); if (cqr!=NULL) break; // if backend driver has an load function ->use it // if no medium is in, wait until it gets inserted if (ti->medium_is_unloaded) { wait_event_interruptible (ti->wq,ti->medium_is_unloaded==0); } return 0; case MTCOMPRESSION: cqr = ti->discipline->mtcompression (ti, mt_count); break; case MTSETPART: cqr = ti->discipline->mtsetpart (ti, mt_count); break; case MTMKPART: cqr = ti->discipline->mtmkpart (ti, mt_count); break; case MTTELL: // return number of block relative to current file cqr = ti->discipline->mttell (ti, mt_count); break; case MTSETBLK: s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags); ti->block_size = mt_count; s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags); #ifdef TAPE_DEBUG debug_text_event (tape_debug_area,6,"c:setblk:"); debug_int_event (tape_debug_area,6,mt_count); #endif return 0; case MTRESET: s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags); ti->kernbuf = ti->userbuf = NULL; tapestate_set (ti, TS_IDLE); ti->block_size = 0; s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags); #ifdef TAPE_DEBUG debug_text_event (tape_debug_area,6,"c:devreset:"); debug_int_event (tape_debug_area,6,ti->blk_minor); #endif return 0; default: #ifdef TAPE_DEBUG debug_text_event (tape_debug_area,6,"c:inv.mtio"); #endif return -EINVAL; } if (cqr == NULL) { #ifdef TAPE_DEBUG debug_text_event (tape_debug_area,6,"c:ccwg fail"); #endif return -ENOSPC; } s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags); ti->cqr = cqr; ti->wanna_wakeup=0; rc = do_IO (ti->devinfo.irq, cqr->cpaddr, (unsigned long) cqr, 0x00, cqr->options); s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags); wait_event_interruptible (ti->wq,ti->wanna_wakeup); ti->cqr = NULL; if (ti->kernbuf != NULL) { kfree (ti->kernbuf); ti->kernbuf = NULL; } tape_free_request (cqr); // if medium was unloaded, update the corresponding variable. switch (mt_op) { case MTOFFL: case MTUNLOAD: ti->medium_is_unloaded=1; } if (signal_pending (current)) { tapestate_set (ti, TS_IDLE); return -ERESTARTSYS; } s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags); if (((mt_op == MTEOM) || (mt_op == MTRETEN)) && (tapestate_get (ti) == TS_FAILED)) tapestate_set (ti, TS_DONE); if (tapestate_get (ti) == TS_FAILED) { tapestate_set (ti, TS_IDLE); s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags); return ti->rc; } if (tapestate_get (ti) == TS_NOT_OPER) { ti->blk_minor=ti->rew_minor=ti->nor_minor=-1; ti->devinfo.irq=-1; s390irq_spin_unlock_irqrestore (ti->devinfo.irq,lockflags); return -ENODEV; } if (tapestate_get (ti) != TS_DONE) { tapestate_set (ti, TS_IDLE); s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags); return -EIO; } tapestate_set (ti, TS_IDLE); s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags); switch (mt_op) { case MTRETEN: //need to rewind the tape after moving to eom return tape_mtioctop (filp, MTREW, 1); case MTFSFM: //need to skip back over the filemark return tape_mtioctop (filp, MTBSFM, 1); case MTBSF: //need to skip forward over the filemark return tape_mtioctop (filp, MTFSF, 1); } #ifdef TAPE_DEBUG debug_text_event (tape_debug_area,6,"c:mtio done"); #endif return 0; } /* * Tape device io controls. */ int tape_ioctl (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) { long lockflags; tape_info_t *ti; ccw_req_t *cqr; struct mtop op; /* structure for MTIOCTOP */ struct mtpos pos; /* structure for MTIOCPOS */ struct mtget get; int rc; #ifdef TAPE_DEBUG debug_text_event (tape_debug_area,6,"c:ioct"); #endif ti = first_tape_info; while ((ti != NULL) && (ti->rew_minor != MINOR (inode->i_rdev)) && (ti->nor_minor != MINOR (inode->i_rdev))) ti = (tape_info_t *) ti->next; if (ti == NULL) { #ifdef TAPE_DEBUG debug_text_event (tape_debug_area,6,"c:nodev"); #endif return -ENODEV; } // check for discipline ioctl overloading if ((rc = ti->discipline->discipline_ioctl_overload (inode, filp, cmd, arg)) != -EINVAL) { #ifdef TAPE_DEBUG debug_text_event (tape_debug_area,6,"c:ioverloa"); #endif return rc; } switch (cmd) { case MTIOCTOP: /* tape op command */ if (copy_from_user (&op, (char *) arg, sizeof (struct mtop))) { return -EFAULT; } return (tape_mtioctop (filp, op.mt_op, op.mt_count)); case MTIOCPOS: /* query tape position */ cqr = ti->discipline->mttell (ti, 0); s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags); ti->cqr = cqr; ti->wanna_wakeup=0; do_IO (ti->devinfo.irq, cqr->cpaddr, (unsigned long) cqr, 0x00, cqr->options); s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags); wait_event_interruptible (ti->wq,ti->wanna_wakeup); pos.mt_blkno = ti->rc; ti->cqr = NULL; if (ti->kernbuf != NULL) { kfree (ti->kernbuf); ti->kernbuf = NULL; } tape_free_request (cqr); if (signal_pending (current)) { tapestate_set (ti, TS_IDLE); return -ERESTARTSYS; } s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags); tapestate_set (ti, TS_IDLE); s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags); if (copy_to_user ((char *) arg, &pos, sizeof (struct mtpos))) return -EFAULT; return 0; case MTIOCGET: get.mt_erreg = ti->rc; cqr = ti->discipline->mttell (ti, 0); s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags); ti->cqr = cqr; ti->wanna_wakeup=0; do_IO (ti->devinfo.irq, cqr->cpaddr, (unsigned long) cqr, 0x00, cqr->options); s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags); wait_event_interruptible (ti->wq,ti->wanna_wakeup); get.mt_blkno = ti->rc; get.mt_fileno = 0; get.mt_type = MT_ISUNKNOWN; get.mt_resid = ti->devstat.rescnt; get.mt_dsreg = ti->devstat.ii.sense.data[3]; get.mt_gstat = 0; if (ti->devstat.ii.sense.data[1] & 0x08) get.mt_gstat &= GMT_BOT (1); // BOT if (ti->devstat.ii.sense.data[1] & 0x02) get.mt_gstat &= GMT_WR_PROT (1); // write protected if (ti->devstat.ii.sense.data[1] & 0x40) get.mt_gstat &= GMT_ONLINE (1); //drive online ti->cqr = NULL; if (ti->kernbuf != NULL) { kfree (ti->kernbuf); ti->kernbuf = NULL; } tape_free_request (cqr); if (signal_pending (current)) { tapestate_set (ti, TS_IDLE); return -ERESTARTSYS; } s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags); tapestate_set (ti, TS_IDLE); s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags); if (copy_to_user ((char *) arg, &get, sizeof (struct mtget))) return -EFAULT; return 0; default: #ifdef TAPE_DEBUG debug_text_event (tape_debug_area,3,"c:ioct inv"); #endif return -EINVAL; } } /* * Tape device open function. */ int tape_open (struct inode *inode, struct file *filp) { tape_info_t *ti; kdev_t dev; long lockflags; inode = filp->f_dentry->d_inode; ti = first_tape_info; while ((ti != NULL) && (ti->rew_minor != MINOR (inode->i_rdev)) && (ti->nor_minor != MINOR (inode->i_rdev))) ti = (tape_info_t *) ti->next; if (ti == NULL) return -ENODEV; #ifdef TAPE_DEBUG debug_text_event (tape_debug_area,6,"c:open:"); debug_int_event (tape_debug_area,6,ti->blk_minor); #endif s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags); if (tapestate_get (ti) != TS_UNUSED) { s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags); #ifdef TAPE_DEBUG debug_text_event (tape_debug_area,6,"c:dbusy"); #endif return -EBUSY; } tapestate_set (ti, TS_IDLE); s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags); dev = MKDEV (tape_major, MINOR (inode->i_rdev)); /* Get the device */ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags); if (ti->rew_minor == MINOR (inode->i_rdev)) ti->rew_filp = filp; /* save for later reference */ else ti->nor_filp = filp; filp->private_data = ti; /* save the dev.info for later reference */ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags); #ifdef MODULE MOD_INC_USE_COUNT; #endif /* MODULE */ return 0; } /* * Tape device release function. */ int tape_release (struct inode *inode, struct file *filp) { long lockflags; tape_info_t *ti,*lastti; ccw_req_t *cqr = NULL; int rc = 0; ti = first_tape_info; while ((ti != NULL) && (ti->rew_minor != MINOR (inode->i_rdev)) && (ti->nor_minor != MINOR (inode->i_rdev))) ti = (tape_info_t *) ti->next; if ((ti != NULL) && (tapestate_get (ti) == TS_NOT_OPER)) { if (ti==first_tape_info) { first_tape_info=ti->next; } else { lastti=first_tape_info; while (lastti->next!=ti) lastti=lastti->next; lastti->next=ti->next; } kfree(ti); goto out; } if ((ti == NULL) || (tapestate_get (ti) != TS_IDLE)) { #ifdef TAPE_DEBUG debug_text_event (tape_debug_area,6,"c:notidle!"); #endif rc = -ENXIO; /* error in tape_release */ goto out; } #ifdef TAPE_DEBUG debug_text_event (tape_debug_area,6,"c:release:"); debug_int_event (tape_debug_area,6,ti->blk_minor); #endif if (ti->rew_minor == MINOR (inode->i_rdev)) { cqr = ti->discipline->mtrew (ti, 1); if (cqr != NULL) { #ifdef TAPE_DEBUG debug_text_event (tape_debug_area,6,"c:rewrelea"); #endif s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags); tapestate_set (ti, TS_REW_RELEASE_INIT); ti->cqr = cqr; ti->wanna_wakeup=0; rc = do_IO (ti->devinfo.irq, cqr->cpaddr, (unsigned long) cqr, 0x00, cqr->options); s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags); wait_event (ti->wq,ti->wanna_wakeup); ti->cqr = NULL; tape_free_request (cqr); } } s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags); tapestate_set (ti, TS_UNUSED); s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags); out: #ifdef MODULE MOD_DEC_USE_COUNT; #endif /* MODULE */ return rc; }