Skip to content

Commit

Permalink
[S390] pm: dasd power management callbacks.
Browse files Browse the repository at this point in the history
Introduce the power management callbacks to the dasd driver. On suspend
the dasd devices are stopped and removed from the focus of alias
management.
On resume they are reinitialized by rereading the device characteristics
and adding the device to the alias management.
In case the device has gone away during suspend it will caught in the
suspend state with stopped flag set to UNRESUMED. After it appears again
the restore function is called again.

Signed-off-by: Stefan Haberland <stefan.haberland@de.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
  • Loading branch information
Stefan Haberland authored and Martin Schwidefsky committed Jun 16, 2009
1 parent ad285ae commit d41dd12
Show file tree
Hide file tree
Showing 5 changed files with 218 additions and 19 deletions.
109 changes: 107 additions & 2 deletions drivers/s390/block/dasd.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
* 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
*
* Copyright IBM Corp. 1999, 2009
*/

#define KMSG_COMPONENT "dasd"
Expand Down Expand Up @@ -61,6 +60,7 @@ static int dasd_flush_block_queue(struct dasd_block *);
static void dasd_device_tasklet(struct dasd_device *);
static void dasd_block_tasklet(struct dasd_block *);
static void do_kick_device(struct work_struct *);
static void do_restore_device(struct work_struct *);
static void dasd_return_cqr_cb(struct dasd_ccw_req *, void *);
static void dasd_device_timeout(unsigned long);
static void dasd_block_timeout(unsigned long);
Expand Down Expand Up @@ -109,6 +109,7 @@ struct dasd_device *dasd_alloc_device(void)
device->timer.function = dasd_device_timeout;
device->timer.data = (unsigned long) device;
INIT_WORK(&device->kick_work, do_kick_device);
INIT_WORK(&device->restore_device, do_restore_device);
device->state = DASD_STATE_NEW;
device->target = DASD_STATE_NEW;

Expand Down Expand Up @@ -511,6 +512,25 @@ void dasd_kick_device(struct dasd_device *device)
schedule_work(&device->kick_work);
}

/*
* dasd_restore_device will schedule a call do do_restore_device to the kernel
* event daemon.
*/
static void do_restore_device(struct work_struct *work)
{
struct dasd_device *device = container_of(work, struct dasd_device,
restore_device);
device->cdev->drv->restore(device->cdev);
dasd_put_device(device);
}

void dasd_restore_device(struct dasd_device *device)
{
dasd_get_device(device);
/* queue call to dasd_restore_device to the kernel event daemon. */
schedule_work(&device->restore_device);
}

/*
* Set the target state for a device and starts the state change.
*/
Expand Down Expand Up @@ -908,6 +928,12 @@ int dasd_start_IO(struct dasd_ccw_req *cqr)
DBF_DEV_EVENT(DBF_DEBUG, device, "%s",
"start_IO: -EIO device gone, retry");
break;
case -EINVAL:
/* most likely caused in power management context */
DBF_DEV_EVENT(DBF_DEBUG, device, "%s",
"start_IO: -EINVAL device currently "
"not accessible");
break;
default:
/* internal error 11 - unknown rc */
snprintf(errorstring, ERRORLENGTH, "11 %d", rc);
Expand Down Expand Up @@ -2400,6 +2426,12 @@ int dasd_generic_notify(struct ccw_device *cdev, int event)
case CIO_OPER:
/* FIXME: add a sanity check. */
device->stopped &= ~DASD_STOPPED_DC_WAIT;
if (device->stopped & DASD_UNRESUMED_PM) {
device->stopped &= ~DASD_UNRESUMED_PM;
dasd_restore_device(device);
ret = 1;
break;
}
dasd_schedule_device_bh(device);
if (device->block)
dasd_schedule_block_bh(device->block);
Expand All @@ -2410,6 +2442,79 @@ int dasd_generic_notify(struct ccw_device *cdev, int event)
return ret;
}

int dasd_generic_pm_freeze(struct ccw_device *cdev)
{
struct dasd_ccw_req *cqr, *n;
int rc;
struct list_head freeze_queue;
struct dasd_device *device = dasd_device_from_cdev(cdev);

if (IS_ERR(device))
return PTR_ERR(device);
/* disallow new I/O */
device->stopped |= DASD_STOPPED_PM;
/* clear active requests */
INIT_LIST_HEAD(&freeze_queue);
spin_lock_irq(get_ccwdev_lock(cdev));
rc = 0;
list_for_each_entry_safe(cqr, n, &device->ccw_queue, devlist) {
/* Check status and move request to flush_queue */
if (cqr->status == DASD_CQR_IN_IO) {
rc = device->discipline->term_IO(cqr);
if (rc) {
/* unable to terminate requeust */
dev_err(&device->cdev->dev,
"Unable to terminate request %p "
"on suspend\n", cqr);
spin_unlock_irq(get_ccwdev_lock(cdev));
dasd_put_device(device);
return rc;
}
}
list_move_tail(&cqr->devlist, &freeze_queue);
}

spin_unlock_irq(get_ccwdev_lock(cdev));

list_for_each_entry_safe(cqr, n, &freeze_queue, devlist) {
wait_event(dasd_flush_wq,
(cqr->status != DASD_CQR_CLEAR_PENDING));
if (cqr->status == DASD_CQR_CLEARED)
cqr->status = DASD_CQR_QUEUED;
}
/* move freeze_queue to start of the ccw_queue */
spin_lock_irq(get_ccwdev_lock(cdev));
list_splice_tail(&freeze_queue, &device->ccw_queue);
spin_unlock_irq(get_ccwdev_lock(cdev));

if (device->discipline->freeze)
rc = device->discipline->freeze(device);

dasd_put_device(device);
return rc;
}
EXPORT_SYMBOL_GPL(dasd_generic_pm_freeze);

int dasd_generic_restore_device(struct ccw_device *cdev)
{
struct dasd_device *device = dasd_device_from_cdev(cdev);
int rc = 0;

if (IS_ERR(device))
return PTR_ERR(device);

dasd_schedule_device_bh(device);
if (device->block)
dasd_schedule_block_bh(device->block);

if (device->discipline->restore)
rc = device->discipline->restore(device);

dasd_put_device(device);
return rc;
}
EXPORT_SYMBOL_GPL(dasd_generic_restore_device);

static struct dasd_ccw_req *dasd_generic_build_rdc(struct dasd_device *device,
void *rdc_buffer,
int rdc_buffer_size,
Expand Down
1 change: 1 addition & 0 deletions drivers/s390/block/dasd_devmap.c
Original file line number Diff line number Diff line change
Expand Up @@ -1098,6 +1098,7 @@ dasd_get_uid(struct ccw_device *cdev, struct dasd_uid *uid)
spin_unlock(&dasd_devmap_lock);
return 0;
}
EXPORT_SYMBOL_GPL(dasd_get_uid);

/*
* Register the given device unique identifier into devmap struct.
Expand Down
108 changes: 95 additions & 13 deletions drivers/s390/block/dasd_eckd.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@
* 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,2000
* Copyright IBM Corp. 1999, 2009
* EMC Symmetrix ioctl Copyright EMC Corporation, 2008
* Author.........: Nigel Hislop <hislop_nigel@emc.com>
*
*/

#define KMSG_COMPONENT "dasd"
Expand Down Expand Up @@ -104,17 +103,6 @@ dasd_eckd_set_online(struct ccw_device *cdev)
return dasd_generic_set_online(cdev, &dasd_eckd_discipline);
}

static struct ccw_driver dasd_eckd_driver = {
.name = "dasd-eckd",
.owner = THIS_MODULE,
.ids = dasd_eckd_ids,
.probe = dasd_eckd_probe,
.remove = dasd_generic_remove,
.set_offline = dasd_generic_set_offline,
.set_online = dasd_eckd_set_online,
.notify = dasd_generic_notify,
};

static const int sizes_trk0[] = { 28, 148, 84 };
#define LABEL_SIZE 140

Expand Down Expand Up @@ -3236,6 +3224,98 @@ static void dasd_eckd_dump_sense(struct dasd_device *device,
dasd_eckd_dump_sense_ccw(device, req, irb);
}

int dasd_eckd_pm_freeze(struct dasd_device *device)
{
/*
* the device should be disconnected from our LCU structure
* on restore we will reconnect it and reread LCU specific
* information like PAV support that might have changed
*/
dasd_alias_remove_device(device);
dasd_alias_disconnect_device_from_lcu(device);

return 0;
}

int dasd_eckd_restore_device(struct dasd_device *device)
{
struct dasd_eckd_private *private;
int is_known, rc;
struct dasd_uid temp_uid;

/* allow new IO again */
device->stopped &= ~DASD_STOPPED_PM;

private = (struct dasd_eckd_private *) device->private;

/* Read Configuration Data */
rc = dasd_eckd_read_conf(device);
if (rc)
goto out_err;

/* Generate device unique id and register in devmap */
rc = dasd_eckd_generate_uid(device, &private->uid);
dasd_get_uid(device->cdev, &temp_uid);
if (memcmp(&private->uid, &temp_uid, sizeof(struct dasd_uid)) != 0)
dev_err(&device->cdev->dev, "The UID of the DASD has changed\n");
if (rc)
goto out_err;
dasd_set_uid(device->cdev, &private->uid);

/* register lcu with alias handling, enable PAV if this is a new lcu */
is_known = dasd_alias_make_device_known_to_lcu(device);
if (is_known < 0)
return is_known;
if (!is_known) {
/* new lcu found */
rc = dasd_eckd_validate_server(device); /* will switch pav on */
if (rc)
goto out_err;
}

/* Read Feature Codes */
rc = dasd_eckd_read_features(device);
if (rc)
goto out_err;

/* Read Device Characteristics */
memset(&private->rdc_data, 0, sizeof(private->rdc_data));
rc = dasd_generic_read_dev_chars(device, "ECKD",
&private->rdc_data, 64);
if (rc) {
DBF_EVENT(DBF_WARNING,
"Read device characteristics failed, rc=%d for "
"device: %s", rc, dev_name(&device->cdev->dev));
goto out_err;
}

/* add device to alias management */
dasd_alias_add_device(device);

return 0;

out_err:
/*
* if the resume failed for the DASD we put it in
* an UNRESUMED stop state
*/
device->stopped |= DASD_UNRESUMED_PM;
return 0;
}

static struct ccw_driver dasd_eckd_driver = {
.name = "dasd-eckd",
.owner = THIS_MODULE,
.ids = dasd_eckd_ids,
.probe = dasd_eckd_probe,
.remove = dasd_generic_remove,
.set_offline = dasd_generic_set_offline,
.set_online = dasd_eckd_set_online,
.notify = dasd_generic_notify,
.freeze = dasd_generic_pm_freeze,
.thaw = dasd_generic_restore_device,
.restore = dasd_generic_restore_device,
};

/*
* max_blocks is dependent on the amount of storage that is available
Expand Down Expand Up @@ -3274,6 +3354,8 @@ static struct dasd_discipline dasd_eckd_discipline = {
.dump_sense_dbf = dasd_eckd_dump_sense_dbf,
.fill_info = dasd_eckd_fill_info,
.ioctl = dasd_eckd_ioctl,
.freeze = dasd_eckd_pm_freeze,
.restore = dasd_eckd_restore_device,
};

static int __init
Expand Down
6 changes: 4 additions & 2 deletions drivers/s390/block/dasd_fba.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
* 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
*
* Copyright IBM Corp. 1999, 2009
*/

#define KMSG_COMPONENT "dasd"
Expand Down Expand Up @@ -75,6 +74,9 @@ static struct ccw_driver dasd_fba_driver = {
.set_offline = dasd_generic_set_offline,
.set_online = dasd_fba_set_online,
.notify = dasd_generic_notify,
.freeze = dasd_generic_pm_freeze,
.thaw = dasd_generic_restore_device,
.restore = dasd_generic_restore_device,
};

static void
Expand Down
13 changes: 11 additions & 2 deletions drivers/s390/block/dasd_int.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
* 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
*
* Copyright IBM Corp. 1999, 2009
*/

#ifndef DASD_INT_H
Expand Down Expand Up @@ -295,6 +294,10 @@ struct dasd_discipline {
int (*fill_geometry) (struct dasd_block *, struct hd_geometry *);
int (*fill_info) (struct dasd_device *, struct dasd_information2_t *);
int (*ioctl) (struct dasd_block *, unsigned int, void __user *);

/* suspend/resume functions */
int (*freeze) (struct dasd_device *);
int (*restore) (struct dasd_device *);
};

extern struct dasd_discipline *dasd_diag_discipline_pointer;
Expand Down Expand Up @@ -367,6 +370,7 @@ struct dasd_device {
atomic_t tasklet_scheduled;
struct tasklet_struct tasklet;
struct work_struct kick_work;
struct work_struct restore_device;
struct timer_list timer;

debug_info_t *debug_area;
Expand Down Expand Up @@ -410,6 +414,8 @@ struct dasd_block {
#define DASD_STOPPED_PENDING 4 /* long busy */
#define DASD_STOPPED_DC_WAIT 8 /* disconnected, wait */
#define DASD_STOPPED_SU 16 /* summary unit check handling */
#define DASD_STOPPED_PM 32 /* pm state transition */
#define DASD_UNRESUMED_PM 64 /* pm resume failed state */

/* per device flags */
#define DASD_FLAG_OFFLINE 3 /* device is in offline processing */
Expand Down Expand Up @@ -556,6 +562,7 @@ void dasd_free_block(struct dasd_block *);
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_restore_device(struct dasd_device *);

void dasd_add_request_head(struct dasd_ccw_req *);
void dasd_add_request_tail(struct dasd_ccw_req *);
Expand All @@ -578,6 +585,8 @@ 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);
void dasd_generic_handle_state_change(struct dasd_device *);
int dasd_generic_pm_freeze(struct ccw_device *);
int dasd_generic_restore_device(struct ccw_device *);

int dasd_generic_read_dev_chars(struct dasd_device *, char *, void *, int);
char *dasd_get_sense(struct irb *);
Expand Down

0 comments on commit d41dd12

Please sign in to comment.