tape_block.c 11.6 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 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 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 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 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492
/*
 *  drivers/s390/char/tape_block.c
 *    block device frontend for tape device driver
 *
 *  S390 and zSeries version
 *    Copyright (C) 2001,2003 IBM Deutschland Entwicklung GmbH, IBM Corporation
 *    Author(s): Carsten Otte <cotte@de.ibm.com>
 *		 Tuan Ngo-Anh <ngoanh@de.ibm.com>
 *		 Martin Schwidefsky <schwidefsky@de.ibm.com>
 *		 Stefan Bader <shbader@de.ibm.com>
 */

#include <linux/fs.h>
#include <linux/config.h>
#include <linux/module.h>
#include <linux/blkdev.h>
#include <linux/interrupt.h>
#include <linux/buffer_head.h>

#include <asm/debug.h>

#define TAPE_DBF_AREA	tape_core_dbf

#include "tape.h"

#define PRINTK_HEADER "TAPE_BLOCK: "

#define TAPEBLOCK_MAX_SEC	100
#define TAPEBLOCK_MIN_REQUEUE	3

/*
 * 2003/11/25  Stefan Bader <shbader@de.ibm.com>
 *
 * In 2.5/2.6 the block device request function is very likely to be called
 * with disabled interrupts (e.g. generic_unplug_device). So the driver can't
 * just call any function that tries to allocate CCW requests from that con-
 * text since it might sleep. There are two choices to work around this:
 *	a) do not allocate with kmalloc but use its own memory pool
 *      b) take requests from the queue outside that context, knowing that
 *         allocation might sleep
 */

/*
 * file operation structure for tape block frontend
 */
static int tapeblock_open(struct inode *, struct file *);
static int tapeblock_release(struct inode *, struct file *);
static int tapeblock_ioctl(struct inode *, struct file *, unsigned int,
				unsigned long);
static int tapeblock_medium_changed(struct gendisk *);
static int tapeblock_revalidate_disk(struct gendisk *);

static struct block_device_operations tapeblock_fops = {
	.owner		 = THIS_MODULE,
	.open		 = tapeblock_open,
	.release	 = tapeblock_release,
	.ioctl           = tapeblock_ioctl,
	.media_changed   = tapeblock_medium_changed,
	.revalidate_disk = tapeblock_revalidate_disk,
};

static int tapeblock_major = 0;

static void
tapeblock_trigger_requeue(struct tape_device *device)
{
	/* Protect against rescheduling. */
	if (atomic_compare_and_swap(0, 1, &device->blk_data.requeue_scheduled))
		return;
	schedule_work(&device->blk_data.requeue_task);
}

/*
 * Post finished request.
 */
static inline void
tapeblock_end_request(struct request *req, int uptodate)
{
	if (end_that_request_first(req, uptodate, req->hard_nr_sectors))
		BUG();
	end_that_request_last(req);
}

static void
__tapeblock_end_request(struct tape_request *ccw_req, void *data)
{
	struct tape_device *device;
	struct request *req;

	DBF_LH(6, "__tapeblock_end_request()\n");

	device = ccw_req->device;
	req = (struct request *) data;
	tapeblock_end_request(req, ccw_req->rc == 0);
	if (ccw_req->rc == 0)
		/* Update position. */
		device->blk_data.block_position =
			(req->sector + req->nr_sectors) >> TAPEBLOCK_HSEC_S2B;
	else
		/* We lost the position information due to an error. */
		device->blk_data.block_position = -1;
	device->discipline->free_bread(ccw_req);
	if (!list_empty(&device->req_queue) ||
	    elv_next_request(device->blk_data.request_queue))
		tapeblock_trigger_requeue(device);
}

/*
 * Feed the tape device CCW queue with requests supplied in a list.
 */
static inline int
tapeblock_start_request(struct tape_device *device, struct request *req)
{
	struct tape_request *	ccw_req;
	int			rc;

	DBF_LH(6, "tapeblock_start_request(%p, %p)\n", device, req);

	ccw_req = device->discipline->bread(device, req);
	if (IS_ERR(ccw_req)) {
		DBF_EVENT(1, "TBLOCK: bread failed\n");
		tapeblock_end_request(req, 0);
		return PTR_ERR(ccw_req);
	}
	ccw_req->callback = __tapeblock_end_request;
	ccw_req->callback_data = (void *) req;
	ccw_req->retries = TAPEBLOCK_RETRIES;

	rc = tape_do_io_async(device, ccw_req);
	if (rc) {
		/*
		 * Start/enqueueing failed. No retries in
		 * this case.
		 */
		tapeblock_end_request(req, 0);
		device->discipline->free_bread(ccw_req);
	}

	return rc;
}

/*
 * Move requests from the block device request queue to the tape device ccw
 * queue.
 */
static void
tapeblock_requeue(void *data) {
	struct tape_device *	device;
	request_queue_t *	queue;
	int			nr_queued;
	struct request *	req;
	struct list_head *	l;
	int			rc;

	device = (struct tape_device *) data;
	if (!device)
		return;

	spin_lock_irq(get_ccwdev_lock(device->cdev));
	queue  = device->blk_data.request_queue;

	/* Count number of requests on ccw queue. */
	nr_queued = 0;
	list_for_each(l, &device->req_queue)
		nr_queued++;
	spin_unlock(get_ccwdev_lock(device->cdev));

	spin_lock(&device->blk_data.request_queue_lock);
	while (
		!blk_queue_plugged(queue) &&
		elv_next_request(queue)   &&
		nr_queued < TAPEBLOCK_MIN_REQUEUE
	) {
		req = elv_next_request(queue);
		if (rq_data_dir(req) == WRITE) {
			DBF_EVENT(1, "TBLOCK: Rejecting write request\n");
			blkdev_dequeue_request(req);
			tapeblock_end_request(req, 0);
			continue;
		}
		spin_unlock_irq(&device->blk_data.request_queue_lock);
		rc = tapeblock_start_request(device, req);
		spin_lock_irq(&device->blk_data.request_queue_lock);
		blkdev_dequeue_request(req);
		nr_queued++;
	}
	spin_unlock_irq(&device->blk_data.request_queue_lock);
	atomic_set(&device->blk_data.requeue_scheduled, 0);
}

/*
 * Tape request queue function. Called from ll_rw_blk.c
 */
static void
tapeblock_request_fn(request_queue_t *queue)
{
	struct tape_device *device;

	device = (struct tape_device *) queue->queuedata;
	DBF_LH(6, "tapeblock_request_fn(device=%p)\n", device);
	if (device == NULL)
		BUG();

	tapeblock_trigger_requeue(device);
}

/*
 * This function is called for every new tapedevice
 */
int
tapeblock_setup_device(struct tape_device * device)
{
	struct tape_blk_data *	blkdat;
	struct gendisk *	disk;
	int			rc;

	blkdat = &device->blk_data;
	spin_lock_init(&blkdat->request_queue_lock);
	atomic_set(&blkdat->requeue_scheduled, 0);

	blkdat->request_queue = blk_init_queue(
		tapeblock_request_fn,
		&blkdat->request_queue_lock
	);
	if (!blkdat->request_queue)
		return -ENOMEM;

	elevator_exit(blkdat->request_queue->elevator);
	rc = elevator_init(blkdat->request_queue, "noop");
	if (rc)
		goto cleanup_queue;

	blk_queue_hardsect_size(blkdat->request_queue, TAPEBLOCK_HSEC_SIZE);
	blk_queue_max_sectors(blkdat->request_queue, TAPEBLOCK_MAX_SEC);
	blk_queue_max_phys_segments(blkdat->request_queue, -1L);
	blk_queue_max_hw_segments(blkdat->request_queue, -1L);
	blk_queue_max_segment_size(blkdat->request_queue, -1L);
	blk_queue_segment_boundary(blkdat->request_queue, -1L);

	disk = alloc_disk(1);
	if (!disk) {
		rc = -ENOMEM;
		goto cleanup_queue;
	}

	disk->major = tapeblock_major;
	disk->first_minor = device->first_minor;
	disk->fops = &tapeblock_fops;
	disk->private_data = tape_get_device_reference(device);
	disk->queue = blkdat->request_queue;
	set_capacity(disk, 0);
	sprintf(disk->disk_name, "btibm%d",
		device->first_minor / TAPE_MINORS_PER_DEV);

	blkdat->disk = disk;
	blkdat->medium_changed = 1;
	blkdat->request_queue->queuedata = tape_get_device_reference(device);

	add_disk(disk);

	INIT_WORK(&blkdat->requeue_task, tapeblock_requeue,
		tape_get_device_reference(device));

	return 0;

cleanup_queue:
	blk_cleanup_queue(blkdat->request_queue);
	blkdat->request_queue = NULL;

	return rc;
}

void
tapeblock_cleanup_device(struct tape_device *device)
{
	flush_scheduled_work();
	device->blk_data.requeue_task.data = tape_put_device(device);

	if (!device->blk_data.disk) {
		PRINT_ERR("(%s): No gendisk to clean up!\n",
			device->cdev->dev.bus_id);
		goto cleanup_queue;
	}

	del_gendisk(device->blk_data.disk);
	device->blk_data.disk->private_data =
		tape_put_device(device->blk_data.disk->private_data);
	put_disk(device->blk_data.disk);

	device->blk_data.disk = NULL;
cleanup_queue:
	device->blk_data.request_queue->queuedata = tape_put_device(device);

	blk_cleanup_queue(device->blk_data.request_queue);
	device->blk_data.request_queue = NULL;
}

/*
 * Detect number of blocks of the tape.
 * FIXME: can we extent this to detect the blocks size as well ?
 */
static int
tapeblock_revalidate_disk(struct gendisk *disk)
{
	struct tape_device *	device;
	unsigned int		nr_of_blks;
	int			rc;

	device = (struct tape_device *) disk->private_data;
	if (!device)
		BUG();

	if (!device->blk_data.medium_changed)
		return 0;

	PRINT_INFO("Detecting media size...\n");
	rc = tape_mtop(device, MTFSFM, 1);
	if (rc)
		return rc;

	rc = tape_mtop(device, MTTELL, 1);
	if (rc < 0)
		return rc;

	DBF_LH(3, "Image file ends at %d\n", rc);
	nr_of_blks = rc;

	/* This will fail for the first file. Catch the error by checking the
	 * position. */
	tape_mtop(device, MTBSF, 1);

	rc = tape_mtop(device, MTTELL, 1);
	if (rc < 0)
		return rc;

	if (rc > nr_of_blks)
		return -EINVAL;

	DBF_LH(3, "Image file starts at %d\n", rc);
	device->bof = rc;
	nr_of_blks -= rc;

	PRINT_INFO("Found %i blocks on media\n", nr_of_blks);
	set_capacity(device->blk_data.disk,
		nr_of_blks*(TAPEBLOCK_HSEC_SIZE/512));

	device->blk_data.block_position = 0;
	device->blk_data.medium_changed = 0;
	return 0;
}

static int
tapeblock_medium_changed(struct gendisk *disk)
{
	struct tape_device *device;

	device = (struct tape_device *) disk->private_data;
	DBF_LH(6, "tapeblock_medium_changed(%p) = %d\n",
		device, device->blk_data.medium_changed);

	return device->blk_data.medium_changed;
}

/*
 * Block frontend tape device open function.
 */
static int
tapeblock_open(struct inode *inode, struct file *filp)
{
	struct gendisk *	disk;
	struct tape_device *	device;
	int			rc;

	disk   = inode->i_bdev->bd_disk;
	device = tape_get_device_reference(disk->private_data);

	if (device->required_tapemarks) {
		DBF_EVENT(2, "TBLOCK: missing tapemarks\n");
		PRINT_ERR("TBLOCK: Refusing to open tape with missing"
			" end of file marks.\n");
		rc = -EPERM;
		goto put_device;
	}

	rc = tape_open(device);
	if (rc)
		goto put_device;

	rc = tapeblock_revalidate_disk(disk);
	if (rc)
		goto release;

	/*
	 * Note: The reference to <device> is hold until the release function
	 *       is called.
	 */
	tape_state_set(device, TS_BLKUSE);
	return 0;

release:
	tape_release(device);
 put_device:
	tape_put_device(device);
	return rc;
}

/*
 * Block frontend tape device release function.
 *
 * Note: One reference to the tape device was made by the open function. So
 *       we just get the pointer here and release the reference.
 */
static int
tapeblock_release(struct inode *inode, struct file *filp)
{
	struct gendisk *disk = inode->i_bdev->bd_disk;
	struct tape_device *device = disk->private_data;

	tape_state_set(device, TS_IN_USE);
	tape_release(device);
	tape_put_device(device);

	return 0;
}

/*
 * Support of some generic block device IOCTLs.
 */
static int
tapeblock_ioctl(
	struct inode *		inode,
	struct file *		file,
	unsigned int		command,
	unsigned long		arg
) {
	int rc;
	int minor;
	struct gendisk *disk = inode->i_bdev->bd_disk;
	struct tape_device *device = disk->private_data;

	rc     = 0;
	disk   = inode->i_bdev->bd_disk;
	if (!disk)
		BUG();
	device = disk->private_data;
	if (!device)
		BUG();
	minor  = iminor(inode);

	DBF_LH(6, "tapeblock_ioctl(0x%0x)\n", command);
	DBF_LH(6, "device = %d:%d\n", tapeblock_major, minor);

	switch (command) {
		/* Refuse some IOCTL calls without complaining (mount). */
		case 0x5310:		/* CDROMMULTISESSION */
			rc = -EINVAL;
			break;
		default:
			PRINT_WARN("invalid ioctl 0x%x\n", command);
			rc = -EINVAL;
	}

	return rc;
}

/*
 * Initialize block device frontend.
 */
int
tapeblock_init(void)
{
	int rc;

	/* Register the tape major number to the kernel */
	rc = register_blkdev(tapeblock_major, "tBLK");
	if (rc < 0)
		return rc;

	if (tapeblock_major == 0)
		tapeblock_major = rc;
	PRINT_INFO("tape gets major %d for block device\n", tapeblock_major);
	return 0;
}

/*
 * Deregister major for block device frontend
 */
void
tapeblock_exit(void)
{
	unregister_blkdev(tapeblock_major, "tBLK");
}