From patchwork Fri Jul 27 09:00:46 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Aaron Lu X-Patchwork-Id: 173597 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 19A6E2C0099 for ; Fri, 27 Jul 2012 19:04:15 +1000 (EST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752814Ab2G0JCu (ORCPT ); Fri, 27 Jul 2012 05:02:50 -0400 Received: from ch1ehsobe002.messaging.microsoft.com ([216.32.181.182]:10840 "EHLO ch1outboundpool.messaging.microsoft.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752415Ab2G0JCX (ORCPT ); Fri, 27 Jul 2012 05:02:23 -0400 Received: from mail200-ch1-R.bigfish.com (10.43.68.253) by CH1EHSOBE001.bigfish.com (10.43.70.51) with Microsoft SMTP Server id 14.1.225.23; Fri, 27 Jul 2012 09:02:22 +0000 Received: from mail200-ch1 (localhost [127.0.0.1]) by mail200-ch1-R.bigfish.com (Postfix) with ESMTP id C61CC32053C; Fri, 27 Jul 2012 09:02:22 +0000 (UTC) X-Forefront-Antispam-Report: CIP:163.181.249.109; KIP:(null); UIP:(null); IPV:NLI; H:ausb3twp02.amd.com; RD:none; EFVD:NLI X-SpamScore: 0 X-BigFish: VPS0(zzzz1202hzz8275bhz2dh668h839hd24he5bhf0ah107ah) Received: from mail200-ch1 (localhost.localdomain [127.0.0.1]) by mail200-ch1 (MessageSwitch) id 134337974062913_31245; Fri, 27 Jul 2012 09:02:20 +0000 (UTC) Received: from CH1EHSMHS009.bigfish.com (snatpool1.int.messaging.microsoft.com [10.43.68.251]) by mail200-ch1.bigfish.com (Postfix) with ESMTP id 0C4B3A005E; Fri, 27 Jul 2012 09:02:20 +0000 (UTC) Received: from ausb3twp02.amd.com (163.181.249.109) by CH1EHSMHS009.bigfish.com (10.43.70.9) with Microsoft SMTP Server id 14.1.225.23; Fri, 27 Jul 2012 09:02:19 +0000 X-WSS-ID: 0M7TAFS-02-6OQ-02 X-M-MSG: Received: from sausexedgep01.amd.com (sausexedgep01-ext.amd.com [163.181.249.72]) (using TLSv1 with cipher AES128-SHA (128/128 bits)) (No client certificate requested) by ausb3twp02.amd.com (Axway MailGate 3.8.1) with ESMTP id 277E4C80D2; Fri, 27 Jul 2012 04:02:16 -0500 (CDT) Received: from sausexhtp02.amd.com (163.181.3.152) by sausexedgep01.amd.com (163.181.36.54) with Microsoft SMTP Server (TLS) id 8.3.192.1; Fri, 27 Jul 2012 04:02:36 -0500 Received: from sausexmb1.amd.com (163.181.3.156) by sausexhtp02.amd.com (163.181.3.152) with Microsoft SMTP Server id 8.3.213.0; Fri, 27 Jul 2012 04:02:17 -0500 Received: from storexbh1.amd.com ([10.1.1.17]) by sausexmb1.amd.com with Microsoft SMTPSVC(6.0.3790.3959); Fri, 27 Jul 2012 04:02:17 -0500 Received: from sshaexmb1.amd.com ([10.237.2.11]) by storexbh1.amd.com with Microsoft SMTPSVC(6.0.3790.4675); Fri, 27 Jul 2012 05:02:15 -0400 Received: from aarontestpc.amd.com ([10.237.73.134]) by sshaexmb1.amd.com with Microsoft SMTPSVC(6.0.3790.4675); Fri, 27 Jul 2012 17:01:59 +0800 From: Aaron Lu To: James Bottomley , Jeff Garzik , Alan Stern , Sergei Shtylyov , Oliver Neukum CC: Jeff Wu , Lin Ming , , , , , Aaron Lu , Aaron Lu Subject: [PATCH v4 3/7] scsi: sr: support zero power ODD(ZPODD) Date: Fri, 27 Jul 2012 17:00:46 +0800 Message-ID: <1343379650-2867-4-git-send-email-aaron.lu@amd.com> X-Mailer: git-send-email 1.7.11.3 In-Reply-To: <1343379650-2867-1-git-send-email-aaron.lu@amd.com> References: <1343379650-2867-1-git-send-email-aaron.lu@amd.com> X-OriginalArrivalTime: 27 Jul 2012 09:01:59.0299 (UTC) FILETIME=[7AEABD30:01CD6BD6] MIME-Version: 1.0 X-OriginatorOrg: amd.com Sender: linux-ide-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-ide@vger.kernel.org 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 --- 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 #include #include +#include #include #include @@ -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 */