dasd_ioctl.c 12.7 KB
Newer Older
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
/*
 * File...........: linux/drivers/s390/block/dasd_ioctl.c
 * Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
 *		    Horst Hummel <Horst.Hummel@de.ibm.com>
 *		    Carsten Otte <Cotte@de.ibm.com>
 *		    Martin Schwidefsky <schwidefsky@de.ibm.com>
 * Bugreports.to..: <Linux390@de.ibm.com>
 * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999-2001
 *
 * i/o controls for the dasd driver.
 *
 * 05/04/02 split from dasd.c, code restructuring.
 */
#include <linux/config.h>
#include <linux/version.h>
#include <linux/interrupt.h>
#include <linux/major.h>
#include <linux/fs.h>
#include <linux/blkpg.h>
#include <linux/blk.h>

#include <asm/irq.h>
#include <asm/uaccess.h>

/* This is ugly... */
#define PRINTK_HEADER "dasd_ioctl:"

#include "dasd_int.h"

/*
 * SECTION: ioctl functions.
 */
static struct list_head dasd_ioctl_list = LIST_HEAD_INIT(dasd_ioctl_list);

/*
 * Find the ioctl with number no.
 */
static dasd_ioctl_list_t *
dasd_find_ioctl(int no)
{
	struct list_head *curr;
	list_for_each (curr, &dasd_ioctl_list) {
		if (list_entry (curr, dasd_ioctl_list_t, list)->no == no) {
			return list_entry (curr, dasd_ioctl_list_t, list);
		}
	}
	return NULL;
}

/*
 * Register ioctl with number no.
 */
int
dasd_ioctl_no_register(struct module *owner, int no, dasd_ioctl_fn_t handler)
{
	dasd_ioctl_list_t *new;
	if (dasd_find_ioctl(no))
		return -EBUSY;
	new = kmalloc(sizeof (dasd_ioctl_list_t), GFP_KERNEL);
	if (new == NULL)
		return -ENOMEM;
	new->owner = owner;
	new->no = no;
	new->handler = handler;
	list_add(&new->list, &dasd_ioctl_list);
	MOD_INC_USE_COUNT;
	return 0;
}

/*
 * Deregister ioctl with number no.
 */
int
dasd_ioctl_no_unregister(struct module *owner, int no, dasd_ioctl_fn_t handler)
{
	dasd_ioctl_list_t *old = dasd_find_ioctl(no);
	if (old == NULL)
		return -ENOENT;
	if (old->no != no || old->handler != handler || owner != old->owner)
		return -EINVAL;
	list_del(&old->list);
	kfree(old);
	MOD_DEC_USE_COUNT;
	return 0;
}

int
dasd_ioctl(struct inode *inp, struct file *filp,
	   unsigned int no, unsigned long data)
{
Alexander Viro's avatar
Alexander Viro committed
91 92
	struct block_device *bdev = inp->i_bdev;
	dasd_device_t *device = bdev->bd_disk->private_data;
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
	dasd_ioctl_list_t *ioctl;
	struct list_head *l;
	const char *dir;
	int rc;

	if ((_IOC_DIR(no) != _IOC_NONE) && (data == 0)) {
		PRINT_DEBUG("empty data ptr");
		return -EINVAL;
	}
	dir = _IOC_DIR (no) == _IOC_NONE ? "0" :
		_IOC_DIR (no) == _IOC_READ ? "r" :
		_IOC_DIR (no) == _IOC_WRITE ? "w" : 
		_IOC_DIR (no) == (_IOC_READ | _IOC_WRITE) ? "rw" : "u";
	DBF_DEV_EVENT(DBF_DEBUG, device,
		      "ioctl 0x%08x %s'0x%x'%d(%d) with data %8lx", no,
		      dir, _IOC_TYPE(no), _IOC_NR(no), _IOC_SIZE(no), data);
	/* Search for ioctl no in the ioctl list. */
	list_for_each(l, &dasd_ioctl_list) {
		ioctl = list_entry(l, dasd_ioctl_list_t, list);
		if (ioctl->no == no) {
			/* Found a matching ioctl. Call it. */
			if (ioctl->owner) {
				if (try_inc_mod_count(ioctl->owner) != 0)
					continue;
117
				rc = ioctl->handler(bdev, no, data);
118 119
				__MOD_DEC_USE_COUNT(ioctl->owner);
			} else
120
				rc = ioctl->handler(bdev, no, data);
121 122 123 124 125 126 127 128 129 130
			return rc;
		}
	}
	/* No ioctl with number no. */
	DBF_DEV_EVENT(DBF_INFO, device,
		      "unknown ioctl 0x%08x=%s'0x%x'%d(%d) data %8lx", no,
		      dir, _IOC_TYPE(no), _IOC_NR(no), _IOC_SIZE(no), data);
	return -ENOTTY;
}

131 132
static int
dasd_ioctl_api_version(struct block_device *bdev, int no, long args)
133 134 135 136 137 138 139 140
{
	int ver = DASD_API_VERSION;
	return put_user(ver, (int *) args);
}

/*
 * Enable device.
 */
141 142
static int
dasd_ioctl_enable(struct block_device *bdev, int no, long args)
143 144 145 146 147 148 149
{
	dasd_devmap_t *devmap;
	dasd_device_t *device;
	int devno;

	if (!capable(CAP_SYS_ADMIN))
		return -EACCES;
150
	devmap = dasd_devmap_from_bdev(bdev);
151 152 153 154 155 156 157 158 159 160 161 162 163
	device = (devmap != NULL) ?
		dasd_get_device(devmap) : ERR_PTR(-ENODEV);
	if (IS_ERR(device))
		return PTR_ERR(device);
	devno = device->devinfo.devno;
	dasd_enable_devices(devno, devno);
	dasd_put_device(devmap);
	return 0;
}

/*
 * Disable device.
 */
164 165
static int
dasd_ioctl_disable(struct block_device *bdev, int no, long args)
166 167 168 169 170 171
{
	dasd_devmap_t *devmap;
	dasd_device_t *device;

	if (!capable(CAP_SYS_ADMIN))
		return -EACCES;
172
	devmap = dasd_devmap_from_bdev(bdev);
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
	device = (devmap != NULL) ?
		dasd_get_device(devmap) : ERR_PTR(-ENODEV);
	if (IS_ERR(device))
		return PTR_ERR(device);
	/*
	 * Man this is sick. We don't do a real disable but only downgrade
	 * the device to DASD_STATE_BASIC. The reason is that dasdfmt uses
	 * BIODASDDISABLE to disable accesses to the device via the block
	 * device layer but it still wants to do i/o on the device by
	 * using the BIODASDFMT ioctl. Therefore the correct state for the
	 * device is DASD_STATE_BASIC that allows to do basic i/o.
	 */
	dasd_set_target_state(device, DASD_STATE_BASIC);
	dasd_put_device(devmap);
	return 0;
}

/*
 * performs formatting of _device_ according to _fdata_
 * Note: The discipline's format_function is assumed to deliver formatting
 * commands to format a single unit of the device. In terms of the ECKD
 * devices this means CCWs are generated to format a single track.
 */
static int
dasd_format(dasd_device_t * device, format_data_t * fdata)
{
	dasd_ccw_req_t *cqr;
	int rc;

	if (device->discipline->format_device == NULL)
		return -EPERM;

	if (atomic_read(&device->open_count) > 1) {
		DEV_MESSAGE(KERN_WARNING, device, "%s",
			    "dasd_format: device is open! ");
		return -EBUSY;
	}

	DBF_DEV_EVENT(DBF_NOTICE, device,
		      "formatting units %d to %d (%d B blocks) flags %d",
		      fdata->start_unit,
		      fdata->stop_unit, fdata->blksize, fdata->intensity);

	while (fdata->start_unit <= fdata->stop_unit) {
		cqr = device->discipline->format_device(device, fdata);
		if (IS_ERR(cqr))
			return PTR_ERR(cqr);
		rc = dasd_sleep_on_interruptible(cqr);
		dasd_sfree_request(cqr, cqr->device);
		if (rc) {
			if (rc != -ERESTARTSYS)
				DEV_MESSAGE(KERN_ERR, device,
					    " Formatting of unit %d failed "
					    "with rc = %d",
					    fdata->start_unit, rc);
			return rc;
		}
		fdata->start_unit++;
	}
	return 0;
}

/*
 * Format device.
 */
238 239
static int
dasd_ioctl_format(struct block_device *bdev, int no, long args)
240 241 242 243
{
	dasd_devmap_t *devmap;
	dasd_device_t *device;
	format_data_t fdata;
Alexander Viro's avatar
Alexander Viro committed
244
	int rc;
245 246 247 248 249 250

	if (!capable(CAP_SYS_ADMIN))
		return -EACCES;
	if (!args)
		return -EINVAL;
	/* fdata == NULL is no longer a valid arg to dasd_format ! */
251
	devmap = dasd_devmap_from_bdev(bdev);
252 253 254 255 256 257 258 259 260 261
	device = (devmap != NULL) ?
		dasd_get_device(devmap) : ERR_PTR(-ENODEV);
	if (IS_ERR(device))
		return PTR_ERR(device);

	rc = 0;
	if (devmap->features & DASD_FEATURE_READONLY)
		rc = -EROFS;
	else if (copy_from_user(&fdata, (void *) args, sizeof (format_data_t)))
		rc = -EFAULT;
Alexander Viro's avatar
Alexander Viro committed
262
	else if (bdev != bdev->bd_contains) {
263 264 265 266 267 268 269 270 271 272 273 274 275
		DEV_MESSAGE(KERN_WARNING, device, "%s",
			    "Cannot low-level format a partition");
		rc = -EINVAL;
	} else
		rc = dasd_format(device, &fdata);
	dasd_put_device(devmap);
	return rc;
}

#ifdef CONFIG_DASD_PROFILE
/*
 * Reset device profile information
 */
276 277
static int
dasd_ioctl_reset_profile(struct block_device *bdev, int no, long args)
278 279 280 281 282 283
{
	dasd_devmap_t *devmap;
	dasd_device_t *device;

	if (!capable(CAP_SYS_ADMIN))
		return -EACCES;
284
	devmap = dasd_devmap_from_bdev(bdev);
285 286 287 288 289 290 291 292 293 294 295 296
	device = (devmap != NULL) ?
		dasd_get_device(devmap) : ERR_PTR(-ENODEV);
	if (IS_ERR(device))
		return PTR_ERR(device);
	memset(&device->profile, 0, sizeof (dasd_profile_info_t));
	dasd_put_device(devmap);
	return 0;
}

/*
 * Return device profile information
 */
297 298
static int
dasd_ioctl_read_profile(struct block_device *bdev, int no, long args)
299 300 301 302 303
{
	dasd_devmap_t *devmap;
	dasd_device_t *device;
	int rc;

304
	devmap = dasd_devmap_from_bdev(bdev);
305 306 307 308 309 310 311 312 313 314 315 316
	device = (devmap != NULL) ?
		dasd_get_device(devmap) : ERR_PTR(-ENODEV);
	if (IS_ERR(device))
		return PTR_ERR(device);
	rc = 0;
	if (copy_to_user((long *) args, (long *) &device->profile,
			 sizeof (dasd_profile_info_t)))
		rc = -EFAULT;
	dasd_put_device(devmap);
	return rc;
}
#else
317 318
static int
dasd_ioctl_reset_profile(struct block_device *bdev, int no, long args)
319 320 321 322
{
	return -ENOSYS;
}

323 324
static int
dasd_ioctl_read_profile(struct block_device *bdev, int no, long args)
325 326 327 328 329 330 331 332
{
	return -ENOSYS;
}
#endif

/*
 * Return dasd information. Used for BIODASDINFO and BIODASDINFO2.
 */
333 334
static int
dasd_ioctl_information(struct block_device *bdev, int no, long args)
335 336 337
{
	dasd_devmap_t *devmap;
	dasd_device_t *device;
338
	dasd_information2_t *dasd_info;
339 340 341
	unsigned long flags;
	int rc;

342
	devmap = dasd_devmap_from_bdev(bdev);
343 344 345 346 347 348 349 350 351
	device = (devmap != NULL) ?
		dasd_get_device(devmap) : ERR_PTR(-ENODEV);
	if (IS_ERR(device))
		return PTR_ERR(device);
	if (!device->discipline->fill_info) {
		dasd_put_device(devmap);
		return -EINVAL;
	}

352 353 354 355 356 357
	dasd_info = kmalloc(sizeof(dasd_information2_t), GFP_KERNEL);
	if (dasd_info == NULL) {
		dasd_put_device(devmap);
		return -ENOMEM;
	}
	rc = device->discipline->fill_info(device, dasd_info);
358 359
	if (rc) {
		dasd_put_device(devmap);
360
		kfree(dasd_info);
361 362 363
		return rc;
	}

364 365 366 367 368 369 370 371
	dasd_info->devno = device->devinfo.devno;
	dasd_info->schid = device->devinfo.irq;
	dasd_info->cu_type = device->devinfo.sid_data.cu_type;
	dasd_info->cu_model = device->devinfo.sid_data.cu_model;
	dasd_info->dev_type = device->devinfo.sid_data.dev_type;
	dasd_info->dev_model = device->devinfo.sid_data.dev_model;
	dasd_info->open_count = atomic_read(&device->open_count);
	dasd_info->status = device->state;
372 373 374 375 376 377 378
	
	/*
	 * check if device is really formatted
	 * LDL / CDL was returned by 'fill_info'
	 */
	if ((device->state < DASD_STATE_READY) ||
	    (dasd_check_blocksize(device->bp_block)))
379
		dasd_info->format = DASD_FORMAT_NONE;
380
	
381
	dasd_info->features = devmap->features;
382 383
	
	if (device->discipline)
384
		memcpy(dasd_info->type, device->discipline->name, 4);
385
	else
386 387 388
		memcpy(dasd_info->type, "none", 4);
	dasd_info->req_queue_len = 0;
	dasd_info->chanq_len = 0;
389 390 391 392 393 394 395
	if (device->request_queue->request_fn) {
		struct list_head *l;
#ifdef DASD_EXTENDED_PROFILING
		{
			struct list_head *l;
			spin_lock_irqsave(&device->lock, flags);
			list_for_each(l, &device->request_queue->queue_head)
396
				dasd_info->req_queue_len++;
397 398 399 400 401
			spin_unlock_irqrestore(&device->lock, flags);
		}
#endif				/* DASD_EXTENDED_PROFILING */
		spin_lock_irqsave(get_irq_lock(device->devinfo.irq), flags);
		list_for_each(l, &device->ccw_queue)
402
			dasd_info->chanq_len++;
403 404 405 406 407
		spin_unlock_irqrestore(get_irq_lock(device->devinfo.irq),
				       flags);
	}
	
	rc = 0;
408
	if (copy_to_user((long *) args, (long *) dasd_info,
409 410 411 412 413
			 ((no == (unsigned int) BIODASDINFO2) ?
			  sizeof (dasd_information2_t) :
			  sizeof (dasd_information_t))))
		rc = -EFAULT;
	dasd_put_device(devmap);
414
	kfree(dasd_info);
415 416 417 418 419 420
	return rc;
}

/*
 * Set read only
 */
421 422
static int
dasd_ioctl_set_ro(struct block_device *bdev, int no, long args)
423 424 425 426 427 428 429
{
	dasd_devmap_t *devmap;
	dasd_device_t *device;
	int intval, i;

	if (!capable(CAP_SYS_ADMIN))
		return -EACCES;
Alexander Viro's avatar
Alexander Viro committed
430
	if (bdev != bdev->bd_contains)
431 432 433 434
		// ro setting is not allowed for partitions
		return -EINVAL;
	if (get_user(intval, (int *) args))
		return -EFAULT;
435
	devmap = dasd_devmap_from_bdev(bdev);
436 437 438 439 440 441 442 443
	device = (devmap != NULL) ?
		dasd_get_device(devmap) : ERR_PTR(-ENODEV);
	if (IS_ERR(device))
		return PTR_ERR(device);
	if (intval)
		devmap->features |= DASD_FEATURE_READONLY;
	else
		devmap->features &= ~DASD_FEATURE_READONLY;
444
	set_disk_ro(bdev->bd_disk, intval);
445
	device->ro_flag = intval;
446 447 448 449 450 451 452
	dasd_put_device(devmap);
	return 0;
}

/*
 * Return disk geometry.
 */
453 454
static int
dasd_ioctl_getgeo(struct block_device *bdev, int no, long args)
455 456 457 458 459 460
{
	struct hd_geometry geo = { 0, };
	dasd_devmap_t *devmap;
	dasd_device_t *device;
	int rc;

461
	devmap = dasd_devmap_from_bdev(bdev);
462 463 464 465 466 467 468 469
	device = (devmap != NULL) ?
		dasd_get_device(devmap) : ERR_PTR(-ENODEV);
	if (IS_ERR(device))
		return PTR_ERR(device);
	rc = 0;
	if (device != NULL && device->discipline != NULL &&
	    device->discipline->fill_geometry != NULL) {
		device->discipline->fill_geometry(device, &geo);
470
		geo.start = get_start_sect(bdev);
471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522
		if (copy_to_user((struct hd_geometry *) args, &geo,
				 sizeof (struct hd_geometry)))
			rc = -EFAULT;
	} else
		rc = -EINVAL;
	dasd_put_device(devmap);
	return rc;
}

/*
 * List of static ioctls.
 */
static struct { int no; dasd_ioctl_fn_t fn; } dasd_ioctls[] =
{
	{ BIODASDDISABLE, dasd_ioctl_disable },
	{ BIODASDENABLE, dasd_ioctl_enable },
	{ BIODASDFMT, dasd_ioctl_format },
	{ BIODASDINFO, dasd_ioctl_information },
	{ BIODASDINFO2, dasd_ioctl_information },
	{ BIODASDPRRD, dasd_ioctl_read_profile },
	{ BIODASDPRRST, dasd_ioctl_reset_profile },
	{ BLKROSET, dasd_ioctl_set_ro },
	{ DASDAPIVER, dasd_ioctl_api_version },
	{ HDIO_GETGEO, dasd_ioctl_getgeo },
	{ -1, NULL }
};

int
dasd_ioctl_init(void)
{
	int i;

	for (i = 0; dasd_ioctls[i].no != -1; i++)
		dasd_ioctl_no_register(NULL, dasd_ioctls[i].no,
				       dasd_ioctls[i].fn);
	return 0;

}

void
dasd_ioctl_exit(void)
{
	int i;

	for (i = 0; dasd_ioctls[i].no != -1; i++)
		dasd_ioctl_no_unregister(NULL, dasd_ioctls[i].no,
					 dasd_ioctls[i].fn);

}

EXPORT_SYMBOL(dasd_ioctl_no_register);
EXPORT_SYMBOL(dasd_ioctl_no_unregister);