summaryrefslogtreecommitdiffstats
path: root/fs/seq_file.c
Commit message (Expand)AuthorAge
* seq_file: Add seq_vprintf function and export itSteven Whitehouse2012-06-11
* Merge tag 'module-for-3.4' of git://git.kernel.org/pub/scm/linux/kernel/git/p...Linus Torvalds2012-03-24
|\
| * fs: reduce the use of module.h wherever possiblePaul Gortmaker2012-02-28
* | seq_file: add seq_set_overflow(), seq_overflow()KAMEZAWA Hiroyuki2012-03-23
* | procfs: speed up /proc/pid/stat, statmKAMEZAWA Hiroyuki2012-03-23
* | procfs: add num_to_str() to speed up /proc/statKAMEZAWA Hiroyuki2012-03-23
* | seq_file: fix mishandling of consecutive pread() invocations.Earl Chew2012-03-21
|/
* constify seq_file stuffAl Viro2012-01-03
* fix apparmor dereferencing potentially freed dentry, sanitize __d_path() APIAl Viro2011-12-06
* fs: take dcache_lock inside __d_pathChristoph Hellwig2010-10-25
* fs/seq_file.c: Remove unnecessary casts of private_dataJoe Perches2010-09-23
* seq_file: fix new kernel-doc warningsRandy Dunlap2010-03-07
* seq_file: add RCU versions of new hlist/list iterators (v3)stephen hemminger2010-02-22
* seq_file: Add helpers for iteration over a hlistLi Zefan2010-02-10
* vfs: seq_file: add helpers for data fillingMiklos Szeredi2009-09-24
* seq_file: return a negative error code when seq_path_root() fails.Tetsuo Handa2009-09-24
* seq_file: add function to write binary dataPeter Oberparleiter2009-06-18
* cpumask: fix seq_bitmap_*() functions.Rusty Russell2009-03-30
* seq_file: properly cope with preadEric Biederman2009-02-18
* seq_file: fix big-enough lseek() + read()Alexey Dobriyan2009-02-05
* seq_file: move traverse so it can be used from seq_readEric Biederman2009-02-05
* Merge branch 'cpus4096-for-linus-3' of git://git.kernel.org/pub/scm/linux/ker...Linus Torvalds2009-01-03
|\
| * bitmap: fix seq_bitmap and seq_cpumask to take const pointerRusty Russell2008-12-29
* | expand some comments (d_path / seq_path)Arjan van de Ven2008-12-31
|/
* vfs, seqfile: export mangle_path() generallyIngo Molnar2008-11-28
* vfs, seqfile: fix comment style on mangle_pathTörök Edwin2008-11-23
* vfs, seqfile: make mangle_path() globalTörök Edwin2008-11-23
* seq_file: add seq_cpumask_list(), seq_nodemask_list()Lai Jiangshan2008-10-20
* seq_file: don't call bitmap_scnprintf_len()Lai Jiangshan2008-10-20
* [PATCH] deal with the first call of ->show() generating no outputAl Viro2008-08-25
* seq_file: add seq_cpumask(), seq_nodemask()Alexey Dobriyan2008-08-12
* [patch 2/7] vfs: mountinfo: add seq_file_root()Miklos Szeredi2008-04-23
* [patch 1/7] vfs: mountinfo: add dentry_path()Ram Pai2008-04-23
* Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/vir...Linus Torvalds2008-04-22
|\
| * [PATCH] teach seq_file to discard entriesAl Viro2008-04-21
* | fs: use loff_t type instead of long longDavid Sterba2008-04-22
|/
* d_path: Make d_path() use a struct pathJan Blunck2008-02-15
* d_path: Make seq_path() use a struct path argumentJan Blunck2008-02-15
* [FS] seq_file: Introduce the seq_open_private()Pavel Emelyanov2007-10-10
* seq_file: more atomicity in traverse()Alexey Dobriyan2007-07-16
* mutex_unlock() later in seq_lseek()Alexey Dobriyan2007-07-16
* Make common helpers for seq_files that work with list_headsPavel Emelianov2007-07-10
* [PATCH] VFS: change struct file to use struct pathJosef "Jeff" Sipek2006-12-08
* [PATCH] struct seq_operations and struct file_operations constificationHelge Deller2006-12-07
* [PATCH] sem2mutex: fs/seq_file.cIngo Molnar2006-03-23
* [PATCH] allow callers of seq_open do allocation themselvesAl Viro2005-11-07
* [PATCH] DocBook: fix some descriptionsMartin Waitz2005-05-01
* Linux-2.6.12-rc2Linus Torvalds2005-04-16
d='n368' href='#n368'>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
/*
 *  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>
 */

#define KMSG_COMPONENT "tape"

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

#include <asm/debug.h>

#define TAPE_DBF_AREA	tape_core_dbf

#include "tape.h"

#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 block_device *, fmode_t);
static int tapeblock_release(struct gendisk *, fmode_t);
static int tapeblock_ioctl(struct block_device *, fmode_t, 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,
	.locked_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_cmpxchg(&device->blk_data.requeue_scheduled, 0, 1) != 0)
		return;
	schedule_work(&device->blk_data.requeue_task);
}

/*
 * Post finished request.
 */
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;
	blk_end_request_all(req, (ccw_req->rc == 0) ? 0 : -EIO);
	if (ccw_req->rc == 0)
		/* Update position. */
		device->blk_data.block_position =
		  (blk_rq_pos(req) + blk_rq_sectors(req)) >> 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) ||
	    blk_peek_request(device->blk_data.request_queue))
		tapeblock_trigger_requeue(device);
}

/*
 * Feed the tape device CCW queue with requests supplied in a list.
 */
static 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");
		blk_end_request_all(req, -EIO);
		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.
		 */
		blk_end_request_all(req, -EIO);
		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(struct work_struct *work) {
	struct tape_blk_data *	blkdat;
	struct tape_device *	device;
	struct request_queue *	queue;
	int			nr_queued;
	struct request *	req;
	struct list_head *	l;
	int			rc;

	blkdat = container_of(work, struct tape_blk_data, requeue_task);
	device = blkdat->device;
	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_irq(&device->blk_data.request_queue_lock);
	while (
		!blk_queue_plugged(queue) &&
		(req = blk_fetch_request(queue)) &&
		nr_queued < TAPEBLOCK_MIN_REQUEUE
	) {
		if (rq_data_dir(req) == WRITE) {
			DBF_EVENT(1, "TBLOCK: Rejecting write request\n");
			spin_unlock_irq(&device->blk_data.request_queue_lock);
			blk_end_request_all(req, -EIO);
			spin_lock_irq(&device->blk_data.request_queue_lock);
			continue;
		}
		nr_queued++;
		spin_unlock_irq(&device->blk_data.request_queue_lock);
		rc = tapeblock_start_request(device, req);
		spin_lock_irq(&device->blk_data.request_queue_lock);
	}
	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(struct request_queue *queue)
{
	struct tape_device *device;

	device = (struct tape_device *) queue->queuedata;
	DBF_LH(6, "tapeblock_request_fn(device=%p)\n", device);
	BUG_ON(device == NULL);
	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;
	blkdat->device = device;
	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_logical_block_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);

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

	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();
	tape_put_device(device);

	if (!device->blk_data.disk) {
		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;
	BUG_ON(!device);

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

	dev_info(&device->cdev->dev, "Determining the size of the recorded "
		"area...\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;

	dev_info(&device->cdev->dev, "The size of the recorded area is %i "
		"blocks\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 block_device *bdev, fmode_t mode)
{
	struct gendisk *	disk = bdev->bd_disk;
	struct tape_device *	device;
	int			rc;

	device = tape_get_device_reference(disk->private_data);

	if (device->required_tapemarks) {
		DBF_EVENT(2, "TBLOCK: missing tapemarks\n");
		dev_warn(&device->cdev->dev, "Opening the tape failed because"
			" of 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 gendisk *disk, fmode_t mode)
{
	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 block_device *	bdev,
	fmode_t			mode,
	unsigned int		command,
	unsigned long		arg
) {
	int rc;
	int minor;
	struct gendisk *disk = bdev->bd_disk;
	struct tape_device *device;

	rc     = 0;
	BUG_ON(!disk);
	device = disk->private_data;
	BUG_ON(!device);
	minor  = MINOR(bdev->bd_dev);

	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:
			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;
	return 0;
}

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