Creation of Cybook 2416 (actually Gen4) repository
This commit is contained in:
66
drivers/s390/block/Kconfig
Normal file
66
drivers/s390/block/Kconfig
Normal file
@@ -0,0 +1,66 @@
|
||||
if S390 && BLOCK
|
||||
|
||||
comment "S/390 block device drivers"
|
||||
depends on S390
|
||||
|
||||
config BLK_DEV_XPRAM
|
||||
tristate "XPRAM disk support"
|
||||
depends on S390
|
||||
help
|
||||
Select this option if you want to use your expanded storage on S/390
|
||||
or zSeries as a disk. This is useful as a _fast_ swap device if you
|
||||
want to access more than 2G of memory when running in 31 bit mode.
|
||||
This option is also available as a module which will be called
|
||||
xpram. If unsure, say "N".
|
||||
|
||||
config DCSSBLK
|
||||
tristate "DCSSBLK support"
|
||||
help
|
||||
Support for dcss block device
|
||||
|
||||
config DASD
|
||||
tristate "Support for DASD devices"
|
||||
depends on CCW
|
||||
help
|
||||
Enable this option if you want to access DASDs directly utilizing
|
||||
S/390s channel subsystem commands. This is necessary for running
|
||||
natively on a single image or an LPAR.
|
||||
|
||||
config DASD_PROFILE
|
||||
bool "Profiling support for dasd devices"
|
||||
depends on DASD
|
||||
help
|
||||
Enable this option if you want to see profiling information
|
||||
in /proc/dasd/statistics.
|
||||
|
||||
config DASD_ECKD
|
||||
tristate "Support for ECKD Disks"
|
||||
depends on DASD
|
||||
help
|
||||
ECKD devices are the most commonly used devices. You should enable
|
||||
this option unless you are very sure to have no ECKD device.
|
||||
|
||||
config DASD_FBA
|
||||
tristate "Support for FBA Disks"
|
||||
depends on DASD
|
||||
help
|
||||
Select this option to be able to access FBA devices. It is safe to
|
||||
say "Y".
|
||||
|
||||
config DASD_DIAG
|
||||
tristate "Support for DIAG access to Disks"
|
||||
depends on DASD
|
||||
help
|
||||
Select this option if you want to use Diagnose250 command to access
|
||||
Disks under VM. If you are not running under VM or unsure what it is,
|
||||
say "N".
|
||||
|
||||
config DASD_EER
|
||||
bool "Extended error reporting (EER)"
|
||||
depends on DASD
|
||||
help
|
||||
This driver provides a character device interface to the
|
||||
DASD extended error reporting. This is only needed if you want to
|
||||
use applications written for the EER facility.
|
||||
|
||||
endif
|
||||
19
drivers/s390/block/Makefile
Normal file
19
drivers/s390/block/Makefile
Normal file
@@ -0,0 +1,19 @@
|
||||
#
|
||||
# S/390 block devices
|
||||
#
|
||||
|
||||
dasd_eckd_mod-objs := dasd_eckd.o dasd_3990_erp.o dasd_9343_erp.o
|
||||
dasd_fba_mod-objs := dasd_fba.o dasd_3370_erp.o dasd_9336_erp.o
|
||||
dasd_diag_mod-objs := dasd_diag.o
|
||||
dasd_mod-objs := dasd.o dasd_ioctl.o dasd_proc.o dasd_devmap.o \
|
||||
dasd_genhd.o dasd_erp.o
|
||||
ifdef CONFIG_DASD_EER
|
||||
dasd_mod-objs += dasd_eer.o
|
||||
endif
|
||||
|
||||
obj-$(CONFIG_DASD) += dasd_mod.o
|
||||
obj-$(CONFIG_DASD_DIAG) += dasd_diag_mod.o
|
||||
obj-$(CONFIG_DASD_ECKD) += dasd_eckd_mod.o
|
||||
obj-$(CONFIG_DASD_FBA) += dasd_fba_mod.o
|
||||
obj-$(CONFIG_BLK_DEV_XPRAM) += xpram.o
|
||||
obj-$(CONFIG_DCSSBLK) += dcssblk.o
|
||||
2252
drivers/s390/block/dasd.c
Normal file
2252
drivers/s390/block/dasd.c
Normal file
File diff suppressed because it is too large
Load Diff
84
drivers/s390/block/dasd_3370_erp.c
Normal file
84
drivers/s390/block/dasd_3370_erp.c
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* File...........: linux/drivers/s390/block/dasd_3370_erp.c
|
||||
* Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
|
||||
* Bugreports.to..: <Linux390@de.ibm.com>
|
||||
* (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 2000
|
||||
*
|
||||
*/
|
||||
|
||||
#define PRINTK_HEADER "dasd_erp(3370)"
|
||||
|
||||
#include "dasd_int.h"
|
||||
|
||||
|
||||
/*
|
||||
* DASD_3370_ERP_EXAMINE
|
||||
*
|
||||
* DESCRIPTION
|
||||
* Checks only for fatal/no/recover error.
|
||||
* A detailed examination of the sense data is done later outside
|
||||
* the interrupt handler.
|
||||
*
|
||||
* The logic is based on the 'IBM 3880 Storage Control Reference' manual
|
||||
* 'Chapter 7. 3370 Sense Data'.
|
||||
*
|
||||
* RETURN VALUES
|
||||
* dasd_era_none no error
|
||||
* dasd_era_fatal for all fatal (unrecoverable errors)
|
||||
* dasd_era_recover for all others.
|
||||
*/
|
||||
dasd_era_t
|
||||
dasd_3370_erp_examine(struct dasd_ccw_req * cqr, struct irb * irb)
|
||||
{
|
||||
char *sense = irb->ecw;
|
||||
|
||||
/* check for successful execution first */
|
||||
if (irb->scsw.cstat == 0x00 &&
|
||||
irb->scsw.dstat == (DEV_STAT_CHN_END | DEV_STAT_DEV_END))
|
||||
return dasd_era_none;
|
||||
if (sense[0] & 0x80) { /* CMD reject */
|
||||
return dasd_era_fatal;
|
||||
}
|
||||
if (sense[0] & 0x40) { /* Drive offline */
|
||||
return dasd_era_recover;
|
||||
}
|
||||
if (sense[0] & 0x20) { /* Bus out parity */
|
||||
return dasd_era_recover;
|
||||
}
|
||||
if (sense[0] & 0x10) { /* equipment check */
|
||||
if (sense[1] & 0x80) {
|
||||
return dasd_era_fatal;
|
||||
}
|
||||
return dasd_era_recover;
|
||||
}
|
||||
if (sense[0] & 0x08) { /* data check */
|
||||
if (sense[1] & 0x80) {
|
||||
return dasd_era_fatal;
|
||||
}
|
||||
return dasd_era_recover;
|
||||
}
|
||||
if (sense[0] & 0x04) { /* overrun */
|
||||
if (sense[1] & 0x80) {
|
||||
return dasd_era_fatal;
|
||||
}
|
||||
return dasd_era_recover;
|
||||
}
|
||||
if (sense[1] & 0x40) { /* invalid blocksize */
|
||||
return dasd_era_fatal;
|
||||
}
|
||||
if (sense[1] & 0x04) { /* file protected */
|
||||
return dasd_era_recover;
|
||||
}
|
||||
if (sense[1] & 0x01) { /* operation incomplete */
|
||||
return dasd_era_recover;
|
||||
}
|
||||
if (sense[2] & 0x80) { /* check data erroor */
|
||||
return dasd_era_recover;
|
||||
}
|
||||
if (sense[2] & 0x10) { /* Env. data present */
|
||||
return dasd_era_recover;
|
||||
}
|
||||
/* examine the 24 byte sense data */
|
||||
return dasd_era_recover;
|
||||
|
||||
} /* END dasd_3370_erp_examine */
|
||||
2715
drivers/s390/block/dasd_3990_erp.c
Normal file
2715
drivers/s390/block/dasd_3990_erp.c
Normal file
File diff suppressed because it is too large
Load Diff
41
drivers/s390/block/dasd_9336_erp.c
Normal file
41
drivers/s390/block/dasd_9336_erp.c
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* File...........: linux/drivers/s390/block/dasd_9336_erp.c
|
||||
* Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
|
||||
* Bugreports.to..: <Linux390@de.ibm.com>
|
||||
* (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 2000
|
||||
*
|
||||
*/
|
||||
|
||||
#define PRINTK_HEADER "dasd_erp(9336)"
|
||||
|
||||
#include "dasd_int.h"
|
||||
|
||||
|
||||
/*
|
||||
* DASD_9336_ERP_EXAMINE
|
||||
*
|
||||
* DESCRIPTION
|
||||
* Checks only for fatal/no/recover error.
|
||||
* A detailed examination of the sense data is done later outside
|
||||
* the interrupt handler.
|
||||
*
|
||||
* The logic is based on the 'IBM 3880 Storage Control Reference' manual
|
||||
* 'Chapter 7. 9336 Sense Data'.
|
||||
*
|
||||
* RETURN VALUES
|
||||
* dasd_era_none no error
|
||||
* dasd_era_fatal for all fatal (unrecoverable errors)
|
||||
* dasd_era_recover for all others.
|
||||
*/
|
||||
dasd_era_t
|
||||
dasd_9336_erp_examine(struct dasd_ccw_req * cqr, struct irb * irb)
|
||||
{
|
||||
/* check for successful execution first */
|
||||
if (irb->scsw.cstat == 0x00 &&
|
||||
irb->scsw.dstat == (DEV_STAT_CHN_END | DEV_STAT_DEV_END))
|
||||
return dasd_era_none;
|
||||
|
||||
/* examine the 24 byte sense data */
|
||||
return dasd_era_recover;
|
||||
|
||||
} /* END dasd_9336_erp_examine */
|
||||
21
drivers/s390/block/dasd_9343_erp.c
Normal file
21
drivers/s390/block/dasd_9343_erp.c
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* File...........: linux/drivers/s390/block/dasd_9345_erp.c
|
||||
* Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
|
||||
* Bugreports.to..: <Linux390@de.ibm.com>
|
||||
* (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 2000
|
||||
*
|
||||
*/
|
||||
|
||||
#define PRINTK_HEADER "dasd_erp(9343)"
|
||||
|
||||
#include "dasd_int.h"
|
||||
|
||||
dasd_era_t
|
||||
dasd_9343_erp_examine(struct dasd_ccw_req * cqr, struct irb * irb)
|
||||
{
|
||||
if (irb->scsw.cstat == 0x00 &&
|
||||
irb->scsw.dstat == (DEV_STAT_CHN_END | DEV_STAT_DEV_END))
|
||||
return dasd_era_none;
|
||||
|
||||
return dasd_era_recover;
|
||||
}
|
||||
1093
drivers/s390/block/dasd_devmap.c
Normal file
1093
drivers/s390/block/dasd_devmap.c
Normal file
File diff suppressed because it is too large
Load Diff
624
drivers/s390/block/dasd_diag.c
Normal file
624
drivers/s390/block/dasd_diag.c
Normal file
@@ -0,0 +1,624 @@
|
||||
/*
|
||||
* File...........: linux/drivers/s390/block/dasd_diag.c
|
||||
* Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
|
||||
* Based on.......: linux/drivers/s390/block/mdisk.c
|
||||
* ...............: by Hartmunt Penner <hpenner@de.ibm.com>
|
||||
* Bugreports.to..: <Linux390@de.ibm.com>
|
||||
* (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999,2000
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/stddef.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/hdreg.h>
|
||||
#include <linux/bio.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/jiffies.h>
|
||||
|
||||
#include <asm/dasd.h>
|
||||
#include <asm/debug.h>
|
||||
#include <asm/ebcdic.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/s390_ext.h>
|
||||
#include <asm/todclk.h>
|
||||
#include <asm/vtoc.h>
|
||||
|
||||
#include "dasd_int.h"
|
||||
#include "dasd_diag.h"
|
||||
|
||||
#define PRINTK_HEADER "dasd(diag):"
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
/* The maximum number of blocks per request (max_blocks) is dependent on the
|
||||
* amount of storage that is available in the static I/O buffer for each
|
||||
* device. Currently each device gets 2 pages. We want to fit two requests
|
||||
* into the available memory so that we can immediately start the next if one
|
||||
* finishes. */
|
||||
#define DIAG_MAX_BLOCKS (((2 * PAGE_SIZE - sizeof(struct dasd_ccw_req) - \
|
||||
sizeof(struct dasd_diag_req)) / \
|
||||
sizeof(struct dasd_diag_bio)) / 2)
|
||||
#define DIAG_MAX_RETRIES 32
|
||||
#define DIAG_TIMEOUT 50 * HZ
|
||||
|
||||
static struct dasd_discipline dasd_diag_discipline;
|
||||
|
||||
struct dasd_diag_private {
|
||||
struct dasd_diag_characteristics rdc_data;
|
||||
struct dasd_diag_rw_io iob;
|
||||
struct dasd_diag_init_io iib;
|
||||
blocknum_t pt_block;
|
||||
};
|
||||
|
||||
struct dasd_diag_req {
|
||||
unsigned int block_count;
|
||||
struct dasd_diag_bio bio[0];
|
||||
};
|
||||
|
||||
static const u8 DASD_DIAG_CMS1[] = { 0xc3, 0xd4, 0xe2, 0xf1 };/* EBCDIC CMS1 */
|
||||
|
||||
/* Perform DIAG250 call with block I/O parameter list iob (input and output)
|
||||
* and function code cmd.
|
||||
* In case of an exception return 3. Otherwise return result of bitwise OR of
|
||||
* resulting condition code and DIAG return code. */
|
||||
static inline int dia250(void *iob, int cmd)
|
||||
{
|
||||
register unsigned long reg2 asm ("2") = (unsigned long) iob;
|
||||
typedef union {
|
||||
struct dasd_diag_init_io init_io;
|
||||
struct dasd_diag_rw_io rw_io;
|
||||
} addr_type;
|
||||
int rc;
|
||||
|
||||
rc = 3;
|
||||
asm volatile(
|
||||
" diag 2,%2,0x250\n"
|
||||
"0: ipm %0\n"
|
||||
" srl %0,28\n"
|
||||
" or %0,3\n"
|
||||
"1:\n"
|
||||
EX_TABLE(0b,1b)
|
||||
: "+d" (rc), "=m" (*(addr_type *) iob)
|
||||
: "d" (cmd), "d" (reg2), "m" (*(addr_type *) iob)
|
||||
: "3", "cc");
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Initialize block I/O to DIAG device using the specified blocksize and
|
||||
* block offset. On success, return zero and set end_block to contain the
|
||||
* number of blocks on the device minus the specified offset. Return non-zero
|
||||
* otherwise. */
|
||||
static inline int
|
||||
mdsk_init_io(struct dasd_device *device, unsigned int blocksize,
|
||||
blocknum_t offset, blocknum_t *end_block)
|
||||
{
|
||||
struct dasd_diag_private *private;
|
||||
struct dasd_diag_init_io *iib;
|
||||
int rc;
|
||||
|
||||
private = (struct dasd_diag_private *) device->private;
|
||||
iib = &private->iib;
|
||||
memset(iib, 0, sizeof (struct dasd_diag_init_io));
|
||||
|
||||
iib->dev_nr = _ccw_device_get_device_number(device->cdev);
|
||||
iib->block_size = blocksize;
|
||||
iib->offset = offset;
|
||||
iib->flaga = DASD_DIAG_FLAGA_DEFAULT;
|
||||
|
||||
rc = dia250(iib, INIT_BIO);
|
||||
|
||||
if ((rc & 3) == 0 && end_block)
|
||||
*end_block = iib->end_block;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Remove block I/O environment for device. Return zero on success, non-zero
|
||||
* otherwise. */
|
||||
static inline int
|
||||
mdsk_term_io(struct dasd_device * device)
|
||||
{
|
||||
struct dasd_diag_private *private;
|
||||
struct dasd_diag_init_io *iib;
|
||||
int rc;
|
||||
|
||||
private = (struct dasd_diag_private *) device->private;
|
||||
iib = &private->iib;
|
||||
memset(iib, 0, sizeof (struct dasd_diag_init_io));
|
||||
iib->dev_nr = _ccw_device_get_device_number(device->cdev);
|
||||
rc = dia250(iib, TERM_BIO);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Error recovery for failed DIAG requests - try to reestablish the DIAG
|
||||
* environment. */
|
||||
static void
|
||||
dasd_diag_erp(struct dasd_device *device)
|
||||
{
|
||||
int rc;
|
||||
|
||||
mdsk_term_io(device);
|
||||
rc = mdsk_init_io(device, device->bp_block, 0, NULL);
|
||||
if (rc)
|
||||
DEV_MESSAGE(KERN_WARNING, device, "DIAG ERP unsuccessful, "
|
||||
"rc=%d", rc);
|
||||
}
|
||||
|
||||
/* Start a given request at the device. Return zero on success, non-zero
|
||||
* otherwise. */
|
||||
static int
|
||||
dasd_start_diag(struct dasd_ccw_req * cqr)
|
||||
{
|
||||
struct dasd_device *device;
|
||||
struct dasd_diag_private *private;
|
||||
struct dasd_diag_req *dreq;
|
||||
int rc;
|
||||
|
||||
device = cqr->device;
|
||||
if (cqr->retries < 0) {
|
||||
DEV_MESSAGE(KERN_WARNING, device, "DIAG start_IO: request %p "
|
||||
"- no retry left)", cqr);
|
||||
cqr->status = DASD_CQR_FAILED;
|
||||
return -EIO;
|
||||
}
|
||||
private = (struct dasd_diag_private *) device->private;
|
||||
dreq = (struct dasd_diag_req *) cqr->data;
|
||||
|
||||
private->iob.dev_nr = _ccw_device_get_device_number(device->cdev);
|
||||
private->iob.key = 0;
|
||||
private->iob.flags = DASD_DIAG_RWFLAG_ASYNC;
|
||||
private->iob.block_count = dreq->block_count;
|
||||
private->iob.interrupt_params = (addr_t) cqr;
|
||||
private->iob.bio_list = dreq->bio;
|
||||
private->iob.flaga = DASD_DIAG_FLAGA_DEFAULT;
|
||||
|
||||
cqr->startclk = get_clock();
|
||||
cqr->starttime = jiffies;
|
||||
cqr->retries--;
|
||||
|
||||
rc = dia250(&private->iob, RW_BIO);
|
||||
switch (rc) {
|
||||
case 0: /* Synchronous I/O finished successfully */
|
||||
cqr->stopclk = get_clock();
|
||||
cqr->status = DASD_CQR_DONE;
|
||||
/* Indicate to calling function that only a dasd_schedule_bh()
|
||||
and no timer is needed */
|
||||
rc = -EACCES;
|
||||
break;
|
||||
case 8: /* Asynchronous I/O was started */
|
||||
cqr->status = DASD_CQR_IN_IO;
|
||||
rc = 0;
|
||||
break;
|
||||
default: /* Error condition */
|
||||
cqr->status = DASD_CQR_QUEUED;
|
||||
DEV_MESSAGE(KERN_WARNING, device, "dia250 returned rc=%d", rc);
|
||||
dasd_diag_erp(device);
|
||||
rc = -EIO;
|
||||
break;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Terminate given request at the device. */
|
||||
static int
|
||||
dasd_diag_term_IO(struct dasd_ccw_req * cqr)
|
||||
{
|
||||
struct dasd_device *device;
|
||||
|
||||
device = cqr->device;
|
||||
mdsk_term_io(device);
|
||||
mdsk_init_io(device, device->bp_block, 0, NULL);
|
||||
cqr->status = DASD_CQR_CLEAR;
|
||||
cqr->stopclk = get_clock();
|
||||
dasd_schedule_bh(device);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Handle external interruption. */
|
||||
static void
|
||||
dasd_ext_handler(__u16 code)
|
||||
{
|
||||
struct dasd_ccw_req *cqr, *next;
|
||||
struct dasd_device *device;
|
||||
unsigned long long expires;
|
||||
unsigned long flags;
|
||||
u8 int_code, status;
|
||||
addr_t ip;
|
||||
int rc;
|
||||
|
||||
int_code = *((u8 *) DASD_DIAG_LC_INT_CODE);
|
||||
status = *((u8 *) DASD_DIAG_LC_INT_STATUS);
|
||||
switch (int_code) {
|
||||
case DASD_DIAG_CODE_31BIT:
|
||||
ip = (addr_t) *((u32 *) DASD_DIAG_LC_INT_PARM_31BIT);
|
||||
break;
|
||||
case DASD_DIAG_CODE_64BIT:
|
||||
ip = (addr_t) *((u64 *) DASD_DIAG_LC_INT_PARM_64BIT);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
if (!ip) { /* no intparm: unsolicited interrupt */
|
||||
MESSAGE(KERN_DEBUG, "%s", "caught unsolicited interrupt");
|
||||
return;
|
||||
}
|
||||
cqr = (struct dasd_ccw_req *) ip;
|
||||
device = (struct dasd_device *) cqr->device;
|
||||
if (strncmp(device->discipline->ebcname, (char *) &cqr->magic, 4)) {
|
||||
DEV_MESSAGE(KERN_WARNING, device,
|
||||
" magic number of dasd_ccw_req 0x%08X doesn't"
|
||||
" match discipline 0x%08X",
|
||||
cqr->magic, *(int *) (&device->discipline->name));
|
||||
return;
|
||||
}
|
||||
|
||||
/* get irq lock to modify request queue */
|
||||
spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
|
||||
|
||||
/* Check for a pending clear operation */
|
||||
if (cqr->status == DASD_CQR_CLEAR) {
|
||||
cqr->status = DASD_CQR_QUEUED;
|
||||
dasd_clear_timer(device);
|
||||
dasd_schedule_bh(device);
|
||||
spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
|
||||
return;
|
||||
}
|
||||
|
||||
cqr->stopclk = get_clock();
|
||||
|
||||
expires = 0;
|
||||
if (status == 0) {
|
||||
cqr->status = DASD_CQR_DONE;
|
||||
/* Start first request on queue if possible -> fast_io. */
|
||||
if (!list_empty(&device->ccw_queue)) {
|
||||
next = list_entry(device->ccw_queue.next,
|
||||
struct dasd_ccw_req, list);
|
||||
if (next->status == DASD_CQR_QUEUED) {
|
||||
rc = dasd_start_diag(next);
|
||||
if (rc == 0)
|
||||
expires = next->expires;
|
||||
else if (rc != -EACCES)
|
||||
DEV_MESSAGE(KERN_WARNING, device, "%s",
|
||||
"Interrupt fastpath "
|
||||
"failed!");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cqr->status = DASD_CQR_QUEUED;
|
||||
DEV_MESSAGE(KERN_WARNING, device, "interrupt status for "
|
||||
"request %p was %d (%d retries left)", cqr, status,
|
||||
cqr->retries);
|
||||
dasd_diag_erp(device);
|
||||
}
|
||||
|
||||
if (expires != 0)
|
||||
dasd_set_timer(device, expires);
|
||||
else
|
||||
dasd_clear_timer(device);
|
||||
dasd_schedule_bh(device);
|
||||
|
||||
spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
|
||||
}
|
||||
|
||||
/* Check whether device can be controlled by DIAG discipline. Return zero on
|
||||
* success, non-zero otherwise. */
|
||||
static int
|
||||
dasd_diag_check_device(struct dasd_device *device)
|
||||
{
|
||||
struct dasd_diag_private *private;
|
||||
struct dasd_diag_characteristics *rdc_data;
|
||||
struct dasd_diag_bio bio;
|
||||
struct vtoc_cms_label *label;
|
||||
blocknum_t end_block;
|
||||
unsigned int sb, bsize;
|
||||
int rc;
|
||||
|
||||
private = (struct dasd_diag_private *) device->private;
|
||||
if (private == NULL) {
|
||||
private = kzalloc(sizeof(struct dasd_diag_private),GFP_KERNEL);
|
||||
if (private == NULL) {
|
||||
DEV_MESSAGE(KERN_WARNING, device, "%s",
|
||||
"memory allocation failed for private data");
|
||||
return -ENOMEM;
|
||||
}
|
||||
device->private = (void *) private;
|
||||
}
|
||||
/* Read Device Characteristics */
|
||||
rdc_data = (void *) &(private->rdc_data);
|
||||
rdc_data->dev_nr = _ccw_device_get_device_number(device->cdev);
|
||||
rdc_data->rdc_len = sizeof (struct dasd_diag_characteristics);
|
||||
|
||||
rc = diag210((struct diag210 *) rdc_data);
|
||||
if (rc) {
|
||||
DEV_MESSAGE(KERN_WARNING, device, "failed to retrieve device "
|
||||
"information (rc=%d)", rc);
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
/* Figure out position of label block */
|
||||
switch (private->rdc_data.vdev_class) {
|
||||
case DEV_CLASS_FBA:
|
||||
private->pt_block = 1;
|
||||
break;
|
||||
case DEV_CLASS_ECKD:
|
||||
private->pt_block = 2;
|
||||
break;
|
||||
default:
|
||||
DEV_MESSAGE(KERN_WARNING, device, "unsupported device class "
|
||||
"(class=%d)", private->rdc_data.vdev_class);
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
DBF_DEV_EVENT(DBF_INFO, device,
|
||||
"%04X: %04X on real %04X/%02X",
|
||||
rdc_data->dev_nr,
|
||||
rdc_data->vdev_type,
|
||||
rdc_data->rdev_type, rdc_data->rdev_model);
|
||||
|
||||
/* terminate all outstanding operations */
|
||||
mdsk_term_io(device);
|
||||
|
||||
/* figure out blocksize of device */
|
||||
label = (struct vtoc_cms_label *) get_zeroed_page(GFP_KERNEL);
|
||||
if (label == NULL) {
|
||||
DEV_MESSAGE(KERN_WARNING, device, "%s",
|
||||
"No memory to allocate initialization request");
|
||||
return -ENOMEM;
|
||||
}
|
||||
rc = 0;
|
||||
end_block = 0;
|
||||
/* try all sizes - needed for ECKD devices */
|
||||
for (bsize = 512; bsize <= PAGE_SIZE; bsize <<= 1) {
|
||||
mdsk_init_io(device, bsize, 0, &end_block);
|
||||
memset(&bio, 0, sizeof (struct dasd_diag_bio));
|
||||
bio.type = MDSK_READ_REQ;
|
||||
bio.block_number = private->pt_block + 1;
|
||||
bio.buffer = label;
|
||||
memset(&private->iob, 0, sizeof (struct dasd_diag_rw_io));
|
||||
private->iob.dev_nr = rdc_data->dev_nr;
|
||||
private->iob.key = 0;
|
||||
private->iob.flags = 0; /* do synchronous io */
|
||||
private->iob.block_count = 1;
|
||||
private->iob.interrupt_params = 0;
|
||||
private->iob.bio_list = &bio;
|
||||
private->iob.flaga = DASD_DIAG_FLAGA_DEFAULT;
|
||||
rc = dia250(&private->iob, RW_BIO);
|
||||
if (rc == 3) {
|
||||
DEV_MESSAGE(KERN_WARNING, device, "%s",
|
||||
"DIAG call failed");
|
||||
rc = -EOPNOTSUPP;
|
||||
goto out;
|
||||
}
|
||||
mdsk_term_io(device);
|
||||
if (rc == 0)
|
||||
break;
|
||||
}
|
||||
if (bsize > PAGE_SIZE) {
|
||||
DEV_MESSAGE(KERN_WARNING, device, "device access failed "
|
||||
"(rc=%d)", rc);
|
||||
rc = -EIO;
|
||||
goto out;
|
||||
}
|
||||
/* check for label block */
|
||||
if (memcmp(label->label_id, DASD_DIAG_CMS1,
|
||||
sizeof(DASD_DIAG_CMS1)) == 0) {
|
||||
/* get formatted blocksize from label block */
|
||||
bsize = (unsigned int) label->block_size;
|
||||
device->blocks = (unsigned long) label->block_count;
|
||||
} else
|
||||
device->blocks = end_block;
|
||||
device->bp_block = bsize;
|
||||
device->s2b_shift = 0; /* bits to shift 512 to get a block */
|
||||
for (sb = 512; sb < bsize; sb = sb << 1)
|
||||
device->s2b_shift++;
|
||||
rc = mdsk_init_io(device, device->bp_block, 0, NULL);
|
||||
if (rc) {
|
||||
DEV_MESSAGE(KERN_WARNING, device, "DIAG initialization "
|
||||
"failed (rc=%d)", rc);
|
||||
rc = -EIO;
|
||||
} else {
|
||||
DEV_MESSAGE(KERN_INFO, device,
|
||||
"(%ld B/blk): %ldkB",
|
||||
(unsigned long) device->bp_block,
|
||||
(unsigned long) (device->blocks <<
|
||||
device->s2b_shift) >> 1);
|
||||
}
|
||||
out:
|
||||
free_page((long) label);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Fill in virtual disk geometry for device. Return zero on success, non-zero
|
||||
* otherwise. */
|
||||
static int
|
||||
dasd_diag_fill_geometry(struct dasd_device *device, struct hd_geometry *geo)
|
||||
{
|
||||
if (dasd_check_blocksize(device->bp_block) != 0)
|
||||
return -EINVAL;
|
||||
geo->cylinders = (device->blocks << device->s2b_shift) >> 10;
|
||||
geo->heads = 16;
|
||||
geo->sectors = 128 >> device->s2b_shift;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static dasd_era_t
|
||||
dasd_diag_examine_error(struct dasd_ccw_req * cqr, struct irb * stat)
|
||||
{
|
||||
return dasd_era_fatal;
|
||||
}
|
||||
|
||||
static dasd_erp_fn_t
|
||||
dasd_diag_erp_action(struct dasd_ccw_req * cqr)
|
||||
{
|
||||
return dasd_default_erp_action;
|
||||
}
|
||||
|
||||
static dasd_erp_fn_t
|
||||
dasd_diag_erp_postaction(struct dasd_ccw_req * cqr)
|
||||
{
|
||||
return dasd_default_erp_postaction;
|
||||
}
|
||||
|
||||
/* Create DASD request from block device request. Return pointer to new
|
||||
* request on success, ERR_PTR otherwise. */
|
||||
static struct dasd_ccw_req *
|
||||
dasd_diag_build_cp(struct dasd_device * device, struct request *req)
|
||||
{
|
||||
struct dasd_ccw_req *cqr;
|
||||
struct dasd_diag_req *dreq;
|
||||
struct dasd_diag_bio *dbio;
|
||||
struct bio *bio;
|
||||
struct bio_vec *bv;
|
||||
char *dst;
|
||||
unsigned int count, datasize;
|
||||
sector_t recid, first_rec, last_rec;
|
||||
unsigned int blksize, off;
|
||||
unsigned char rw_cmd;
|
||||
int i;
|
||||
|
||||
if (rq_data_dir(req) == READ)
|
||||
rw_cmd = MDSK_READ_REQ;
|
||||
else if (rq_data_dir(req) == WRITE)
|
||||
rw_cmd = MDSK_WRITE_REQ;
|
||||
else
|
||||
return ERR_PTR(-EINVAL);
|
||||
blksize = device->bp_block;
|
||||
/* Calculate record id of first and last block. */
|
||||
first_rec = req->sector >> device->s2b_shift;
|
||||
last_rec = (req->sector + req->nr_sectors - 1) >> device->s2b_shift;
|
||||
/* Check struct bio and count the number of blocks for the request. */
|
||||
count = 0;
|
||||
rq_for_each_bio(bio, req) {
|
||||
bio_for_each_segment(bv, bio, i) {
|
||||
if (bv->bv_len & (blksize - 1))
|
||||
/* Fba can only do full blocks. */
|
||||
return ERR_PTR(-EINVAL);
|
||||
count += bv->bv_len >> (device->s2b_shift + 9);
|
||||
}
|
||||
}
|
||||
/* Paranoia. */
|
||||
if (count != last_rec - first_rec + 1)
|
||||
return ERR_PTR(-EINVAL);
|
||||
/* Build the request */
|
||||
datasize = sizeof(struct dasd_diag_req) +
|
||||
count*sizeof(struct dasd_diag_bio);
|
||||
cqr = dasd_smalloc_request(dasd_diag_discipline.name, 0,
|
||||
datasize, device);
|
||||
if (IS_ERR(cqr))
|
||||
return cqr;
|
||||
|
||||
dreq = (struct dasd_diag_req *) cqr->data;
|
||||
dreq->block_count = count;
|
||||
dbio = dreq->bio;
|
||||
recid = first_rec;
|
||||
rq_for_each_bio(bio, req) {
|
||||
bio_for_each_segment(bv, bio, i) {
|
||||
dst = page_address(bv->bv_page) + bv->bv_offset;
|
||||
for (off = 0; off < bv->bv_len; off += blksize) {
|
||||
memset(dbio, 0, sizeof (struct dasd_diag_bio));
|
||||
dbio->type = rw_cmd;
|
||||
dbio->block_number = recid + 1;
|
||||
dbio->buffer = dst;
|
||||
dbio++;
|
||||
dst += blksize;
|
||||
recid++;
|
||||
}
|
||||
}
|
||||
}
|
||||
cqr->retries = DIAG_MAX_RETRIES;
|
||||
cqr->buildclk = get_clock();
|
||||
if (req->cmd_flags & REQ_FAILFAST)
|
||||
set_bit(DASD_CQR_FLAGS_FAILFAST, &cqr->flags);
|
||||
cqr->device = device;
|
||||
cqr->expires = DIAG_TIMEOUT;
|
||||
cqr->status = DASD_CQR_FILLED;
|
||||
return cqr;
|
||||
}
|
||||
|
||||
/* Release DASD request. Return non-zero if request was successful, zero
|
||||
* otherwise. */
|
||||
static int
|
||||
dasd_diag_free_cp(struct dasd_ccw_req *cqr, struct request *req)
|
||||
{
|
||||
int status;
|
||||
|
||||
status = cqr->status == DASD_CQR_DONE;
|
||||
dasd_sfree_request(cqr, cqr->device);
|
||||
return status;
|
||||
}
|
||||
|
||||
/* Fill in IOCTL data for device. */
|
||||
static int
|
||||
dasd_diag_fill_info(struct dasd_device * device,
|
||||
struct dasd_information2_t * info)
|
||||
{
|
||||
struct dasd_diag_private *private;
|
||||
|
||||
private = (struct dasd_diag_private *) device->private;
|
||||
info->label_block = (unsigned int) private->pt_block;
|
||||
info->FBA_layout = 1;
|
||||
info->format = DASD_FORMAT_LDL;
|
||||
info->characteristics_size = sizeof (struct dasd_diag_characteristics);
|
||||
memcpy(info->characteristics,
|
||||
&((struct dasd_diag_private *) device->private)->rdc_data,
|
||||
sizeof (struct dasd_diag_characteristics));
|
||||
info->confdata_size = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
dasd_diag_dump_sense(struct dasd_device *device, struct dasd_ccw_req * req,
|
||||
struct irb *stat)
|
||||
{
|
||||
DEV_MESSAGE(KERN_ERR, device, "%s",
|
||||
"dump sense not available for DIAG data");
|
||||
}
|
||||
|
||||
static struct dasd_discipline dasd_diag_discipline = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "DIAG",
|
||||
.ebcname = "DIAG",
|
||||
.max_blocks = DIAG_MAX_BLOCKS,
|
||||
.check_device = dasd_diag_check_device,
|
||||
.fill_geometry = dasd_diag_fill_geometry,
|
||||
.start_IO = dasd_start_diag,
|
||||
.term_IO = dasd_diag_term_IO,
|
||||
.examine_error = dasd_diag_examine_error,
|
||||
.erp_action = dasd_diag_erp_action,
|
||||
.erp_postaction = dasd_diag_erp_postaction,
|
||||
.build_cp = dasd_diag_build_cp,
|
||||
.free_cp = dasd_diag_free_cp,
|
||||
.dump_sense = dasd_diag_dump_sense,
|
||||
.fill_info = dasd_diag_fill_info,
|
||||
};
|
||||
|
||||
static int __init
|
||||
dasd_diag_init(void)
|
||||
{
|
||||
if (!MACHINE_IS_VM) {
|
||||
MESSAGE_LOG(KERN_INFO,
|
||||
"Machine is not VM: %s "
|
||||
"discipline not initializing",
|
||||
dasd_diag_discipline.name);
|
||||
return -ENODEV;
|
||||
}
|
||||
ASCEBC(dasd_diag_discipline.ebcname, 4);
|
||||
|
||||
ctl_set_bit(0, 9);
|
||||
register_external_interrupt(0x2603, dasd_ext_handler);
|
||||
dasd_diag_discipline_pointer = &dasd_diag_discipline;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit
|
||||
dasd_diag_cleanup(void)
|
||||
{
|
||||
unregister_external_interrupt(0x2603, dasd_ext_handler);
|
||||
ctl_clear_bit(0, 9);
|
||||
dasd_diag_discipline_pointer = NULL;
|
||||
}
|
||||
|
||||
module_init(dasd_diag_init);
|
||||
module_exit(dasd_diag_cleanup);
|
||||
127
drivers/s390/block/dasd_diag.h
Normal file
127
drivers/s390/block/dasd_diag.h
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* File...........: linux/drivers/s390/block/dasd_diag.h
|
||||
* Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
|
||||
* Based on.......: linux/drivers/s390/block/mdisk.h
|
||||
* ...............: by Hartmunt Penner <hpenner@de.ibm.com>
|
||||
* Bugreports.to..: <Linux390@de.ibm.com>
|
||||
* (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999,2000
|
||||
*
|
||||
*/
|
||||
|
||||
#define MDSK_WRITE_REQ 0x01
|
||||
#define MDSK_READ_REQ 0x02
|
||||
|
||||
#define INIT_BIO 0x00
|
||||
#define RW_BIO 0x01
|
||||
#define TERM_BIO 0x02
|
||||
|
||||
#define DEV_CLASS_FBA 0x01
|
||||
#define DEV_CLASS_ECKD 0x04
|
||||
|
||||
#define DASD_DIAG_LC_INT_CODE 132
|
||||
#define DASD_DIAG_LC_INT_STATUS 133
|
||||
#define DASD_DIAG_LC_INT_PARM_31BIT 128
|
||||
#define DASD_DIAG_LC_INT_PARM_64BIT 4536
|
||||
#define DASD_DIAG_CODE_31BIT 0x03
|
||||
#define DASD_DIAG_CODE_64BIT 0x07
|
||||
|
||||
#define DASD_DIAG_RWFLAG_ASYNC 0x02
|
||||
#define DASD_DIAG_RWFLAG_NOCACHE 0x01
|
||||
|
||||
#define DASD_DIAG_FLAGA_FORMAT_64BIT 0x80
|
||||
|
||||
struct dasd_diag_characteristics {
|
||||
u16 dev_nr;
|
||||
u16 rdc_len;
|
||||
u8 vdev_class;
|
||||
u8 vdev_type;
|
||||
u8 vdev_status;
|
||||
u8 vdev_flags;
|
||||
u8 rdev_class;
|
||||
u8 rdev_type;
|
||||
u8 rdev_model;
|
||||
u8 rdev_features;
|
||||
} __attribute__ ((packed, aligned(4)));
|
||||
|
||||
|
||||
#ifdef CONFIG_64BIT
|
||||
#define DASD_DIAG_FLAGA_DEFAULT DASD_DIAG_FLAGA_FORMAT_64BIT
|
||||
|
||||
typedef u64 blocknum_t;
|
||||
typedef s64 sblocknum_t;
|
||||
|
||||
struct dasd_diag_bio {
|
||||
u8 type;
|
||||
u8 status;
|
||||
u8 spare1[2];
|
||||
u32 alet;
|
||||
blocknum_t block_number;
|
||||
void *buffer;
|
||||
} __attribute__ ((packed, aligned(8)));
|
||||
|
||||
struct dasd_diag_init_io {
|
||||
u16 dev_nr;
|
||||
u8 flaga;
|
||||
u8 spare1[21];
|
||||
u32 block_size;
|
||||
u8 spare2[4];
|
||||
blocknum_t offset;
|
||||
sblocknum_t start_block;
|
||||
blocknum_t end_block;
|
||||
u8 spare3[8];
|
||||
} __attribute__ ((packed, aligned(8)));
|
||||
|
||||
struct dasd_diag_rw_io {
|
||||
u16 dev_nr;
|
||||
u8 flaga;
|
||||
u8 spare1[21];
|
||||
u8 key;
|
||||
u8 flags;
|
||||
u8 spare2[2];
|
||||
u32 block_count;
|
||||
u32 alet;
|
||||
u8 spare3[4];
|
||||
u64 interrupt_params;
|
||||
struct dasd_diag_bio *bio_list;
|
||||
u8 spare4[8];
|
||||
} __attribute__ ((packed, aligned(8)));
|
||||
#else /* CONFIG_64BIT */
|
||||
#define DASD_DIAG_FLAGA_DEFAULT 0x0
|
||||
|
||||
typedef u32 blocknum_t;
|
||||
typedef s32 sblocknum_t;
|
||||
|
||||
struct dasd_diag_bio {
|
||||
u8 type;
|
||||
u8 status;
|
||||
u16 spare1;
|
||||
blocknum_t block_number;
|
||||
u32 alet;
|
||||
void *buffer;
|
||||
} __attribute__ ((packed, aligned(8)));
|
||||
|
||||
struct dasd_diag_init_io {
|
||||
u16 dev_nr;
|
||||
u8 flaga;
|
||||
u8 spare1[21];
|
||||
u32 block_size;
|
||||
blocknum_t offset;
|
||||
sblocknum_t start_block;
|
||||
blocknum_t end_block;
|
||||
u8 spare2[24];
|
||||
} __attribute__ ((packed, aligned(8)));
|
||||
|
||||
struct dasd_diag_rw_io {
|
||||
u16 dev_nr;
|
||||
u8 flaga;
|
||||
u8 spare1[21];
|
||||
u8 key;
|
||||
u8 flags;
|
||||
u8 spare2[2];
|
||||
u32 block_count;
|
||||
u32 alet;
|
||||
struct dasd_diag_bio *bio_list;
|
||||
u32 interrupt_params;
|
||||
u8 spare3[20];
|
||||
} __attribute__ ((packed, aligned(8)));
|
||||
#endif /* CONFIG_64BIT */
|
||||
1757
drivers/s390/block/dasd_eckd.c
Normal file
1757
drivers/s390/block/dasd_eckd.c
Normal file
File diff suppressed because it is too large
Load Diff
368
drivers/s390/block/dasd_eckd.h
Normal file
368
drivers/s390/block/dasd_eckd.h
Normal file
@@ -0,0 +1,368 @@
|
||||
/*
|
||||
* File...........: linux/drivers/s390/block/dasd_eckd.h
|
||||
* Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
|
||||
* Horst Hummel <Horst.Hummel@de.ibm.com>
|
||||
* Bugreports.to..: <Linux390@de.ibm.com>
|
||||
* (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999,2000
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DASD_ECKD_H
|
||||
#define DASD_ECKD_H
|
||||
|
||||
/*****************************************************************************
|
||||
* SECTION: CCW Definitions
|
||||
****************************************************************************/
|
||||
#define DASD_ECKD_CCW_WRITE 0x05
|
||||
#define DASD_ECKD_CCW_READ 0x06
|
||||
#define DASD_ECKD_CCW_WRITE_HOME_ADDRESS 0x09
|
||||
#define DASD_ECKD_CCW_READ_HOME_ADDRESS 0x0a
|
||||
#define DASD_ECKD_CCW_WRITE_KD 0x0d
|
||||
#define DASD_ECKD_CCW_READ_KD 0x0e
|
||||
#define DASD_ECKD_CCW_ERASE 0x11
|
||||
#define DASD_ECKD_CCW_READ_COUNT 0x12
|
||||
#define DASD_ECKD_CCW_SLCK 0x14
|
||||
#define DASD_ECKD_CCW_WRITE_RECORD_ZERO 0x15
|
||||
#define DASD_ECKD_CCW_READ_RECORD_ZERO 0x16
|
||||
#define DASD_ECKD_CCW_WRITE_CKD 0x1d
|
||||
#define DASD_ECKD_CCW_READ_CKD 0x1e
|
||||
#define DASD_ECKD_CCW_PSF 0x27
|
||||
#define DASD_ECKD_CCW_RSSD 0x3e
|
||||
#define DASD_ECKD_CCW_LOCATE_RECORD 0x47
|
||||
#define DASD_ECKD_CCW_SNSS 0x54
|
||||
#define DASD_ECKD_CCW_DEFINE_EXTENT 0x63
|
||||
#define DASD_ECKD_CCW_WRITE_MT 0x85
|
||||
#define DASD_ECKD_CCW_READ_MT 0x86
|
||||
#define DASD_ECKD_CCW_WRITE_KD_MT 0x8d
|
||||
#define DASD_ECKD_CCW_READ_KD_MT 0x8e
|
||||
#define DASD_ECKD_CCW_RELEASE 0x94
|
||||
#define DASD_ECKD_CCW_READ_CKD_MT 0x9e
|
||||
#define DASD_ECKD_CCW_WRITE_CKD_MT 0x9d
|
||||
#define DASD_ECKD_CCW_RESERVE 0xB4
|
||||
|
||||
/*
|
||||
* Perform Subsystem Function / Sub-Orders
|
||||
*/
|
||||
#define PSF_ORDER_PRSSD 0x18
|
||||
#define PSF_ORDER_SSC 0x1D
|
||||
|
||||
/*****************************************************************************
|
||||
* SECTION: Type Definitions
|
||||
****************************************************************************/
|
||||
|
||||
struct eckd_count {
|
||||
__u16 cyl;
|
||||
__u16 head;
|
||||
__u8 record;
|
||||
__u8 kl;
|
||||
__u16 dl;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct ch_t {
|
||||
__u16 cyl;
|
||||
__u16 head;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct chs_t {
|
||||
__u16 cyl;
|
||||
__u16 head;
|
||||
__u32 sector;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct chr_t {
|
||||
__u16 cyl;
|
||||
__u16 head;
|
||||
__u8 record;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct geom_t {
|
||||
__u16 cyl;
|
||||
__u16 head;
|
||||
__u32 sector;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct eckd_home {
|
||||
__u8 skip_control[14];
|
||||
__u16 cell_number;
|
||||
__u8 physical_addr[3];
|
||||
__u8 flag;
|
||||
struct ch_t track_addr;
|
||||
__u8 reserved;
|
||||
__u8 key_length;
|
||||
__u8 reserved2[2];
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct DE_eckd_data {
|
||||
struct {
|
||||
unsigned char perm:2; /* Permissions on this extent */
|
||||
unsigned char reserved:1;
|
||||
unsigned char seek:2; /* Seek control */
|
||||
unsigned char auth:2; /* Access authorization */
|
||||
unsigned char pci:1; /* PCI Fetch mode */
|
||||
} __attribute__ ((packed)) mask;
|
||||
struct {
|
||||
unsigned char mode:2; /* Architecture mode */
|
||||
unsigned char ckd:1; /* CKD Conversion */
|
||||
unsigned char operation:3; /* Operation mode */
|
||||
unsigned char cfw:1; /* Cache fast write */
|
||||
unsigned char dfw:1; /* DASD fast write */
|
||||
} __attribute__ ((packed)) attributes;
|
||||
__u16 blk_size; /* Blocksize */
|
||||
__u16 fast_write_id;
|
||||
__u8 ga_additional; /* Global Attributes Additional */
|
||||
__u8 ga_extended; /* Global Attributes Extended */
|
||||
struct ch_t beg_ext;
|
||||
struct ch_t end_ext;
|
||||
unsigned long long ep_sys_time; /* Ext Parameter - System Time Stamp */
|
||||
__u8 ep_format; /* Extended Parameter format byte */
|
||||
__u8 ep_prio; /* Extended Parameter priority I/O byte */
|
||||
__u8 ep_reserved[6]; /* Extended Parameter Reserved */
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct LO_eckd_data {
|
||||
struct {
|
||||
unsigned char orientation:2;
|
||||
unsigned char operation:6;
|
||||
} __attribute__ ((packed)) operation;
|
||||
struct {
|
||||
unsigned char last_bytes_used:1;
|
||||
unsigned char reserved:6;
|
||||
unsigned char read_count_suffix:1;
|
||||
} __attribute__ ((packed)) auxiliary;
|
||||
__u8 unused;
|
||||
__u8 count;
|
||||
struct ch_t seek_addr;
|
||||
struct chr_t search_arg;
|
||||
__u8 sector;
|
||||
__u16 length;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct dasd_eckd_characteristics {
|
||||
__u16 cu_type;
|
||||
struct {
|
||||
unsigned char support:2;
|
||||
unsigned char async:1;
|
||||
unsigned char reserved:1;
|
||||
unsigned char cache_info:1;
|
||||
unsigned char model:3;
|
||||
} __attribute__ ((packed)) cu_model;
|
||||
__u16 dev_type;
|
||||
__u8 dev_model;
|
||||
struct {
|
||||
unsigned char mult_burst:1;
|
||||
unsigned char RT_in_LR:1;
|
||||
unsigned char reserved1:1;
|
||||
unsigned char RD_IN_LR:1;
|
||||
unsigned char reserved2:4;
|
||||
unsigned char reserved3:8;
|
||||
unsigned char defect_wr:1;
|
||||
unsigned char XRC_supported:1;
|
||||
unsigned char reserved4:1;
|
||||
unsigned char striping:1;
|
||||
unsigned char reserved5:4;
|
||||
unsigned char cfw:1;
|
||||
unsigned char reserved6:2;
|
||||
unsigned char cache:1;
|
||||
unsigned char dual_copy:1;
|
||||
unsigned char dfw:1;
|
||||
unsigned char reset_alleg:1;
|
||||
unsigned char sense_down:1;
|
||||
} __attribute__ ((packed)) facilities;
|
||||
__u8 dev_class;
|
||||
__u8 unit_type;
|
||||
__u16 no_cyl;
|
||||
__u16 trk_per_cyl;
|
||||
__u8 sec_per_trk;
|
||||
__u8 byte_per_track[3];
|
||||
__u16 home_bytes;
|
||||
__u8 formula;
|
||||
union {
|
||||
struct {
|
||||
__u8 f1;
|
||||
__u16 f2;
|
||||
__u16 f3;
|
||||
} __attribute__ ((packed)) f_0x01;
|
||||
struct {
|
||||
__u8 f1;
|
||||
__u8 f2;
|
||||
__u8 f3;
|
||||
__u8 f4;
|
||||
__u8 f5;
|
||||
} __attribute__ ((packed)) f_0x02;
|
||||
} __attribute__ ((packed)) factors;
|
||||
__u16 first_alt_trk;
|
||||
__u16 no_alt_trk;
|
||||
__u16 first_dia_trk;
|
||||
__u16 no_dia_trk;
|
||||
__u16 first_sup_trk;
|
||||
__u16 no_sup_trk;
|
||||
__u8 MDR_ID;
|
||||
__u8 OBR_ID;
|
||||
__u8 director;
|
||||
__u8 rd_trk_set;
|
||||
__u16 max_rec_zero;
|
||||
__u8 reserved1;
|
||||
__u8 RWANY_in_LR;
|
||||
__u8 factor6;
|
||||
__u8 factor7;
|
||||
__u8 factor8;
|
||||
__u8 reserved2[3];
|
||||
__u8 reserved3[10];
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct dasd_eckd_confdata {
|
||||
struct {
|
||||
struct {
|
||||
unsigned char identifier:2;
|
||||
unsigned char token_id:1;
|
||||
unsigned char sno_valid:1;
|
||||
unsigned char subst_sno:1;
|
||||
unsigned char recNED:1;
|
||||
unsigned char emuNED:1;
|
||||
unsigned char reserved:1;
|
||||
} __attribute__ ((packed)) flags;
|
||||
__u8 descriptor;
|
||||
__u8 dev_class;
|
||||
__u8 reserved;
|
||||
unsigned char dev_type[6];
|
||||
unsigned char dev_model[3];
|
||||
unsigned char HDA_manufacturer[3];
|
||||
unsigned char HDA_location[2];
|
||||
unsigned char HDA_seqno[12];
|
||||
__u8 ID;
|
||||
__u8 unit_addr;
|
||||
} __attribute__ ((packed)) ned1;
|
||||
union {
|
||||
struct {
|
||||
struct {
|
||||
unsigned char identifier:2;
|
||||
unsigned char token_id:1;
|
||||
unsigned char sno_valid:1;
|
||||
unsigned char subst_sno:1;
|
||||
unsigned char recNED:1;
|
||||
unsigned char emuNED:1;
|
||||
unsigned char reserved:1;
|
||||
} __attribute__ ((packed)) flags;
|
||||
__u8 descriptor;
|
||||
__u8 reserved[2];
|
||||
unsigned char dev_type[6];
|
||||
unsigned char dev_model[3];
|
||||
unsigned char DASD_manufacturer[3];
|
||||
unsigned char DASD_location[2];
|
||||
unsigned char DASD_seqno[12];
|
||||
__u16 ID;
|
||||
} __attribute__ ((packed)) ned;
|
||||
struct {
|
||||
unsigned char flags; /* byte 0 */
|
||||
unsigned char res2[7]; /* byte 1- 7 */
|
||||
unsigned char sua_flags; /* byte 8 */
|
||||
__u8 base_unit_addr; /* byte 9 */
|
||||
unsigned char res3[22]; /* byte 10-31 */
|
||||
} __attribute__ ((packed)) sneq;
|
||||
} __attribute__ ((packed)) ned2;
|
||||
struct {
|
||||
struct {
|
||||
unsigned char identifier:2;
|
||||
unsigned char token_id:1;
|
||||
unsigned char sno_valid:1;
|
||||
unsigned char subst_sno:1;
|
||||
unsigned char recNED:1;
|
||||
unsigned char emuNED:1;
|
||||
unsigned char reserved:1;
|
||||
} __attribute__ ((packed)) flags;
|
||||
__u8 descriptor;
|
||||
__u8 reserved[2];
|
||||
unsigned char cont_type[6];
|
||||
unsigned char cont_model[3];
|
||||
unsigned char cont_manufacturer[3];
|
||||
unsigned char cont_location[2];
|
||||
unsigned char cont_seqno[12];
|
||||
__u16 ID;
|
||||
} __attribute__ ((packed)) ned3;
|
||||
struct {
|
||||
struct {
|
||||
unsigned char identifier:2;
|
||||
unsigned char token_id:1;
|
||||
unsigned char sno_valid:1;
|
||||
unsigned char subst_sno:1;
|
||||
unsigned char recNED:1;
|
||||
unsigned char emuNED:1;
|
||||
unsigned char reserved:1;
|
||||
} __attribute__ ((packed)) flags;
|
||||
__u8 descriptor;
|
||||
__u8 reserved[2];
|
||||
unsigned char cont_type[6];
|
||||
unsigned char empty[3];
|
||||
unsigned char cont_manufacturer[3];
|
||||
unsigned char cont_location[2];
|
||||
unsigned char cont_seqno[12];
|
||||
__u16 ID;
|
||||
} __attribute__ ((packed)) ned4;
|
||||
unsigned char ned5[32];
|
||||
unsigned char ned6[32];
|
||||
unsigned char ned7[32];
|
||||
struct {
|
||||
struct {
|
||||
unsigned char identifier:2;
|
||||
unsigned char reserved:6;
|
||||
} __attribute__ ((packed)) flags;
|
||||
__u8 selector;
|
||||
__u16 interfaceID;
|
||||
__u32 reserved;
|
||||
__u16 subsystemID;
|
||||
struct {
|
||||
unsigned char sp0:1;
|
||||
unsigned char sp1:1;
|
||||
unsigned char reserved:5;
|
||||
unsigned char scluster:1;
|
||||
} __attribute__ ((packed)) spathID;
|
||||
__u8 unit_address;
|
||||
__u8 dev_ID;
|
||||
__u8 dev_address;
|
||||
__u8 adapterID;
|
||||
__u16 link_address;
|
||||
struct {
|
||||
unsigned char parallel:1;
|
||||
unsigned char escon:1;
|
||||
unsigned char reserved:1;
|
||||
unsigned char ficon:1;
|
||||
unsigned char reserved2:4;
|
||||
} __attribute__ ((packed)) protocol_type;
|
||||
struct {
|
||||
unsigned char PID_in_236:1;
|
||||
unsigned char reserved:7;
|
||||
} __attribute__ ((packed)) format_flags;
|
||||
__u8 log_dev_address;
|
||||
unsigned char reserved2[12];
|
||||
} __attribute__ ((packed)) neq;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct dasd_eckd_path {
|
||||
__u8 opm;
|
||||
__u8 ppm;
|
||||
__u8 npm;
|
||||
};
|
||||
|
||||
/*
|
||||
* Perform Subsystem Function - Prepare for Read Subsystem Data
|
||||
*/
|
||||
struct dasd_psf_prssd_data {
|
||||
unsigned char order;
|
||||
unsigned char flags;
|
||||
unsigned char reserved[4];
|
||||
unsigned char suborder;
|
||||
unsigned char varies[9];
|
||||
} __attribute__ ((packed));
|
||||
|
||||
/*
|
||||
* Perform Subsystem Function - Set Subsystem Characteristics
|
||||
*/
|
||||
struct dasd_psf_ssc_data {
|
||||
unsigned char order;
|
||||
unsigned char flags;
|
||||
unsigned char cu_type[4];
|
||||
unsigned char suborder;
|
||||
unsigned char reserved[59];
|
||||
} __attribute__((packed));
|
||||
|
||||
#endif /* DASD_ECKD_H */
|
||||
695
drivers/s390/block/dasd_eer.c
Normal file
695
drivers/s390/block/dasd_eer.c
Normal file
@@ -0,0 +1,695 @@
|
||||
/*
|
||||
* Character device driver for extended error reporting.
|
||||
*
|
||||
* Copyright (C) 2005 IBM Corporation
|
||||
* extended error reporting for DASD ECKD devices
|
||||
* Author(s): Stefan Weinhuber <wein@de.ibm.com>
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/poll.h>
|
||||
|
||||
#include <asm/uaccess.h>
|
||||
#include <asm/semaphore.h>
|
||||
#include <asm/atomic.h>
|
||||
#include <asm/ebcdic.h>
|
||||
|
||||
#include "dasd_int.h"
|
||||
#include "dasd_eckd.h"
|
||||
|
||||
#ifdef PRINTK_HEADER
|
||||
#undef PRINTK_HEADER
|
||||
#endif /* PRINTK_HEADER */
|
||||
#define PRINTK_HEADER "dasd(eer):"
|
||||
|
||||
/*
|
||||
* SECTION: the internal buffer
|
||||
*/
|
||||
|
||||
/*
|
||||
* The internal buffer is meant to store obaque blobs of data, so it does
|
||||
* not know of higher level concepts like triggers.
|
||||
* It consists of a number of pages that are used as a ringbuffer. Each data
|
||||
* blob is stored in a simple record that consists of an integer, which
|
||||
* contains the size of the following data, and the data bytes themselfes.
|
||||
*
|
||||
* To allow for multiple independent readers we create one internal buffer
|
||||
* each time the device is opened and destroy the buffer when the file is
|
||||
* closed again. The number of pages used for this buffer is determined by
|
||||
* the module parmeter eer_pages.
|
||||
*
|
||||
* One record can be written to a buffer by using the functions
|
||||
* - dasd_eer_start_record (one time per record to write the size to the
|
||||
* buffer and reserve the space for the data)
|
||||
* - dasd_eer_write_buffer (one or more times per record to write the data)
|
||||
* The data can be written in several steps but you will have to compute
|
||||
* the total size up front for the invocation of dasd_eer_start_record.
|
||||
* If the ringbuffer is full, dasd_eer_start_record will remove the required
|
||||
* number of old records.
|
||||
*
|
||||
* A record is typically read in two steps, first read the integer that
|
||||
* specifies the size of the following data, then read the data.
|
||||
* Both can be done by
|
||||
* - dasd_eer_read_buffer
|
||||
*
|
||||
* For all mentioned functions you need to get the bufferlock first and keep
|
||||
* it until a complete record is written or read.
|
||||
*
|
||||
* All information necessary to keep track of an internal buffer is kept in
|
||||
* a struct eerbuffer. The buffer specific to a file pointer is strored in
|
||||
* the private_data field of that file. To be able to write data to all
|
||||
* existing buffers, each buffer is also added to the bufferlist.
|
||||
* If the user does not want to read a complete record in one go, we have to
|
||||
* keep track of the rest of the record. residual stores the number of bytes
|
||||
* that are still to deliver. If the rest of the record is invalidated between
|
||||
* two reads then residual will be set to -1 so that the next read will fail.
|
||||
* All entries in the eerbuffer structure are protected with the bufferlock.
|
||||
* To avoid races between writing to a buffer on the one side and creating
|
||||
* and destroying buffers on the other side, the bufferlock must also be used
|
||||
* to protect the bufferlist.
|
||||
*/
|
||||
|
||||
static int eer_pages = 5;
|
||||
module_param(eer_pages, int, S_IRUGO|S_IWUSR);
|
||||
|
||||
struct eerbuffer {
|
||||
struct list_head list;
|
||||
char **buffer;
|
||||
int buffersize;
|
||||
int buffer_page_count;
|
||||
int head;
|
||||
int tail;
|
||||
int residual;
|
||||
};
|
||||
|
||||
static LIST_HEAD(bufferlist);
|
||||
static DEFINE_SPINLOCK(bufferlock);
|
||||
static DECLARE_WAIT_QUEUE_HEAD(dasd_eer_read_wait_queue);
|
||||
|
||||
/*
|
||||
* How many free bytes are available on the buffer.
|
||||
* Needs to be called with bufferlock held.
|
||||
*/
|
||||
static int dasd_eer_get_free_bytes(struct eerbuffer *eerb)
|
||||
{
|
||||
if (eerb->head < eerb->tail)
|
||||
return eerb->tail - eerb->head - 1;
|
||||
return eerb->buffersize - eerb->head + eerb->tail -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* How many bytes of buffer space are used.
|
||||
* Needs to be called with bufferlock held.
|
||||
*/
|
||||
static int dasd_eer_get_filled_bytes(struct eerbuffer *eerb)
|
||||
{
|
||||
|
||||
if (eerb->head >= eerb->tail)
|
||||
return eerb->head - eerb->tail;
|
||||
return eerb->buffersize - eerb->tail + eerb->head;
|
||||
}
|
||||
|
||||
/*
|
||||
* The dasd_eer_write_buffer function just copies count bytes of data
|
||||
* to the buffer. Make sure to call dasd_eer_start_record first, to
|
||||
* make sure that enough free space is available.
|
||||
* Needs to be called with bufferlock held.
|
||||
*/
|
||||
static void dasd_eer_write_buffer(struct eerbuffer *eerb,
|
||||
char *data, int count)
|
||||
{
|
||||
|
||||
unsigned long headindex,localhead;
|
||||
unsigned long rest, len;
|
||||
char *nextdata;
|
||||
|
||||
nextdata = data;
|
||||
rest = count;
|
||||
while (rest > 0) {
|
||||
headindex = eerb->head / PAGE_SIZE;
|
||||
localhead = eerb->head % PAGE_SIZE;
|
||||
len = min(rest, PAGE_SIZE - localhead);
|
||||
memcpy(eerb->buffer[headindex]+localhead, nextdata, len);
|
||||
nextdata += len;
|
||||
rest -= len;
|
||||
eerb->head += len;
|
||||
if (eerb->head == eerb->buffersize)
|
||||
eerb->head = 0; /* wrap around */
|
||||
BUG_ON(eerb->head > eerb->buffersize);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Needs to be called with bufferlock held.
|
||||
*/
|
||||
static int dasd_eer_read_buffer(struct eerbuffer *eerb, char *data, int count)
|
||||
{
|
||||
|
||||
unsigned long tailindex,localtail;
|
||||
unsigned long rest, len, finalcount;
|
||||
char *nextdata;
|
||||
|
||||
finalcount = min(count, dasd_eer_get_filled_bytes(eerb));
|
||||
nextdata = data;
|
||||
rest = finalcount;
|
||||
while (rest > 0) {
|
||||
tailindex = eerb->tail / PAGE_SIZE;
|
||||
localtail = eerb->tail % PAGE_SIZE;
|
||||
len = min(rest, PAGE_SIZE - localtail);
|
||||
memcpy(nextdata, eerb->buffer[tailindex] + localtail, len);
|
||||
nextdata += len;
|
||||
rest -= len;
|
||||
eerb->tail += len;
|
||||
if (eerb->tail == eerb->buffersize)
|
||||
eerb->tail = 0; /* wrap around */
|
||||
BUG_ON(eerb->tail > eerb->buffersize);
|
||||
}
|
||||
return finalcount;
|
||||
}
|
||||
|
||||
/*
|
||||
* Whenever you want to write a blob of data to the internal buffer you
|
||||
* have to start by using this function first. It will write the number
|
||||
* of bytes that will be written to the buffer. If necessary it will remove
|
||||
* old records to make room for the new one.
|
||||
* Needs to be called with bufferlock held.
|
||||
*/
|
||||
static int dasd_eer_start_record(struct eerbuffer *eerb, int count)
|
||||
{
|
||||
int tailcount;
|
||||
|
||||
if (count + sizeof(count) > eerb->buffersize)
|
||||
return -ENOMEM;
|
||||
while (dasd_eer_get_free_bytes(eerb) < count + sizeof(count)) {
|
||||
if (eerb->residual > 0) {
|
||||
eerb->tail += eerb->residual;
|
||||
if (eerb->tail >= eerb->buffersize)
|
||||
eerb->tail -= eerb->buffersize;
|
||||
eerb->residual = -1;
|
||||
}
|
||||
dasd_eer_read_buffer(eerb, (char *) &tailcount,
|
||||
sizeof(tailcount));
|
||||
eerb->tail += tailcount;
|
||||
if (eerb->tail >= eerb->buffersize)
|
||||
eerb->tail -= eerb->buffersize;
|
||||
}
|
||||
dasd_eer_write_buffer(eerb, (char*) &count, sizeof(count));
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
/*
|
||||
* Release pages that are not used anymore.
|
||||
*/
|
||||
static void dasd_eer_free_buffer_pages(char **buf, int no_pages)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < no_pages; i++)
|
||||
free_page((unsigned long) buf[i]);
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate a new set of memory pages.
|
||||
*/
|
||||
static int dasd_eer_allocate_buffer_pages(char **buf, int no_pages)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < no_pages; i++) {
|
||||
buf[i] = (char *) get_zeroed_page(GFP_KERNEL);
|
||||
if (!buf[i]) {
|
||||
dasd_eer_free_buffer_pages(buf, i);
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* SECTION: The extended error reporting functionality
|
||||
*/
|
||||
|
||||
/*
|
||||
* When a DASD device driver wants to report an error, it calls the
|
||||
* function dasd_eer_write and gives the respective trigger ID as
|
||||
* parameter. Currently there are four kinds of triggers:
|
||||
*
|
||||
* DASD_EER_FATALERROR: all kinds of unrecoverable I/O problems
|
||||
* DASD_EER_PPRCSUSPEND: PPRC was suspended
|
||||
* DASD_EER_NOPATH: There is no path to the device left.
|
||||
* DASD_EER_STATECHANGE: The state of the device has changed.
|
||||
*
|
||||
* For the first three triggers all required information can be supplied by
|
||||
* the caller. For these triggers a record is written by the function
|
||||
* dasd_eer_write_standard_trigger.
|
||||
*
|
||||
* The DASD_EER_STATECHANGE trigger is special since a sense subsystem
|
||||
* status ccw need to be executed to gather the necessary sense data first.
|
||||
* The dasd_eer_snss function will queue the SNSS request and the request
|
||||
* callback will then call dasd_eer_write with the DASD_EER_STATCHANGE
|
||||
* trigger.
|
||||
*
|
||||
* To avoid memory allocations at runtime, the necessary memory is allocated
|
||||
* when the extended error reporting is enabled for a device (by
|
||||
* dasd_eer_probe). There is one sense subsystem status request for each
|
||||
* eer enabled DASD device. The presence of the cqr in device->eer_cqr
|
||||
* indicates that eer is enable for the device. The use of the snss request
|
||||
* is protected by the DASD_FLAG_EER_IN_USE bit. When this flag indicates
|
||||
* that the cqr is currently in use, dasd_eer_snss cannot start a second
|
||||
* request but sets the DASD_FLAG_EER_SNSS flag instead. The callback of
|
||||
* the SNSS request will check the bit and call dasd_eer_snss again.
|
||||
*/
|
||||
|
||||
#define SNSS_DATA_SIZE 44
|
||||
|
||||
#define DASD_EER_BUSID_SIZE 10
|
||||
struct dasd_eer_header {
|
||||
__u32 total_size;
|
||||
__u32 trigger;
|
||||
__u64 tv_sec;
|
||||
__u64 tv_usec;
|
||||
char busid[DASD_EER_BUSID_SIZE];
|
||||
} __attribute__ ((packed));
|
||||
|
||||
/*
|
||||
* The following function can be used for those triggers that have
|
||||
* all necessary data available when the function is called.
|
||||
* If the parameter cqr is not NULL, the chain of requests will be searched
|
||||
* for valid sense data, and all valid sense data sets will be added to
|
||||
* the triggers data.
|
||||
*/
|
||||
static void dasd_eer_write_standard_trigger(struct dasd_device *device,
|
||||
struct dasd_ccw_req *cqr,
|
||||
int trigger)
|
||||
{
|
||||
struct dasd_ccw_req *temp_cqr;
|
||||
int data_size;
|
||||
struct timeval tv;
|
||||
struct dasd_eer_header header;
|
||||
unsigned long flags;
|
||||
struct eerbuffer *eerb;
|
||||
|
||||
/* go through cqr chain and count the valid sense data sets */
|
||||
data_size = 0;
|
||||
for (temp_cqr = cqr; temp_cqr; temp_cqr = temp_cqr->refers)
|
||||
if (temp_cqr->irb.esw.esw0.erw.cons)
|
||||
data_size += 32;
|
||||
|
||||
header.total_size = sizeof(header) + data_size + 4; /* "EOR" */
|
||||
header.trigger = trigger;
|
||||
do_gettimeofday(&tv);
|
||||
header.tv_sec = tv.tv_sec;
|
||||
header.tv_usec = tv.tv_usec;
|
||||
strncpy(header.busid, device->cdev->dev.bus_id, DASD_EER_BUSID_SIZE);
|
||||
|
||||
spin_lock_irqsave(&bufferlock, flags);
|
||||
list_for_each_entry(eerb, &bufferlist, list) {
|
||||
dasd_eer_start_record(eerb, header.total_size);
|
||||
dasd_eer_write_buffer(eerb, (char *) &header, sizeof(header));
|
||||
for (temp_cqr = cqr; temp_cqr; temp_cqr = temp_cqr->refers)
|
||||
if (temp_cqr->irb.esw.esw0.erw.cons)
|
||||
dasd_eer_write_buffer(eerb, cqr->irb.ecw, 32);
|
||||
dasd_eer_write_buffer(eerb, "EOR", 4);
|
||||
}
|
||||
spin_unlock_irqrestore(&bufferlock, flags);
|
||||
wake_up_interruptible(&dasd_eer_read_wait_queue);
|
||||
}
|
||||
|
||||
/*
|
||||
* This function writes a DASD_EER_STATECHANGE trigger.
|
||||
*/
|
||||
static void dasd_eer_write_snss_trigger(struct dasd_device *device,
|
||||
struct dasd_ccw_req *cqr,
|
||||
int trigger)
|
||||
{
|
||||
int data_size;
|
||||
int snss_rc;
|
||||
struct timeval tv;
|
||||
struct dasd_eer_header header;
|
||||
unsigned long flags;
|
||||
struct eerbuffer *eerb;
|
||||
|
||||
snss_rc = (cqr->status == DASD_CQR_FAILED) ? -EIO : 0;
|
||||
if (snss_rc)
|
||||
data_size = 0;
|
||||
else
|
||||
data_size = SNSS_DATA_SIZE;
|
||||
|
||||
header.total_size = sizeof(header) + data_size + 4; /* "EOR" */
|
||||
header.trigger = DASD_EER_STATECHANGE;
|
||||
do_gettimeofday(&tv);
|
||||
header.tv_sec = tv.tv_sec;
|
||||
header.tv_usec = tv.tv_usec;
|
||||
strncpy(header.busid, device->cdev->dev.bus_id, DASD_EER_BUSID_SIZE);
|
||||
|
||||
spin_lock_irqsave(&bufferlock, flags);
|
||||
list_for_each_entry(eerb, &bufferlist, list) {
|
||||
dasd_eer_start_record(eerb, header.total_size);
|
||||
dasd_eer_write_buffer(eerb, (char *) &header , sizeof(header));
|
||||
if (!snss_rc)
|
||||
dasd_eer_write_buffer(eerb, cqr->data, SNSS_DATA_SIZE);
|
||||
dasd_eer_write_buffer(eerb, "EOR", 4);
|
||||
}
|
||||
spin_unlock_irqrestore(&bufferlock, flags);
|
||||
wake_up_interruptible(&dasd_eer_read_wait_queue);
|
||||
}
|
||||
|
||||
/*
|
||||
* This function is called for all triggers. It calls the appropriate
|
||||
* function that writes the actual trigger records.
|
||||
*/
|
||||
void dasd_eer_write(struct dasd_device *device, struct dasd_ccw_req *cqr,
|
||||
unsigned int id)
|
||||
{
|
||||
if (!device->eer_cqr)
|
||||
return;
|
||||
switch (id) {
|
||||
case DASD_EER_FATALERROR:
|
||||
case DASD_EER_PPRCSUSPEND:
|
||||
dasd_eer_write_standard_trigger(device, cqr, id);
|
||||
break;
|
||||
case DASD_EER_NOPATH:
|
||||
dasd_eer_write_standard_trigger(device, NULL, id);
|
||||
break;
|
||||
case DASD_EER_STATECHANGE:
|
||||
dasd_eer_write_snss_trigger(device, cqr, id);
|
||||
break;
|
||||
default: /* unknown trigger, so we write it without any sense data */
|
||||
dasd_eer_write_standard_trigger(device, NULL, id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(dasd_eer_write);
|
||||
|
||||
/*
|
||||
* Start a sense subsystem status request.
|
||||
* Needs to be called with the device held.
|
||||
*/
|
||||
void dasd_eer_snss(struct dasd_device *device)
|
||||
{
|
||||
struct dasd_ccw_req *cqr;
|
||||
|
||||
cqr = device->eer_cqr;
|
||||
if (!cqr) /* Device not eer enabled. */
|
||||
return;
|
||||
if (test_and_set_bit(DASD_FLAG_EER_IN_USE, &device->flags)) {
|
||||
/* Sense subsystem status request in use. */
|
||||
set_bit(DASD_FLAG_EER_SNSS, &device->flags);
|
||||
return;
|
||||
}
|
||||
clear_bit(DASD_FLAG_EER_SNSS, &device->flags);
|
||||
cqr->status = DASD_CQR_QUEUED;
|
||||
list_add(&cqr->list, &device->ccw_queue);
|
||||
dasd_schedule_bh(device);
|
||||
}
|
||||
|
||||
/*
|
||||
* Callback function for use with sense subsystem status request.
|
||||
*/
|
||||
static void dasd_eer_snss_cb(struct dasd_ccw_req *cqr, void *data)
|
||||
{
|
||||
struct dasd_device *device = cqr->device;
|
||||
unsigned long flags;
|
||||
|
||||
dasd_eer_write(device, cqr, DASD_EER_STATECHANGE);
|
||||
spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
|
||||
if (device->eer_cqr == cqr) {
|
||||
clear_bit(DASD_FLAG_EER_IN_USE, &device->flags);
|
||||
if (test_bit(DASD_FLAG_EER_SNSS, &device->flags))
|
||||
/* Another SNSS has been requested in the meantime. */
|
||||
dasd_eer_snss(device);
|
||||
cqr = NULL;
|
||||
}
|
||||
spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
|
||||
if (cqr)
|
||||
/*
|
||||
* Extended error recovery has been switched off while
|
||||
* the SNSS request was running. It could even have
|
||||
* been switched off and on again in which case there
|
||||
* is a new ccw in device->eer_cqr. Free the "old"
|
||||
* snss request now.
|
||||
*/
|
||||
dasd_kfree_request(cqr, device);
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable error reporting on a given device.
|
||||
*/
|
||||
int dasd_eer_enable(struct dasd_device *device)
|
||||
{
|
||||
struct dasd_ccw_req *cqr;
|
||||
unsigned long flags;
|
||||
|
||||
if (device->eer_cqr)
|
||||
return 0;
|
||||
|
||||
if (!device->discipline || strcmp(device->discipline->name, "ECKD"))
|
||||
return -EPERM; /* FIXME: -EMEDIUMTYPE ? */
|
||||
|
||||
cqr = dasd_kmalloc_request("ECKD", 1 /* SNSS */,
|
||||
SNSS_DATA_SIZE, device);
|
||||
if (!cqr)
|
||||
return -ENOMEM;
|
||||
|
||||
cqr->device = device;
|
||||
cqr->retries = 255;
|
||||
cqr->expires = 10 * HZ;
|
||||
clear_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags);
|
||||
|
||||
cqr->cpaddr->cmd_code = DASD_ECKD_CCW_SNSS;
|
||||
cqr->cpaddr->count = SNSS_DATA_SIZE;
|
||||
cqr->cpaddr->flags = 0;
|
||||
cqr->cpaddr->cda = (__u32)(addr_t) cqr->data;
|
||||
|
||||
cqr->buildclk = get_clock();
|
||||
cqr->status = DASD_CQR_FILLED;
|
||||
cqr->callback = dasd_eer_snss_cb;
|
||||
|
||||
spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
|
||||
if (!device->eer_cqr) {
|
||||
device->eer_cqr = cqr;
|
||||
cqr = NULL;
|
||||
}
|
||||
spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
|
||||
if (cqr)
|
||||
dasd_kfree_request(cqr, device);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable error reporting on a given device.
|
||||
*/
|
||||
void dasd_eer_disable(struct dasd_device *device)
|
||||
{
|
||||
struct dasd_ccw_req *cqr;
|
||||
unsigned long flags;
|
||||
int in_use;
|
||||
|
||||
if (!device->eer_cqr)
|
||||
return;
|
||||
spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
|
||||
cqr = device->eer_cqr;
|
||||
device->eer_cqr = NULL;
|
||||
clear_bit(DASD_FLAG_EER_SNSS, &device->flags);
|
||||
in_use = test_and_clear_bit(DASD_FLAG_EER_IN_USE, &device->flags);
|
||||
spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
|
||||
if (cqr && !in_use)
|
||||
dasd_kfree_request(cqr, device);
|
||||
}
|
||||
|
||||
/*
|
||||
* SECTION: the device operations
|
||||
*/
|
||||
|
||||
/*
|
||||
* On the one side we need a lock to access our internal buffer, on the
|
||||
* other side a copy_to_user can sleep. So we need to copy the data we have
|
||||
* to transfer in a readbuffer, which is protected by the readbuffer_mutex.
|
||||
*/
|
||||
static char readbuffer[PAGE_SIZE];
|
||||
static DECLARE_MUTEX(readbuffer_mutex);
|
||||
|
||||
static int dasd_eer_open(struct inode *inp, struct file *filp)
|
||||
{
|
||||
struct eerbuffer *eerb;
|
||||
unsigned long flags;
|
||||
|
||||
eerb = kzalloc(sizeof(struct eerbuffer), GFP_KERNEL);
|
||||
if (!eerb)
|
||||
return -ENOMEM;
|
||||
eerb->buffer_page_count = eer_pages;
|
||||
if (eerb->buffer_page_count < 1 ||
|
||||
eerb->buffer_page_count > INT_MAX / PAGE_SIZE) {
|
||||
kfree(eerb);
|
||||
MESSAGE(KERN_WARNING, "can't open device since module "
|
||||
"parameter eer_pages is smaller then 1 or"
|
||||
" bigger then %d", (int)(INT_MAX / PAGE_SIZE));
|
||||
return -EINVAL;
|
||||
}
|
||||
eerb->buffersize = eerb->buffer_page_count * PAGE_SIZE;
|
||||
eerb->buffer = kmalloc(eerb->buffer_page_count * sizeof(char *),
|
||||
GFP_KERNEL);
|
||||
if (!eerb->buffer) {
|
||||
kfree(eerb);
|
||||
return -ENOMEM;
|
||||
}
|
||||
if (dasd_eer_allocate_buffer_pages(eerb->buffer,
|
||||
eerb->buffer_page_count)) {
|
||||
kfree(eerb->buffer);
|
||||
kfree(eerb);
|
||||
return -ENOMEM;
|
||||
}
|
||||
filp->private_data = eerb;
|
||||
spin_lock_irqsave(&bufferlock, flags);
|
||||
list_add(&eerb->list, &bufferlist);
|
||||
spin_unlock_irqrestore(&bufferlock, flags);
|
||||
|
||||
return nonseekable_open(inp,filp);
|
||||
}
|
||||
|
||||
static int dasd_eer_close(struct inode *inp, struct file *filp)
|
||||
{
|
||||
struct eerbuffer *eerb;
|
||||
unsigned long flags;
|
||||
|
||||
eerb = (struct eerbuffer *) filp->private_data;
|
||||
spin_lock_irqsave(&bufferlock, flags);
|
||||
list_del(&eerb->list);
|
||||
spin_unlock_irqrestore(&bufferlock, flags);
|
||||
dasd_eer_free_buffer_pages(eerb->buffer, eerb->buffer_page_count);
|
||||
kfree(eerb->buffer);
|
||||
kfree(eerb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t dasd_eer_read(struct file *filp, char __user *buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
int tc,rc;
|
||||
int tailcount,effective_count;
|
||||
unsigned long flags;
|
||||
struct eerbuffer *eerb;
|
||||
|
||||
eerb = (struct eerbuffer *) filp->private_data;
|
||||
if (down_interruptible(&readbuffer_mutex))
|
||||
return -ERESTARTSYS;
|
||||
|
||||
spin_lock_irqsave(&bufferlock, flags);
|
||||
|
||||
if (eerb->residual < 0) { /* the remainder of this record */
|
||||
/* has been deleted */
|
||||
eerb->residual = 0;
|
||||
spin_unlock_irqrestore(&bufferlock, flags);
|
||||
up(&readbuffer_mutex);
|
||||
return -EIO;
|
||||
} else if (eerb->residual > 0) {
|
||||
/* OK we still have a second half of a record to deliver */
|
||||
effective_count = min(eerb->residual, (int) count);
|
||||
eerb->residual -= effective_count;
|
||||
} else {
|
||||
tc = 0;
|
||||
while (!tc) {
|
||||
tc = dasd_eer_read_buffer(eerb, (char *) &tailcount,
|
||||
sizeof(tailcount));
|
||||
if (!tc) {
|
||||
/* no data available */
|
||||
spin_unlock_irqrestore(&bufferlock, flags);
|
||||
up(&readbuffer_mutex);
|
||||
if (filp->f_flags & O_NONBLOCK)
|
||||
return -EAGAIN;
|
||||
rc = wait_event_interruptible(
|
||||
dasd_eer_read_wait_queue,
|
||||
eerb->head != eerb->tail);
|
||||
if (rc)
|
||||
return rc;
|
||||
if (down_interruptible(&readbuffer_mutex))
|
||||
return -ERESTARTSYS;
|
||||
spin_lock_irqsave(&bufferlock, flags);
|
||||
}
|
||||
}
|
||||
WARN_ON(tc != sizeof(tailcount));
|
||||
effective_count = min(tailcount,(int)count);
|
||||
eerb->residual = tailcount - effective_count;
|
||||
}
|
||||
|
||||
tc = dasd_eer_read_buffer(eerb, readbuffer, effective_count);
|
||||
WARN_ON(tc != effective_count);
|
||||
|
||||
spin_unlock_irqrestore(&bufferlock, flags);
|
||||
|
||||
if (copy_to_user(buf, readbuffer, effective_count)) {
|
||||
up(&readbuffer_mutex);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
up(&readbuffer_mutex);
|
||||
return effective_count;
|
||||
}
|
||||
|
||||
static unsigned int dasd_eer_poll(struct file *filp, poll_table *ptable)
|
||||
{
|
||||
unsigned int mask;
|
||||
unsigned long flags;
|
||||
struct eerbuffer *eerb;
|
||||
|
||||
eerb = (struct eerbuffer *) filp->private_data;
|
||||
poll_wait(filp, &dasd_eer_read_wait_queue, ptable);
|
||||
spin_lock_irqsave(&bufferlock, flags);
|
||||
if (eerb->head != eerb->tail)
|
||||
mask = POLLIN | POLLRDNORM ;
|
||||
else
|
||||
mask = 0;
|
||||
spin_unlock_irqrestore(&bufferlock, flags);
|
||||
return mask;
|
||||
}
|
||||
|
||||
static const struct file_operations dasd_eer_fops = {
|
||||
.open = &dasd_eer_open,
|
||||
.release = &dasd_eer_close,
|
||||
.read = &dasd_eer_read,
|
||||
.poll = &dasd_eer_poll,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static struct miscdevice *dasd_eer_dev = NULL;
|
||||
|
||||
int __init dasd_eer_init(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
dasd_eer_dev = kzalloc(sizeof(*dasd_eer_dev), GFP_KERNEL);
|
||||
if (!dasd_eer_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
dasd_eer_dev->minor = MISC_DYNAMIC_MINOR;
|
||||
dasd_eer_dev->name = "dasd_eer";
|
||||
dasd_eer_dev->fops = &dasd_eer_fops;
|
||||
|
||||
rc = misc_register(dasd_eer_dev);
|
||||
if (rc) {
|
||||
kfree(dasd_eer_dev);
|
||||
dasd_eer_dev = NULL;
|
||||
MESSAGE(KERN_ERR, "%s", "dasd_eer_init could not "
|
||||
"register misc device");
|
||||
return rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void dasd_eer_exit(void)
|
||||
{
|
||||
if (dasd_eer_dev) {
|
||||
WARN_ON(misc_deregister(dasd_eer_dev) != 0);
|
||||
kfree(dasd_eer_dev);
|
||||
dasd_eer_dev = NULL;
|
||||
}
|
||||
}
|
||||
170
drivers/s390/block/dasd_erp.c
Normal file
170
drivers/s390/block/dasd_erp.c
Normal file
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* File...........: linux/drivers/s390/block/dasd.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
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/init.h>
|
||||
|
||||
#include <asm/debug.h>
|
||||
#include <asm/ebcdic.h>
|
||||
#include <asm/uaccess.h>
|
||||
|
||||
/* This is ugly... */
|
||||
#define PRINTK_HEADER "dasd_erp:"
|
||||
|
||||
#include "dasd_int.h"
|
||||
|
||||
struct dasd_ccw_req *
|
||||
dasd_alloc_erp_request(char *magic, int cplength, int datasize,
|
||||
struct dasd_device * device)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct dasd_ccw_req *cqr;
|
||||
char *data;
|
||||
int size;
|
||||
|
||||
/* Sanity checks */
|
||||
BUG_ON( magic == NULL || datasize > PAGE_SIZE ||
|
||||
(cplength*sizeof(struct ccw1)) > PAGE_SIZE);
|
||||
|
||||
size = (sizeof(struct dasd_ccw_req) + 7L) & -8L;
|
||||
if (cplength > 0)
|
||||
size += cplength * sizeof(struct ccw1);
|
||||
if (datasize > 0)
|
||||
size += datasize;
|
||||
spin_lock_irqsave(&device->mem_lock, flags);
|
||||
cqr = (struct dasd_ccw_req *)
|
||||
dasd_alloc_chunk(&device->erp_chunks, size);
|
||||
spin_unlock_irqrestore(&device->mem_lock, flags);
|
||||
if (cqr == NULL)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
memset(cqr, 0, sizeof(struct dasd_ccw_req));
|
||||
data = (char *) cqr + ((sizeof(struct dasd_ccw_req) + 7L) & -8L);
|
||||
cqr->cpaddr = NULL;
|
||||
if (cplength > 0) {
|
||||
cqr->cpaddr = (struct ccw1 *) data;
|
||||
data += cplength*sizeof(struct ccw1);
|
||||
memset(cqr->cpaddr, 0, cplength*sizeof(struct ccw1));
|
||||
}
|
||||
cqr->data = NULL;
|
||||
if (datasize > 0) {
|
||||
cqr->data = data;
|
||||
memset(cqr->data, 0, datasize);
|
||||
}
|
||||
strncpy((char *) &cqr->magic, magic, 4);
|
||||
ASCEBC((char *) &cqr->magic, 4);
|
||||
set_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags);
|
||||
dasd_get_device(device);
|
||||
return cqr;
|
||||
}
|
||||
|
||||
void
|
||||
dasd_free_erp_request(struct dasd_ccw_req * cqr, struct dasd_device * device)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&device->mem_lock, flags);
|
||||
dasd_free_chunk(&device->erp_chunks, cqr);
|
||||
spin_unlock_irqrestore(&device->mem_lock, flags);
|
||||
atomic_dec(&device->ref_count);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* dasd_default_erp_action just retries the current cqr
|
||||
*/
|
||||
struct dasd_ccw_req *
|
||||
dasd_default_erp_action(struct dasd_ccw_req * cqr)
|
||||
{
|
||||
struct dasd_device *device;
|
||||
|
||||
device = cqr->device;
|
||||
|
||||
/* just retry - there is nothing to save ... I got no sense data.... */
|
||||
if (cqr->retries > 0) {
|
||||
DEV_MESSAGE (KERN_DEBUG, device,
|
||||
"default ERP called (%i retries left)",
|
||||
cqr->retries);
|
||||
cqr->lpm = LPM_ANYPATH;
|
||||
cqr->status = DASD_CQR_QUEUED;
|
||||
} else {
|
||||
DEV_MESSAGE (KERN_WARNING, device, "%s",
|
||||
"default ERP called (NO retry left)");
|
||||
cqr->status = DASD_CQR_FAILED;
|
||||
cqr->stopclk = get_clock ();
|
||||
}
|
||||
return cqr;
|
||||
} /* end dasd_default_erp_action */
|
||||
|
||||
/*
|
||||
* DESCRIPTION
|
||||
* Frees all ERPs of the current ERP Chain and set the status
|
||||
* of the original CQR either to DASD_CQR_DONE if ERP was successful
|
||||
* or to DASD_CQR_FAILED if ERP was NOT successful.
|
||||
* NOTE: This function is only called if no discipline postaction
|
||||
* is available
|
||||
*
|
||||
* PARAMETER
|
||||
* erp current erp_head
|
||||
*
|
||||
* RETURN VALUES
|
||||
* cqr pointer to the original CQR
|
||||
*/
|
||||
struct dasd_ccw_req *
|
||||
dasd_default_erp_postaction(struct dasd_ccw_req * cqr)
|
||||
{
|
||||
struct dasd_device *device;
|
||||
int success;
|
||||
|
||||
BUG_ON(cqr->refers == NULL || cqr->function == NULL);
|
||||
|
||||
device = cqr->device;
|
||||
success = cqr->status == DASD_CQR_DONE;
|
||||
|
||||
/* free all ERPs - but NOT the original cqr */
|
||||
while (cqr->refers != NULL) {
|
||||
struct dasd_ccw_req *refers;
|
||||
|
||||
refers = cqr->refers;
|
||||
/* remove the request from the device queue */
|
||||
list_del(&cqr->list);
|
||||
/* free the finished erp request */
|
||||
dasd_free_erp_request(cqr, device);
|
||||
cqr = refers;
|
||||
}
|
||||
|
||||
/* set corresponding status to original cqr */
|
||||
if (success)
|
||||
cqr->status = DASD_CQR_DONE;
|
||||
else {
|
||||
cqr->status = DASD_CQR_FAILED;
|
||||
cqr->stopclk = get_clock();
|
||||
}
|
||||
|
||||
return cqr;
|
||||
|
||||
} /* end default_erp_postaction */
|
||||
|
||||
void
|
||||
dasd_log_sense(struct dasd_ccw_req *cqr, struct irb *irb)
|
||||
{
|
||||
struct dasd_device *device;
|
||||
|
||||
device = cqr->device;
|
||||
/* dump sense data */
|
||||
if (device->discipline && device->discipline->dump_sense)
|
||||
device->discipline->dump_sense(device, cqr, irb);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(dasd_default_erp_action);
|
||||
EXPORT_SYMBOL(dasd_default_erp_postaction);
|
||||
EXPORT_SYMBOL(dasd_alloc_erp_request);
|
||||
EXPORT_SYMBOL(dasd_free_erp_request);
|
||||
EXPORT_SYMBOL(dasd_log_sense);
|
||||
576
drivers/s390/block/dasd_fba.c
Normal file
576
drivers/s390/block/dasd_fba.c
Normal file
@@ -0,0 +1,576 @@
|
||||
/*
|
||||
* File...........: linux/drivers/s390/block/dasd_fba.c
|
||||
* Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
|
||||
* Bugreports.to..: <Linux390@de.ibm.com>
|
||||
* (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999,2000
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/stddef.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <asm/debug.h>
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/hdreg.h> /* HDIO_GETGEO */
|
||||
#include <linux/bio.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
|
||||
#include <asm/idals.h>
|
||||
#include <asm/ebcdic.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/todclk.h>
|
||||
#include <asm/ccwdev.h>
|
||||
|
||||
#include "dasd_int.h"
|
||||
#include "dasd_fba.h"
|
||||
|
||||
#ifdef PRINTK_HEADER
|
||||
#undef PRINTK_HEADER
|
||||
#endif /* PRINTK_HEADER */
|
||||
#define PRINTK_HEADER "dasd(fba):"
|
||||
|
||||
#define DASD_FBA_CCW_WRITE 0x41
|
||||
#define DASD_FBA_CCW_READ 0x42
|
||||
#define DASD_FBA_CCW_LOCATE 0x43
|
||||
#define DASD_FBA_CCW_DEFINE_EXTENT 0x63
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
static struct dasd_discipline dasd_fba_discipline;
|
||||
|
||||
struct dasd_fba_private {
|
||||
struct dasd_fba_characteristics rdc_data;
|
||||
};
|
||||
|
||||
static struct ccw_device_id dasd_fba_ids[] = {
|
||||
{ CCW_DEVICE_DEVTYPE (0x6310, 0, 0x9336, 0), .driver_info = 0x1},
|
||||
{ CCW_DEVICE_DEVTYPE (0x3880, 0, 0x3370, 0), .driver_info = 0x2},
|
||||
{ /* end of list */ },
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(ccw, dasd_fba_ids);
|
||||
|
||||
static struct ccw_driver dasd_fba_driver; /* see below */
|
||||
static int
|
||||
dasd_fba_probe(struct ccw_device *cdev)
|
||||
{
|
||||
return dasd_generic_probe(cdev, &dasd_fba_discipline);
|
||||
}
|
||||
|
||||
static int
|
||||
dasd_fba_set_online(struct ccw_device *cdev)
|
||||
{
|
||||
return dasd_generic_set_online(cdev, &dasd_fba_discipline);
|
||||
}
|
||||
|
||||
static struct ccw_driver dasd_fba_driver = {
|
||||
.name = "dasd-fba",
|
||||
.owner = THIS_MODULE,
|
||||
.ids = dasd_fba_ids,
|
||||
.probe = dasd_fba_probe,
|
||||
.remove = dasd_generic_remove,
|
||||
.set_offline = dasd_generic_set_offline,
|
||||
.set_online = dasd_fba_set_online,
|
||||
.notify = dasd_generic_notify,
|
||||
};
|
||||
|
||||
static void
|
||||
define_extent(struct ccw1 * ccw, struct DE_fba_data *data, int rw,
|
||||
int blksize, int beg, int nr)
|
||||
{
|
||||
ccw->cmd_code = DASD_FBA_CCW_DEFINE_EXTENT;
|
||||
ccw->flags = 0;
|
||||
ccw->count = 16;
|
||||
ccw->cda = (__u32) __pa(data);
|
||||
memset(data, 0, sizeof (struct DE_fba_data));
|
||||
if (rw == WRITE)
|
||||
(data->mask).perm = 0x0;
|
||||
else if (rw == READ)
|
||||
(data->mask).perm = 0x1;
|
||||
else
|
||||
data->mask.perm = 0x2;
|
||||
data->blk_size = blksize;
|
||||
data->ext_loc = beg;
|
||||
data->ext_end = nr - 1;
|
||||
}
|
||||
|
||||
static void
|
||||
locate_record(struct ccw1 * ccw, struct LO_fba_data *data, int rw,
|
||||
int block_nr, int block_ct)
|
||||
{
|
||||
ccw->cmd_code = DASD_FBA_CCW_LOCATE;
|
||||
ccw->flags = 0;
|
||||
ccw->count = 8;
|
||||
ccw->cda = (__u32) __pa(data);
|
||||
memset(data, 0, sizeof (struct LO_fba_data));
|
||||
if (rw == WRITE)
|
||||
data->operation.cmd = 0x5;
|
||||
else if (rw == READ)
|
||||
data->operation.cmd = 0x6;
|
||||
else
|
||||
data->operation.cmd = 0x8;
|
||||
data->blk_nr = block_nr;
|
||||
data->blk_ct = block_ct;
|
||||
}
|
||||
|
||||
static int
|
||||
dasd_fba_check_characteristics(struct dasd_device *device)
|
||||
{
|
||||
struct dasd_fba_private *private;
|
||||
struct ccw_device *cdev = device->cdev;
|
||||
void *rdc_data;
|
||||
int rc;
|
||||
|
||||
private = (struct dasd_fba_private *) device->private;
|
||||
if (private == NULL) {
|
||||
private = kzalloc(sizeof(struct dasd_fba_private), GFP_KERNEL);
|
||||
if (private == NULL) {
|
||||
DEV_MESSAGE(KERN_WARNING, device, "%s",
|
||||
"memory allocation failed for private "
|
||||
"data");
|
||||
return -ENOMEM;
|
||||
}
|
||||
device->private = (void *) private;
|
||||
}
|
||||
/* Read Device Characteristics */
|
||||
rdc_data = (void *) &(private->rdc_data);
|
||||
rc = read_dev_chars(device->cdev, &rdc_data, 32);
|
||||
if (rc) {
|
||||
DEV_MESSAGE(KERN_WARNING, device,
|
||||
"Read device characteristics returned error %d",
|
||||
rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
DEV_MESSAGE(KERN_INFO, device,
|
||||
"%04X/%02X(CU:%04X/%02X) %dMB at(%d B/blk)",
|
||||
cdev->id.dev_type,
|
||||
cdev->id.dev_model,
|
||||
cdev->id.cu_type,
|
||||
cdev->id.cu_model,
|
||||
((private->rdc_data.blk_bdsa *
|
||||
(private->rdc_data.blk_size >> 9)) >> 11),
|
||||
private->rdc_data.blk_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
dasd_fba_do_analysis(struct dasd_device *device)
|
||||
{
|
||||
struct dasd_fba_private *private;
|
||||
int sb, rc;
|
||||
|
||||
private = (struct dasd_fba_private *) device->private;
|
||||
rc = dasd_check_blocksize(private->rdc_data.blk_size);
|
||||
if (rc) {
|
||||
DEV_MESSAGE(KERN_INFO, device, "unknown blocksize %d",
|
||||
private->rdc_data.blk_size);
|
||||
return rc;
|
||||
}
|
||||
device->blocks = private->rdc_data.blk_bdsa;
|
||||
device->bp_block = private->rdc_data.blk_size;
|
||||
device->s2b_shift = 0; /* bits to shift 512 to get a block */
|
||||
for (sb = 512; sb < private->rdc_data.blk_size; sb = sb << 1)
|
||||
device->s2b_shift++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
dasd_fba_fill_geometry(struct dasd_device *device, struct hd_geometry *geo)
|
||||
{
|
||||
if (dasd_check_blocksize(device->bp_block) != 0)
|
||||
return -EINVAL;
|
||||
geo->cylinders = (device->blocks << device->s2b_shift) >> 10;
|
||||
geo->heads = 16;
|
||||
geo->sectors = 128 >> device->s2b_shift;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static dasd_era_t
|
||||
dasd_fba_examine_error(struct dasd_ccw_req * cqr, struct irb * irb)
|
||||
{
|
||||
struct dasd_device *device;
|
||||
struct ccw_device *cdev;
|
||||
|
||||
device = (struct dasd_device *) cqr->device;
|
||||
if (irb->scsw.cstat == 0x00 &&
|
||||
irb->scsw.dstat == (DEV_STAT_CHN_END | DEV_STAT_DEV_END))
|
||||
return dasd_era_none;
|
||||
|
||||
cdev = device->cdev;
|
||||
switch (cdev->id.dev_type) {
|
||||
case 0x3370:
|
||||
return dasd_3370_erp_examine(cqr, irb);
|
||||
case 0x9336:
|
||||
return dasd_9336_erp_examine(cqr, irb);
|
||||
default:
|
||||
return dasd_era_recover;
|
||||
}
|
||||
}
|
||||
|
||||
static dasd_erp_fn_t
|
||||
dasd_fba_erp_action(struct dasd_ccw_req * cqr)
|
||||
{
|
||||
return dasd_default_erp_action;
|
||||
}
|
||||
|
||||
static dasd_erp_fn_t
|
||||
dasd_fba_erp_postaction(struct dasd_ccw_req * cqr)
|
||||
{
|
||||
if (cqr->function == dasd_default_erp_action)
|
||||
return dasd_default_erp_postaction;
|
||||
|
||||
DEV_MESSAGE(KERN_WARNING, cqr->device, "unknown ERP action %p",
|
||||
cqr->function);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct dasd_ccw_req *
|
||||
dasd_fba_build_cp(struct dasd_device * device, struct request *req)
|
||||
{
|
||||
struct dasd_fba_private *private;
|
||||
unsigned long *idaws;
|
||||
struct LO_fba_data *LO_data;
|
||||
struct dasd_ccw_req *cqr;
|
||||
struct ccw1 *ccw;
|
||||
struct bio *bio;
|
||||
struct bio_vec *bv;
|
||||
char *dst;
|
||||
int count, cidaw, cplength, datasize;
|
||||
sector_t recid, first_rec, last_rec;
|
||||
unsigned int blksize, off;
|
||||
unsigned char cmd;
|
||||
int i;
|
||||
|
||||
private = (struct dasd_fba_private *) device->private;
|
||||
if (rq_data_dir(req) == READ) {
|
||||
cmd = DASD_FBA_CCW_READ;
|
||||
} else if (rq_data_dir(req) == WRITE) {
|
||||
cmd = DASD_FBA_CCW_WRITE;
|
||||
} else
|
||||
return ERR_PTR(-EINVAL);
|
||||
blksize = device->bp_block;
|
||||
/* Calculate record id of first and last block. */
|
||||
first_rec = req->sector >> device->s2b_shift;
|
||||
last_rec = (req->sector + req->nr_sectors - 1) >> device->s2b_shift;
|
||||
/* Check struct bio and count the number of blocks for the request. */
|
||||
count = 0;
|
||||
cidaw = 0;
|
||||
rq_for_each_bio(bio, req) {
|
||||
bio_for_each_segment(bv, bio, i) {
|
||||
if (bv->bv_len & (blksize - 1))
|
||||
/* Fba can only do full blocks. */
|
||||
return ERR_PTR(-EINVAL);
|
||||
count += bv->bv_len >> (device->s2b_shift + 9);
|
||||
#if defined(CONFIG_64BIT)
|
||||
if (idal_is_needed (page_address(bv->bv_page),
|
||||
bv->bv_len))
|
||||
cidaw += bv->bv_len / blksize;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
/* Paranoia. */
|
||||
if (count != last_rec - first_rec + 1)
|
||||
return ERR_PTR(-EINVAL);
|
||||
/* 1x define extent + 1x locate record + number of blocks */
|
||||
cplength = 2 + count;
|
||||
/* 1x define extent + 1x locate record */
|
||||
datasize = sizeof(struct DE_fba_data) + sizeof(struct LO_fba_data) +
|
||||
cidaw * sizeof(unsigned long);
|
||||
/*
|
||||
* Find out number of additional locate record ccws if the device
|
||||
* can't do data chaining.
|
||||
*/
|
||||
if (private->rdc_data.mode.bits.data_chain == 0) {
|
||||
cplength += count - 1;
|
||||
datasize += (count - 1)*sizeof(struct LO_fba_data);
|
||||
}
|
||||
/* Allocate the ccw request. */
|
||||
cqr = dasd_smalloc_request(dasd_fba_discipline.name,
|
||||
cplength, datasize, device);
|
||||
if (IS_ERR(cqr))
|
||||
return cqr;
|
||||
ccw = cqr->cpaddr;
|
||||
/* First ccw is define extent. */
|
||||
define_extent(ccw++, cqr->data, rq_data_dir(req),
|
||||
device->bp_block, req->sector, req->nr_sectors);
|
||||
/* Build locate_record + read/write ccws. */
|
||||
idaws = (unsigned long *) (cqr->data + sizeof(struct DE_fba_data));
|
||||
LO_data = (struct LO_fba_data *) (idaws + cidaw);
|
||||
/* Locate record for all blocks for smart devices. */
|
||||
if (private->rdc_data.mode.bits.data_chain != 0) {
|
||||
ccw[-1].flags |= CCW_FLAG_CC;
|
||||
locate_record(ccw++, LO_data++, rq_data_dir(req), 0, count);
|
||||
}
|
||||
recid = first_rec;
|
||||
rq_for_each_bio(bio, req) bio_for_each_segment(bv, bio, i) {
|
||||
dst = page_address(bv->bv_page) + bv->bv_offset;
|
||||
if (dasd_page_cache) {
|
||||
char *copy = kmem_cache_alloc(dasd_page_cache,
|
||||
GFP_DMA | __GFP_NOWARN);
|
||||
if (copy && rq_data_dir(req) == WRITE)
|
||||
memcpy(copy + bv->bv_offset, dst, bv->bv_len);
|
||||
if (copy)
|
||||
dst = copy + bv->bv_offset;
|
||||
}
|
||||
for (off = 0; off < bv->bv_len; off += blksize) {
|
||||
/* Locate record for stupid devices. */
|
||||
if (private->rdc_data.mode.bits.data_chain == 0) {
|
||||
ccw[-1].flags |= CCW_FLAG_CC;
|
||||
locate_record(ccw, LO_data++,
|
||||
rq_data_dir(req),
|
||||
recid - first_rec, 1);
|
||||
ccw->flags = CCW_FLAG_CC;
|
||||
ccw++;
|
||||
} else {
|
||||
if (recid > first_rec)
|
||||
ccw[-1].flags |= CCW_FLAG_DC;
|
||||
else
|
||||
ccw[-1].flags |= CCW_FLAG_CC;
|
||||
}
|
||||
ccw->cmd_code = cmd;
|
||||
ccw->count = device->bp_block;
|
||||
if (idal_is_needed(dst, blksize)) {
|
||||
ccw->cda = (__u32)(addr_t) idaws;
|
||||
ccw->flags = CCW_FLAG_IDA;
|
||||
idaws = idal_create_words(idaws, dst, blksize);
|
||||
} else {
|
||||
ccw->cda = (__u32)(addr_t) dst;
|
||||
ccw->flags = 0;
|
||||
}
|
||||
ccw++;
|
||||
dst += blksize;
|
||||
recid++;
|
||||
}
|
||||
}
|
||||
if (req->cmd_flags & REQ_FAILFAST)
|
||||
set_bit(DASD_CQR_FLAGS_FAILFAST, &cqr->flags);
|
||||
cqr->device = device;
|
||||
cqr->expires = 5 * 60 * HZ; /* 5 minutes */
|
||||
cqr->retries = 32;
|
||||
cqr->buildclk = get_clock();
|
||||
cqr->status = DASD_CQR_FILLED;
|
||||
return cqr;
|
||||
}
|
||||
|
||||
static int
|
||||
dasd_fba_free_cp(struct dasd_ccw_req *cqr, struct request *req)
|
||||
{
|
||||
struct dasd_fba_private *private;
|
||||
struct ccw1 *ccw;
|
||||
struct bio *bio;
|
||||
struct bio_vec *bv;
|
||||
char *dst, *cda;
|
||||
unsigned int blksize, off;
|
||||
int i, status;
|
||||
|
||||
if (!dasd_page_cache)
|
||||
goto out;
|
||||
private = (struct dasd_fba_private *) cqr->device->private;
|
||||
blksize = cqr->device->bp_block;
|
||||
ccw = cqr->cpaddr;
|
||||
/* Skip over define extent & locate record. */
|
||||
ccw++;
|
||||
if (private->rdc_data.mode.bits.data_chain != 0)
|
||||
ccw++;
|
||||
rq_for_each_bio(bio, req) bio_for_each_segment(bv, bio, i) {
|
||||
dst = page_address(bv->bv_page) + bv->bv_offset;
|
||||
for (off = 0; off < bv->bv_len; off += blksize) {
|
||||
/* Skip locate record. */
|
||||
if (private->rdc_data.mode.bits.data_chain == 0)
|
||||
ccw++;
|
||||
if (dst) {
|
||||
if (ccw->flags & CCW_FLAG_IDA)
|
||||
cda = *((char **)((addr_t) ccw->cda));
|
||||
else
|
||||
cda = (char *)((addr_t) ccw->cda);
|
||||
if (dst != cda) {
|
||||
if (rq_data_dir(req) == READ)
|
||||
memcpy(dst, cda, bv->bv_len);
|
||||
kmem_cache_free(dasd_page_cache,
|
||||
(void *)((addr_t)cda & PAGE_MASK));
|
||||
}
|
||||
dst = NULL;
|
||||
}
|
||||
ccw++;
|
||||
}
|
||||
}
|
||||
out:
|
||||
status = cqr->status == DASD_CQR_DONE;
|
||||
dasd_sfree_request(cqr, cqr->device);
|
||||
return status;
|
||||
}
|
||||
|
||||
static int
|
||||
dasd_fba_fill_info(struct dasd_device * device,
|
||||
struct dasd_information2_t * info)
|
||||
{
|
||||
info->label_block = 1;
|
||||
info->FBA_layout = 1;
|
||||
info->format = DASD_FORMAT_LDL;
|
||||
info->characteristics_size = sizeof(struct dasd_fba_characteristics);
|
||||
memcpy(info->characteristics,
|
||||
&((struct dasd_fba_private *) device->private)->rdc_data,
|
||||
sizeof (struct dasd_fba_characteristics));
|
||||
info->confdata_size = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
dasd_fba_dump_sense(struct dasd_device *device, struct dasd_ccw_req * req,
|
||||
struct irb *irb)
|
||||
{
|
||||
char *page;
|
||||
struct ccw1 *act, *end, *last;
|
||||
int len, sl, sct, count;
|
||||
|
||||
page = (char *) get_zeroed_page(GFP_ATOMIC);
|
||||
if (page == NULL) {
|
||||
DEV_MESSAGE(KERN_ERR, device, " %s",
|
||||
"No memory to dump sense data");
|
||||
return;
|
||||
}
|
||||
len = sprintf(page, KERN_ERR PRINTK_HEADER
|
||||
" I/O status report for device %s:\n",
|
||||
device->cdev->dev.bus_id);
|
||||
len += sprintf(page + len, KERN_ERR PRINTK_HEADER
|
||||
" in req: %p CS: 0x%02X DS: 0x%02X\n", req,
|
||||
irb->scsw.cstat, irb->scsw.dstat);
|
||||
len += sprintf(page + len, KERN_ERR PRINTK_HEADER
|
||||
" device %s: Failing CCW: %p\n",
|
||||
device->cdev->dev.bus_id,
|
||||
(void *) (addr_t) irb->scsw.cpa);
|
||||
if (irb->esw.esw0.erw.cons) {
|
||||
for (sl = 0; sl < 4; sl++) {
|
||||
len += sprintf(page + len, KERN_ERR PRINTK_HEADER
|
||||
" Sense(hex) %2d-%2d:",
|
||||
(8 * sl), ((8 * sl) + 7));
|
||||
|
||||
for (sct = 0; sct < 8; sct++) {
|
||||
len += sprintf(page + len, " %02x",
|
||||
irb->ecw[8 * sl + sct]);
|
||||
}
|
||||
len += sprintf(page + len, "\n");
|
||||
}
|
||||
} else {
|
||||
len += sprintf(page + len, KERN_ERR PRINTK_HEADER
|
||||
" SORRY - NO VALID SENSE AVAILABLE\n");
|
||||
}
|
||||
MESSAGE_LOG(KERN_ERR, "%s",
|
||||
page + sizeof(KERN_ERR PRINTK_HEADER));
|
||||
|
||||
/* dump the Channel Program */
|
||||
/* print first CCWs (maximum 8) */
|
||||
act = req->cpaddr;
|
||||
for (last = act; last->flags & (CCW_FLAG_CC | CCW_FLAG_DC); last++);
|
||||
end = min(act + 8, last);
|
||||
len = sprintf(page, KERN_ERR PRINTK_HEADER
|
||||
" Related CP in req: %p\n", req);
|
||||
while (act <= end) {
|
||||
len += sprintf(page + len, KERN_ERR PRINTK_HEADER
|
||||
" CCW %p: %08X %08X DAT:",
|
||||
act, ((int *) act)[0], ((int *) act)[1]);
|
||||
for (count = 0; count < 32 && count < act->count;
|
||||
count += sizeof(int))
|
||||
len += sprintf(page + len, " %08X",
|
||||
((int *) (addr_t) act->cda)
|
||||
[(count>>2)]);
|
||||
len += sprintf(page + len, "\n");
|
||||
act++;
|
||||
}
|
||||
MESSAGE_LOG(KERN_ERR, "%s",
|
||||
page + sizeof(KERN_ERR PRINTK_HEADER));
|
||||
|
||||
|
||||
/* print failing CCW area */
|
||||
len = 0;
|
||||
if (act < ((struct ccw1 *)(addr_t) irb->scsw.cpa) - 2) {
|
||||
act = ((struct ccw1 *)(addr_t) irb->scsw.cpa) - 2;
|
||||
len += sprintf(page + len, KERN_ERR PRINTK_HEADER "......\n");
|
||||
}
|
||||
end = min((struct ccw1 *)(addr_t) irb->scsw.cpa + 2, last);
|
||||
while (act <= end) {
|
||||
len += sprintf(page + len, KERN_ERR PRINTK_HEADER
|
||||
" CCW %p: %08X %08X DAT:",
|
||||
act, ((int *) act)[0], ((int *) act)[1]);
|
||||
for (count = 0; count < 32 && count < act->count;
|
||||
count += sizeof(int))
|
||||
len += sprintf(page + len, " %08X",
|
||||
((int *) (addr_t) act->cda)
|
||||
[(count>>2)]);
|
||||
len += sprintf(page + len, "\n");
|
||||
act++;
|
||||
}
|
||||
|
||||
/* print last CCWs */
|
||||
if (act < last - 2) {
|
||||
act = last - 2;
|
||||
len += sprintf(page + len, KERN_ERR PRINTK_HEADER "......\n");
|
||||
}
|
||||
while (act <= last) {
|
||||
len += sprintf(page + len, KERN_ERR PRINTK_HEADER
|
||||
" CCW %p: %08X %08X DAT:",
|
||||
act, ((int *) act)[0], ((int *) act)[1]);
|
||||
for (count = 0; count < 32 && count < act->count;
|
||||
count += sizeof(int))
|
||||
len += sprintf(page + len, " %08X",
|
||||
((int *) (addr_t) act->cda)
|
||||
[(count>>2)]);
|
||||
len += sprintf(page + len, "\n");
|
||||
act++;
|
||||
}
|
||||
if (len > 0)
|
||||
MESSAGE_LOG(KERN_ERR, "%s",
|
||||
page + sizeof(KERN_ERR PRINTK_HEADER));
|
||||
free_page((unsigned long) page);
|
||||
}
|
||||
|
||||
/*
|
||||
* max_blocks is dependent on the amount of storage that is available
|
||||
* in the static io buffer for each device. Currently each device has
|
||||
* 8192 bytes (=2 pages). For 64 bit one dasd_mchunkt_t structure has
|
||||
* 24 bytes, the struct dasd_ccw_req has 136 bytes and each block can use
|
||||
* up to 16 bytes (8 for the ccw and 8 for the idal pointer). In
|
||||
* addition we have one define extent ccw + 16 bytes of data and a
|
||||
* locate record ccw for each block (stupid devices!) + 16 bytes of data.
|
||||
* That makes:
|
||||
* (8192 - 24 - 136 - 8 - 16) / 40 = 200.2 blocks at maximum.
|
||||
* We want to fit two into the available memory so that we can immediately
|
||||
* start the next request if one finishes off. That makes 100.1 blocks
|
||||
* for one request. Give a little safety and the result is 96.
|
||||
*/
|
||||
static struct dasd_discipline dasd_fba_discipline = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "FBA ",
|
||||
.ebcname = "FBA ",
|
||||
.max_blocks = 96,
|
||||
.check_device = dasd_fba_check_characteristics,
|
||||
.do_analysis = dasd_fba_do_analysis,
|
||||
.fill_geometry = dasd_fba_fill_geometry,
|
||||
.start_IO = dasd_start_IO,
|
||||
.term_IO = dasd_term_IO,
|
||||
.examine_error = dasd_fba_examine_error,
|
||||
.erp_action = dasd_fba_erp_action,
|
||||
.erp_postaction = dasd_fba_erp_postaction,
|
||||
.build_cp = dasd_fba_build_cp,
|
||||
.free_cp = dasd_fba_free_cp,
|
||||
.dump_sense = dasd_fba_dump_sense,
|
||||
.fill_info = dasd_fba_fill_info,
|
||||
};
|
||||
|
||||
static int __init
|
||||
dasd_fba_init(void)
|
||||
{
|
||||
ASCEBC(dasd_fba_discipline.ebcname, 4);
|
||||
return ccw_driver_register(&dasd_fba_driver);
|
||||
}
|
||||
|
||||
static void __exit
|
||||
dasd_fba_cleanup(void)
|
||||
{
|
||||
ccw_driver_unregister(&dasd_fba_driver);
|
||||
}
|
||||
|
||||
module_init(dasd_fba_init);
|
||||
module_exit(dasd_fba_cleanup);
|
||||
72
drivers/s390/block/dasd_fba.h
Normal file
72
drivers/s390/block/dasd_fba.h
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* File...........: linux/drivers/s390/block/dasd_fba.h
|
||||
* Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
|
||||
* Bugreports.to..: <Linux390@de.ibm.com>
|
||||
* (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999,2000
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DASD_FBA_H
|
||||
#define DASD_FBA_H
|
||||
|
||||
struct DE_fba_data {
|
||||
struct {
|
||||
unsigned char perm:2; /* Permissions on this extent */
|
||||
unsigned char zero:2; /* Must be zero */
|
||||
unsigned char da:1; /* usually zero */
|
||||
unsigned char diag:1; /* allow diagnose */
|
||||
unsigned char zero2:2; /* zero */
|
||||
} __attribute__ ((packed)) mask;
|
||||
__u8 zero; /* Must be zero */
|
||||
__u16 blk_size; /* Blocksize */
|
||||
__u32 ext_loc; /* Extent locator */
|
||||
__u32 ext_beg; /* logical number of block 0 in extent */
|
||||
__u32 ext_end; /* logocal number of last block in extent */
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct LO_fba_data {
|
||||
struct {
|
||||
unsigned char zero:4;
|
||||
unsigned char cmd:4;
|
||||
} __attribute__ ((packed)) operation;
|
||||
__u8 auxiliary;
|
||||
__u16 blk_ct;
|
||||
__u32 blk_nr;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct dasd_fba_characteristics {
|
||||
union {
|
||||
__u8 c;
|
||||
struct {
|
||||
unsigned char reserved:1;
|
||||
unsigned char overrunnable:1;
|
||||
unsigned char burst_byte:1;
|
||||
unsigned char data_chain:1;
|
||||
unsigned char zeros:4;
|
||||
} __attribute__ ((packed)) bits;
|
||||
} __attribute__ ((packed)) mode;
|
||||
union {
|
||||
__u8 c;
|
||||
struct {
|
||||
unsigned char zero0:1;
|
||||
unsigned char removable:1;
|
||||
unsigned char shared:1;
|
||||
unsigned char zero1:1;
|
||||
unsigned char mam:1;
|
||||
unsigned char zeros:3;
|
||||
} __attribute__ ((packed)) bits;
|
||||
} __attribute__ ((packed)) features;
|
||||
__u8 dev_class;
|
||||
__u8 unit_type;
|
||||
__u16 blk_size;
|
||||
__u32 blk_per_cycl;
|
||||
__u32 blk_per_bound;
|
||||
__u32 blk_bdsa;
|
||||
__u32 reserved0;
|
||||
__u16 reserved1;
|
||||
__u16 blk_ce;
|
||||
__u32 reserved2;
|
||||
__u16 reserved3;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
#endif /* DASD_FBA_H */
|
||||
181
drivers/s390/block/dasd_genhd.c
Normal file
181
drivers/s390/block/dasd_genhd.c
Normal file
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* File...........: linux/drivers/s390/block/dasd_genhd.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
|
||||
*
|
||||
* gendisk related functions for the dasd driver.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/blkpg.h>
|
||||
|
||||
#include <asm/uaccess.h>
|
||||
|
||||
/* This is ugly... */
|
||||
#define PRINTK_HEADER "dasd_gendisk:"
|
||||
|
||||
#include "dasd_int.h"
|
||||
|
||||
/*
|
||||
* Allocate and register gendisk structure for device.
|
||||
*/
|
||||
int
|
||||
dasd_gendisk_alloc(struct dasd_device *device)
|
||||
{
|
||||
struct gendisk *gdp;
|
||||
int len;
|
||||
|
||||
/* Make sure the minor for this device exists. */
|
||||
if (device->devindex >= DASD_PER_MAJOR)
|
||||
return -EBUSY;
|
||||
|
||||
gdp = alloc_disk(1 << DASD_PARTN_BITS);
|
||||
if (!gdp)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Initialize gendisk structure. */
|
||||
gdp->major = DASD_MAJOR;
|
||||
gdp->first_minor = device->devindex << DASD_PARTN_BITS;
|
||||
gdp->fops = &dasd_device_operations;
|
||||
gdp->driverfs_dev = &device->cdev->dev;
|
||||
|
||||
/*
|
||||
* Set device name.
|
||||
* dasda - dasdz : 26 devices
|
||||
* dasdaa - dasdzz : 676 devices, added up = 702
|
||||
* dasdaaa - dasdzzz : 17576 devices, added up = 18278
|
||||
* dasdaaaa - dasdzzzz : 456976 devices, added up = 475252
|
||||
*/
|
||||
len = sprintf(gdp->disk_name, "dasd");
|
||||
if (device->devindex > 25) {
|
||||
if (device->devindex > 701) {
|
||||
if (device->devindex > 18277)
|
||||
len += sprintf(gdp->disk_name + len, "%c",
|
||||
'a'+(((device->devindex-18278)
|
||||
/17576)%26));
|
||||
len += sprintf(gdp->disk_name + len, "%c",
|
||||
'a'+(((device->devindex-702)/676)%26));
|
||||
}
|
||||
len += sprintf(gdp->disk_name + len, "%c",
|
||||
'a'+(((device->devindex-26)/26)%26));
|
||||
}
|
||||
len += sprintf(gdp->disk_name + len, "%c", 'a'+(device->devindex%26));
|
||||
|
||||
if (device->features & DASD_FEATURE_READONLY)
|
||||
set_disk_ro(gdp, 1);
|
||||
gdp->private_data = device;
|
||||
gdp->queue = device->request_queue;
|
||||
device->gdp = gdp;
|
||||
set_capacity(device->gdp, 0);
|
||||
add_disk(device->gdp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Unregister and free gendisk structure for device.
|
||||
*/
|
||||
void
|
||||
dasd_gendisk_free(struct dasd_device *device)
|
||||
{
|
||||
if (device->gdp) {
|
||||
del_gendisk(device->gdp);
|
||||
device->gdp->queue = NULL;
|
||||
put_disk(device->gdp);
|
||||
device->gdp = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Trigger a partition detection.
|
||||
*/
|
||||
int
|
||||
dasd_scan_partitions(struct dasd_device * device)
|
||||
{
|
||||
struct block_device *bdev;
|
||||
|
||||
bdev = bdget_disk(device->gdp, 0);
|
||||
if (!bdev || blkdev_get(bdev, FMODE_READ, 1) < 0)
|
||||
return -ENODEV;
|
||||
/*
|
||||
* See fs/partition/check.c:register_disk,rescan_partitions
|
||||
* Can't call rescan_partitions directly. Use ioctl.
|
||||
*/
|
||||
ioctl_by_bdev(bdev, BLKRRPART, 0);
|
||||
/*
|
||||
* Since the matching blkdev_put call to the blkdev_get in
|
||||
* this function is not called before dasd_destroy_partitions
|
||||
* the offline open_count limit needs to be increased from
|
||||
* 0 to 1. This is done by setting device->bdev (see
|
||||
* dasd_generic_set_offline). As long as the partition
|
||||
* detection is running no offline should be allowed. That
|
||||
* is why the assignment to device->bdev is done AFTER
|
||||
* the BLKRRPART ioctl.
|
||||
*/
|
||||
device->bdev = bdev;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove all inodes in the system for a device, delete the
|
||||
* partitions and make device unusable by setting its size to zero.
|
||||
*/
|
||||
void
|
||||
dasd_destroy_partitions(struct dasd_device * device)
|
||||
{
|
||||
/* The two structs have 168/176 byte on 31/64 bit. */
|
||||
struct blkpg_partition bpart;
|
||||
struct blkpg_ioctl_arg barg;
|
||||
struct block_device *bdev;
|
||||
|
||||
/*
|
||||
* Get the bdev pointer from the device structure and clear
|
||||
* device->bdev to lower the offline open_count limit again.
|
||||
*/
|
||||
bdev = device->bdev;
|
||||
device->bdev = NULL;
|
||||
|
||||
/*
|
||||
* See fs/partition/check.c:delete_partition
|
||||
* Can't call delete_partitions directly. Use ioctl.
|
||||
* The ioctl also does locking and invalidation.
|
||||
*/
|
||||
memset(&bpart, 0, sizeof(struct blkpg_partition));
|
||||
memset(&barg, 0, sizeof(struct blkpg_ioctl_arg));
|
||||
barg.data = (void __force __user *) &bpart;
|
||||
barg.op = BLKPG_DEL_PARTITION;
|
||||
for (bpart.pno = device->gdp->minors - 1; bpart.pno > 0; bpart.pno--)
|
||||
ioctl_by_bdev(bdev, BLKPG, (unsigned long) &barg);
|
||||
|
||||
invalidate_partition(device->gdp, 0);
|
||||
/* Matching blkdev_put to the blkdev_get in dasd_scan_partitions. */
|
||||
blkdev_put(bdev);
|
||||
set_capacity(device->gdp, 0);
|
||||
}
|
||||
|
||||
int
|
||||
dasd_gendisk_init(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
/* Register to static dasd major 94 */
|
||||
rc = register_blkdev(DASD_MAJOR, "dasd");
|
||||
if (rc != 0) {
|
||||
MESSAGE(KERN_WARNING,
|
||||
"Couldn't register successfully to "
|
||||
"major no %d", DASD_MAJOR);
|
||||
return rc;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
dasd_gendisk_exit(void)
|
||||
{
|
||||
unregister_blkdev(DASD_MAJOR, "dasd");
|
||||
}
|
||||
603
drivers/s390/block/dasd_int.h
Normal file
603
drivers/s390/block/dasd_int.h
Normal file
@@ -0,0 +1,603 @@
|
||||
/*
|
||||
* File...........: linux/drivers/s390/block/dasd_int.h
|
||||
* Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
|
||||
* Horst Hummel <Horst.Hummel@de.ibm.com>
|
||||
* Martin Schwidefsky <schwidefsky@de.ibm.com>
|
||||
* Bugreports.to..: <Linux390@de.ibm.com>
|
||||
* (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999,2000
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DASD_INT_H
|
||||
#define DASD_INT_H
|
||||
|
||||
#ifdef __KERNEL__
|
||||
|
||||
/* we keep old device allocation scheme; IOW, minors are still in 0..255 */
|
||||
#define DASD_PER_MAJOR (1U << (MINORBITS - DASD_PARTN_BITS))
|
||||
#define DASD_PARTN_MASK ((1 << DASD_PARTN_BITS) - 1)
|
||||
|
||||
/*
|
||||
* States a dasd device can have:
|
||||
* new: the dasd_device structure is allocated.
|
||||
* known: the discipline for the device is identified.
|
||||
* basic: the device can do basic i/o.
|
||||
* unfmt: the device could not be analyzed (format is unknown).
|
||||
* ready: partition detection is done and the device is can do block io.
|
||||
* online: the device accepts requests from the block device queue.
|
||||
*
|
||||
* Things to do for startup state transitions:
|
||||
* new -> known: find discipline for the device and create devfs entries.
|
||||
* known -> basic: request irq line for the device.
|
||||
* basic -> ready: do the initial analysis, e.g. format detection,
|
||||
* do block device setup and detect partitions.
|
||||
* ready -> online: schedule the device tasklet.
|
||||
* Things to do for shutdown state transitions:
|
||||
* online -> ready: just set the new device state.
|
||||
* ready -> basic: flush requests from the block device layer, clear
|
||||
* partition information and reset format information.
|
||||
* basic -> known: terminate all requests and free irq.
|
||||
* known -> new: remove devfs entries and forget discipline.
|
||||
*/
|
||||
|
||||
#define DASD_STATE_NEW 0
|
||||
#define DASD_STATE_KNOWN 1
|
||||
#define DASD_STATE_BASIC 2
|
||||
#define DASD_STATE_UNFMT 3
|
||||
#define DASD_STATE_READY 4
|
||||
#define DASD_STATE_ONLINE 5
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/blkdev.h>
|
||||
#include <linux/genhd.h>
|
||||
#include <linux/hdreg.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <asm/ccwdev.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <asm/debug.h>
|
||||
#include <asm/dasd.h>
|
||||
#include <asm/idals.h>
|
||||
|
||||
/*
|
||||
* SECTION: Type definitions
|
||||
*/
|
||||
struct dasd_device;
|
||||
|
||||
typedef enum {
|
||||
dasd_era_fatal = -1, /* no chance to recover */
|
||||
dasd_era_none = 0, /* don't recover, everything alright */
|
||||
dasd_era_msg = 1, /* don't recover, just report... */
|
||||
dasd_era_recover = 2 /* recovery action recommended */
|
||||
} dasd_era_t;
|
||||
|
||||
/* BIT DEFINITIONS FOR SENSE DATA */
|
||||
#define DASD_SENSE_BIT_0 0x80
|
||||
#define DASD_SENSE_BIT_1 0x40
|
||||
#define DASD_SENSE_BIT_2 0x20
|
||||
#define DASD_SENSE_BIT_3 0x10
|
||||
|
||||
/*
|
||||
* SECTION: MACROs for klogd and s390 debug feature (dbf)
|
||||
*/
|
||||
#define DBF_DEV_EVENT(d_level, d_device, d_str, d_data...) \
|
||||
do { \
|
||||
debug_sprintf_event(d_device->debug_area, \
|
||||
d_level, \
|
||||
d_str "\n", \
|
||||
d_data); \
|
||||
} while(0)
|
||||
|
||||
#define DBF_DEV_EXC(d_level, d_device, d_str, d_data...) \
|
||||
do { \
|
||||
debug_sprintf_exception(d_device->debug_area, \
|
||||
d_level, \
|
||||
d_str "\n", \
|
||||
d_data); \
|
||||
} while(0)
|
||||
|
||||
#define DBF_EVENT(d_level, d_str, d_data...)\
|
||||
do { \
|
||||
debug_sprintf_event(dasd_debug_area, \
|
||||
d_level,\
|
||||
d_str "\n", \
|
||||
d_data); \
|
||||
} while(0)
|
||||
|
||||
#define DBF_EXC(d_level, d_str, d_data...)\
|
||||
do { \
|
||||
debug_sprintf_exception(dasd_debug_area, \
|
||||
d_level,\
|
||||
d_str "\n", \
|
||||
d_data); \
|
||||
} while(0)
|
||||
|
||||
/* definition of dbf debug levels */
|
||||
#define DBF_EMERG 0 /* system is unusable */
|
||||
#define DBF_ALERT 1 /* action must be taken immediately */
|
||||
#define DBF_CRIT 2 /* critical conditions */
|
||||
#define DBF_ERR 3 /* error conditions */
|
||||
#define DBF_WARNING 4 /* warning conditions */
|
||||
#define DBF_NOTICE 5 /* normal but significant condition */
|
||||
#define DBF_INFO 6 /* informational */
|
||||
#define DBF_DEBUG 6 /* debug-level messages */
|
||||
|
||||
/* messages to be written via klogd and dbf */
|
||||
#define DEV_MESSAGE(d_loglevel,d_device,d_string,d_args...)\
|
||||
do { \
|
||||
printk(d_loglevel PRINTK_HEADER " %s: " d_string "\n", \
|
||||
d_device->cdev->dev.bus_id, d_args); \
|
||||
DBF_DEV_EVENT(DBF_ALERT, d_device, d_string, d_args); \
|
||||
} while(0)
|
||||
|
||||
#define MESSAGE(d_loglevel,d_string,d_args...)\
|
||||
do { \
|
||||
printk(d_loglevel PRINTK_HEADER " " d_string "\n", d_args); \
|
||||
DBF_EVENT(DBF_ALERT, d_string, d_args); \
|
||||
} while(0)
|
||||
|
||||
/* messages to be written via klogd only */
|
||||
#define DEV_MESSAGE_LOG(d_loglevel,d_device,d_string,d_args...)\
|
||||
do { \
|
||||
printk(d_loglevel PRINTK_HEADER " %s: " d_string "\n", \
|
||||
d_device->cdev->dev.bus_id, d_args); \
|
||||
} while(0)
|
||||
|
||||
#define MESSAGE_LOG(d_loglevel,d_string,d_args...)\
|
||||
do { \
|
||||
printk(d_loglevel PRINTK_HEADER " " d_string "\n", d_args); \
|
||||
} while(0)
|
||||
|
||||
struct dasd_ccw_req {
|
||||
unsigned int magic; /* Eye catcher */
|
||||
struct list_head list; /* list_head for request queueing. */
|
||||
|
||||
/* Where to execute what... */
|
||||
struct dasd_device *device; /* device the request is for */
|
||||
struct ccw1 *cpaddr; /* address of channel program */
|
||||
char status; /* status of this request */
|
||||
short retries; /* A retry counter */
|
||||
unsigned long flags; /* flags of this request */
|
||||
|
||||
/* ... and how */
|
||||
unsigned long starttime; /* jiffies time of request start */
|
||||
int expires; /* expiration period in jiffies */
|
||||
char lpm; /* logical path mask */
|
||||
void *data; /* pointer to data area */
|
||||
|
||||
/* these are important for recovering erroneous requests */
|
||||
struct irb irb; /* device status in case of an error */
|
||||
struct dasd_ccw_req *refers; /* ERP-chain queueing. */
|
||||
void *function; /* originating ERP action */
|
||||
|
||||
/* these are for statistics only */
|
||||
unsigned long long buildclk; /* TOD-clock of request generation */
|
||||
unsigned long long startclk; /* TOD-clock of request start */
|
||||
unsigned long long stopclk; /* TOD-clock of request interrupt */
|
||||
unsigned long long endclk; /* TOD-clock of request termination */
|
||||
|
||||
/* Callback that is called after reaching final status. */
|
||||
void (*callback)(struct dasd_ccw_req *, void *data);
|
||||
void *callback_data;
|
||||
};
|
||||
|
||||
/*
|
||||
* dasd_ccw_req -> status can be:
|
||||
*/
|
||||
#define DASD_CQR_FILLED 0x00 /* request is ready to be processed */
|
||||
#define DASD_CQR_QUEUED 0x01 /* request is queued to be processed */
|
||||
#define DASD_CQR_IN_IO 0x02 /* request is currently in IO */
|
||||
#define DASD_CQR_DONE 0x03 /* request is completed successfully */
|
||||
#define DASD_CQR_ERROR 0x04 /* request is completed with error */
|
||||
#define DASD_CQR_FAILED 0x05 /* request is finally failed */
|
||||
#define DASD_CQR_CLEAR 0x06 /* request is clear pending */
|
||||
|
||||
/* per dasd_ccw_req flags */
|
||||
#define DASD_CQR_FLAGS_USE_ERP 0 /* use ERP for this request */
|
||||
#define DASD_CQR_FLAGS_FAILFAST 1 /* FAILFAST */
|
||||
|
||||
/* Signature for error recovery functions. */
|
||||
typedef struct dasd_ccw_req *(*dasd_erp_fn_t) (struct dasd_ccw_req *);
|
||||
|
||||
/*
|
||||
* the struct dasd_discipline is
|
||||
* sth like a table of virtual functions, if you think of dasd_eckd
|
||||
* inheriting dasd...
|
||||
* no, currently we are not planning to reimplement the driver in C++
|
||||
*/
|
||||
struct dasd_discipline {
|
||||
struct module *owner;
|
||||
char ebcname[8]; /* a name used for tagging and printks */
|
||||
char name[8]; /* a name used for tagging and printks */
|
||||
int max_blocks; /* maximum number of blocks to be chained */
|
||||
|
||||
struct list_head list; /* used for list of disciplines */
|
||||
|
||||
/*
|
||||
* Device recognition functions. check_device is used to verify
|
||||
* the sense data and the information returned by read device
|
||||
* characteristics. It returns 0 if the discipline can be used
|
||||
* for the device in question.
|
||||
* do_analysis is used in the step from device state "basic" to
|
||||
* state "accept". It returns 0 if the device can be made ready,
|
||||
* it returns -EMEDIUMTYPE if the device can't be made ready or
|
||||
* -EAGAIN if do_analysis started a ccw that needs to complete
|
||||
* before the analysis may be repeated.
|
||||
*/
|
||||
int (*check_device)(struct dasd_device *);
|
||||
int (*do_analysis) (struct dasd_device *);
|
||||
|
||||
/*
|
||||
* Device operation functions. build_cp creates a ccw chain for
|
||||
* a block device request, start_io starts the request and
|
||||
* term_IO cancels it (e.g. in case of a timeout). format_device
|
||||
* returns a ccw chain to be used to format the device.
|
||||
*/
|
||||
struct dasd_ccw_req *(*build_cp) (struct dasd_device *,
|
||||
struct request *);
|
||||
int (*start_IO) (struct dasd_ccw_req *);
|
||||
int (*term_IO) (struct dasd_ccw_req *);
|
||||
struct dasd_ccw_req *(*format_device) (struct dasd_device *,
|
||||
struct format_data_t *);
|
||||
int (*free_cp) (struct dasd_ccw_req *, struct request *);
|
||||
/*
|
||||
* Error recovery functions. examine_error() returns a value that
|
||||
* indicates what to do for an error condition. If examine_error()
|
||||
* returns 'dasd_era_recover' erp_action() is called to create a
|
||||
* special error recovery ccw. erp_postaction() is called after
|
||||
* an error recovery ccw has finished its execution. dump_sense
|
||||
* is called for every error condition to print the sense data
|
||||
* to the console.
|
||||
*/
|
||||
dasd_era_t(*examine_error) (struct dasd_ccw_req *, struct irb *);
|
||||
dasd_erp_fn_t(*erp_action) (struct dasd_ccw_req *);
|
||||
dasd_erp_fn_t(*erp_postaction) (struct dasd_ccw_req *);
|
||||
void (*dump_sense) (struct dasd_device *, struct dasd_ccw_req *,
|
||||
struct irb *);
|
||||
|
||||
/* i/o control functions. */
|
||||
int (*fill_geometry) (struct dasd_device *, struct hd_geometry *);
|
||||
int (*fill_info) (struct dasd_device *, struct dasd_information2_t *);
|
||||
int (*ioctl) (struct dasd_device *, unsigned int, void __user *);
|
||||
};
|
||||
|
||||
extern struct dasd_discipline *dasd_diag_discipline_pointer;
|
||||
|
||||
/*
|
||||
* Unique identifier for dasd device.
|
||||
*/
|
||||
struct dasd_uid {
|
||||
__u8 alias;
|
||||
char vendor[4];
|
||||
char serial[15];
|
||||
__u16 ssid;
|
||||
__u8 unit_addr;
|
||||
};
|
||||
|
||||
/*
|
||||
* Notification numbers for extended error reporting notifications:
|
||||
* The DASD_EER_DISABLE notification is sent before a dasd_device (and it's
|
||||
* eer pointer) is freed. The error reporting module needs to do all necessary
|
||||
* cleanup steps.
|
||||
* The DASD_EER_TRIGGER notification sends the actual error reports (triggers).
|
||||
*/
|
||||
#define DASD_EER_DISABLE 0
|
||||
#define DASD_EER_TRIGGER 1
|
||||
|
||||
/* Trigger IDs for extended error reporting DASD_EER_TRIGGER notification */
|
||||
#define DASD_EER_FATALERROR 1
|
||||
#define DASD_EER_NOPATH 2
|
||||
#define DASD_EER_STATECHANGE 3
|
||||
#define DASD_EER_PPRCSUSPEND 4
|
||||
|
||||
struct dasd_device {
|
||||
/* Block device stuff. */
|
||||
struct gendisk *gdp;
|
||||
request_queue_t *request_queue;
|
||||
spinlock_t request_queue_lock;
|
||||
struct block_device *bdev;
|
||||
unsigned int devindex;
|
||||
unsigned long blocks; /* size of volume in blocks */
|
||||
unsigned int bp_block; /* bytes per block */
|
||||
unsigned int s2b_shift; /* log2 (bp_block/512) */
|
||||
unsigned long flags; /* per device flags */
|
||||
unsigned short features; /* copy of devmap-features (read-only!) */
|
||||
|
||||
/* extended error reporting stuff (eer) */
|
||||
struct dasd_ccw_req *eer_cqr;
|
||||
|
||||
/* Device discipline stuff. */
|
||||
struct dasd_discipline *discipline;
|
||||
struct dasd_discipline *base_discipline;
|
||||
char *private;
|
||||
|
||||
/* Device state and target state. */
|
||||
int state, target;
|
||||
int stopped; /* device (ccw_device_start) was stopped */
|
||||
|
||||
/* Open and reference count. */
|
||||
atomic_t ref_count;
|
||||
atomic_t open_count;
|
||||
|
||||
/* ccw queue and memory for static ccw/erp buffers. */
|
||||
struct list_head ccw_queue;
|
||||
spinlock_t mem_lock;
|
||||
void *ccw_mem;
|
||||
void *erp_mem;
|
||||
struct list_head ccw_chunks;
|
||||
struct list_head erp_chunks;
|
||||
|
||||
atomic_t tasklet_scheduled;
|
||||
struct tasklet_struct tasklet;
|
||||
struct work_struct kick_work;
|
||||
struct timer_list timer;
|
||||
|
||||
debug_info_t *debug_area;
|
||||
|
||||
struct ccw_device *cdev;
|
||||
|
||||
#ifdef CONFIG_DASD_PROFILE
|
||||
struct dasd_profile_info_t profile;
|
||||
#endif
|
||||
};
|
||||
|
||||
/* reasons why device (ccw_device_start) was stopped */
|
||||
#define DASD_STOPPED_NOT_ACC 1 /* not accessible */
|
||||
#define DASD_STOPPED_QUIESCE 2 /* Quiesced */
|
||||
#define DASD_STOPPED_PENDING 4 /* long busy */
|
||||
#define DASD_STOPPED_DC_WAIT 8 /* disconnected, wait */
|
||||
#define DASD_STOPPED_DC_EIO 16 /* disconnected, return -EIO */
|
||||
|
||||
/* per device flags */
|
||||
#define DASD_FLAG_DSC_ERROR 2 /* return -EIO when disconnected */
|
||||
#define DASD_FLAG_OFFLINE 3 /* device is in offline processing */
|
||||
#define DASD_FLAG_EER_SNSS 4 /* A SNSS is required */
|
||||
#define DASD_FLAG_EER_IN_USE 5 /* A SNSS request is running */
|
||||
|
||||
void dasd_put_device_wake(struct dasd_device *);
|
||||
|
||||
/*
|
||||
* Reference count inliners
|
||||
*/
|
||||
static inline void
|
||||
dasd_get_device(struct dasd_device *device)
|
||||
{
|
||||
atomic_inc(&device->ref_count);
|
||||
}
|
||||
|
||||
static inline void
|
||||
dasd_put_device(struct dasd_device *device)
|
||||
{
|
||||
if (atomic_dec_return(&device->ref_count) == 0)
|
||||
dasd_put_device_wake(device);
|
||||
}
|
||||
|
||||
/*
|
||||
* The static memory in ccw_mem and erp_mem is managed by a sorted
|
||||
* list of free memory chunks.
|
||||
*/
|
||||
struct dasd_mchunk
|
||||
{
|
||||
struct list_head list;
|
||||
unsigned long size;
|
||||
} __attribute__ ((aligned(8)));
|
||||
|
||||
static inline void
|
||||
dasd_init_chunklist(struct list_head *chunk_list, void *mem,
|
||||
unsigned long size)
|
||||
{
|
||||
struct dasd_mchunk *chunk;
|
||||
|
||||
INIT_LIST_HEAD(chunk_list);
|
||||
chunk = (struct dasd_mchunk *) mem;
|
||||
chunk->size = size - sizeof(struct dasd_mchunk);
|
||||
list_add(&chunk->list, chunk_list);
|
||||
}
|
||||
|
||||
static inline void *
|
||||
dasd_alloc_chunk(struct list_head *chunk_list, unsigned long size)
|
||||
{
|
||||
struct dasd_mchunk *chunk, *tmp;
|
||||
|
||||
size = (size + 7L) & -8L;
|
||||
list_for_each_entry(chunk, chunk_list, list) {
|
||||
if (chunk->size < size)
|
||||
continue;
|
||||
if (chunk->size > size + sizeof(struct dasd_mchunk)) {
|
||||
char *endaddr = (char *) (chunk + 1) + chunk->size;
|
||||
tmp = (struct dasd_mchunk *) (endaddr - size) - 1;
|
||||
tmp->size = size;
|
||||
chunk->size -= size + sizeof(struct dasd_mchunk);
|
||||
chunk = tmp;
|
||||
} else
|
||||
list_del(&chunk->list);
|
||||
return (void *) (chunk + 1);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline void
|
||||
dasd_free_chunk(struct list_head *chunk_list, void *mem)
|
||||
{
|
||||
struct dasd_mchunk *chunk, *tmp;
|
||||
struct list_head *p, *left;
|
||||
|
||||
chunk = (struct dasd_mchunk *)
|
||||
((char *) mem - sizeof(struct dasd_mchunk));
|
||||
/* Find out the left neighbour in chunk_list. */
|
||||
left = chunk_list;
|
||||
list_for_each(p, chunk_list) {
|
||||
if (list_entry(p, struct dasd_mchunk, list) > chunk)
|
||||
break;
|
||||
left = p;
|
||||
}
|
||||
/* Try to merge with right neighbour = next element from left. */
|
||||
if (left->next != chunk_list) {
|
||||
tmp = list_entry(left->next, struct dasd_mchunk, list);
|
||||
if ((char *) (chunk + 1) + chunk->size == (char *) tmp) {
|
||||
list_del(&tmp->list);
|
||||
chunk->size += tmp->size + sizeof(struct dasd_mchunk);
|
||||
}
|
||||
}
|
||||
/* Try to merge with left neighbour. */
|
||||
if (left != chunk_list) {
|
||||
tmp = list_entry(left, struct dasd_mchunk, list);
|
||||
if ((char *) (tmp + 1) + tmp->size == (char *) chunk) {
|
||||
tmp->size += chunk->size + sizeof(struct dasd_mchunk);
|
||||
return;
|
||||
}
|
||||
}
|
||||
__list_add(&chunk->list, left, left->next);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if bsize is in { 512, 1024, 2048, 4096 }
|
||||
*/
|
||||
static inline int
|
||||
dasd_check_blocksize(int bsize)
|
||||
{
|
||||
if (bsize < 512 || bsize > 4096 || (bsize & (bsize - 1)) != 0)
|
||||
return -EMEDIUMTYPE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* externals in dasd.c */
|
||||
#define DASD_PROFILE_ON 1
|
||||
#define DASD_PROFILE_OFF 0
|
||||
|
||||
extern debug_info_t *dasd_debug_area;
|
||||
extern struct dasd_profile_info_t dasd_global_profile;
|
||||
extern unsigned int dasd_profile_level;
|
||||
extern struct block_device_operations dasd_device_operations;
|
||||
|
||||
extern struct kmem_cache *dasd_page_cache;
|
||||
|
||||
struct dasd_ccw_req *
|
||||
dasd_kmalloc_request(char *, int, int, struct dasd_device *);
|
||||
struct dasd_ccw_req *
|
||||
dasd_smalloc_request(char *, int, int, struct dasd_device *);
|
||||
void dasd_kfree_request(struct dasd_ccw_req *, struct dasd_device *);
|
||||
void dasd_sfree_request(struct dasd_ccw_req *, struct dasd_device *);
|
||||
|
||||
static inline int
|
||||
dasd_kmalloc_set_cda(struct ccw1 *ccw, void *cda, struct dasd_device *device)
|
||||
{
|
||||
return set_normalized_cda(ccw, cda);
|
||||
}
|
||||
|
||||
struct dasd_device *dasd_alloc_device(void);
|
||||
void dasd_free_device(struct dasd_device *);
|
||||
|
||||
void dasd_enable_device(struct dasd_device *);
|
||||
void dasd_set_target_state(struct dasd_device *, int);
|
||||
void dasd_kick_device(struct dasd_device *);
|
||||
|
||||
void dasd_add_request_head(struct dasd_ccw_req *);
|
||||
void dasd_add_request_tail(struct dasd_ccw_req *);
|
||||
int dasd_start_IO(struct dasd_ccw_req *);
|
||||
int dasd_term_IO(struct dasd_ccw_req *);
|
||||
void dasd_schedule_bh(struct dasd_device *);
|
||||
int dasd_sleep_on(struct dasd_ccw_req *);
|
||||
int dasd_sleep_on_immediatly(struct dasd_ccw_req *);
|
||||
int dasd_sleep_on_interruptible(struct dasd_ccw_req *);
|
||||
void dasd_set_timer(struct dasd_device *, int);
|
||||
void dasd_clear_timer(struct dasd_device *);
|
||||
int dasd_cancel_req(struct dasd_ccw_req *);
|
||||
int dasd_generic_probe (struct ccw_device *, struct dasd_discipline *);
|
||||
void dasd_generic_remove (struct ccw_device *cdev);
|
||||
int dasd_generic_set_online(struct ccw_device *, struct dasd_discipline *);
|
||||
int dasd_generic_set_offline (struct ccw_device *cdev);
|
||||
int dasd_generic_notify(struct ccw_device *, int);
|
||||
|
||||
/* externals in dasd_devmap.c */
|
||||
extern int dasd_max_devindex;
|
||||
extern int dasd_probeonly;
|
||||
extern int dasd_autodetect;
|
||||
extern int dasd_nopav;
|
||||
|
||||
int dasd_devmap_init(void);
|
||||
void dasd_devmap_exit(void);
|
||||
|
||||
struct dasd_device *dasd_create_device(struct ccw_device *);
|
||||
void dasd_delete_device(struct dasd_device *);
|
||||
|
||||
int dasd_get_uid(struct ccw_device *, struct dasd_uid *);
|
||||
int dasd_set_uid(struct ccw_device *, struct dasd_uid *);
|
||||
int dasd_get_feature(struct ccw_device *, int);
|
||||
int dasd_set_feature(struct ccw_device *, int, int);
|
||||
|
||||
int dasd_add_sysfs_files(struct ccw_device *);
|
||||
void dasd_remove_sysfs_files(struct ccw_device *);
|
||||
|
||||
struct dasd_device *dasd_device_from_cdev(struct ccw_device *);
|
||||
struct dasd_device *dasd_device_from_cdev_locked(struct ccw_device *);
|
||||
struct dasd_device *dasd_device_from_devindex(int);
|
||||
|
||||
int dasd_parse(void);
|
||||
int dasd_busid_known(char *);
|
||||
|
||||
/* externals in dasd_gendisk.c */
|
||||
int dasd_gendisk_init(void);
|
||||
void dasd_gendisk_exit(void);
|
||||
int dasd_gendisk_alloc(struct dasd_device *);
|
||||
void dasd_gendisk_free(struct dasd_device *);
|
||||
int dasd_scan_partitions(struct dasd_device *);
|
||||
void dasd_destroy_partitions(struct dasd_device *);
|
||||
|
||||
/* externals in dasd_ioctl.c */
|
||||
int dasd_ioctl(struct inode *, struct file *, unsigned int, unsigned long);
|
||||
long dasd_compat_ioctl(struct file *, unsigned int, unsigned long);
|
||||
|
||||
/* externals in dasd_proc.c */
|
||||
int dasd_proc_init(void);
|
||||
void dasd_proc_exit(void);
|
||||
|
||||
/* externals in dasd_erp.c */
|
||||
struct dasd_ccw_req *dasd_default_erp_action(struct dasd_ccw_req *);
|
||||
struct dasd_ccw_req *dasd_default_erp_postaction(struct dasd_ccw_req *);
|
||||
struct dasd_ccw_req *dasd_alloc_erp_request(char *, int, int,
|
||||
struct dasd_device *);
|
||||
void dasd_free_erp_request(struct dasd_ccw_req *, struct dasd_device *);
|
||||
void dasd_log_sense(struct dasd_ccw_req *, struct irb *);
|
||||
|
||||
/* externals in dasd_3370_erp.c */
|
||||
dasd_era_t dasd_3370_erp_examine(struct dasd_ccw_req *, struct irb *);
|
||||
|
||||
/* externals in dasd_3990_erp.c */
|
||||
dasd_era_t dasd_3990_erp_examine(struct dasd_ccw_req *, struct irb *);
|
||||
struct dasd_ccw_req *dasd_3990_erp_action(struct dasd_ccw_req *);
|
||||
|
||||
/* externals in dasd_9336_erp.c */
|
||||
dasd_era_t dasd_9336_erp_examine(struct dasd_ccw_req *, struct irb *);
|
||||
|
||||
/* externals in dasd_9336_erp.c */
|
||||
dasd_era_t dasd_9343_erp_examine(struct dasd_ccw_req *, struct irb *);
|
||||
struct dasd_ccw_req *dasd_9343_erp_action(struct dasd_ccw_req *);
|
||||
|
||||
/* externals in dasd_eer.c */
|
||||
#ifdef CONFIG_DASD_EER
|
||||
int dasd_eer_init(void);
|
||||
void dasd_eer_exit(void);
|
||||
int dasd_eer_enable(struct dasd_device *);
|
||||
void dasd_eer_disable(struct dasd_device *);
|
||||
void dasd_eer_write(struct dasd_device *, struct dasd_ccw_req *cqr,
|
||||
unsigned int id);
|
||||
void dasd_eer_snss(struct dasd_device *);
|
||||
|
||||
static inline int dasd_eer_enabled(struct dasd_device *device)
|
||||
{
|
||||
return device->eer_cqr != NULL;
|
||||
}
|
||||
#else
|
||||
#define dasd_eer_init() (0)
|
||||
#define dasd_eer_exit() do { } while (0)
|
||||
#define dasd_eer_enable(d) (0)
|
||||
#define dasd_eer_disable(d) do { } while (0)
|
||||
#define dasd_eer_write(d,c,i) do { } while (0)
|
||||
#define dasd_eer_snss(d) do { } while (0)
|
||||
#define dasd_eer_enabled(d) (0)
|
||||
#endif /* CONFIG_DASD_ERR */
|
||||
|
||||
#endif /* __KERNEL__ */
|
||||
|
||||
#endif /* DASD_H */
|
||||
437
drivers/s390/block/dasd_ioctl.c
Normal file
437
drivers/s390/block/dasd_ioctl.c
Normal file
@@ -0,0 +1,437 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/major.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/blkpg.h>
|
||||
|
||||
#include <asm/ccwdev.h>
|
||||
#include <asm/cmb.h>
|
||||
#include <asm/uaccess.h>
|
||||
|
||||
/* This is ugly... */
|
||||
#define PRINTK_HEADER "dasd_ioctl:"
|
||||
|
||||
#include "dasd_int.h"
|
||||
|
||||
|
||||
static int
|
||||
dasd_ioctl_api_version(void __user *argp)
|
||||
{
|
||||
int ver = DASD_API_VERSION;
|
||||
return put_user(ver, (int __user *)argp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable device.
|
||||
* used by dasdfmt after BIODASDDISABLE to retrigger blocksize detection
|
||||
*/
|
||||
static int
|
||||
dasd_ioctl_enable(struct block_device *bdev)
|
||||
{
|
||||
struct dasd_device *device = bdev->bd_disk->private_data;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EACCES;
|
||||
|
||||
dasd_enable_device(device);
|
||||
/* Formatting the dasd device can change the capacity. */
|
||||
mutex_lock(&bdev->bd_mutex);
|
||||
i_size_write(bdev->bd_inode, (loff_t)get_capacity(device->gdp) << 9);
|
||||
mutex_unlock(&bdev->bd_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable device.
|
||||
* Used by dasdfmt. Disable I/O operations but allow ioctls.
|
||||
*/
|
||||
static int
|
||||
dasd_ioctl_disable(struct block_device *bdev)
|
||||
{
|
||||
struct dasd_device *device = bdev->bd_disk->private_data;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EACCES;
|
||||
|
||||
/*
|
||||
* 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);
|
||||
/*
|
||||
* Set i_size to zero, since read, write, etc. check against this
|
||||
* value.
|
||||
*/
|
||||
mutex_lock(&bdev->bd_mutex);
|
||||
i_size_write(bdev->bd_inode, 0);
|
||||
mutex_unlock(&bdev->bd_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Quiesce device.
|
||||
*/
|
||||
static int
|
||||
dasd_ioctl_quiesce(struct dasd_device *device)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
if (!capable (CAP_SYS_ADMIN))
|
||||
return -EACCES;
|
||||
|
||||
DEV_MESSAGE (KERN_DEBUG, device, "%s",
|
||||
"Quiesce IO on device");
|
||||
spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
|
||||
device->stopped |= DASD_STOPPED_QUIESCE;
|
||||
spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Quiesce device.
|
||||
*/
|
||||
static int
|
||||
dasd_ioctl_resume(struct dasd_device *device)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
if (!capable (CAP_SYS_ADMIN))
|
||||
return -EACCES;
|
||||
|
||||
DEV_MESSAGE (KERN_DEBUG, device, "%s",
|
||||
"resume IO on device");
|
||||
|
||||
spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
|
||||
device->stopped &= ~DASD_STOPPED_QUIESCE;
|
||||
spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
|
||||
|
||||
dasd_schedule_bh (device);
|
||||
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(struct dasd_device * device, struct format_data_t * fdata)
|
||||
{
|
||||
struct dasd_ccw_req *cqr;
|
||||
int rc;
|
||||
|
||||
if (device->discipline->format_device == NULL)
|
||||
return -EPERM;
|
||||
|
||||
if (device->state != DASD_STATE_BASIC) {
|
||||
DEV_MESSAGE(KERN_WARNING, device, "%s",
|
||||
"dasd_format: device is not disabled! ");
|
||||
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);
|
||||
|
||||
/* Since dasdfmt keeps the device open after it was disabled,
|
||||
* there still exists an inode for this device.
|
||||
* We must update i_blkbits, otherwise we might get errors when
|
||||
* enabling the device later.
|
||||
*/
|
||||
if (fdata->start_unit == 0) {
|
||||
struct block_device *bdev = bdget_disk(device->gdp, 0);
|
||||
bdev->bd_inode->i_blkbits = blksize_bits(fdata->blksize);
|
||||
bdput(bdev);
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
static int
|
||||
dasd_ioctl_format(struct block_device *bdev, void __user *argp)
|
||||
{
|
||||
struct dasd_device *device = bdev->bd_disk->private_data;
|
||||
struct format_data_t fdata;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EACCES;
|
||||
if (!argp)
|
||||
return -EINVAL;
|
||||
|
||||
if (device->features & DASD_FEATURE_READONLY)
|
||||
return -EROFS;
|
||||
if (copy_from_user(&fdata, argp, sizeof(struct format_data_t)))
|
||||
return -EFAULT;
|
||||
if (bdev != bdev->bd_contains) {
|
||||
DEV_MESSAGE(KERN_WARNING, device, "%s",
|
||||
"Cannot low-level format a partition");
|
||||
return -EINVAL;
|
||||
}
|
||||
return dasd_format(device, &fdata);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DASD_PROFILE
|
||||
/*
|
||||
* Reset device profile information
|
||||
*/
|
||||
static int
|
||||
dasd_ioctl_reset_profile(struct dasd_device *device)
|
||||
{
|
||||
memset(&device->profile, 0, sizeof (struct dasd_profile_info_t));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return device profile information
|
||||
*/
|
||||
static int
|
||||
dasd_ioctl_read_profile(struct dasd_device *device, void __user *argp)
|
||||
{
|
||||
if (dasd_profile_level == DASD_PROFILE_OFF)
|
||||
return -EIO;
|
||||
if (copy_to_user(argp, &device->profile,
|
||||
sizeof (struct dasd_profile_info_t)))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
static int
|
||||
dasd_ioctl_reset_profile(struct dasd_device *device)
|
||||
{
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
static int
|
||||
dasd_ioctl_read_profile(struct dasd_device *device, void __user *argp)
|
||||
{
|
||||
return -ENOSYS;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Return dasd information. Used for BIODASDINFO and BIODASDINFO2.
|
||||
*/
|
||||
static int
|
||||
dasd_ioctl_information(struct dasd_device *device,
|
||||
unsigned int cmd, void __user *argp)
|
||||
{
|
||||
struct dasd_information2_t *dasd_info;
|
||||
unsigned long flags;
|
||||
int rc;
|
||||
struct ccw_device *cdev;
|
||||
|
||||
if (!device->discipline->fill_info)
|
||||
return -EINVAL;
|
||||
|
||||
dasd_info = kzalloc(sizeof(struct dasd_information2_t), GFP_KERNEL);
|
||||
if (dasd_info == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
rc = device->discipline->fill_info(device, dasd_info);
|
||||
if (rc) {
|
||||
kfree(dasd_info);
|
||||
return rc;
|
||||
}
|
||||
|
||||
cdev = device->cdev;
|
||||
|
||||
dasd_info->devno = _ccw_device_get_device_number(device->cdev);
|
||||
dasd_info->schid = _ccw_device_get_subchannel_number(device->cdev);
|
||||
dasd_info->cu_type = cdev->id.cu_type;
|
||||
dasd_info->cu_model = cdev->id.cu_model;
|
||||
dasd_info->dev_type = cdev->id.dev_type;
|
||||
dasd_info->dev_model = cdev->id.dev_model;
|
||||
dasd_info->status = device->state;
|
||||
/*
|
||||
* The open_count is increased for every opener, that includes
|
||||
* the blkdev_get in dasd_scan_partitions.
|
||||
* This must be hidden from user-space.
|
||||
*/
|
||||
dasd_info->open_count = atomic_read(&device->open_count);
|
||||
if (!device->bdev)
|
||||
dasd_info->open_count++;
|
||||
|
||||
/*
|
||||
* 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)))
|
||||
dasd_info->format = DASD_FORMAT_NONE;
|
||||
|
||||
dasd_info->features |=
|
||||
((device->features & DASD_FEATURE_READONLY) != 0);
|
||||
|
||||
if (device->discipline)
|
||||
memcpy(dasd_info->type, device->discipline->name, 4);
|
||||
else
|
||||
memcpy(dasd_info->type, "none", 4);
|
||||
|
||||
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)
|
||||
dasd_info->req_queue_len++;
|
||||
spin_unlock_irqrestore(&device->lock, flags);
|
||||
}
|
||||
#endif /* DASD_EXTENDED_PROFILING */
|
||||
spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
|
||||
list_for_each(l, &device->ccw_queue)
|
||||
dasd_info->chanq_len++;
|
||||
spin_unlock_irqrestore(get_ccwdev_lock(device->cdev),
|
||||
flags);
|
||||
}
|
||||
|
||||
rc = 0;
|
||||
if (copy_to_user(argp, dasd_info,
|
||||
((cmd == (unsigned int) BIODASDINFO2) ?
|
||||
sizeof (struct dasd_information2_t) :
|
||||
sizeof (struct dasd_information_t))))
|
||||
rc = -EFAULT;
|
||||
kfree(dasd_info);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set read only
|
||||
*/
|
||||
static int
|
||||
dasd_ioctl_set_ro(struct block_device *bdev, void __user *argp)
|
||||
{
|
||||
struct dasd_device *device = bdev->bd_disk->private_data;
|
||||
int intval;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EACCES;
|
||||
if (bdev != bdev->bd_contains)
|
||||
// ro setting is not allowed for partitions
|
||||
return -EINVAL;
|
||||
if (get_user(intval, (int __user *)argp))
|
||||
return -EFAULT;
|
||||
|
||||
set_disk_ro(bdev->bd_disk, intval);
|
||||
return dasd_set_feature(device->cdev, DASD_FEATURE_READONLY, intval);
|
||||
}
|
||||
|
||||
static int
|
||||
dasd_ioctl_readall_cmb(struct dasd_device *device, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct cmbdata __user *argp = (void __user *) arg;
|
||||
size_t size = _IOC_SIZE(cmd);
|
||||
struct cmbdata data;
|
||||
int ret;
|
||||
|
||||
ret = cmf_readall(device->cdev, &data);
|
||||
if (!ret && copy_to_user(argp, &data, min(size, sizeof(*argp))))
|
||||
return -EFAULT;
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
dasd_ioctl(struct inode *inode, struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct block_device *bdev = inode->i_bdev;
|
||||
struct dasd_device *device = bdev->bd_disk->private_data;
|
||||
void __user *argp = (void __user *)arg;
|
||||
|
||||
if (!device)
|
||||
return -ENODEV;
|
||||
|
||||
if ((_IOC_DIR(cmd) != _IOC_NONE) && !arg) {
|
||||
PRINT_DEBUG("empty data ptr");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case BIODASDDISABLE:
|
||||
return dasd_ioctl_disable(bdev);
|
||||
case BIODASDENABLE:
|
||||
return dasd_ioctl_enable(bdev);
|
||||
case BIODASDQUIESCE:
|
||||
return dasd_ioctl_quiesce(device);
|
||||
case BIODASDRESUME:
|
||||
return dasd_ioctl_resume(device);
|
||||
case BIODASDFMT:
|
||||
return dasd_ioctl_format(bdev, argp);
|
||||
case BIODASDINFO:
|
||||
return dasd_ioctl_information(device, cmd, argp);
|
||||
case BIODASDINFO2:
|
||||
return dasd_ioctl_information(device, cmd, argp);
|
||||
case BIODASDPRRD:
|
||||
return dasd_ioctl_read_profile(device, argp);
|
||||
case BIODASDPRRST:
|
||||
return dasd_ioctl_reset_profile(device);
|
||||
case BLKROSET:
|
||||
return dasd_ioctl_set_ro(bdev, argp);
|
||||
case DASDAPIVER:
|
||||
return dasd_ioctl_api_version(argp);
|
||||
case BIODASDCMFENABLE:
|
||||
return enable_cmf(device->cdev);
|
||||
case BIODASDCMFDISABLE:
|
||||
return disable_cmf(device->cdev);
|
||||
case BIODASDREADALLCMB:
|
||||
return dasd_ioctl_readall_cmb(device, cmd, arg);
|
||||
default:
|
||||
/* if the discipline has an ioctl method try it. */
|
||||
if (device->discipline->ioctl) {
|
||||
int rval = device->discipline->ioctl(device, cmd, argp);
|
||||
if (rval != -ENOIOCTLCMD)
|
||||
return rval;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
long
|
||||
dasd_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
int rval;
|
||||
|
||||
lock_kernel();
|
||||
rval = dasd_ioctl(filp->f_path.dentry->d_inode, filp, cmd, arg);
|
||||
unlock_kernel();
|
||||
|
||||
return (rval == -EINVAL) ? -ENOIOCTLCMD : rval;
|
||||
}
|
||||
338
drivers/s390/block/dasd_proc.c
Normal file
338
drivers/s390/block/dasd_proc.c
Normal file
@@ -0,0 +1,338 @@
|
||||
/*
|
||||
* File...........: linux/drivers/s390/block/dasd_proc.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-2002
|
||||
*
|
||||
* /proc interface for the dasd driver.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/proc_fs.h>
|
||||
|
||||
#include <asm/debug.h>
|
||||
#include <asm/uaccess.h>
|
||||
|
||||
/* This is ugly... */
|
||||
#define PRINTK_HEADER "dasd_proc:"
|
||||
|
||||
#include "dasd_int.h"
|
||||
|
||||
static struct proc_dir_entry *dasd_proc_root_entry = NULL;
|
||||
static struct proc_dir_entry *dasd_devices_entry = NULL;
|
||||
static struct proc_dir_entry *dasd_statistics_entry = NULL;
|
||||
|
||||
static char *
|
||||
dasd_get_user_string(const char __user *user_buf, size_t user_len)
|
||||
{
|
||||
char *buffer;
|
||||
|
||||
buffer = kmalloc(user_len + 1, GFP_KERNEL);
|
||||
if (buffer == NULL)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
if (copy_from_user(buffer, user_buf, user_len) != 0) {
|
||||
kfree(buffer);
|
||||
return ERR_PTR(-EFAULT);
|
||||
}
|
||||
/* got the string, now strip linefeed. */
|
||||
if (buffer[user_len - 1] == '\n')
|
||||
buffer[user_len - 1] = 0;
|
||||
else
|
||||
buffer[user_len] = 0;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static int
|
||||
dasd_devices_show(struct seq_file *m, void *v)
|
||||
{
|
||||
struct dasd_device *device;
|
||||
char *substr;
|
||||
|
||||
device = dasd_device_from_devindex((unsigned long) v - 1);
|
||||
if (IS_ERR(device))
|
||||
return 0;
|
||||
/* Print device number. */
|
||||
seq_printf(m, "%s", device->cdev->dev.bus_id);
|
||||
/* Print discipline string. */
|
||||
if (device != NULL && device->discipline != NULL)
|
||||
seq_printf(m, "(%s)", device->discipline->name);
|
||||
else
|
||||
seq_printf(m, "(none)");
|
||||
/* Print kdev. */
|
||||
if (device->gdp)
|
||||
seq_printf(m, " at (%3d:%6d)",
|
||||
device->gdp->major, device->gdp->first_minor);
|
||||
else
|
||||
seq_printf(m, " at (???:??????)");
|
||||
/* Print device name. */
|
||||
if (device->gdp)
|
||||
seq_printf(m, " is %-8s", device->gdp->disk_name);
|
||||
else
|
||||
seq_printf(m, " is ????????");
|
||||
/* Print devices features. */
|
||||
substr = (device->features & DASD_FEATURE_READONLY) ? "(ro)" : " ";
|
||||
seq_printf(m, "%4s: ", substr);
|
||||
/* Print device status information. */
|
||||
switch ((device != NULL) ? device->state : -1) {
|
||||
case -1:
|
||||
seq_printf(m, "unknown");
|
||||
break;
|
||||
case DASD_STATE_NEW:
|
||||
seq_printf(m, "new");
|
||||
break;
|
||||
case DASD_STATE_KNOWN:
|
||||
seq_printf(m, "detected");
|
||||
break;
|
||||
case DASD_STATE_BASIC:
|
||||
seq_printf(m, "basic");
|
||||
break;
|
||||
case DASD_STATE_UNFMT:
|
||||
seq_printf(m, "unformatted");
|
||||
break;
|
||||
case DASD_STATE_READY:
|
||||
case DASD_STATE_ONLINE:
|
||||
seq_printf(m, "active ");
|
||||
if (dasd_check_blocksize(device->bp_block))
|
||||
seq_printf(m, "n/f ");
|
||||
else
|
||||
seq_printf(m,
|
||||
"at blocksize: %d, %ld blocks, %ld MB",
|
||||
device->bp_block, device->blocks,
|
||||
((device->bp_block >> 9) *
|
||||
device->blocks) >> 11);
|
||||
break;
|
||||
default:
|
||||
seq_printf(m, "no stat");
|
||||
break;
|
||||
}
|
||||
dasd_put_device(device);
|
||||
if (dasd_probeonly)
|
||||
seq_printf(m, "(probeonly)");
|
||||
seq_printf(m, "\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void *dasd_devices_start(struct seq_file *m, loff_t *pos)
|
||||
{
|
||||
if (*pos >= dasd_max_devindex)
|
||||
return NULL;
|
||||
return (void *)((unsigned long) *pos + 1);
|
||||
}
|
||||
|
||||
static void *dasd_devices_next(struct seq_file *m, void *v, loff_t *pos)
|
||||
{
|
||||
++*pos;
|
||||
return dasd_devices_start(m, pos);
|
||||
}
|
||||
|
||||
static void dasd_devices_stop(struct seq_file *m, void *v)
|
||||
{
|
||||
}
|
||||
|
||||
static struct seq_operations dasd_devices_seq_ops = {
|
||||
.start = dasd_devices_start,
|
||||
.next = dasd_devices_next,
|
||||
.stop = dasd_devices_stop,
|
||||
.show = dasd_devices_show,
|
||||
};
|
||||
|
||||
static int dasd_devices_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return seq_open(file, &dasd_devices_seq_ops);
|
||||
}
|
||||
|
||||
static const struct file_operations dasd_devices_file_ops = {
|
||||
.open = dasd_devices_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = seq_release,
|
||||
};
|
||||
|
||||
static int
|
||||
dasd_calc_metrics(char *page, char **start, off_t off,
|
||||
int count, int *eof, int len)
|
||||
{
|
||||
len = (len > off) ? len - off : 0;
|
||||
if (len > count)
|
||||
len = count;
|
||||
if (len < count)
|
||||
*eof = 1;
|
||||
*start = page + off;
|
||||
return len;
|
||||
}
|
||||
|
||||
static char *
|
||||
dasd_statistics_array(char *str, unsigned int *array, int shift)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 32; i++) {
|
||||
str += sprintf(str, "%7d ", array[i] >> shift);
|
||||
if (i == 15)
|
||||
str += sprintf(str, "\n");
|
||||
}
|
||||
str += sprintf(str,"\n");
|
||||
return str;
|
||||
}
|
||||
|
||||
static int
|
||||
dasd_statistics_read(char *page, char **start, off_t off,
|
||||
int count, int *eof, void *data)
|
||||
{
|
||||
unsigned long len;
|
||||
#ifdef CONFIG_DASD_PROFILE
|
||||
struct dasd_profile_info_t *prof;
|
||||
char *str;
|
||||
int shift;
|
||||
|
||||
/* check for active profiling */
|
||||
if (dasd_profile_level == DASD_PROFILE_OFF) {
|
||||
len = sprintf(page, "Statistics are off - they might be "
|
||||
"switched on using 'echo set on > "
|
||||
"/proc/dasd/statistics'\n");
|
||||
return dasd_calc_metrics(page, start, off, count, eof, len);
|
||||
}
|
||||
|
||||
prof = &dasd_global_profile;
|
||||
/* prevent couter 'overflow' on output */
|
||||
for (shift = 0; (prof->dasd_io_reqs >> shift) > 9999999; shift++);
|
||||
|
||||
str = page;
|
||||
str += sprintf(str, "%d dasd I/O requests\n", prof->dasd_io_reqs);
|
||||
str += sprintf(str, "with %d sectors(512B each)\n",
|
||||
prof->dasd_io_sects);
|
||||
str += sprintf(str,
|
||||
" __<4 ___8 __16 __32 __64 _128 "
|
||||
" _256 _512 __1k __2k __4k __8k "
|
||||
" _16k _32k _64k 128k\n");
|
||||
str += sprintf(str,
|
||||
" _256 _512 __1M __2M __4M __8M "
|
||||
" _16M _32M _64M 128M 256M 512M "
|
||||
" __1G __2G __4G " " _>4G\n");
|
||||
|
||||
str += sprintf(str, "Histogram of sizes (512B secs)\n");
|
||||
str = dasd_statistics_array(str, prof->dasd_io_secs, shift);
|
||||
str += sprintf(str, "Histogram of I/O times (microseconds)\n");
|
||||
str = dasd_statistics_array(str, prof->dasd_io_times, shift);
|
||||
str += sprintf(str, "Histogram of I/O times per sector\n");
|
||||
str = dasd_statistics_array(str, prof->dasd_io_timps, shift);
|
||||
str += sprintf(str, "Histogram of I/O time till ssch\n");
|
||||
str = dasd_statistics_array(str, prof->dasd_io_time1, shift);
|
||||
str += sprintf(str, "Histogram of I/O time between ssch and irq\n");
|
||||
str = dasd_statistics_array(str, prof->dasd_io_time2, shift);
|
||||
str += sprintf(str, "Histogram of I/O time between ssch "
|
||||
"and irq per sector\n");
|
||||
str = dasd_statistics_array(str, prof->dasd_io_time2ps, shift);
|
||||
str += sprintf(str, "Histogram of I/O time between irq and end\n");
|
||||
str = dasd_statistics_array(str, prof->dasd_io_time3, shift);
|
||||
str += sprintf(str, "# of req in chanq at enqueuing (1..32) \n");
|
||||
str = dasd_statistics_array(str, prof->dasd_io_nr_req, shift);
|
||||
len = str - page;
|
||||
#else
|
||||
len = sprintf(page, "Statistics are not activated in this kernel\n");
|
||||
#endif
|
||||
return dasd_calc_metrics(page, start, off, count, eof, len);
|
||||
}
|
||||
|
||||
static int
|
||||
dasd_statistics_write(struct file *file, const char __user *user_buf,
|
||||
unsigned long user_len, void *data)
|
||||
{
|
||||
#ifdef CONFIG_DASD_PROFILE
|
||||
char *buffer, *str;
|
||||
|
||||
if (user_len > 65536)
|
||||
user_len = 65536;
|
||||
buffer = dasd_get_user_string(user_buf, user_len);
|
||||
if (IS_ERR(buffer))
|
||||
return PTR_ERR(buffer);
|
||||
MESSAGE_LOG(KERN_INFO, "/proc/dasd/statictics: '%s'", buffer);
|
||||
|
||||
/* check for valid verbs */
|
||||
for (str = buffer; isspace(*str); str++);
|
||||
if (strncmp(str, "set", 3) == 0 && isspace(str[3])) {
|
||||
/* 'set xxx' was given */
|
||||
for (str = str + 4; isspace(*str); str++);
|
||||
if (strcmp(str, "on") == 0) {
|
||||
/* switch on statistics profiling */
|
||||
dasd_profile_level = DASD_PROFILE_ON;
|
||||
MESSAGE(KERN_INFO, "%s", "Statistics switched on");
|
||||
} else if (strcmp(str, "off") == 0) {
|
||||
/* switch off and reset statistics profiling */
|
||||
memset(&dasd_global_profile,
|
||||
0, sizeof (struct dasd_profile_info_t));
|
||||
dasd_profile_level = DASD_PROFILE_OFF;
|
||||
MESSAGE(KERN_INFO, "%s", "Statistics switched off");
|
||||
} else
|
||||
goto out_error;
|
||||
} else if (strncmp(str, "reset", 5) == 0) {
|
||||
/* reset the statistics */
|
||||
memset(&dasd_global_profile, 0,
|
||||
sizeof (struct dasd_profile_info_t));
|
||||
MESSAGE(KERN_INFO, "%s", "Statistics reset");
|
||||
} else
|
||||
goto out_error;
|
||||
kfree(buffer);
|
||||
return user_len;
|
||||
out_error:
|
||||
MESSAGE(KERN_WARNING, "%s",
|
||||
"/proc/dasd/statistics: only 'set on', 'set off' "
|
||||
"and 'reset' are supported verbs");
|
||||
kfree(buffer);
|
||||
return -EINVAL;
|
||||
#else
|
||||
MESSAGE(KERN_WARNING, "%s",
|
||||
"/proc/dasd/statistics: is not activated in this kernel");
|
||||
return user_len;
|
||||
#endif /* CONFIG_DASD_PROFILE */
|
||||
}
|
||||
|
||||
/*
|
||||
* Create dasd proc-fs entries.
|
||||
* In case creation failed, cleanup and return -ENOENT.
|
||||
*/
|
||||
int
|
||||
dasd_proc_init(void)
|
||||
{
|
||||
dasd_proc_root_entry = proc_mkdir("dasd", &proc_root);
|
||||
if (!dasd_proc_root_entry)
|
||||
goto out_nodasd;
|
||||
dasd_proc_root_entry->owner = THIS_MODULE;
|
||||
dasd_devices_entry = create_proc_entry("devices",
|
||||
S_IFREG | S_IRUGO | S_IWUSR,
|
||||
dasd_proc_root_entry);
|
||||
if (!dasd_devices_entry)
|
||||
goto out_nodevices;
|
||||
dasd_devices_entry->proc_fops = &dasd_devices_file_ops;
|
||||
dasd_devices_entry->owner = THIS_MODULE;
|
||||
dasd_statistics_entry = create_proc_entry("statistics",
|
||||
S_IFREG | S_IRUGO | S_IWUSR,
|
||||
dasd_proc_root_entry);
|
||||
if (!dasd_statistics_entry)
|
||||
goto out_nostatistics;
|
||||
dasd_statistics_entry->read_proc = dasd_statistics_read;
|
||||
dasd_statistics_entry->write_proc = dasd_statistics_write;
|
||||
dasd_statistics_entry->owner = THIS_MODULE;
|
||||
return 0;
|
||||
|
||||
out_nostatistics:
|
||||
remove_proc_entry("devices", dasd_proc_root_entry);
|
||||
out_nodevices:
|
||||
remove_proc_entry("dasd", &proc_root);
|
||||
out_nodasd:
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
void
|
||||
dasd_proc_exit(void)
|
||||
{
|
||||
remove_proc_entry("devices", dasd_proc_root_entry);
|
||||
remove_proc_entry("statistics", dasd_proc_root_entry);
|
||||
remove_proc_entry("dasd", &proc_root);
|
||||
}
|
||||
810
drivers/s390/block/dcssblk.c
Normal file
810
drivers/s390/block/dcssblk.c
Normal file
@@ -0,0 +1,810 @@
|
||||
/*
|
||||
* dcssblk.c -- the S/390 block driver for dcss memory
|
||||
*
|
||||
* Authors: Carsten Otte, Stefan Weinhuber, Gerald Schaefer
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/blkdev.h>
|
||||
#include <asm/extmem.h>
|
||||
#include <asm/io.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <asm/s390_rdev.h>
|
||||
|
||||
//#define DCSSBLK_DEBUG /* Debug messages on/off */
|
||||
#define DCSSBLK_NAME "dcssblk"
|
||||
#define DCSSBLK_MINORS_PER_DISK 1
|
||||
#define DCSSBLK_PARM_LEN 400
|
||||
|
||||
#ifdef DCSSBLK_DEBUG
|
||||
#define PRINT_DEBUG(x...) printk(KERN_DEBUG DCSSBLK_NAME " debug: " x)
|
||||
#else
|
||||
#define PRINT_DEBUG(x...) do {} while (0)
|
||||
#endif
|
||||
#define PRINT_INFO(x...) printk(KERN_INFO DCSSBLK_NAME " info: " x)
|
||||
#define PRINT_WARN(x...) printk(KERN_WARNING DCSSBLK_NAME " warning: " x)
|
||||
#define PRINT_ERR(x...) printk(KERN_ERR DCSSBLK_NAME " error: " x)
|
||||
|
||||
|
||||
static int dcssblk_open(struct inode *inode, struct file *filp);
|
||||
static int dcssblk_release(struct inode *inode, struct file *filp);
|
||||
static int dcssblk_make_request(struct request_queue *q, struct bio *bio);
|
||||
static int dcssblk_direct_access(struct block_device *bdev, sector_t secnum,
|
||||
unsigned long *data);
|
||||
|
||||
static char dcssblk_segments[DCSSBLK_PARM_LEN] = "\0";
|
||||
|
||||
static int dcssblk_major;
|
||||
static struct block_device_operations dcssblk_devops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = dcssblk_open,
|
||||
.release = dcssblk_release,
|
||||
.direct_access = dcssblk_direct_access,
|
||||
};
|
||||
|
||||
static ssize_t dcssblk_add_store(struct device * dev, struct device_attribute *attr, const char * buf,
|
||||
size_t count);
|
||||
static ssize_t dcssblk_remove_store(struct device * dev, struct device_attribute *attr, const char * buf,
|
||||
size_t count);
|
||||
static ssize_t dcssblk_save_store(struct device * dev, struct device_attribute *attr, const char * buf,
|
||||
size_t count);
|
||||
static ssize_t dcssblk_save_show(struct device *dev, struct device_attribute *attr, char *buf);
|
||||
static ssize_t dcssblk_shared_store(struct device * dev, struct device_attribute *attr, const char * buf,
|
||||
size_t count);
|
||||
static ssize_t dcssblk_shared_show(struct device *dev, struct device_attribute *attr, char *buf);
|
||||
|
||||
static DEVICE_ATTR(add, S_IWUSR, NULL, dcssblk_add_store);
|
||||
static DEVICE_ATTR(remove, S_IWUSR, NULL, dcssblk_remove_store);
|
||||
static DEVICE_ATTR(save, S_IWUSR | S_IRUGO, dcssblk_save_show,
|
||||
dcssblk_save_store);
|
||||
static DEVICE_ATTR(shared, S_IWUSR | S_IRUGO, dcssblk_shared_show,
|
||||
dcssblk_shared_store);
|
||||
|
||||
static struct device *dcssblk_root_dev;
|
||||
|
||||
struct dcssblk_dev_info {
|
||||
struct list_head lh;
|
||||
struct device dev;
|
||||
char segment_name[BUS_ID_SIZE];
|
||||
atomic_t use_count;
|
||||
struct gendisk *gd;
|
||||
unsigned long start;
|
||||
unsigned long end;
|
||||
int segment_type;
|
||||
unsigned char save_pending;
|
||||
unsigned char is_shared;
|
||||
struct request_queue *dcssblk_queue;
|
||||
};
|
||||
|
||||
static struct list_head dcssblk_devices = LIST_HEAD_INIT(dcssblk_devices);
|
||||
static struct rw_semaphore dcssblk_devices_sem;
|
||||
|
||||
/*
|
||||
* release function for segment device.
|
||||
*/
|
||||
static void
|
||||
dcssblk_release_segment(struct device *dev)
|
||||
{
|
||||
PRINT_DEBUG("segment release fn called for %s\n", dev->bus_id);
|
||||
kfree(container_of(dev, struct dcssblk_dev_info, dev));
|
||||
module_put(THIS_MODULE);
|
||||
}
|
||||
|
||||
/*
|
||||
* get a minor number. needs to be called with
|
||||
* down_write(&dcssblk_devices_sem) and the
|
||||
* device needs to be enqueued before the semaphore is
|
||||
* freed.
|
||||
*/
|
||||
static int
|
||||
dcssblk_assign_free_minor(struct dcssblk_dev_info *dev_info)
|
||||
{
|
||||
int minor, found;
|
||||
struct dcssblk_dev_info *entry;
|
||||
|
||||
if (dev_info == NULL)
|
||||
return -EINVAL;
|
||||
for (minor = 0; minor < (1<<MINORBITS); minor++) {
|
||||
found = 0;
|
||||
// test if minor available
|
||||
list_for_each_entry(entry, &dcssblk_devices, lh)
|
||||
if (minor == entry->gd->first_minor)
|
||||
found++;
|
||||
if (!found) break; // got unused minor
|
||||
}
|
||||
if (found)
|
||||
return -EBUSY;
|
||||
dev_info->gd->first_minor = minor;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* get the struct dcssblk_dev_info from dcssblk_devices
|
||||
* for the given name.
|
||||
* down_read(&dcssblk_devices_sem) must be held.
|
||||
*/
|
||||
static struct dcssblk_dev_info *
|
||||
dcssblk_get_device_by_name(char *name)
|
||||
{
|
||||
struct dcssblk_dev_info *entry;
|
||||
|
||||
list_for_each_entry(entry, &dcssblk_devices, lh) {
|
||||
if (!strcmp(name, entry->segment_name)) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* print appropriate error message for segment_load()/segment_type()
|
||||
* return code
|
||||
*/
|
||||
static void
|
||||
dcssblk_segment_warn(int rc, char* seg_name)
|
||||
{
|
||||
switch (rc) {
|
||||
case -ENOENT:
|
||||
PRINT_WARN("cannot load/query segment %s, does not exist\n",
|
||||
seg_name);
|
||||
break;
|
||||
case -ENOSYS:
|
||||
PRINT_WARN("cannot load/query segment %s, not running on VM\n",
|
||||
seg_name);
|
||||
break;
|
||||
case -EIO:
|
||||
PRINT_WARN("cannot load/query segment %s, hardware error\n",
|
||||
seg_name);
|
||||
break;
|
||||
case -ENOTSUPP:
|
||||
PRINT_WARN("cannot load/query segment %s, is a multi-part "
|
||||
"segment\n", seg_name);
|
||||
break;
|
||||
case -ENOSPC:
|
||||
PRINT_WARN("cannot load/query segment %s, overlaps with "
|
||||
"storage\n", seg_name);
|
||||
break;
|
||||
case -EBUSY:
|
||||
PRINT_WARN("cannot load/query segment %s, overlaps with "
|
||||
"already loaded dcss\n", seg_name);
|
||||
break;
|
||||
case -EPERM:
|
||||
PRINT_WARN("cannot load/query segment %s, already loaded in "
|
||||
"incompatible mode\n", seg_name);
|
||||
break;
|
||||
case -ENOMEM:
|
||||
PRINT_WARN("cannot load/query segment %s, out of memory\n",
|
||||
seg_name);
|
||||
break;
|
||||
case -ERANGE:
|
||||
PRINT_WARN("cannot load/query segment %s, exceeds kernel "
|
||||
"mapping range\n", seg_name);
|
||||
break;
|
||||
default:
|
||||
PRINT_WARN("cannot load/query segment %s, return value %i\n",
|
||||
seg_name, rc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* device attribute for switching shared/nonshared (exclusive)
|
||||
* operation (show + store)
|
||||
*/
|
||||
static ssize_t
|
||||
dcssblk_shared_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct dcssblk_dev_info *dev_info;
|
||||
|
||||
dev_info = container_of(dev, struct dcssblk_dev_info, dev);
|
||||
return sprintf(buf, dev_info->is_shared ? "1\n" : "0\n");
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
dcssblk_shared_store(struct device *dev, struct device_attribute *attr, const char *inbuf, size_t count)
|
||||
{
|
||||
struct dcssblk_dev_info *dev_info;
|
||||
int rc;
|
||||
|
||||
if ((count > 1) && (inbuf[1] != '\n') && (inbuf[1] != '\0')) {
|
||||
PRINT_WARN("Invalid value, must be 0 or 1\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
down_write(&dcssblk_devices_sem);
|
||||
dev_info = container_of(dev, struct dcssblk_dev_info, dev);
|
||||
if (atomic_read(&dev_info->use_count)) {
|
||||
PRINT_ERR("share: segment %s is busy!\n",
|
||||
dev_info->segment_name);
|
||||
rc = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
if (inbuf[0] == '1') {
|
||||
// reload segment in shared mode
|
||||
rc = segment_modify_shared(dev_info->segment_name,
|
||||
SEGMENT_SHARED);
|
||||
if (rc < 0) {
|
||||
BUG_ON(rc == -EINVAL);
|
||||
if (rc != -EAGAIN)
|
||||
goto removeseg;
|
||||
} else {
|
||||
dev_info->is_shared = 1;
|
||||
switch (dev_info->segment_type) {
|
||||
case SEG_TYPE_SR:
|
||||
case SEG_TYPE_ER:
|
||||
case SEG_TYPE_SC:
|
||||
set_disk_ro(dev_info->gd,1);
|
||||
}
|
||||
}
|
||||
} else if (inbuf[0] == '0') {
|
||||
// reload segment in exclusive mode
|
||||
if (dev_info->segment_type == SEG_TYPE_SC) {
|
||||
PRINT_ERR("Segment type SC (%s) cannot be loaded in "
|
||||
"non-shared mode\n", dev_info->segment_name);
|
||||
rc = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
rc = segment_modify_shared(dev_info->segment_name,
|
||||
SEGMENT_EXCLUSIVE);
|
||||
if (rc < 0) {
|
||||
BUG_ON(rc == -EINVAL);
|
||||
if (rc != -EAGAIN)
|
||||
goto removeseg;
|
||||
} else {
|
||||
dev_info->is_shared = 0;
|
||||
set_disk_ro(dev_info->gd, 0);
|
||||
}
|
||||
} else {
|
||||
PRINT_WARN("Invalid value, must be 0 or 1\n");
|
||||
rc = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
rc = count;
|
||||
goto out;
|
||||
|
||||
removeseg:
|
||||
PRINT_ERR("Could not reload segment %s, removing it now!\n",
|
||||
dev_info->segment_name);
|
||||
list_del(&dev_info->lh);
|
||||
|
||||
del_gendisk(dev_info->gd);
|
||||
blk_cleanup_queue(dev_info->dcssblk_queue);
|
||||
dev_info->gd->queue = NULL;
|
||||
put_disk(dev_info->gd);
|
||||
device_unregister(dev);
|
||||
put_device(dev);
|
||||
out:
|
||||
up_write(&dcssblk_devices_sem);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* device attribute for save operation on current copy
|
||||
* of the segment. If the segment is busy, saving will
|
||||
* become pending until it gets released, which can be
|
||||
* undone by storing a non-true value to this entry.
|
||||
* (show + store)
|
||||
*/
|
||||
static ssize_t
|
||||
dcssblk_save_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct dcssblk_dev_info *dev_info;
|
||||
|
||||
dev_info = container_of(dev, struct dcssblk_dev_info, dev);
|
||||
return sprintf(buf, dev_info->save_pending ? "1\n" : "0\n");
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
dcssblk_save_store(struct device *dev, struct device_attribute *attr, const char *inbuf, size_t count)
|
||||
{
|
||||
struct dcssblk_dev_info *dev_info;
|
||||
|
||||
if ((count > 1) && (inbuf[1] != '\n') && (inbuf[1] != '\0')) {
|
||||
PRINT_WARN("Invalid value, must be 0 or 1\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
dev_info = container_of(dev, struct dcssblk_dev_info, dev);
|
||||
|
||||
down_write(&dcssblk_devices_sem);
|
||||
if (inbuf[0] == '1') {
|
||||
if (atomic_read(&dev_info->use_count) == 0) {
|
||||
// device is idle => we save immediately
|
||||
PRINT_INFO("Saving segment %s\n",
|
||||
dev_info->segment_name);
|
||||
segment_save(dev_info->segment_name);
|
||||
} else {
|
||||
// device is busy => we save it when it becomes
|
||||
// idle in dcssblk_release
|
||||
PRINT_INFO("Segment %s is currently busy, it will "
|
||||
"be saved when it becomes idle...\n",
|
||||
dev_info->segment_name);
|
||||
dev_info->save_pending = 1;
|
||||
}
|
||||
} else if (inbuf[0] == '0') {
|
||||
if (dev_info->save_pending) {
|
||||
// device is busy & the user wants to undo his save
|
||||
// request
|
||||
dev_info->save_pending = 0;
|
||||
PRINT_INFO("Pending save for segment %s deactivated\n",
|
||||
dev_info->segment_name);
|
||||
}
|
||||
} else {
|
||||
up_write(&dcssblk_devices_sem);
|
||||
PRINT_WARN("Invalid value, must be 0 or 1\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
up_write(&dcssblk_devices_sem);
|
||||
return count;
|
||||
}
|
||||
|
||||
/*
|
||||
* device attribute for adding devices
|
||||
*/
|
||||
static ssize_t
|
||||
dcssblk_add_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
int rc, i;
|
||||
struct dcssblk_dev_info *dev_info;
|
||||
char *local_buf;
|
||||
unsigned long seg_byte_size;
|
||||
|
||||
dev_info = NULL;
|
||||
if (dev != dcssblk_root_dev) {
|
||||
rc = -EINVAL;
|
||||
goto out_nobuf;
|
||||
}
|
||||
local_buf = kmalloc(count + 1, GFP_KERNEL);
|
||||
if (local_buf == NULL) {
|
||||
rc = -ENOMEM;
|
||||
goto out_nobuf;
|
||||
}
|
||||
/*
|
||||
* parse input
|
||||
*/
|
||||
for (i = 0; ((buf[i] != '\0') && (buf[i] != '\n') && i < count); i++) {
|
||||
local_buf[i] = toupper(buf[i]);
|
||||
}
|
||||
local_buf[i] = '\0';
|
||||
if ((i == 0) || (i > 8)) {
|
||||
rc = -ENAMETOOLONG;
|
||||
goto out;
|
||||
}
|
||||
/*
|
||||
* already loaded?
|
||||
*/
|
||||
down_read(&dcssblk_devices_sem);
|
||||
dev_info = dcssblk_get_device_by_name(local_buf);
|
||||
up_read(&dcssblk_devices_sem);
|
||||
if (dev_info != NULL) {
|
||||
PRINT_WARN("Segment %s already loaded!\n", local_buf);
|
||||
rc = -EEXIST;
|
||||
goto out;
|
||||
}
|
||||
/*
|
||||
* get a struct dcssblk_dev_info
|
||||
*/
|
||||
dev_info = kzalloc(sizeof(struct dcssblk_dev_info), GFP_KERNEL);
|
||||
if (dev_info == NULL) {
|
||||
rc = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
strcpy(dev_info->segment_name, local_buf);
|
||||
strlcpy(dev_info->dev.bus_id, local_buf, BUS_ID_SIZE);
|
||||
dev_info->dev.release = dcssblk_release_segment;
|
||||
INIT_LIST_HEAD(&dev_info->lh);
|
||||
|
||||
dev_info->gd = alloc_disk(DCSSBLK_MINORS_PER_DISK);
|
||||
if (dev_info->gd == NULL) {
|
||||
rc = -ENOMEM;
|
||||
goto free_dev_info;
|
||||
}
|
||||
dev_info->gd->major = dcssblk_major;
|
||||
dev_info->gd->fops = &dcssblk_devops;
|
||||
dev_info->dcssblk_queue = blk_alloc_queue(GFP_KERNEL);
|
||||
dev_info->gd->queue = dev_info->dcssblk_queue;
|
||||
dev_info->gd->private_data = dev_info;
|
||||
dev_info->gd->driverfs_dev = &dev_info->dev;
|
||||
/*
|
||||
* load the segment
|
||||
*/
|
||||
rc = segment_load(local_buf, SEGMENT_SHARED,
|
||||
&dev_info->start, &dev_info->end);
|
||||
if (rc < 0) {
|
||||
dcssblk_segment_warn(rc, dev_info->segment_name);
|
||||
goto dealloc_gendisk;
|
||||
}
|
||||
seg_byte_size = (dev_info->end - dev_info->start + 1);
|
||||
set_capacity(dev_info->gd, seg_byte_size >> 9); // size in sectors
|
||||
PRINT_INFO("Loaded segment %s, size = %lu Byte, "
|
||||
"capacity = %lu (512 Byte) sectors\n", local_buf,
|
||||
seg_byte_size, seg_byte_size >> 9);
|
||||
|
||||
dev_info->segment_type = rc;
|
||||
dev_info->save_pending = 0;
|
||||
dev_info->is_shared = 1;
|
||||
dev_info->dev.parent = dcssblk_root_dev;
|
||||
|
||||
/*
|
||||
* get minor, add to list
|
||||
*/
|
||||
down_write(&dcssblk_devices_sem);
|
||||
rc = dcssblk_assign_free_minor(dev_info);
|
||||
if (rc) {
|
||||
up_write(&dcssblk_devices_sem);
|
||||
PRINT_ERR("No free minor number available! "
|
||||
"Unloading segment...\n");
|
||||
goto unload_seg;
|
||||
}
|
||||
sprintf(dev_info->gd->disk_name, "dcssblk%d",
|
||||
dev_info->gd->first_minor);
|
||||
list_add_tail(&dev_info->lh, &dcssblk_devices);
|
||||
|
||||
if (!try_module_get(THIS_MODULE)) {
|
||||
rc = -ENODEV;
|
||||
goto list_del;
|
||||
}
|
||||
/*
|
||||
* register the device
|
||||
*/
|
||||
rc = device_register(&dev_info->dev);
|
||||
if (rc) {
|
||||
PRINT_ERR("Segment %s could not be registered RC=%d\n",
|
||||
local_buf, rc);
|
||||
module_put(THIS_MODULE);
|
||||
goto list_del;
|
||||
}
|
||||
get_device(&dev_info->dev);
|
||||
rc = device_create_file(&dev_info->dev, &dev_attr_shared);
|
||||
if (rc)
|
||||
goto unregister_dev;
|
||||
rc = device_create_file(&dev_info->dev, &dev_attr_save);
|
||||
if (rc)
|
||||
goto unregister_dev;
|
||||
|
||||
add_disk(dev_info->gd);
|
||||
|
||||
blk_queue_make_request(dev_info->dcssblk_queue, dcssblk_make_request);
|
||||
blk_queue_hardsect_size(dev_info->dcssblk_queue, 4096);
|
||||
|
||||
switch (dev_info->segment_type) {
|
||||
case SEG_TYPE_SR:
|
||||
case SEG_TYPE_ER:
|
||||
case SEG_TYPE_SC:
|
||||
set_disk_ro(dev_info->gd,1);
|
||||
break;
|
||||
default:
|
||||
set_disk_ro(dev_info->gd,0);
|
||||
break;
|
||||
}
|
||||
PRINT_DEBUG("Segment %s loaded successfully\n", local_buf);
|
||||
up_write(&dcssblk_devices_sem);
|
||||
rc = count;
|
||||
goto out;
|
||||
|
||||
unregister_dev:
|
||||
PRINT_ERR("device_create_file() failed!\n");
|
||||
list_del(&dev_info->lh);
|
||||
blk_cleanup_queue(dev_info->dcssblk_queue);
|
||||
dev_info->gd->queue = NULL;
|
||||
put_disk(dev_info->gd);
|
||||
device_unregister(&dev_info->dev);
|
||||
segment_unload(dev_info->segment_name);
|
||||
put_device(&dev_info->dev);
|
||||
up_write(&dcssblk_devices_sem);
|
||||
goto out;
|
||||
list_del:
|
||||
list_del(&dev_info->lh);
|
||||
up_write(&dcssblk_devices_sem);
|
||||
unload_seg:
|
||||
segment_unload(local_buf);
|
||||
dealloc_gendisk:
|
||||
blk_cleanup_queue(dev_info->dcssblk_queue);
|
||||
dev_info->gd->queue = NULL;
|
||||
put_disk(dev_info->gd);
|
||||
free_dev_info:
|
||||
kfree(dev_info);
|
||||
out:
|
||||
kfree(local_buf);
|
||||
out_nobuf:
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* device attribute for removing devices
|
||||
*/
|
||||
static ssize_t
|
||||
dcssblk_remove_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
struct dcssblk_dev_info *dev_info;
|
||||
int rc, i;
|
||||
char *local_buf;
|
||||
|
||||
if (dev != dcssblk_root_dev) {
|
||||
return -EINVAL;
|
||||
}
|
||||
local_buf = kmalloc(count + 1, GFP_KERNEL);
|
||||
if (local_buf == NULL) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
/*
|
||||
* parse input
|
||||
*/
|
||||
for (i = 0; ((*(buf+i)!='\0') && (*(buf+i)!='\n') && i < count); i++) {
|
||||
local_buf[i] = toupper(buf[i]);
|
||||
}
|
||||
local_buf[i] = '\0';
|
||||
if ((i == 0) || (i > 8)) {
|
||||
rc = -ENAMETOOLONG;
|
||||
goto out_buf;
|
||||
}
|
||||
|
||||
down_write(&dcssblk_devices_sem);
|
||||
dev_info = dcssblk_get_device_by_name(local_buf);
|
||||
if (dev_info == NULL) {
|
||||
up_write(&dcssblk_devices_sem);
|
||||
PRINT_WARN("Segment %s is not loaded!\n", local_buf);
|
||||
rc = -ENODEV;
|
||||
goto out_buf;
|
||||
}
|
||||
if (atomic_read(&dev_info->use_count) != 0) {
|
||||
up_write(&dcssblk_devices_sem);
|
||||
PRINT_WARN("Segment %s is in use!\n", local_buf);
|
||||
rc = -EBUSY;
|
||||
goto out_buf;
|
||||
}
|
||||
list_del(&dev_info->lh);
|
||||
|
||||
del_gendisk(dev_info->gd);
|
||||
blk_cleanup_queue(dev_info->dcssblk_queue);
|
||||
dev_info->gd->queue = NULL;
|
||||
put_disk(dev_info->gd);
|
||||
device_unregister(&dev_info->dev);
|
||||
segment_unload(dev_info->segment_name);
|
||||
PRINT_DEBUG("Segment %s unloaded successfully\n",
|
||||
dev_info->segment_name);
|
||||
put_device(&dev_info->dev);
|
||||
up_write(&dcssblk_devices_sem);
|
||||
|
||||
rc = count;
|
||||
out_buf:
|
||||
kfree(local_buf);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int
|
||||
dcssblk_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct dcssblk_dev_info *dev_info;
|
||||
int rc;
|
||||
|
||||
dev_info = inode->i_bdev->bd_disk->private_data;
|
||||
if (NULL == dev_info) {
|
||||
rc = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
atomic_inc(&dev_info->use_count);
|
||||
inode->i_bdev->bd_block_size = 4096;
|
||||
rc = 0;
|
||||
out:
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int
|
||||
dcssblk_release(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct dcssblk_dev_info *dev_info;
|
||||
int rc;
|
||||
|
||||
dev_info = inode->i_bdev->bd_disk->private_data;
|
||||
if (NULL == dev_info) {
|
||||
rc = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
down_write(&dcssblk_devices_sem);
|
||||
if (atomic_dec_and_test(&dev_info->use_count)
|
||||
&& (dev_info->save_pending)) {
|
||||
PRINT_INFO("Segment %s became idle and is being saved now\n",
|
||||
dev_info->segment_name);
|
||||
segment_save(dev_info->segment_name);
|
||||
dev_info->save_pending = 0;
|
||||
}
|
||||
up_write(&dcssblk_devices_sem);
|
||||
rc = 0;
|
||||
out:
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int
|
||||
dcssblk_make_request(request_queue_t *q, struct bio *bio)
|
||||
{
|
||||
struct dcssblk_dev_info *dev_info;
|
||||
struct bio_vec *bvec;
|
||||
unsigned long index;
|
||||
unsigned long page_addr;
|
||||
unsigned long source_addr;
|
||||
unsigned long bytes_done;
|
||||
int i;
|
||||
|
||||
bytes_done = 0;
|
||||
dev_info = bio->bi_bdev->bd_disk->private_data;
|
||||
if (dev_info == NULL)
|
||||
goto fail;
|
||||
if ((bio->bi_sector & 7) != 0 || (bio->bi_size & 4095) != 0)
|
||||
/* Request is not page-aligned. */
|
||||
goto fail;
|
||||
if (((bio->bi_size >> 9) + bio->bi_sector)
|
||||
> get_capacity(bio->bi_bdev->bd_disk)) {
|
||||
/* Request beyond end of DCSS segment. */
|
||||
goto fail;
|
||||
}
|
||||
/* verify data transfer direction */
|
||||
if (dev_info->is_shared) {
|
||||
switch (dev_info->segment_type) {
|
||||
case SEG_TYPE_SR:
|
||||
case SEG_TYPE_ER:
|
||||
case SEG_TYPE_SC:
|
||||
/* cannot write to these segments */
|
||||
if (bio_data_dir(bio) == WRITE) {
|
||||
PRINT_WARN("rejecting write to ro segment %s\n", dev_info->dev.bus_id);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
index = (bio->bi_sector >> 3);
|
||||
bio_for_each_segment(bvec, bio, i) {
|
||||
page_addr = (unsigned long)
|
||||
page_address(bvec->bv_page) + bvec->bv_offset;
|
||||
source_addr = dev_info->start + (index<<12) + bytes_done;
|
||||
if (unlikely(page_addr & 4095) != 0 || (bvec->bv_len & 4095) != 0)
|
||||
// More paranoia.
|
||||
goto fail;
|
||||
if (bio_data_dir(bio) == READ) {
|
||||
memcpy((void*)page_addr, (void*)source_addr,
|
||||
bvec->bv_len);
|
||||
} else {
|
||||
memcpy((void*)source_addr, (void*)page_addr,
|
||||
bvec->bv_len);
|
||||
}
|
||||
bytes_done += bvec->bv_len;
|
||||
}
|
||||
bio_endio(bio, bytes_done, 0);
|
||||
return 0;
|
||||
fail:
|
||||
bio_io_error(bio, bio->bi_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
dcssblk_direct_access (struct block_device *bdev, sector_t secnum,
|
||||
unsigned long *data)
|
||||
{
|
||||
struct dcssblk_dev_info *dev_info;
|
||||
unsigned long pgoff;
|
||||
|
||||
dev_info = bdev->bd_disk->private_data;
|
||||
if (!dev_info)
|
||||
return -ENODEV;
|
||||
if (secnum % (PAGE_SIZE/512))
|
||||
return -EINVAL;
|
||||
pgoff = secnum / (PAGE_SIZE / 512);
|
||||
if ((pgoff+1)*PAGE_SIZE-1 > dev_info->end - dev_info->start)
|
||||
return -ERANGE;
|
||||
*data = (unsigned long) (dev_info->start+pgoff*PAGE_SIZE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
dcssblk_check_params(void)
|
||||
{
|
||||
int rc, i, j, k;
|
||||
char buf[9];
|
||||
struct dcssblk_dev_info *dev_info;
|
||||
|
||||
for (i = 0; (i < DCSSBLK_PARM_LEN) && (dcssblk_segments[i] != '\0');
|
||||
i++) {
|
||||
for (j = i; (dcssblk_segments[j] != ',') &&
|
||||
(dcssblk_segments[j] != '\0') &&
|
||||
(dcssblk_segments[j] != '(') &&
|
||||
(j - i) < 8; j++)
|
||||
{
|
||||
buf[j-i] = dcssblk_segments[j];
|
||||
}
|
||||
buf[j-i] = '\0';
|
||||
rc = dcssblk_add_store(dcssblk_root_dev, NULL, buf, j-i);
|
||||
if ((rc >= 0) && (dcssblk_segments[j] == '(')) {
|
||||
for (k = 0; buf[k] != '\0'; k++)
|
||||
buf[k] = toupper(buf[k]);
|
||||
if (!strncmp(&dcssblk_segments[j], "(local)", 7)) {
|
||||
down_read(&dcssblk_devices_sem);
|
||||
dev_info = dcssblk_get_device_by_name(buf);
|
||||
up_read(&dcssblk_devices_sem);
|
||||
if (dev_info)
|
||||
dcssblk_shared_store(&dev_info->dev,
|
||||
NULL, "0\n", 2);
|
||||
}
|
||||
}
|
||||
while ((dcssblk_segments[j] != ',') &&
|
||||
(dcssblk_segments[j] != '\0'))
|
||||
{
|
||||
j++;
|
||||
}
|
||||
if (dcssblk_segments[j] == '\0')
|
||||
break;
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The init/exit functions.
|
||||
*/
|
||||
static void __exit
|
||||
dcssblk_exit(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
PRINT_DEBUG("DCSSBLOCK EXIT...\n");
|
||||
s390_root_dev_unregister(dcssblk_root_dev);
|
||||
rc = unregister_blkdev(dcssblk_major, DCSSBLK_NAME);
|
||||
if (rc) {
|
||||
PRINT_ERR("unregister_blkdev() failed!\n");
|
||||
}
|
||||
PRINT_DEBUG("...finished!\n");
|
||||
}
|
||||
|
||||
static int __init
|
||||
dcssblk_init(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
PRINT_DEBUG("DCSSBLOCK INIT...\n");
|
||||
dcssblk_root_dev = s390_root_dev_register("dcssblk");
|
||||
if (IS_ERR(dcssblk_root_dev)) {
|
||||
PRINT_ERR("device_register() failed!\n");
|
||||
return PTR_ERR(dcssblk_root_dev);
|
||||
}
|
||||
rc = device_create_file(dcssblk_root_dev, &dev_attr_add);
|
||||
if (rc) {
|
||||
PRINT_ERR("device_create_file(add) failed!\n");
|
||||
s390_root_dev_unregister(dcssblk_root_dev);
|
||||
return rc;
|
||||
}
|
||||
rc = device_create_file(dcssblk_root_dev, &dev_attr_remove);
|
||||
if (rc) {
|
||||
PRINT_ERR("device_create_file(remove) failed!\n");
|
||||
s390_root_dev_unregister(dcssblk_root_dev);
|
||||
return rc;
|
||||
}
|
||||
rc = register_blkdev(0, DCSSBLK_NAME);
|
||||
if (rc < 0) {
|
||||
PRINT_ERR("Can't get dynamic major!\n");
|
||||
s390_root_dev_unregister(dcssblk_root_dev);
|
||||
return rc;
|
||||
}
|
||||
dcssblk_major = rc;
|
||||
init_rwsem(&dcssblk_devices_sem);
|
||||
|
||||
dcssblk_check_params();
|
||||
|
||||
PRINT_DEBUG("...finished!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
module_init(dcssblk_init);
|
||||
module_exit(dcssblk_exit);
|
||||
|
||||
module_param_string(segments, dcssblk_segments, DCSSBLK_PARM_LEN, 0444);
|
||||
MODULE_PARM_DESC(segments, "Name of DCSS segment(s) to be loaded, "
|
||||
"comma-separated list, each name max. 8 chars.\n"
|
||||
"Adding \"(local)\" to segment name equals echoing 0 to "
|
||||
"/sys/devices/dcssblk/<segment name>/shared after loading "
|
||||
"the segment - \n"
|
||||
"e.g. segments=\"mydcss1,mydcss2,mydcss3(local)\"");
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
440
drivers/s390/block/xpram.c
Normal file
440
drivers/s390/block/xpram.c
Normal file
@@ -0,0 +1,440 @@
|
||||
/*
|
||||
* Xpram.c -- the S/390 expanded memory RAM-disk
|
||||
*
|
||||
* significant parts of this code are based on
|
||||
* the sbull device driver presented in
|
||||
* A. Rubini: Linux Device Drivers
|
||||
*
|
||||
* Author of XPRAM specific coding: Reinhard Buendgen
|
||||
* buendgen@de.ibm.com
|
||||
* Rewrite for 2.5: Martin Schwidefsky <schwidefsky@de.ibm.com>
|
||||
*
|
||||
* External interfaces:
|
||||
* Interfaces to linux kernel
|
||||
* xpram_setup: read kernel parameters
|
||||
* Device specific file operations
|
||||
* xpram_iotcl
|
||||
* xpram_open
|
||||
*
|
||||
* "ad-hoc" partitioning:
|
||||
* the expanded memory can be partitioned among several devices
|
||||
* (with different minors). The partitioning set up can be
|
||||
* set by kernel or module parameters (int devs & int sizes[])
|
||||
*
|
||||
* Potential future improvements:
|
||||
* generic hard disk support to replace ad-hoc partitioning
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/ctype.h> /* isdigit, isxdigit */
|
||||
#include <linux/errno.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/blkdev.h>
|
||||
#include <linux/blkpg.h>
|
||||
#include <linux/hdreg.h> /* HDIO_GETGEO */
|
||||
#include <linux/sysdev.h>
|
||||
#include <linux/bio.h>
|
||||
#include <asm/uaccess.h>
|
||||
|
||||
#define XPRAM_NAME "xpram"
|
||||
#define XPRAM_DEVS 1 /* one partition */
|
||||
#define XPRAM_MAX_DEVS 32 /* maximal number of devices (partitions) */
|
||||
|
||||
#define PRINT_DEBUG(x...) printk(KERN_DEBUG XPRAM_NAME " debug:" x)
|
||||
#define PRINT_INFO(x...) printk(KERN_INFO XPRAM_NAME " info:" x)
|
||||
#define PRINT_WARN(x...) printk(KERN_WARNING XPRAM_NAME " warning:" x)
|
||||
#define PRINT_ERR(x...) printk(KERN_ERR XPRAM_NAME " error:" x)
|
||||
|
||||
|
||||
typedef struct {
|
||||
unsigned int size; /* size of xpram segment in pages */
|
||||
unsigned int offset; /* start page of xpram segment */
|
||||
} xpram_device_t;
|
||||
|
||||
static xpram_device_t xpram_devices[XPRAM_MAX_DEVS];
|
||||
static unsigned int xpram_sizes[XPRAM_MAX_DEVS];
|
||||
static struct gendisk *xpram_disks[XPRAM_MAX_DEVS];
|
||||
static unsigned int xpram_pages;
|
||||
static int xpram_devs;
|
||||
|
||||
/*
|
||||
* Parameter parsing functions.
|
||||
*/
|
||||
static int __initdata devs = XPRAM_DEVS;
|
||||
static char __initdata *sizes[XPRAM_MAX_DEVS];
|
||||
|
||||
module_param(devs, int, 0);
|
||||
module_param_array(sizes, charp, NULL, 0);
|
||||
|
||||
MODULE_PARM_DESC(devs, "number of devices (\"partitions\"), " \
|
||||
"the default is " __MODULE_STRING(XPRAM_DEVS) "\n");
|
||||
MODULE_PARM_DESC(sizes, "list of device (partition) sizes " \
|
||||
"the defaults are 0s \n" \
|
||||
"All devices with size 0 equally partition the "
|
||||
"remaining space on the expanded strorage not "
|
||||
"claimed by explicit sizes\n");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
/*
|
||||
* Copy expanded memory page (4kB) into main memory
|
||||
* Arguments
|
||||
* page_addr: address of target page
|
||||
* xpage_index: index of expandeded memory page
|
||||
* Return value
|
||||
* 0: if operation succeeds
|
||||
* -EIO: if pgin failed
|
||||
* -ENXIO: if xpram has vanished
|
||||
*/
|
||||
static int xpram_page_in (unsigned long page_addr, unsigned int xpage_index)
|
||||
{
|
||||
int cc = 2; /* return unused cc 2 if pgin traps */
|
||||
|
||||
asm volatile(
|
||||
" .insn rre,0xb22e0000,%1,%2\n" /* pgin %1,%2 */
|
||||
"0: ipm %0\n"
|
||||
" srl %0,28\n"
|
||||
"1:\n"
|
||||
EX_TABLE(0b,1b)
|
||||
: "+d" (cc) : "a" (__pa(page_addr)), "d" (xpage_index) : "cc");
|
||||
if (cc == 3)
|
||||
return -ENXIO;
|
||||
if (cc == 2) {
|
||||
PRINT_ERR("expanded storage lost!\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
if (cc == 1) {
|
||||
PRINT_ERR("page in failed for page index %u.\n",
|
||||
xpage_index);
|
||||
return -EIO;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Copy a 4kB page of main memory to an expanded memory page
|
||||
* Arguments
|
||||
* page_addr: address of source page
|
||||
* xpage_index: index of expandeded memory page
|
||||
* Return value
|
||||
* 0: if operation succeeds
|
||||
* -EIO: if pgout failed
|
||||
* -ENXIO: if xpram has vanished
|
||||
*/
|
||||
static long xpram_page_out (unsigned long page_addr, unsigned int xpage_index)
|
||||
{
|
||||
int cc = 2; /* return unused cc 2 if pgin traps */
|
||||
|
||||
asm volatile(
|
||||
" .insn rre,0xb22f0000,%1,%2\n" /* pgout %1,%2 */
|
||||
"0: ipm %0\n"
|
||||
" srl %0,28\n"
|
||||
"1:\n"
|
||||
EX_TABLE(0b,1b)
|
||||
: "+d" (cc) : "a" (__pa(page_addr)), "d" (xpage_index) : "cc");
|
||||
if (cc == 3)
|
||||
return -ENXIO;
|
||||
if (cc == 2) {
|
||||
PRINT_ERR("expanded storage lost!\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
if (cc == 1) {
|
||||
PRINT_ERR("page out failed for page index %u.\n",
|
||||
xpage_index);
|
||||
return -EIO;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if xpram is available.
|
||||
*/
|
||||
static int __init xpram_present(void)
|
||||
{
|
||||
unsigned long mem_page;
|
||||
int rc;
|
||||
|
||||
mem_page = (unsigned long) __get_free_page(GFP_KERNEL);
|
||||
if (!mem_page)
|
||||
return -ENOMEM;
|
||||
rc = xpram_page_in(mem_page, 0);
|
||||
free_page(mem_page);
|
||||
return rc ? -ENXIO : 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return index of the last available xpram page.
|
||||
*/
|
||||
static unsigned long __init xpram_highest_page_index(void)
|
||||
{
|
||||
unsigned int page_index, add_bit;
|
||||
unsigned long mem_page;
|
||||
|
||||
mem_page = (unsigned long) __get_free_page(GFP_KERNEL);
|
||||
if (!mem_page)
|
||||
return 0;
|
||||
|
||||
page_index = 0;
|
||||
add_bit = 1ULL << (sizeof(unsigned int)*8 - 1);
|
||||
while (add_bit > 0) {
|
||||
if (xpram_page_in(mem_page, page_index | add_bit) == 0)
|
||||
page_index |= add_bit;
|
||||
add_bit >>= 1;
|
||||
}
|
||||
|
||||
free_page (mem_page);
|
||||
|
||||
return page_index;
|
||||
}
|
||||
|
||||
/*
|
||||
* Block device make request function.
|
||||
*/
|
||||
static int xpram_make_request(request_queue_t *q, struct bio *bio)
|
||||
{
|
||||
xpram_device_t *xdev = bio->bi_bdev->bd_disk->private_data;
|
||||
struct bio_vec *bvec;
|
||||
unsigned int index;
|
||||
unsigned long page_addr;
|
||||
unsigned long bytes;
|
||||
int i;
|
||||
|
||||
if ((bio->bi_sector & 7) != 0 || (bio->bi_size & 4095) != 0)
|
||||
/* Request is not page-aligned. */
|
||||
goto fail;
|
||||
if ((bio->bi_size >> 12) > xdev->size)
|
||||
/* Request size is no page-aligned. */
|
||||
goto fail;
|
||||
if ((bio->bi_sector >> 3) > 0xffffffffU - xdev->offset)
|
||||
goto fail;
|
||||
index = (bio->bi_sector >> 3) + xdev->offset;
|
||||
bio_for_each_segment(bvec, bio, i) {
|
||||
page_addr = (unsigned long)
|
||||
kmap(bvec->bv_page) + bvec->bv_offset;
|
||||
bytes = bvec->bv_len;
|
||||
if ((page_addr & 4095) != 0 || (bytes & 4095) != 0)
|
||||
/* More paranoia. */
|
||||
goto fail;
|
||||
while (bytes > 0) {
|
||||
if (bio_data_dir(bio) == READ) {
|
||||
if (xpram_page_in(page_addr, index) != 0)
|
||||
goto fail;
|
||||
} else {
|
||||
if (xpram_page_out(page_addr, index) != 0)
|
||||
goto fail;
|
||||
}
|
||||
page_addr += 4096;
|
||||
bytes -= 4096;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
set_bit(BIO_UPTODATE, &bio->bi_flags);
|
||||
bytes = bio->bi_size;
|
||||
bio->bi_size = 0;
|
||||
bio->bi_end_io(bio, bytes, 0);
|
||||
return 0;
|
||||
fail:
|
||||
bio_io_error(bio, bio->bi_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int xpram_getgeo(struct block_device *bdev, struct hd_geometry *geo)
|
||||
{
|
||||
unsigned long size;
|
||||
|
||||
/*
|
||||
* get geometry: we have to fake one... trim the size to a
|
||||
* multiple of 64 (32k): tell we have 16 sectors, 4 heads,
|
||||
* whatever cylinders. Tell also that data starts at sector. 4.
|
||||
*/
|
||||
size = (xpram_pages * 8) & ~0x3f;
|
||||
geo->cylinders = size >> 6;
|
||||
geo->heads = 4;
|
||||
geo->sectors = 16;
|
||||
geo->start = 4;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct block_device_operations xpram_devops =
|
||||
{
|
||||
.owner = THIS_MODULE,
|
||||
.getgeo = xpram_getgeo,
|
||||
};
|
||||
|
||||
/*
|
||||
* Setup xpram_sizes array.
|
||||
*/
|
||||
static int __init xpram_setup_sizes(unsigned long pages)
|
||||
{
|
||||
unsigned long mem_needed;
|
||||
unsigned long mem_auto;
|
||||
unsigned long long size;
|
||||
int mem_auto_no;
|
||||
int i;
|
||||
|
||||
/* Check number of devices. */
|
||||
if (devs <= 0 || devs > XPRAM_MAX_DEVS) {
|
||||
PRINT_ERR("invalid number %d of devices\n",devs);
|
||||
return -EINVAL;
|
||||
}
|
||||
xpram_devs = devs;
|
||||
|
||||
/*
|
||||
* Copy sizes array to xpram_sizes and align partition
|
||||
* sizes to page boundary.
|
||||
*/
|
||||
mem_needed = 0;
|
||||
mem_auto_no = 0;
|
||||
for (i = 0; i < xpram_devs; i++) {
|
||||
if (sizes[i]) {
|
||||
size = simple_strtoull(sizes[i], &sizes[i], 0);
|
||||
switch (sizes[i][0]) {
|
||||
case 'g':
|
||||
case 'G':
|
||||
size <<= 20;
|
||||
break;
|
||||
case 'm':
|
||||
case 'M':
|
||||
size <<= 10;
|
||||
}
|
||||
xpram_sizes[i] = (size + 3) & -4UL;
|
||||
}
|
||||
if (xpram_sizes[i])
|
||||
mem_needed += xpram_sizes[i];
|
||||
else
|
||||
mem_auto_no++;
|
||||
}
|
||||
|
||||
PRINT_INFO(" number of devices (partitions): %d \n", xpram_devs);
|
||||
for (i = 0; i < xpram_devs; i++) {
|
||||
if (xpram_sizes[i])
|
||||
PRINT_INFO(" size of partition %d: %u kB\n",
|
||||
i, xpram_sizes[i]);
|
||||
else
|
||||
PRINT_INFO(" size of partition %d to be set "
|
||||
"automatically\n",i);
|
||||
}
|
||||
PRINT_DEBUG(" memory needed (for sized partitions): %lu kB\n",
|
||||
mem_needed);
|
||||
PRINT_DEBUG(" partitions to be sized automatically: %d\n",
|
||||
mem_auto_no);
|
||||
|
||||
if (mem_needed > pages * 4) {
|
||||
PRINT_ERR("Not enough expanded memory available\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* partitioning:
|
||||
* xpram_sizes[i] != 0; partition i has size xpram_sizes[i] kB
|
||||
* else: ; all partitions with zero xpram_sizes[i]
|
||||
* partition equally the remaining space
|
||||
*/
|
||||
if (mem_auto_no) {
|
||||
mem_auto = ((pages - mem_needed / 4) / mem_auto_no) * 4;
|
||||
PRINT_INFO(" automatically determined "
|
||||
"partition size: %lu kB\n", mem_auto);
|
||||
for (i = 0; i < xpram_devs; i++)
|
||||
if (xpram_sizes[i] == 0)
|
||||
xpram_sizes[i] = mem_auto;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct request_queue *xpram_queue;
|
||||
|
||||
static int __init xpram_setup_blkdev(void)
|
||||
{
|
||||
unsigned long offset;
|
||||
int i, rc = -ENOMEM;
|
||||
|
||||
for (i = 0; i < xpram_devs; i++) {
|
||||
struct gendisk *disk = alloc_disk(1);
|
||||
if (!disk)
|
||||
goto out;
|
||||
xpram_disks[i] = disk;
|
||||
}
|
||||
|
||||
/*
|
||||
* Register xpram major.
|
||||
*/
|
||||
rc = register_blkdev(XPRAM_MAJOR, XPRAM_NAME);
|
||||
if (rc < 0)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* Assign the other needed values: make request function, sizes and
|
||||
* hardsect size. All the minor devices feature the same value.
|
||||
*/
|
||||
xpram_queue = blk_alloc_queue(GFP_KERNEL);
|
||||
if (!xpram_queue) {
|
||||
rc = -ENOMEM;
|
||||
goto out_unreg;
|
||||
}
|
||||
blk_queue_make_request(xpram_queue, xpram_make_request);
|
||||
blk_queue_hardsect_size(xpram_queue, 4096);
|
||||
|
||||
/*
|
||||
* Setup device structures.
|
||||
*/
|
||||
offset = 0;
|
||||
for (i = 0; i < xpram_devs; i++) {
|
||||
struct gendisk *disk = xpram_disks[i];
|
||||
|
||||
xpram_devices[i].size = xpram_sizes[i] / 4;
|
||||
xpram_devices[i].offset = offset;
|
||||
offset += xpram_devices[i].size;
|
||||
disk->major = XPRAM_MAJOR;
|
||||
disk->first_minor = i;
|
||||
disk->fops = &xpram_devops;
|
||||
disk->private_data = &xpram_devices[i];
|
||||
disk->queue = xpram_queue;
|
||||
sprintf(disk->disk_name, "slram%d", i);
|
||||
set_capacity(disk, xpram_sizes[i] << 1);
|
||||
add_disk(disk);
|
||||
}
|
||||
|
||||
return 0;
|
||||
out_unreg:
|
||||
unregister_blkdev(XPRAM_MAJOR, XPRAM_NAME);
|
||||
out:
|
||||
while (i--)
|
||||
put_disk(xpram_disks[i]);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Finally, the init/exit functions.
|
||||
*/
|
||||
static void __exit xpram_exit(void)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < xpram_devs; i++) {
|
||||
del_gendisk(xpram_disks[i]);
|
||||
put_disk(xpram_disks[i]);
|
||||
}
|
||||
unregister_blkdev(XPRAM_MAJOR, XPRAM_NAME);
|
||||
blk_cleanup_queue(xpram_queue);
|
||||
}
|
||||
|
||||
static int __init xpram_init(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
/* Find out size of expanded memory. */
|
||||
if (xpram_present() != 0) {
|
||||
PRINT_WARN("No expanded memory available\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
xpram_pages = xpram_highest_page_index() + 1;
|
||||
PRINT_INFO(" %u pages expanded memory found (%lu KB).\n",
|
||||
xpram_pages, (unsigned long) xpram_pages*4);
|
||||
rc = xpram_setup_sizes(xpram_pages);
|
||||
if (rc)
|
||||
return rc;
|
||||
return xpram_setup_blkdev();
|
||||
}
|
||||
|
||||
module_init(xpram_init);
|
||||
module_exit(xpram_exit);
|
||||
Reference in New Issue
Block a user