tapeblock.c 12.3 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1 2 3 4 5
/***************************************************************************
 *
 *  drivers/s390/char/tapeblock.c
 *    block device frontend for tape device driver
 *
Linus Torvalds's avatar
Linus Torvalds committed
6 7 8 9 10
 *  S390 and zSeries version
 *    Copyright (C) 2001 IBM Corporation
 *    Author(s): Carsten Otte <cotte@de.ibm.com>
 *               Tuan Ngo-Anh <ngoanh@de.ibm.com>
 *
Linus Torvalds's avatar
Linus Torvalds committed
11 12 13 14 15
 *
 ****************************************************************************
 */

#include "tapedefs.h"
Linus Torvalds's avatar
Linus Torvalds committed
16
#include <linux/config.h>
Linus Torvalds's avatar
Linus Torvalds committed
17 18 19 20 21 22 23 24
#include <linux/blkdev.h>
#include <linux/blk.h>
#include <linux/version.h>
#include <linux/interrupt.h>
#include <asm/debug.h>
#include <asm/s390dyn.h>
#include <linux/compatmac.h>
#ifdef MODULE
Linus Torvalds's avatar
Linus Torvalds committed
25
#define __NO_VERSION__
Linus Torvalds's avatar
Linus Torvalds committed
26 27 28 29 30 31 32 33 34 35 36
#include <linux/module.h>
#endif
#include "tape.h"
#include "tapeblock.h"

#define PRINTK_HEADER "TBLOCK:"

/*
 * file operation structure for tape devices
 */
static struct block_device_operations tapeblock_fops = {
37 38 39 40
	.owner		= THIS_MODULE,
	.open		= tapeblock_open,
	.release	= tapeblock_release,
};
Linus Torvalds's avatar
Linus Torvalds committed
41 42 43 44 45 46

int    tapeblock_major = 0;

static void tape_request_fn (request_queue_t * queue);
static request_queue_t* tapeblock_getqueue (kdev_t kdev);

Linus Torvalds's avatar
Linus Torvalds committed
47
#ifdef CONFIG_DEVFS_FS
48 49 50
devfs_handle_t
tapeblock_mkdevfstree (tape_dev_t* td) {
    devfs_handle_t rc=NULL;
51 52 53
    char name[16];
    sprintf (name, "tape/%04x/block", td->devinfo.devno);
    rc=td->blk_data.devfs_block_dir=devfs_mk_dir (NULL, name, NULL);
54 55 56 57 58 59 60 61 62 63
    if (rc==NULL) goto out_undo;
    rc=td->blk_data.devfs_disc=devfs_register(td->blk_data.devfs_block_dir, "disc",DEVFS_FL_DEFAULT,
				    tapeblock_major, td->first_minor,
				    TAPEBLOCK_DEVFSMODE, &tapeblock_fops, td);
    if (rc==NULL) goto out_undo;
    goto out;
 out_undo:
    tapeblock_rmdevfstree(td);
 out:
    return rc;
Linus Torvalds's avatar
Linus Torvalds committed
64 65 66
}

void
67 68 69 70 71
tapeblock_rmdevfstree (tape_dev_t* td) {
    if (td->blk_data.devfs_disc)
            devfs_unregister(td->blk_data.devfs_disc);
    if (td->blk_data.devfs_block_dir)
            devfs_unregister(td->blk_data.devfs_block_dir);
Linus Torvalds's avatar
Linus Torvalds committed
72 73
}
#endif
Linus Torvalds's avatar
Linus Torvalds committed
74 75

void 
76
tapeblock_setup(tape_dev_t* td) {
77
    blk_queue_hardsect_size(&ti->request_queue, 2048);
78
    blk_init_queue (&td->blk_data.request_queue, tape_request_fn); 
Linus Torvalds's avatar
Linus Torvalds committed
79
#ifdef CONFIG_DEVFS_FS
80
    tapeblock_mkdevfstree(td);
Linus Torvalds's avatar
Linus Torvalds committed
81
#endif
82
    set_device_ro (MKDEV(tapeblock_major, td->first_minor), 1);
Linus Torvalds's avatar
Linus Torvalds committed
83 84
}

Linus Torvalds's avatar
Linus Torvalds committed
85
int
Linus Torvalds's avatar
Linus Torvalds committed
86
tapeblock_init(void) {
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
	int result;
	tape_frontend_t* blkfront,*temp;
	tape_dev_t* td;
	tape_init();
	/* Register the tape major number to the kernel */
	result = register_blkdev(tapeblock_major, "tBLK", &tapeblock_fops);
	if (result < 0) {
		PRINT_WARN(KERN_ERR "tape: can't get major %d for block device\n", tapeblock_major);
		result=-ENODEV;
		goto out;
	}
	if (tapeblock_major == 0) tapeblock_major = result;   /* accept dynamic major number*/
	INIT_BLK_DEV(tapeblock_major,tape_request_fn,tapeblock_getqueue,NULL);
	PRINT_WARN(KERN_ERR " tape gets major %d for block device\n", tapeblock_major);
	max_sectors[tapeblock_major] = (int*) kmalloc (256*sizeof(int),GFP_KERNEL);
	if (max_sectors[tapeblock_major]==NULL) goto out_undo_hardsect_size;
	memset(max_sectors[tapeblock_major],0,256*sizeof(int));
	blkfront = kmalloc(sizeof(tape_frontend_t),GFP_KERNEL);
	if (blkfront==NULL) goto out_undo_max_sectors;
	blkfront->device_setup=(tape_setup_device_t)tapeblock_setup;
Linus Torvalds's avatar
Linus Torvalds committed
107
#ifdef CONFIG_DEVFS_FS
108 109
	blkfront->mkdevfstree = tapeblock_mkdevfstree;
	blkfront->rmdevfstree = tapeblock_rmdevfstree;
Linus Torvalds's avatar
Linus Torvalds committed
110
#endif
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
	blkfront->next=NULL;
	if (tape_first_front==NULL) {
		tape_first_front=blkfront;
	} else {
		temp=tape_first_front;
		while (temp->next!=NULL) 
			temp=temp->next;
		temp->next=blkfront;
	}
	td=tape_first_dev;
	while (td!=NULL) {
		tapeblock_setup(td);
		td=td->next;
	}
	result=0;
	goto out;
out_undo_max_sectors:
	kfree(max_sectors[tapeblock_major]);
out_undo_hardsect_size:
out_undo_blk_size:
out_undo_bdev:
	unregister_blkdev(tapeblock_major, "tBLK");
	result=-ENOMEM;
	max_sectors[tapeblock_major]=NULL;
	tapeblock_major=-1;    
out:
	return result;
Linus Torvalds's avatar
Linus Torvalds committed
138 139 140 141 142
}


void 
tapeblock_uninit(void) {
143 144 145 146 147 148 149 150 151 152 153
	if (tapeblock_major==-1)
	        goto out; /* init failed so there is nothing to clean up */
	if (max_sectors[tapeblock_major]!=NULL) {
		kfree (max_sectors[tapeblock_major]);
		max_sectors[tapeblock_major]=NULL;
	}

	unregister_blkdev(tapeblock_major, "tBLK");

out:
	return;
Linus Torvalds's avatar
Linus Torvalds committed
154 155
}

Linus Torvalds's avatar
Linus Torvalds committed
156
int
Linus Torvalds's avatar
Linus Torvalds committed
157
tapeblock_open(struct inode *inode, struct file *filp) {
158 159 160
	tape_dev_t *td = NULL;
	int rc = 0;
	long lockflags;
Linus Torvalds's avatar
Linus Torvalds committed
161

162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181

	tape_sprintf_event (tape_dbf_area,6,"b:open:  %x\n",td->first_minor);

	inode = filp->f_dentry->d_inode;

	td = tape_get_device_by_minor(MINOR (inode->i_rdev));
	if (td == NULL){
		rc = -ENODEV;
		goto error;
	}
	s390irq_spin_lock_irqsave (td->devinfo.irq, lockflags);
	if (tape_state_get(td) == TS_NOT_OPER) {
		tape_sprintf_event (tape_dbf_area,6,"c:nodev\n");
		rc = -ENODEV;
		goto out_rel_lock;
	}
	if (tape_state_get (td) != TS_UNUSED) {
		tape_sprintf_event (tape_dbf_area,6,"b:dbusy\n");
		rc = -EBUSY;
		goto out_rel_lock;
Linus Torvalds's avatar
Linus Torvalds committed
182
	}
183 184 185 186
	tape_state_set (td, TS_IN_USE);
        td->blk_data.position=-1;
	s390irq_spin_unlock_irqrestore (td->devinfo.irq, lockflags);
        rc=tapeblock_mediumdetect(td);
Linus Torvalds's avatar
Linus Torvalds committed
187
        if (rc) {
188 189 190
	    s390irq_spin_lock_irqsave (td->devinfo.irq, lockflags);
	    tape_state_set (td, TS_UNUSED);
	    goto out_rel_lock; // in case of errors, we don't have a size of the medium
Linus Torvalds's avatar
Linus Torvalds committed
191
	}
192 193 194 195 196 197 198 199 200 201 202 203 204
	if ( td->discipline->owner )
		__MOD_INC_USE_COUNT(td->discipline->owner);
	s390irq_spin_lock_irqsave (td->devinfo.irq, lockflags);
	td->filp = filp;
	filp->private_data = td;/* save the dev.info for later reference */
out_rel_lock:
	s390irq_spin_unlock_irqrestore (td->devinfo.irq, lockflags);
error:
	if(rc != 0){
		if (td != NULL)
			tape_put_device(td);
	}
	return rc;
Linus Torvalds's avatar
Linus Torvalds committed
205 206
}

Linus Torvalds's avatar
Linus Torvalds committed
207
int
Linus Torvalds's avatar
Linus Torvalds committed
208 209
tapeblock_release(struct inode *inode, struct file *filp) {
	long lockflags;
210 211 212 213 214
	tape_dev_t *td = NULL;
	int rc = 0;
	if((!inode) || !(inode->i_rdev)) {
		rc = -EINVAL;
		goto out;
Linus Torvalds's avatar
Linus Torvalds committed
215
	}
216 217 218 219
	td = tape_get_device_by_minor(MINOR (inode->i_rdev));
	if (td==NULL) {
		rc = -ENODEV;
		goto out;
Linus Torvalds's avatar
Linus Torvalds committed
220
	}
221 222 223 224 225 226 227 228 229 230 231 232 233 234
	s390irq_spin_lock_irqsave (td->devinfo.irq, lockflags);

	tape_sprintf_event (tape_dbf_area,6,"b:release: %x\n",td->first_minor);
	if(tape_state_get(td) == TS_IN_USE)
		tape_state_set (td, TS_UNUSED);
	else if (tape_state_get(td) != TS_NOT_OPER) 
		BUG();
	s390irq_spin_unlock_irqrestore (td->devinfo.irq, lockflags);
	tape_put_device(td);
	tape_put_device(td); /* 2x ! */
	if ( td->discipline->owner )
		__MOD_DEC_USE_COUNT(td->discipline->owner);
out:
	return rc;
Linus Torvalds's avatar
Linus Torvalds committed
235 236 237
}

static void
238
tapeblock_end_request(tape_dev_t* td) {
Linus Torvalds's avatar
Linus Torvalds committed
239 240
    struct buffer_head *bh;
    int uptodate;
241 242 243 244 245 246
    tape_ccw_req_t *treq = tape_get_active_ccw_req(td);
    if(treq == NULL){
	uptodate = 0;
    }
    else
    	uptodate=(treq->rc == 0); // is the buffer up to date?
Linus Torvalds's avatar
Linus Torvalds committed
247
    if (uptodate) {
248
	tape_sprintf_event (tape_dbf_area,6,"b:done: %x\n",(unsigned long)treq);
Linus Torvalds's avatar
Linus Torvalds committed
249
    } else {
250
	tape_sprintf_event (tape_dbf_area,3,"b:failed: %x\n",(unsigned long)treq);
Linus Torvalds's avatar
Linus Torvalds committed
251 252
    }
    // now inform ll_rw_block about a request status
253 254
    while ((bh = td->blk_data.current_request->bh) != NULL) {
	td->blk_data.current_request->bh = bh->b_reqnext;
Linus Torvalds's avatar
Linus Torvalds committed
255 256 257
	bh->b_reqnext = NULL;
	bh->b_end_io (bh, uptodate);
    }
258
    if (!end_that_request_first (td->blk_data.current_request, uptodate, "tBLK")) {
Linus Torvalds's avatar
Linus Torvalds committed
259
#ifndef DEVICE_NO_RANDOM
260
	add_blkdev_randomness (MAJOR (td->blk_data.current_request->rq_dev));
Linus Torvalds's avatar
Linus Torvalds committed
261
#endif
262 263 264 265 266
	end_that_request_last (td->blk_data.current_request);
    }
    if (treq!=NULL) {
	    tape_remove_ccw_req(td,treq);
	    td->discipline->free_bread(treq);
Linus Torvalds's avatar
Linus Torvalds committed
267
    }
268
    td->blk_data.current_request=NULL;
Linus Torvalds's avatar
Linus Torvalds committed
269 270 271 272
    return;
}

static void
273
tapeblock_exec_IO (tape_dev_t* td) {
Linus Torvalds's avatar
Linus Torvalds committed
274 275
    int rc;
    struct request* req;
276 277 278 279 280 281 282 283 284 285 286
    tape_ccw_req_t *treq = tape_get_active_ccw_req(td);

    if (treq) { // process done/failed request
	while (treq->rc != 0 && td->blk_data.blk_retries>0) {
	    td->blk_data.blk_retries--;
	    td->blk_data.position=-1;
	    td->discipline->bread_enable_locate(treq);
	    tape_sprintf_event (tape_dbf_area,3,"b:retryreq: %x\n",(unsigned long)treq);
	    rc = tape_do_io_irq(td,treq,TAPE_SCHED_BLOCK);
	    if (rc != 0) {
		tape_sprintf_event (tape_dbf_area,3,"b:doIOfail: %x\n",(unsigned long)treq);
Linus Torvalds's avatar
Linus Torvalds committed
287 288 289 290
		continue; // one retry lost 'cause doIO failed
	    }
	    return;
	}
291
	tapeblock_end_request (td); // check state, inform user, free mem, dev=idl
Linus Torvalds's avatar
Linus Torvalds committed
292
    }
293 294
    if(TAPE_BUSY(td)) BUG(); // tape should be idle now, request should be freed!
    if (tape_state_get (td) == TS_NOT_OPER) {
Linus Torvalds's avatar
Linus Torvalds committed
295 296
	return;
    }
297
	if (list_empty (&td->blk_data.request_queue.queue_head)) {
Linus Torvalds's avatar
Linus Torvalds committed
298
	// nothing more to do or device has dissapeared;)
299
	tape_sprintf_event (tape_dbf_area,6,"b:Qempty\n");
Linus Torvalds's avatar
Linus Torvalds committed
300 301 302
	return;
    }
    // queue is not empty, fetch a request and start IO!
303
    req=td->blk_data.current_request=tape_next_request(&td->blk_data.request_queue);
Linus Torvalds's avatar
Linus Torvalds committed
304 305 306 307
    if (req==NULL) {
	BUG(); // Yo. The queue was not reported empy, but no request found. This is _bad_.
    }
    if (req->cmd!=READ) { // we only support reading
308 309
	tapeblock_end_request (td); // check state, inform user, free mem, dev=idl
	tapeblock_schedule_exec_io(td);
Linus Torvalds's avatar
Linus Torvalds committed
310 311
	return;
    }
312 313
    treq=td->discipline->bread(req,td,tapeblock_major); //build channel program from request
    if (!treq) {
Linus Torvalds's avatar
Linus Torvalds committed
314
	// ccw generation failed. we try again later.
315 316 317
	tape_sprintf_event (tape_dbf_area,3,"b:cqrNULL\n");
	tapeblock_schedule_exec_io(td);
	td->blk_data.current_request=NULL;
Linus Torvalds's avatar
Linus Torvalds committed
318 319
	return;
    }
320 321 322
    td->blk_data.blk_retries = TAPEBLOCK_RETRIES;
    rc = tape_do_io_irq(td,treq,TAPE_SCHED_BLOCK);
    if (rc != 0) {
Linus Torvalds's avatar
Linus Torvalds committed
323
	// okay. ssch failed. we try later.
324 325 326 327 328
	tape_sprintf_event (tape_dbf_area,3,"b:doIOfail\n");
	tape_remove_ccw_req(td,treq);
	td->discipline->free_bread(treq);
	td->blk_data.current_request=NULL;
	tapeblock_schedule_exec_io(td);
Linus Torvalds's avatar
Linus Torvalds committed
329 330 331
	return;
    }
    // our request is in IO. we remove it from the queue and exit
332
    tape_dequeue_request (&td->blk_data.request_queue,req);
Linus Torvalds's avatar
Linus Torvalds committed
333 334 335
}

static void 
336
do_tape_request (tape_dev_t * td) {
Linus Torvalds's avatar
Linus Torvalds committed
337
    long lockflags;
338 339 340 341
    if (td==NULL) BUG();
    s390irq_spin_lock_irqsave (td->devinfo.irq, lockflags);
    if (tape_state_get(td)!=TS_IN_USE) {
	s390irq_spin_unlock_irqrestore(td->devinfo.irq,lockflags);
Linus Torvalds's avatar
Linus Torvalds committed
342 343
	return;
    }
344 345
    tapeblock_exec_IO(td);
    s390irq_spin_unlock_irqrestore(td->devinfo.irq,lockflags);
Linus Torvalds's avatar
Linus Torvalds committed
346 347 348
}

static void
349
run_tapeblock_exec_IO (tape_dev_t* td) {
Linus Torvalds's avatar
Linus Torvalds committed
350
    long flags_390irq,flags_ior;
Linus Torvalds's avatar
Linus Torvalds committed
351 352 353
    request_queue_t *q = &tape->request_queue;

    spin_lock_irqsave (&q->queue_lock, flags_ior);
354 355 356 357
    s390irq_spin_lock_irqsave(td->devinfo.irq,flags_390irq);
    atomic_set(&td->blk_data.bh_scheduled,0);
    tapeblock_exec_IO(td);
    s390irq_spin_unlock_irqrestore(td->devinfo.irq,flags_390irq);
Linus Torvalds's avatar
Linus Torvalds committed
358
    spin_unlock_irqrestore (&q->queue_lock, flags_ior);
Linus Torvalds's avatar
Linus Torvalds committed
359 360 361
}

void
362
tapeblock_schedule_exec_io (tape_dev_t *td)
Linus Torvalds's avatar
Linus Torvalds committed
363 364
{
	/* Protect against rescheduling, when already running */
365
        if (atomic_compare_and_swap(0,1,&td->blk_data.bh_scheduled)) {
Linus Torvalds's avatar
Linus Torvalds committed
366 367
                return;
        }
368 369 370 371
	INIT_LIST_HEAD(&td->blk_data.bh_tq.list);
	td->blk_data.bh_tq.sync = 0;
	td->blk_data.bh_tq.routine = (void *) (void *) run_tapeblock_exec_IO;
	td->blk_data.bh_tq.data = td;
Linus Torvalds's avatar
Linus Torvalds committed
372

373
	queue_task (&td->blk_data.bh_tq, &tq_immediate);
Linus Torvalds's avatar
Linus Torvalds committed
374 375 376 377 378
	mark_bh (IMMEDIATE_BH);
	return;
}

static void  tape_request_fn (request_queue_t* queue) {
379 380 381 382 383
	tape_dev_t* td=tape_get_device_by_queue(queue);
	if (td!=NULL) {
		do_tape_request(td);
		tape_put_device(td);
	}		
Linus Torvalds's avatar
Linus Torvalds committed
384 385 386
}

static request_queue_t* tapeblock_getqueue (kdev_t kdev) {
387 388 389
	tape_dev_t* td=tape_get_device_by_minor(MINOR(kdev));
	if (td!=NULL) return &td->blk_data.request_queue;
	else return NULL;
Linus Torvalds's avatar
Linus Torvalds committed
390 391
}

392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442
int tapeblock_mediumdetect(tape_dev_t* td) {
	tape_ccw_req_t *treq;
	unsigned int nr_of_blks;
	int rc;
	PRINT_INFO("Detecting media size...\n");

	/* Rewind */

	treq = td->discipline->ioctl (td, MTREW, 1, &rc);
	if (treq == NULL)
		return rc;
	rc = tape_do_io_and_wait (td,treq,TAPE_WAIT_INTERRUPTIBLE);
	TAPE_MERGE_RC(treq,rc);
	tape_free_ccw_req (treq);
	if (rc)
		return rc;

	/* FSF */

	treq=td->discipline->ioctl (td, MTFSF,1,&rc);
	if (treq == NULL) 
		return rc;
	rc = tape_do_io_and_wait (td,treq,TAPE_WAIT_INTERRUPTIBLE);
	TAPE_MERGE_RC(treq,rc);
	tape_free_ccw_req (treq);
	if (rc)
		return rc;

	/* TELL */

	treq = td->discipline->ioctl (td, MTTELL, 1, &rc);
	if (treq == NULL) 
		return rc;
	rc = tape_do_io_and_wait(td,treq,TAPE_WAIT_INTERRUPTIBLE);
	TAPE_MERGE_RC(treq,rc);
	nr_of_blks = *((int*)(treq->kernbuf)) - 1; /* don't count FM */
	tape_free_ccw_req (treq);
	if(rc)
		return rc;

	/* Rewind */

	treq = td->discipline->ioctl (td, MTREW, 1, &rc);
        if (treq == NULL)
		return rc;
        rc = tape_do_io_and_wait(td,treq,TAPE_WAIT_INTERRUPTIBLE);
	TAPE_MERGE_RC(treq,rc);
        tape_free_ccw_req (treq);
	if(rc)	
		return rc;
	return 0;
Linus Torvalds's avatar
Linus Torvalds committed
443
}