|
|
|
[PATCH v4 3/7] scsi: sr: support zero power ODD(ZPODD) | |
| [Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
|
|
The ODD will be placed into suspend state when:
1 For tray type ODD, no media inside and door closed;
2 For slot type ODD, no media inside;
And together with ACPI, when we suspend the ODD's parent(the port it
attached to), we will omit the power altogether to reduce power
consumption(done in libata-acpi.c).
The ODD can be resumed either by user or by software.
For user to resume the suspended ODD:
1 For tray type ODD, press the eject button;
2 For slot type ODD, insert a disc;
Once such events happened, an ACPI notification will be sent and in our
handler, we will power up the ODD and set its status back to
active(again in libata-acpi.c).
For software to resume the suspended ODD, we did this in ODD's
open/release function: we scsi_autopm_get/put_device in scsi_cd_get/put.
On old distros, the udisk daemon will poll the ODD and thus ODD will be
open/closed every 2 seconds. To make use of ZPODD, udisks' poll has to
be inhibited:
$ udisks --inhibit-polling /dev/sr0
All of the above depends on if the device can be powered off runtime,
which is reflected by the can_power_off flag.
Signed-off-by: Aaron Lu <aaron.lu@xxxxxxx>
---
drivers/ata/libata-acpi.c | 4 +-
drivers/scsi/sr.c | 131 ++++++++++++++++++++++++++++++++++++++++++++-
drivers/scsi/sr.h | 2 +
include/scsi/scsi_device.h | 1 +
4 files changed, 136 insertions(+), 2 deletions(-)
diff --git a/drivers/ata/libata-acpi.c b/drivers/ata/libata-acpi.c
index 902b5a4..a2b16c9 100644
--- a/drivers/ata/libata-acpi.c
+++ b/drivers/ata/libata-acpi.c
@@ -985,8 +985,10 @@ static void ata_acpi_wake_dev(acpi_handle handle, u32 event, void *context)
struct ata_device *ata_dev = context;
if (event == ACPI_NOTIFY_DEVICE_WAKE && ata_dev &&
- pm_runtime_suspended(&ata_dev->sdev->sdev_gendev))
+ pm_runtime_suspended(&ata_dev->sdev->sdev_gendev)) {
+ ata_dev->sdev->wakeup_by_user = 1;
scsi_autopm_get_device(ata_dev->sdev);
+ }
}
static void ata_acpi_add_pm_notifier(struct ata_device *dev)
diff --git a/drivers/scsi/sr.c b/drivers/scsi/sr.c
index abfefab..acfd10a 100644
--- a/drivers/scsi/sr.c
+++ b/drivers/scsi/sr.c
@@ -45,6 +45,7 @@
#include <linux/blkdev.h>
#include <linux/mutex.h>
#include <linux/slab.h>
+#include <linux/pm_runtime.h>
#include <asm/uaccess.h>
#include <scsi/scsi.h>
@@ -79,6 +80,8 @@ static DEFINE_MUTEX(sr_mutex);
static int sr_probe(struct device *);
static int sr_remove(struct device *);
static int sr_done(struct scsi_cmnd *);
+static int sr_suspend(struct device *, pm_message_t msg);
+static int sr_resume(struct device *);
static struct scsi_driver sr_template = {
.owner = THIS_MODULE,
@@ -86,6 +89,8 @@ static struct scsi_driver sr_template = {
.name = "sr",
.probe = sr_probe,
.remove = sr_remove,
+ .suspend = sr_suspend,
+ .resume = sr_resume,
},
.done = sr_done,
};
@@ -147,8 +152,12 @@ static inline struct scsi_cd *scsi_cd_get(struct gendisk *disk)
kref_get(&cd->kref);
if (scsi_device_get(cd->device))
goto out_put;
+ if (cd->device->can_power_off && scsi_autopm_get_device(cd->device))
+ goto out_pm;
goto out;
+ out_pm:
+ scsi_device_put(cd->device);
out_put:
kref_put(&cd->kref, sr_kref_release);
cd = NULL;
@@ -164,9 +173,93 @@ static void scsi_cd_put(struct scsi_cd *cd)
mutex_lock(&sr_ref_mutex);
kref_put(&cd->kref, sr_kref_release);
scsi_device_put(sdev);
+ if (sdev->can_power_off)
+ scsi_autopm_put_device_autosuspend(sdev);
mutex_unlock(&sr_ref_mutex);
}
+static int sr_suspend(struct device *dev, pm_message_t msg)
+{
+ int poweroff;
+ struct scsi_sense_hdr sshdr;
+ struct scsi_cd *cd = dev_get_drvdata(dev);
+
+ /* no action for system pm */
+ if (!PMSG_IS_AUTO(msg))
+ return 0;
+
+ /* do another TUR to see if the ODD is still ready to be powered off */
+ scsi_test_unit_ready(cd->device, SR_TIMEOUT, MAX_RETRIES, &sshdr);
+
+ if (cd->cdi.mask & CDC_CLOSE_TRAY)
+ /* no media for caddy/slot type ODD */
+ poweroff = scsi_sense_valid(&sshdr) && sshdr.asc == 0x3a;
+ else
+ /* no media and door closed for tray type ODD */
+ poweroff = scsi_sense_valid(&sshdr) && sshdr.asc == 0x3a &&
+ sshdr.ascq == 0x01;
+
+ if (!poweroff) {
+ pm_runtime_get_noresume(dev);
+ atomic_set(&cd->suspend_count, 1);
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static int sr_resume(struct device *dev)
+{
+ struct scsi_cd *cd;
+ struct scsi_sense_hdr sshdr;
+
+ cd = dev_get_drvdata(dev);
+
+ /* get the disk ready */
+ scsi_test_unit_ready(cd->device, SR_TIMEOUT, MAX_RETRIES, &sshdr);
+
+ /* if user wakes up the ODD, eject the tray */
+ if (cd->device->wakeup_by_user) {
+ cd->device->wakeup_by_user = 0;
+ if (!(cd->cdi.mask & CDC_CLOSE_TRAY))
+ sr_tray_move(&cd->cdi, 1);
+ atomic_set(&cd->suspend_count, 1);
+ }
+
+ return 0;
+}
+
+static int sr_unit_load_done(struct scsi_cd *cd)
+{
+ u8 buf[8];
+ u8 cmd[] = { GET_EVENT_STATUS_NOTIFICATION,
+ 1, /* polled */
+ 0, 0, /* reserved */
+ 1 << 6, /* notification class: Device Busy */
+ 0, 0, /* reserved */
+ 0, sizeof(buf), /* allocation length */
+ 0, /* control */
+ };
+ struct event_header *eh = (void *)buf;
+ struct device_busy_event_desc *desc = (void *)(buf + 4);
+ struct scsi_sense_hdr sshdr;
+ int result;
+
+ result = scsi_execute_req(cd->device, cmd, DMA_FROM_DEVICE, buf,
+ sizeof(buf), &sshdr, SR_TIMEOUT, MAX_RETRIES, NULL);
+
+ if (result || be16_to_cpu(eh->data_len) < sizeof(*desc))
+ return 0;
+
+ if (eh->nea || eh->notification_class != 0x6)
+ return 0;
+
+ if (desc->device_busy_event == 2 && desc->device_busy_status == 0)
+ return 1;
+ else
+ return 0;
+}
+
static unsigned int sr_get_events(struct scsi_device *sdev)
{
u8 buf[8];
@@ -215,12 +308,21 @@ static unsigned int sr_check_events(struct cdrom_device_info *cdi,
bool last_present;
struct scsi_sense_hdr sshdr;
unsigned int events;
- int ret;
+ int ret, poweroff;
/* no changer support */
if (CDSL_CURRENT != slot)
return 0;
+ if (pm_runtime_suspended(&cd->device->sdev_gendev))
+ return 0;
+
+ /* if the logical unit just finished loading/unloading, do a TUR */
+ if (cd->device->can_power_off && cd->dbml && sr_unit_load_done(cd)) {
+ events = 0;
+ goto do_tur;
+ }
+
events = sr_get_events(cd->device);
cd->get_event_changed |= events & DISK_EVENT_MEDIA_CHANGE;
@@ -270,6 +372,20 @@ do_tur:
cd->tur_changed = true;
}
+ if (cd->device->can_power_off && !cd->media_present) {
+ if (cd->cdi.mask & CDC_CLOSE_TRAY)
+ poweroff = 1;
+ else
+ poweroff = sshdr.ascq == 0x01;
+ /*
+ * This function might be called concurrently by a kernel
+ * thread (in-kernel polling) and old versions of udisks,
+ * to avoid put the device twice, an atomic operation is used.
+ */
+ if (poweroff && atomic_add_unless(&cd->suspend_count, -1, 0))
+ scsi_autopm_put_device_autosuspend(cd->device);
+ }
+
if (cd->ignore_get_event)
return events;
@@ -703,6 +819,14 @@ static int sr_probe(struct device *dev)
get_capabilities(cd);
blk_queue_prep_rq(sdev->request_queue, sr_prep_fn);
sr_vendor_init(cd);
+ /* zero power support */
+ if (sdev->can_power_off) {
+ check_dbml(cd);
+ /* default delay time is 3 minutes */
+ pm_runtime_set_autosuspend_delay(dev, 180 * 1000);
+ pm_runtime_use_autosuspend(dev);
+ atomic_set(&cd->suspend_count, 1);
+ }
disk->driverfs_dev = &sdev->sdev_gendev;
set_capacity(disk, cd->capacity);
@@ -988,6 +1112,11 @@ static int sr_remove(struct device *dev)
{
struct scsi_cd *cd = dev_get_drvdata(dev);
+ /* disable runtime pm and possibly resume the device */
+ if (cd->device->can_power_off &&
+ !atomic_dec_and_test(&cd->suspend_count))
+ scsi_autopm_get_device(cd->device);
+
blk_queue_prep_rq(cd->device->request_queue, scsi_prep_fn);
del_gendisk(cd->disk);
diff --git a/drivers/scsi/sr.h b/drivers/scsi/sr.h
index 7cc40ad..649fe76 100644
--- a/drivers/scsi/sr.h
+++ b/drivers/scsi/sr.h
@@ -49,6 +49,8 @@ typedef struct scsi_cd {
bool get_event_changed:1; /* changed according to GET_EVENT */
bool ignore_get_event:1; /* GET_EVENT is unreliable, use TUR */
+ atomic_t suspend_count;
+
struct cdrom_device_info cdi;
/* We hold gendisk and scsi_device references on probe and use
* the refs on this kref to decide when to release them */
diff --git a/include/scsi/scsi_device.h b/include/scsi/scsi_device.h
index 3636146..4bc4ac4 100644
--- a/include/scsi/scsi_device.h
+++ b/include/scsi/scsi_device.h
@@ -155,6 +155,7 @@ struct scsi_device {
unsigned try_rc_10_first:1; /* Try READ_CAPACACITY_10 first */
unsigned is_visible:1; /* is the device visible in sysfs */
unsigned can_power_off:1; /* Device supports runtime power off */
+ unsigned wakeup_by_user:1; /* User wakes up the ODD */
unsigned wce_default_on:1; /* Cache is ON by default */
DECLARE_BITMAP(supported_events, SDEV_EVT_MAXBITS); /* supported events */
--
1.7.11.3
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
[Other Archives] [Linux Kernel Newbies] [Linux Driver Development] [Linux Kbuild] [Fedora Kernel] [Linux Kernel Testers] [Linux SH] [Linux Omap] [Linux Tape] [Linux Input] [Linux Kernel Janitors] [Linux Kernel Packagers] [Linux Doc] [Linux Man Pages] [Linux API] [Linux Memory Management] [Linux Modules] [Linux Standards] [Kernel Announce] [Netdev] [Git] [Linux PCI] Linux CAN Development [Linux I2C] [Linux RDMA] [Linux NUMA] [Netfilter] [Netfilter Devel] [SELinux] [Bugtraq] [FIO] [Linux Perf Users] [Linux Serial] [Linux PPP] [Linux ISDN] [Linux Next] [Kernel Stable Commits] [Linux Tip Commits] [Kernel MM Commits] [Linux Security Module] [AutoFS] [Filesystem Development] [Ext3 Filesystem] [Linux bcache] [Ext4 Filesystem] [Linux BTRFS] [Linux CEPH Filesystem] [Linux XFS] [XFS] [Linux NFS] [Linux CIFS] [Ecryptfs] [Linux NILFS] [Linux Cachefs] [Reiser FS] [Initramfs] [Linux FB Devel] [Linux OpenGL] [DRI Devel] [Fastboot] [Linux RT Users] [Linux RT Stable] [eCos] [Corosync] [Linux Clusters] [LVS Devel] [Hot Plug] [Linux Virtualization] [KVM] [KVM PPC] [KVM ia64] [Linux Containers] [Linux Hexagon] [Linux Cgroups] [Util Linux] [Wireless] [Linux Bluetooth] [Bluez Devel] [Ethernet Bridging] [Embedded Linux] [Barebox] [Linux MMC] [Linux IIO] [Sparse] [Smatch] [Linux Arch] [x86 Platform Driver] [Linux ACPI] [Linux IBM ACPI] [LM Sensors] [CPU Freq] [Linux Power Management] [Linmodems] [Linux DCCP] [Linux SCTP] [ALSA Devel] [Linux USB] [Linux PA RISC] [Linux Samsung SOC] [MIPS Linux] [IBM S/390 Linux] [ARM Linux] [ARM Kernel] [ARM MSM] [Tegra Devel] [Sparc Linux] [Linux Security] [Linux Sound] [Linux Media] [Video 4 Linux] [Linux IRDA Users] [Linux for the blind] [Linux RAID] [Linux ATA RAID] [Device Mapper] [Linux SCSI] [SCSI Target Devel] [Linux SCSI Target Infrastructure] [Linux IDE] [Linux SMP] [Linux AXP] [Linux Alpha] [Linux M68K] [Linux ia64] [Linux 8086] [Linux x86_64] [Linux Config] [Linux Apps] [Linux MSDOS] [Linux X.25] [Linux Crypto] [DM Crypt] [Linux Trace Users] [Linux Btrace] [Linux Watchdog] [Utrace Devel] [Linux C Programming] [Linux Assembly] [Dash] [DWARVES] [Hail Devel] [Linux Kernel Debugger] [Linux gcc] [Gcc Help] [X.Org] [Wine]
![]() |
![]() |
[Older Kernel Discussion] [Yosemite National Park Forum] [Large Format Photos] [Gimp] [Yosemite Photos] [Stuff]