lkml.org 
[lkml]   [2008]   [Feb]   [25]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patches in this message
    /
    Date
    From
    Subject[ugly patch] Save .15W-.5W by AHCI powersaving
    Hi!

    This is a patch (very ugly, assumes you have just one disk) to bring
    powersaving to AHCI. You need Alan's SCSI autosuspend (attached) patch
    as a base.

    It saves .5W compared to config with disk spinning, and even .15W
    compared to hdparm -y... on my thinkpad x60 anyway.

    It is also mandatory first step towards sleepy linux ;-).

    Pavel
    diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c
    index 29e71bd..0197b1f 100644
    --- a/drivers/ata/ahci.c
    +++ b/drivers/ata/ahci.c
    @@ -259,8 +260,8 @@ static void ahci_fill_cmd_slot(struct ah
    u32 opts);
    #ifdef CONFIG_PM
    static int ahci_port_suspend(struct ata_port *ap, pm_message_t mesg);
    -static int ahci_pci_device_suspend(struct pci_dev *pdev, pm_message_t mesg);
    -static int ahci_pci_device_resume(struct pci_dev *pdev);
    +int ahci_pci_device_suspend(struct pci_dev *pdev, pm_message_t mesg);
    +int ahci_pci_device_resume(struct pci_dev *pdev);
    #endif

    static struct class_device_attribute *ahci_shost_attrs[] = {
    @@ -268,6 +269,35 @@ static struct class_device_attribute *ah
    NULL
    };

    +struct pci_dev *my_pdev;
    +int autosuspend_enabled;
    +
    +/* The host and its devices are all idle so we can autosuspend */
    +static int autosuspend(struct Scsi_Host *host)
    +{
    + if (my_pdev && autosuspend_enabled) {
    + printk("ahci: should autosuspend\n");
    + ahci_pci_device_suspend(my_pdev, PMSG_SUSPEND);
    + return 0;
    + }
    + printk("ahci: autosuspend disabled\n");
    + return -EINVAL;
    +}
    +
    +/* The host needs to be autoresumed */
    +static int autoresume(struct Scsi_Host *host)
    +{
    + if (my_pdev && autosuspend_enabled) {
    + printk("ahci: should autoresume\n");
    + ahci_pci_device_resume(my_pdev);
    + return 0;
    + }
    + printk("ahci: autoresume disabled\n");
    + return -EINVAL;
    +}
    +
    +
    +
    static struct scsi_host_template ahci_sht = {
    .module = THIS_MODULE,
    .name = DRV_NAME,
    @@ -286,6 +322,8 @@ static struct scsi_host_template ahci_sh
    .slave_destroy = ata_scsi_slave_destroy,
    .bios_param = ata_std_bios_param,
    .shost_attrs = ahci_shost_attrs,
    + .autosuspend = autosuspend,
    + .autoresume = autoresume,
    };

    static const struct ata_port_operations ahci_ops = {
    @@ -2300,8 +2356,12 @@ static int ahci_init_one(struct pci_dev
    ahci_print_info(host);

    pci_set_master(pdev);
    - return ata_host_activate(host, pdev->irq, ahci_interrupt, IRQF_SHARED,
    +
    + rc = ata_host_activate(host, pdev->irq, ahci_interrupt, IRQF_SHARED,
    &ahci_sht);
    + pci_save_state(pdev);
    + my_pdev = pdev;
    + return rc;
    }

    static int __init ahci_init(void)
    diff --git a/drivers/ata/libata-eh.c b/drivers/ata/libata-eh.c
    index 4e31071..5c40ac2 100644
    --- a/drivers/ata/libata-eh.c
    +++ b/drivers/ata/libata-eh.c
    @@ -380,7 +381,7 @@ enum scsi_eh_timer_return ata_scsi_timed
    * Inherited from SCSI layer (none, can sleep)
    *
    * RETURNS:
    - * Zero.
    + * Nothing.
    */
    void ata_scsi_error(struct Scsi_Host *host)
    {
    diff --git a/drivers/scsi/scsi_error.c b/drivers/scsi/scsi_error.c
    index c838e65..0edc25e 100644
    --- a/drivers/scsi/scsi_error.c
    +++ b/drivers/scsi/scsi_error.c
    @@ -484,6 +484,8 @@ static int scsi_try_host_reset(struct sc
    if (scsi_autoresume_host(shost) != 0)
    return FAILED;

    + rtn = shost->hostt->eh_host_reset_handler(scmd);
    +
    if (rtn == SUCCESS) {
    if (!shost->hostt->skip_settle_delay)
    ssleep(HOST_RESET_SETTLE_TIME);
    @@ -1577,7 +1579,11 @@ int scsi_error_handler(void *data)
    * what we need to do to get it up and online again (if we can).
    * If we fail, we end up taking the thing offline.
    */
    +#if 0
    + /* libata uses scsi_error_handler to suspend its parts; we deadlock
    + if we try to autoresume here */
    autoresume_rc = scsi_autoresume_host(shost);
    +#endif
    if (shost->transportt->eh_strategy_handler)
    shost->transportt->eh_strategy_handler(shost);
    else
    @@ -1591,8 +1597,10 @@ int scsi_error_handler(void *data)
    * which are still online.
    */
    scsi_restart_operations(shost);
    +#if 0
    if (autoresume_rc == 0)
    scsi_autosuspend_host(shost);
    +#endif
    set_current_state(TASK_INTERRUPTIBLE);
    }
    __set_current_state(TASK_RUNNING);
    diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
    index 233feee..3c598e0 100644
    --- a/drivers/scsi/scsi_lib.c
    +++ b/drivers/scsi/scsi_lib.c
    @@ -67,6 +67,8 @@ #undef SP

    static struct kmem_cache *scsi_bidi_sdb_cache;

    +void scsi_run_queue(struct request_queue *q);
    +
    /*
    * Function: scsi_unprep_request()
    *
    diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c
    index 5393e15..25bebc1 100644
    --- a/drivers/scsi/scsi_sysfs.c
    +++ b/drivers/scsi/scsi_sysfs.c
    @@ -340,6 +340,21 @@ static int scsi_bus_uevent(struct device
    return 0;
    }

    +static int scsi_bus_remove(struct device *dev)
    +{
    + struct device_driver *drv = dev->driver;
    + struct scsi_device *sdev = to_scsi_device(dev);
    + int err = 0;
    +
    + /* reset the prep_fn back to the default since the
    + * driver may have altered it and it's being removed */
    + blk_queue_prep_rq(sdev->request_queue, scsi_prep_fn);
    +
    + if (drv && drv->remove)
    + err = drv->remove(dev);
    +
    + return 0;
    +}

    struct bus_type scsi_bus_type = {
    .name = "scsi",
    diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
    index 16add9a..9ca12d1 100644
    --- a/drivers/scsi/sd.c
    +++ b/drivers/scsi/sd.c
    @@ -1596,6 +1596,8 @@ static int sd_revalidate_disk(struct gen
    return 0;
    }

    +struct device *my_scsi_disk;
    +
    /**
    * sd_probe - called during driver initialization and whenever a
    * new scsi device is attached to the system. It is called once
    @@ -1715,6 +1717,7 @@ static int sd_probe(struct device *dev)

    scsi_use_ULD_pm(sdp, 1);
    scsi_autosuspend_device(sdp);
    + my_scsi_disk = dev;
    return 0;

    out_suspend:
    @@ -1835,6 +1838,8 @@ static int sd_suspend(struct device *dev
    struct scsi_disk *sdkp = scsi_disk_get_from_dev(dev);
    int ret = 0;

    + printk("sleepy: sd_suspend start\n");
    +
    if (!sdkp)
    return 0; /* this can happen */



    --
    (english) http://www.livejournal.com/~pavelmachek
    (cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
    From: Alan Stern <stern@rowland.harvard.edu>

    Alan Stern's autosuspend-for-SCSI patches.

    ---
    commit 8b534a6dc550b4115627e35535f07753d5612800
    tree a8e2b2eafb680fb8c5c5bcad049d29f23f576b43
    parent 9c269a8b0695a4b18d36b918cb14b757f73e6a49
    author Pavel <pavel@amd.ucw.cz> Mon, 25 Feb 2008 13:43:18 +0100
    committer Pavel <pavel@amd.ucw.cz> Mon, 25 Feb 2008 13:43:18 +0100

    Documentation/scsi/scsi_mid_low_api.txt | 50 ++++++++++++++++++++++++++++
    drivers/scsi/Kconfig | 12 +++++++
    drivers/scsi/Makefile | 3 +-
    drivers/scsi/hosts.c | 20 ++++-------
    drivers/scsi/scsi.c | 8 ++++
    drivers/scsi/scsi_error.c | 36 +++++++++++++-------
    drivers/scsi/scsi_lib.c | 7 ++--
    drivers/scsi/scsi_priv.h | 38 +++++++++++++++++++++
    drivers/scsi/scsi_scan.c | 9 +++++
    drivers/scsi/scsi_sysfs.c | 56 ++++---------------------------
    drivers/scsi/sd.c | 12 ++++++-
    drivers/scsi/sg.c | 10 ++++++
    drivers/usb/storage/scsiglue.c | 35 +++++++++++++++++--
    drivers/usb/storage/usb.c | 15 ++++----
    include/scsi/scsi_device.h | 23 ++++++++++++-
    include/scsi/scsi_host.h | 24 +++++++++++++
    16 files changed, 264 insertions(+), 94 deletions(-)

    diff --git a/Documentation/scsi/scsi_mid_low_api.txt b/Documentation/scsi/scsi_mid_low_api.txt
    index a6d5354..98dc005 100644
    --- a/Documentation/scsi/scsi_mid_low_api.txt
    +++ b/Documentation/scsi/scsi_mid_low_api.txt
    @@ -782,6 +782,8 @@ In some cases more detail is given in sc
    The interface functions are listed below in alphabetical order.

    Summary:
    + autoresume - perform dynamic (runtime) host resume
    + autosuspend - perform dynamic (runtime) host suspend
    bios_param - fetch head, sector, cylinder info for a disk
    detect - detects HBAs this driver wants to control
    eh_timed_out - notify the host that a command timer expired
    @@ -802,6 +804,54 @@ Summary:
    Details:

    /**
    + * autoresume - perform dynamic (runtime) host resume
    + * @shp: host to resume
    + *
    + * Resume (return to an operational power level) the specified host.
    + * Return 0 if the resume was successful, otherwise a negative
    + * error code.
    + *
    + * Locks: struct Scsi_Host::pm_mutex held throughout the call.
    + *
    + * Calling context: process
    + *
    + * Notes: If the host is not currently suspended, this method does
    + * need to do anything.
    + *
    + * Optionally defined in: LLD
    + **/
    + int autoresume(struct Scsi_Host *shp)
    +
    +
    +/**
    + * autosuspend - perform dynamic (runtime) host suspend
    + * @shp: host to suspend
    + *
    + * Suspend (change to a non-operational low-power state) the
    + * specified host.
    + * Return 0 if the suspend was successful (or was successfully
    + * queued, or was successfully ignored), otherwise a negative
    + * error code.
    + *
    + * Locks: struct Scsi_Host::pm_mutex held throughout the call.
    + *
    + * Calling context: process
    + *
    + * Notes: The suspend need not be carried out immediately (or indeed
    + * at all); it may be delayed indefinitely. The real meaning of this
    + * method call is that all of the host's devices are now idle. It can
    + * happen that an autosuspend is quickly followed by an autoresume,
    + * so it is beneficial if the suspend is delayed by a few seconds.
    + * A host is assumed to be at full power (resumed) when it is
    + * first created. In the absence of errors, the LLD will receive a
    + * strictly alternating sequence of autosuspend, autoresume,... calls.
    + *
    + * Optionally defined in: LLD
    + **/
    + int autosuspend(struct Scsi_Host *shp)
    +
    +
    +/**
    * bios_param - fetch head, sector, cylinder info for a disk
    * @sdev: pointer to scsi device context (defined in
    * include/scsi/scsi_device.h)
    diff --git a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig
    index a7a0813..9bada81 100644
    --- a/drivers/scsi/Kconfig
    +++ b/drivers/scsi/Kconfig
    @@ -57,6 +57,18 @@ config SCSI_PROC_FS

    If unsure say Y.

    +config SCSI_DYNAMIC_PM
    + bool "SCSI dynamic Power Management support (EXPERIMENTAL)"
    + depends on SCSI && PM && EXPERIMENTAL
    + ---help---
    + This option enables support for dynamic (or runtime)
    + power management of SCSI devices and host adapters.
    + If you say Y here, you can use the sysfs "power/level"
    + and "power/autosuspend" files to control manual or
    + automatic suspend/resume of individual SCSI devices.
    +
    + If unsure say N.
    +
    comment "SCSI support type (disk, tape, CD-ROM)"
    depends on SCSI

    diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile
    index 925c26b..634a0e4 100644
    --- a/drivers/scsi/Makefile
    +++ b/drivers/scsi/Makefile
    @@ -139,7 +139,8 @@ obj-$(CONFIG_SCSI_WAIT_SCAN) += scsi_wai
    scsi_mod-y += scsi.o hosts.o scsi_ioctl.o constants.o \
    scsicam.o scsi_error.o scsi_lib.o
    scsi_mod-$(CONFIG_SCSI_DMA) += scsi_lib_dma.o
    -scsi_mod-y += scsi_scan.o scsi_sysfs.o scsi_devinfo.o
    +scsi_mod-y += scsi_scan.o scsi_sysfs.o scsi_devinfo.o \
    + scsi_pm.o
    scsi_mod-$(CONFIG_SCSI_NETLINK) += scsi_netlink.o
    scsi_mod-$(CONFIG_SYSCTL) += scsi_sysctl.o
    scsi_mod-$(CONFIG_SCSI_PROC_FS) += scsi_proc.o
    diff --git a/drivers/scsi/hosts.c b/drivers/scsi/hosts.c
    index 880c78b..250cdab 100644
    --- a/drivers/scsi/hosts.c
    +++ b/drivers/scsi/hosts.c
    @@ -154,24 +154,15 @@ EXPORT_SYMBOL(scsi_host_set_state);
    **/
    void scsi_remove_host(struct Scsi_Host *shost)
    {
    - unsigned long flags;
    - mutex_lock(&shost->scan_mutex);
    - spin_lock_irqsave(shost->host_lock, flags);
    - if (scsi_host_set_state(shost, SHOST_CANCEL))
    - if (scsi_host_set_state(shost, SHOST_CANCEL_RECOVERY)) {
    - spin_unlock_irqrestore(shost->host_lock, flags);
    - mutex_unlock(&shost->scan_mutex);
    - return;
    - }
    - spin_unlock_irqrestore(shost->host_lock, flags);
    - mutex_unlock(&shost->scan_mutex);
    + if (scsi_pm_host_stop(shost))
    + return;
    scsi_forget_host(shost);
    scsi_proc_host_rm(shost);

    - spin_lock_irqsave(shost->host_lock, flags);
    + spin_lock_irq(shost->host_lock);
    if (scsi_host_set_state(shost, SHOST_DEL))
    BUG_ON(scsi_host_set_state(shost, SHOST_DEL_RECOVERY));
    - spin_unlock_irqrestore(shost->host_lock, flags);
    + spin_unlock_irq(shost->host_lock);

    transport_unregister_device(&shost->shost_gendev);
    class_device_unregister(&shost->shost_classdev);
    @@ -236,6 +227,7 @@ int scsi_add_host(struct Scsi_Host *shos
    if (error)
    goto out_destroy_host;

    + scsi_autosuspend_host(shost);
    scsi_proc_host_add(shost);
    return error;

    @@ -386,6 +378,8 @@ struct Scsi_Host *scsi_host_alloc(struct
    snprintf(shost->shost_classdev.class_id, BUS_ID_SIZE, "host%d",
    shost->host_no);

    + scsi_pm_host_initialize(shost);
    +
    shost->ehandler = kthread_run(scsi_error_handler, shost,
    "scsi_eh_%d", shost->host_no);
    if (IS_ERR(shost->ehandler)) {
    diff --git a/drivers/scsi/scsi.c b/drivers/scsi/scsi.c
    index fecba05..4fa9db4 100644
    --- a/drivers/scsi/scsi.c
    +++ b/drivers/scsi/scsi.c
    @@ -1133,15 +1133,20 @@ static int __init init_scsi(void)
    error = scsi_init_sysctl();
    if (error)
    goto cleanup_hosts;
    - error = scsi_sysfs_register();
    + error = scsi_init_pm();
    if (error)
    goto cleanup_sysctl;
    + error = scsi_sysfs_register();
    + if (error)
    + goto cleanup_pm;

    scsi_netlink_init();

    printk(KERN_NOTICE "SCSI subsystem initialized\n");
    return 0;

    +cleanup_pm:
    + scsi_exit_pm();
    cleanup_sysctl:
    scsi_exit_sysctl();
    cleanup_hosts:
    @@ -1161,6 +1166,7 @@ static void __exit exit_scsi(void)
    {
    scsi_netlink_exit();
    scsi_sysfs_unregister();
    + scsi_exit_pm();
    scsi_exit_sysctl();
    scsi_exit_hosts();
    scsi_exit_devinfo();
    diff --git a/drivers/scsi/scsi_error.c b/drivers/scsi/scsi_error.c
    index 045a086..c838e65 100644
    --- a/drivers/scsi/scsi_error.c
    +++ b/drivers/scsi/scsi_error.c
    @@ -468,30 +468,32 @@ static void scsi_eh_done(struct scsi_cmn

    /**
    * scsi_try_host_reset - ask host adapter to reset itself
    - * @scmd: SCSI cmd to send hsot reset.
    + * @scmd: SCSI cmd to send host reset.
    */
    static int scsi_try_host_reset(struct scsi_cmnd *scmd)
    {
    unsigned long flags;
    int rtn;
    + struct Scsi_Host *shost = scmd->device->host;

    SCSI_LOG_ERROR_RECOVERY(3, printk("%s: Snd Host RST\n",
    __FUNCTION__));

    if (!scmd->device->host->hostt->eh_host_reset_handler)
    return FAILED;
    -
    - rtn = scmd->device->host->hostt->eh_host_reset_handler(scmd);
    + if (scsi_autoresume_host(shost) != 0)
    + return FAILED;

    if (rtn == SUCCESS) {
    - if (!scmd->device->host->hostt->skip_settle_delay)
    + if (!shost->hostt->skip_settle_delay)
    ssleep(HOST_RESET_SETTLE_TIME);
    - spin_lock_irqsave(scmd->device->host->host_lock, flags);
    - scsi_report_bus_reset(scmd->device->host,
    + spin_lock_irqsave(shost->host_lock, flags);
    + scsi_report_bus_reset(shost,
    scmd_channel(scmd));
    - spin_unlock_irqrestore(scmd->device->host->host_lock, flags);
    + spin_unlock_irqrestore(shost->host_lock, flags);
    }

    + scsi_autosuspend_host(shost);
    return rtn;
    }

    @@ -503,24 +505,28 @@ static int scsi_try_bus_reset(struct scs
    {
    unsigned long flags;
    int rtn;
    + struct Scsi_Host *shost = scmd->device->host;

    SCSI_LOG_ERROR_RECOVERY(3, printk("%s: Snd Bus RST\n",
    __FUNCTION__));

    - if (!scmd->device->host->hostt->eh_bus_reset_handler)
    + if (!shost->hostt->eh_bus_reset_handler)
    + return FAILED;
    + if (scsi_autoresume_host(shost) != 0)
    return FAILED;

    - rtn = scmd->device->host->hostt->eh_bus_reset_handler(scmd);
    + rtn = shost->hostt->eh_bus_reset_handler(scmd);

    if (rtn == SUCCESS) {
    - if (!scmd->device->host->hostt->skip_settle_delay)
    + if (!shost->hostt->skip_settle_delay)
    ssleep(BUS_RESET_SETTLE_TIME);
    - spin_lock_irqsave(scmd->device->host->host_lock, flags);
    - scsi_report_bus_reset(scmd->device->host,
    + spin_lock_irqsave(shost->host_lock, flags);
    + scsi_report_bus_reset(shost,
    scmd_channel(scmd));
    - spin_unlock_irqrestore(scmd->device->host->host_lock, flags);
    + spin_unlock_irqrestore(shost->host_lock, flags);
    }

    + scsi_autosuspend_host(shost);
    return rtn;
    }

    @@ -1541,6 +1547,7 @@ static void scsi_unjam_host(struct Scsi_
    int scsi_error_handler(void *data)
    {
    struct Scsi_Host *shost = data;
    + int autoresume_rc;

    /*
    * We use TASK_INTERRUPTIBLE so that the thread is not
    @@ -1570,6 +1577,7 @@ int scsi_error_handler(void *data)
    * what we need to do to get it up and online again (if we can).
    * If we fail, we end up taking the thing offline.
    */
    + autoresume_rc = scsi_autoresume_host(shost);
    if (shost->transportt->eh_strategy_handler)
    shost->transportt->eh_strategy_handler(shost);
    else
    @@ -1583,6 +1591,8 @@ int scsi_error_handler(void *data)
    * which are still online.
    */
    scsi_restart_operations(shost);
    + if (autoresume_rc == 0)
    + scsi_autosuspend_host(shost);
    set_current_state(TASK_INTERRUPTIBLE);
    }
    __set_current_state(TASK_RUNNING);
    diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
    index 135c1d0..233feee 100644
    --- a/drivers/scsi/scsi_lib.c
    +++ b/drivers/scsi/scsi_lib.c
    @@ -67,8 +67,6 @@ #undef SP

    static struct kmem_cache *scsi_bidi_sdb_cache;

    -static void scsi_run_queue(struct request_queue *q);
    -
    /*
    * Function: scsi_unprep_request()
    *
    @@ -461,6 +459,7 @@ void scsi_device_unbusy(struct scsi_devi
    spin_unlock(shost->host_lock);
    spin_lock(sdev->request_queue->queue_lock);
    sdev->device_busy--;
    + scsi_mark_last_busy(sdev);
    spin_unlock_irqrestore(sdev->request_queue->queue_lock, flags);
    }

    @@ -522,7 +521,7 @@ static void scsi_single_lun_run(struct s
    * Notes: The previous command was completely finished, start
    * a new one if possible.
    */
    -static void scsi_run_queue(struct request_queue *q)
    +void scsi_run_queue(struct request_queue *q)
    {
    struct scsi_device *sdev = q->queuedata;
    struct Scsi_Host *shost = sdev->host;
    @@ -1201,6 +1200,8 @@ int scsi_prep_state_check(struct scsi_de
    ret = BLKPREP_KILL;
    break;
    case SDEV_QUIESCE:
    + ret = scsi_pm_state_check(sdev, req);
    + break;
    case SDEV_BLOCK:
    /*
    * If the devices is blocked we defer normal commands.
    diff --git a/drivers/scsi/scsi_priv.h b/drivers/scsi/scsi_priv.h
    index 3f34e93..c331de4 100644
    --- a/drivers/scsi/scsi_priv.h
    +++ b/drivers/scsi/scsi_priv.h
    @@ -4,11 +4,13 @@ #define _SCSI_PRIV_H
    #include <linux/device.h>

    struct request_queue;
    +struct request;
    struct scsi_cmnd;
    struct scsi_device;
    struct scsi_host_template;
    struct Scsi_Host;
    struct scsi_nl_hdr;
    +struct workqueue_struct;


    /*
    @@ -67,6 +69,7 @@ int scsi_eh_get_sense(struct list_head *
    extern int scsi_maybe_unblock_host(struct scsi_device *sdev);
    extern void scsi_device_unbusy(struct scsi_device *sdev);
    extern int scsi_queue_insert(struct scsi_cmnd *cmd, int reason);
    +extern void scsi_run_queue(struct request_queue *q);
    extern void scsi_next_command(struct scsi_cmnd *cmd);
    extern void scsi_io_completion(struct scsi_cmnd *, unsigned int);
    extern void scsi_run_host_queues(struct Scsi_Host *shost);
    @@ -132,6 +135,41 @@ static inline void scsi_netlink_init(voi
    static inline void scsi_netlink_exit(void) {}
    #endif

    +/* scsi_pm.c */
    +extern int scsi_bus_suspend(struct device *, pm_message_t);
    +extern int scsi_bus_resume(struct device *);
    +extern int scsi_pm_state_check(struct scsi_device *, struct request *);
    +extern int scsi_pm_device_stop(struct scsi_device *);
    +extern int scsi_pm_host_stop(struct Scsi_Host *);
    +#ifdef CONFIG_SCSI_DYNAMIC_PM
    +extern void scsi_autosuspend_host(struct Scsi_Host *);
    +extern int scsi_autoresume_host(struct Scsi_Host *);
    +extern void scsi_pm_host_initialize(struct Scsi_Host *);
    +extern void scsi_mark_last_busy(struct scsi_device *);
    +extern void scsi_use_ULD_pm(struct scsi_device *, int);
    +extern void scsi_autosuspend_device(struct scsi_device *);
    +extern int scsi_autoresume_device(struct scsi_device *);
    +extern int scsi_pm_create_device_files(struct scsi_device *);
    +extern void scsi_pm_device_initialize(struct scsi_device *);
    +extern int scsi_init_pm(void);
    +extern void scsi_exit_pm(void);
    +#else
    +static inline void scsi_autosuspend_host(struct Scsi_Host *shost) {}
    +static inline int scsi_autoresume_host(struct Scsi_Host *shost)
    + { return 0; }
    +static inline void scsi_pm_host_initialize(struct Scsi_Host *shost) {}
    +static inline void scsi_mark_last_busy(struct scsi_device *sdev) {}
    +static inline void scsi_use_ULD_pm(struct scsi_device *sdev, int v) {}
    +static inline void scsi_autosuspend_device(struct scsi_device *sdev) {}
    +static inline int scsi_autoresume_device(struct scsi_device *sdev)
    + { return 0; }
    +static inline int scsi_pm_create_device_files(struct scsi_device *sdev)
    + { return 0; }
    +static inline void scsi_pm_device_initialize(struct scsi_device *sdev) {}
    +static inline int scsi_init_pm(void) { return 0; }
    +static inline void scsi_exit_pm(void) {}
    +#endif /* CONFIG_SCSI_DYNAMIC_PM */
    +
    /*
    * internal scsi timeout functions: for use by mid-layer and transport
    * classes.
    diff --git a/drivers/scsi/scsi_scan.c b/drivers/scsi/scsi_scan.c
    index 1dc165a..c48a143 100644
    --- a/drivers/scsi/scsi_scan.c
    +++ b/drivers/scsi/scsi_scan.c
    @@ -297,6 +297,9 @@ static struct scsi_device *scsi_alloc_sd
    scsi_adjust_queue_depth(sdev, 0, sdev->host->cmd_per_lun);

    scsi_sysfs_device_initialize(sdev);
    + scsi_pm_device_initialize(sdev);
    + if (scsi_autoresume_host(shost) != 0)
    + goto out_device_destroy;

    if (shost->hostt->slave_alloc) {
    ret = shost->hostt->slave_alloc(sdev);
    @@ -307,6 +310,7 @@ static struct scsi_device *scsi_alloc_sd
    */
    if (ret == -ENXIO)
    display_failure_msg = 0;
    + scsi_autosuspend_host(shost);
    goto out_device_destroy;
    }
    }
    @@ -922,6 +926,7 @@ static int scsi_add_lun(struct scsi_devi
    static inline void scsi_destroy_sdev(struct scsi_device *sdev)
    {
    scsi_device_set_state(sdev, SDEV_DEL);
    + scsi_autosuspend_host(sdev->host);
    if (sdev->host->hostt->slave_destroy)
    sdev->host->hostt->slave_destroy(sdev);
    transport_destroy_device(&sdev->sdev_gendev);
    @@ -1084,6 +1089,7 @@ static int scsi_probe_and_add_lun(struct

    res = scsi_add_lun(sdev, result, &bflags, shost->async_scan);
    if (res == SCSI_SCAN_LUN_PRESENT) {
    + scsi_autosuspend_device(sdev);
    if (bflags & BLIST_KEY) {
    sdev->lockable = 0;
    scsi_unlock_floptical(sdev, result);
    @@ -1785,6 +1791,8 @@ static void scsi_finish_async_scan(struc

    static void do_scsi_scan_host(struct Scsi_Host *shost)
    {
    + if (scsi_autoresume_host(shost) != 0)
    + return;
    if (shost->hostt->scan_finished) {
    unsigned long start = jiffies;
    if (shost->hostt->scan_start)
    @@ -1796,6 +1804,7 @@ static void do_scsi_scan_host(struct Scs
    scsi_scan_host_selected(shost, SCAN_WILD_CARD, SCAN_WILD_CARD,
    SCAN_WILD_CARD, 0);
    }
    + scsi_autosuspend_host(shost);
    }

    static int do_scan_async(void *_data)
    diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c
    index ed83cdb..5393e15 100644
    --- a/drivers/scsi/scsi_sysfs.c
    +++ b/drivers/scsi/scsi_sysfs.c
    @@ -340,54 +340,6 @@ static int scsi_bus_uevent(struct device
    return 0;
    }

    -static int scsi_bus_suspend(struct device * dev, pm_message_t state)
    -{
    - struct device_driver *drv = dev->driver;
    - struct scsi_device *sdev = to_scsi_device(dev);
    - int err;
    -
    - err = scsi_device_quiesce(sdev);
    - if (err)
    - return err;
    -
    - if (drv && drv->suspend) {
    - err = drv->suspend(dev, state);
    - if (err)
    - return err;
    - }
    -
    - return 0;
    -}
    -
    -static int scsi_bus_resume(struct device * dev)
    -{
    - struct device_driver *drv = dev->driver;
    - struct scsi_device *sdev = to_scsi_device(dev);
    - int err = 0;
    -
    - if (drv && drv->resume)
    - err = drv->resume(dev);
    -
    - scsi_device_resume(sdev);
    -
    - return err;
    -}
    -
    -static int scsi_bus_remove(struct device *dev)
    -{
    - struct device_driver *drv = dev->driver;
    - struct scsi_device *sdev = to_scsi_device(dev);
    - int err = 0;
    -
    - /* reset the prep_fn back to the default since the
    - * driver may have altered it and it's being removed */
    - blk_queue_prep_rq(sdev->request_queue, scsi_prep_fn);
    -
    - if (drv && drv->remove)
    - err = drv->remove(dev);
    -
    - return 0;
    -}

    struct bus_type scsi_bus_type = {
    .name = "scsi",
    @@ -814,6 +766,12 @@ int scsi_sysfs_add_sdev(struct scsi_devi
    goto out;
    }

    + error = scsi_pm_create_device_files(sdev);
    + if (error) {
    + __scsi_remove_device(sdev);
    + goto out;
    + }
    +
    error = bsg_register_queue(rq, &sdev->sdev_gendev, NULL);

    if (error)
    @@ -854,7 +812,7 @@ void __scsi_remove_device(struct scsi_de
    {
    struct device *dev = &sdev->sdev_gendev;

    - if (scsi_device_set_state(sdev, SDEV_CANCEL) != 0)
    + if (scsi_pm_device_stop(sdev) != 0)
    return;

    bsg_unregister_queue(sdev->request_queue);
    diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
    index 37df8bb..16add9a 100644
    --- a/drivers/scsi/sd.c
    +++ b/drivers/scsi/sd.c
    @@ -61,6 +61,7 @@ #include <scsi/scsicam.h>
    #include <scsi/sd.h>

    #include "scsi_logging.h"
    +#include "scsi_priv.h"

    MODULE_AUTHOR("Eric Youngdale");
    MODULE_DESCRIPTION("SCSI disk (sd) driver");
    @@ -1649,6 +1650,10 @@ static int sd_probe(struct device *dev)
    if (error)
    goto out_put;

    + error = scsi_autoresume_device(sdp);
    + if (error)
    + goto out_put;
    +
    sdkp->device = sdp;
    sdkp->driver = &sd_template;
    sdkp->disk = gd;
    @@ -1668,7 +1673,7 @@ static int sd_probe(struct device *dev)
    strncpy(sdkp->cdev.class_id, sdp->sdev_gendev.bus_id, BUS_ID_SIZE);

    if (class_device_add(&sdkp->cdev))
    - goto out_put;
    + goto out_suspend;

    get_device(&sdp->sdev_gendev);

    @@ -1708,8 +1713,12 @@ static int sd_probe(struct device *dev)
    sd_printk(KERN_NOTICE, sdkp, "Attached SCSI %sdisk\n",
    sdp->removable ? "removable " : "");

    + scsi_use_ULD_pm(sdp, 1);
    + scsi_autosuspend_device(sdp);
    return 0;

    + out_suspend:
    + scsi_autosuspend_device(sdp);
    out_put:
    put_disk(gd);
    out_free:
    @@ -1735,6 +1744,7 @@ static int sd_remove(struct device *dev)

    class_device_del(&sdkp->cdev);
    del_gendisk(sdkp->disk);
    + scsi_use_ULD_pm(sdkp->device, 0);
    sd_shutdown(dev);

    mutex_lock(&sd_ref_mutex);
    diff --git a/drivers/scsi/sg.c b/drivers/scsi/sg.c
    index e5156aa..27e3669 100644
    --- a/drivers/scsi/sg.c
    +++ b/drivers/scsi/sg.c
    @@ -57,6 +57,7 @@ #include <scsi/scsi_driver.h>
    #include <scsi/scsi_ioctl.h>
    #include <scsi/sg.h>

    +#include "scsi_priv.h"
    #include "scsi_logging.h"

    #ifdef CONFIG_SCSI_PROC_FS
    @@ -226,6 +227,7 @@ sg_open(struct inode *inode, struct file
    Sg_fd *sfp;
    int res;
    int retval;
    + int autoresume_rc = 1;

    nonseekable_open(inode, filp);
    SCSI_LOG_TIMEOUT(3, printk("sg_open: dev=%d, flags=0x%x\n", dev, flags));
    @@ -241,6 +243,10 @@ sg_open(struct inode *inode, struct file
    if (retval)
    return retval;

    + retval = autoresume_rc = scsi_autoresume_device(sdp->device);
    + if (retval)
    + goto error_out;
    +
    if (!((flags & O_NONBLOCK) ||
    scsi_block_when_processing_errors(sdp->device))) {
    retval = -ENXIO;
    @@ -298,6 +304,8 @@ sg_open(struct inode *inode, struct file
    return 0;

    error_out:
    + if (autoresume_rc == 0)
    + scsi_autosuspend_device(sdp->device);
    scsi_device_put(sdp->device);
    return retval;
    }
    @@ -313,6 +321,8 @@ sg_release(struct inode *inode, struct f
    return -ENXIO;
    SCSI_LOG_TIMEOUT(3, printk("sg_release: %s\n", sdp->disk->disk_name));
    sg_fasync(-1, filp, 0); /* remove filp from async notification list */
    + scsi_autosuspend_device(sdp->device);
    +
    if (0 == sg_remove_sfp(sdp, sfp)) { /* Returns 1 when sdp gone */
    if (!sdp->detached) {
    scsi_device_put(sdp->device);
    diff --git a/drivers/usb/storage/scsiglue.c b/drivers/usb/storage/scsiglue.c
    index 8c1e295..ca954fa 100644
    --- a/drivers/usb/storage/scsiglue.c
    +++ b/drivers/usb/storage/scsiglue.c
    @@ -299,10 +299,15 @@ static int device_reset(struct scsi_cmnd

    US_DEBUGP("%s called\n", __FUNCTION__);

    - /* lock the device pointers and do the reset */
    - mutex_lock(&(us->dev_mutex));
    - result = us->transport_reset(us);
    - mutex_unlock(&us->dev_mutex);
    + result = usb_autopm_get_interface(us->pusb_intf);
    + if (result == 0) {
    +
    + /* lock the device pointers and do the reset */
    + mutex_lock(&(us->dev_mutex));
    + result = us->transport_reset(us);
    + mutex_unlock(&us->dev_mutex);
    + usb_autopm_put_interface(us->pusb_intf);
    + }

    return result < 0 ? FAILED : SUCCESS;
    }
    @@ -345,6 +350,24 @@ void usb_stor_report_bus_reset(struct us
    scsi_unlock(host);
    }

    +/* The host and its devices are all idle so we can autosuspend */
    +static int autosuspend(struct Scsi_Host *host)
    +{
    + struct us_data *us = host_to_us(host);
    +
    + usb_autopm_put_interface(us->pusb_intf);
    + return 0;
    +}
    +
    +/* The host needs to be autoresumed */
    +static int autoresume(struct Scsi_Host *host)
    +{
    + struct us_data *us = host_to_us(host);
    +
    + return usb_autopm_get_interface(us->pusb_intf);
    +}
    +
    +
    /***********************************************************************
    * /proc/scsi/ functions
    ***********************************************************************/
    @@ -471,6 +494,10 @@ struct scsi_host_template usb_stor_host_
    .eh_device_reset_handler = device_reset,
    .eh_bus_reset_handler = bus_reset,

    + /* dynamic power management */
    + .autosuspend = autosuspend,
    + .autoresume = autoresume,
    +
    /* queue commands only, only one command per LUN */
    .can_queue = 1,
    .cmd_per_lun = 1,
    diff --git a/drivers/usb/storage/usb.c b/drivers/usb/storage/usb.c
    index ac6114e..a574646 100644
    --- a/drivers/usb/storage/usb.c
    +++ b/drivers/usb/storage/usb.c
    @@ -184,16 +184,14 @@ static int storage_suspend(struct usb_in
    {
    struct us_data *us = usb_get_intfdata(iface);

    + US_DEBUGP("%s\n", __FUNCTION__);
    +
    /* Wait until no command is running */
    mutex_lock(&us->dev_mutex);

    - US_DEBUGP("%s\n", __FUNCTION__);
    if (us->suspend_resume_hook)
    (us->suspend_resume_hook)(us, US_SUSPEND);

    - /* When runtime PM is working, we'll set a flag to indicate
    - * whether we should autoresume when a SCSI request arrives. */
    -
    mutex_unlock(&us->dev_mutex);
    return 0;
    }
    @@ -202,13 +200,10 @@ static int storage_resume(struct usb_int
    {
    struct us_data *us = usb_get_intfdata(iface);

    - mutex_lock(&us->dev_mutex);
    -
    US_DEBUGP("%s\n", __FUNCTION__);
    if (us->suspend_resume_hook)
    (us->suspend_resume_hook)(us, US_RESUME);

    - mutex_unlock(&us->dev_mutex);
    return 0;
    }

    @@ -928,6 +923,7 @@ static int usb_stor_scan_thread(void * _
    /* Should we unbind if no devices were detected? */
    }

    + usb_autopm_put_interface(us->pusb_intf);
    complete_and_exit(&us->scanning_done, 0);
    }

    @@ -957,6 +953,9 @@ static int storage_probe(struct usb_inte
    return -ENOMEM;
    }

    + /* Don't autosuspend until the SCSI core tells us */
    + usb_autopm_get_interface(intf);
    +
    /*
    * Allow 16-byte CDBs and thus > 2TB
    */
    @@ -1017,6 +1016,7 @@ static int storage_probe(struct usb_inte
    goto BadDevice;
    }

    + usb_autopm_get_interface(intf); /* dropped in the scanning thread */
    wake_up_process(th);

    return 0;
    @@ -1054,6 +1054,7 @@ #endif
    .pre_reset = storage_pre_reset,
    .post_reset = storage_post_reset,
    .id_table = storage_usb_ids,
    + .supports_autosuspend = 1,
    };

    static int __init usb_stor_init(void)
    diff --git a/include/scsi/scsi_device.h b/include/scsi/scsi_device.h
    index ab7acbe..1c181c4 100644
    --- a/include/scsi/scsi_device.h
    +++ b/include/scsi/scsi_device.h
    @@ -36,8 +36,9 @@ enum scsi_device_state {
    * Only error handler commands allowed */
    SDEV_DEL, /* device deleted
    * no commands allowed */
    - SDEV_QUIESCE, /* Device quiescent. No block commands
    - * will be accepted, only specials (which
    + SDEV_QUIESCE, /* Device quiescent or suspended.
    + * No block commands will be accepted,
    + * only specials (which
    * originate in the mid-layer) */
    SDEV_OFFLINE, /* Device offlined (by error handling or
    * user request */
    @@ -161,6 +162,24 @@ #define SCSI_DEFAULT_DEVICE_BLOCKED 3

    struct execute_work ew; /* used to get process context on put */

    +#ifdef CONFIG_SCSI_DYNAMIC_PM
    + struct mutex pm_mutex; /* protect PM data & operations */
    + struct work_struct autoresume_work;
    +
    + unsigned long last_busy; /* time of last use */
    + int autosuspend_delay; /* delay in jiffies */
    + int pm_usage_cnt; /* usage counter for autosuspend */
    +
    + unsigned is_suspended:1;
    + unsigned pm_in_progress:1; /* performing suspend or resume */
    + unsigned auto_pm:1; /* doing autosuspend or autoresume */
    + unsigned autosuspend_disabled:1; /* autosuspend & autoresume */
    + unsigned autoresume_disabled:1; /* disabled by the user */
    + unsigned skip_sys_resume:1; /* skip the next system resume */
    + unsigned use_ULD_pm:1; /* call the Upper-Level Driver's
    + * suspend/resume methods */
    +#endif /* CONFIG_SCSI_DYNAMIC_PM */
    +
    enum scsi_device_state sdev_state;
    unsigned long sdev_data[0];
    } __attribute__((aligned(sizeof(unsigned long))));
    diff --git a/include/scsi/scsi_host.h b/include/scsi/scsi_host.h
    index 530ff4c..906c4b5 100644
    --- a/include/scsi/scsi_host.h
    +++ b/include/scsi/scsi_host.h
    @@ -176,6 +176,22 @@ #endif
    int (* eh_host_reset_handler)(struct scsi_cmnd *);

    /*
    + * Power management routines. These are optional; you should
    + * implement them if you want your LLD to perform dynamic Power
    + * Management. The autosuspend method will be called whenever
    + * all the devices below a host have been suspended (are in an
    + * idle state), at which time the host adapter can safely be
    + * autosuspended. The autoresume method will be called whenever
    + * a suspended host must be resumed for one of its devices to
    + * carry out a command. Both routines are always called in a
    + * process context with interrupts enabled.
    + *
    + * Status: OPTIONAL
    + */
    + int (* autosuspend)(struct Scsi_Host *);
    + int (* autoresume)(struct Scsi_Host *);
    +
    + /*
    * Before the mid layer attempts to scan for a new device where none
    * currently exists, it will call this entry in your driver. Should
    * your driver need to allocate any structs or perform any other init
    @@ -657,6 +673,14 @@ struct Scsi_Host {
    struct device shost_gendev;
    struct class_device shost_classdev;

    +#ifdef CONFIG_SCSI_DYNAMIC_PM
    + struct mutex pm_mutex; /* protect PM data & operations */
    + struct delayed_work autosuspend_work;
    +
    + int pm_usage_cnt; /* usage counter for autosuspend */
    + unsigned is_suspended:1;
    +#endif /* CONFIG_SCSI_DYNAMIC_PM */
    +
    /*
    * List of hosts per template.
    *
    \
     
     \ /
      Last update: 2008-02-25 14:49    [W:6.302 / U:0.724 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site