From patchwork Thu Jul 26 10:05:25 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: aaron lu X-Patchwork-Id: 1241081 Return-Path: X-Original-To: patchwork-linux-pm@patchwork.kernel.org Delivered-To: patchwork-process-083081@patchwork1.kernel.org Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by patchwork1.kernel.org (Postfix) with ESMTP id C67744025E for ; Thu, 26 Jul 2012 10:07:54 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752020Ab2GZKHH (ORCPT ); Thu, 26 Jul 2012 06:07:07 -0400 Received: from va3ehsobe010.messaging.microsoft.com ([216.32.180.30]:18753 "EHLO va3outboundpool.messaging.microsoft.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751872Ab2GZKGW (ORCPT ); Thu, 26 Jul 2012 06:06:22 -0400 Received: from mail78-va3-R.bigfish.com (10.7.14.251) by VA3EHSOBE009.bigfish.com (10.7.40.29) with Microsoft SMTP Server id 14.1.225.23; Thu, 26 Jul 2012 10:06:22 +0000 Received: from mail78-va3 (localhost [127.0.0.1]) by mail78-va3-R.bigfish.com (Postfix) with ESMTP id E681A6015C; Thu, 26 Jul 2012 10:06:21 +0000 (UTC) X-Forefront-Antispam-Report: CIP:163.181.249.108; KIP:(null); UIP:(null); IPV:NLI; H:ausb3twp01.amd.com; RD:none; EFVD:NLI X-SpamScore: 0 X-BigFish: VPS0(zzzz1202hzz8275bhz2dh668h839hd24he5bhf0ah107ah) Received: from mail78-va3 (localhost.localdomain [127.0.0.1]) by mail78-va3 (MessageSwitch) id 1343297179860107_11035; Thu, 26 Jul 2012 10:06:19 +0000 (UTC) Received: from VA3EHSMHS028.bigfish.com (unknown [10.7.14.250]) by mail78-va3.bigfish.com (Postfix) with ESMTP id C99234C00EB; Thu, 26 Jul 2012 10:06:19 +0000 (UTC) Received: from ausb3twp01.amd.com (163.181.249.108) by VA3EHSMHS028.bigfish.com (10.7.99.38) with Microsoft SMTP Server id 14.1.225.23; Thu, 26 Jul 2012 10:06:20 +0000 X-WSS-ID: 0M7RIQH-01-4I3-02 X-M-MSG: Received: from sausexedgep02.amd.com (sausexedgep02-ext.amd.com [163.181.249.73]) (using TLSv1 with cipher AES128-SHA (128/128 bits)) (No client certificate requested) by ausb3twp01.amd.com (Axway MailGate 3.8.1) with ESMTP id 22A6D10280A3; Thu, 26 Jul 2012 05:06:16 -0500 (CDT) Received: from sausexhtp01.amd.com (163.181.3.165) by sausexedgep02.amd.com (163.181.36.59) with Microsoft SMTP Server (TLS) id 8.3.192.1; Thu, 26 Jul 2012 05:06:32 -0500 Received: from sausexmb1.amd.com (163.181.3.156) by sausexhtp01.amd.com (163.181.3.165) with Microsoft SMTP Server id 8.3.213.0; Thu, 26 Jul 2012 05:06:16 -0500 Received: from storexbh1.amd.com ([10.1.1.17]) by sausexmb1.amd.com with Microsoft SMTPSVC(6.0.3790.3959); Thu, 26 Jul 2012 05:06:16 -0500 Received: from sshaexmb1.amd.com ([10.237.2.11]) by storexbh1.amd.com with Microsoft SMTPSVC(6.0.3790.4675); Thu, 26 Jul 2012 06:06:14 -0400 Received: from aarontestpc.amd.com ([10.237.73.70]) by sshaexmb1.amd.com with Microsoft SMTPSVC(6.0.3790.4675); Thu, 26 Jul 2012 18:05:58 +0800 From: Aaron Lu To: James Bottomley , Alan Stern CC: Jeff Garzik , Lin Ming , Jeff Wu , , , , , Aaron Lu , Aaron Lu Subject: [PATCH v3 3/7] scsi: sr: support zero power ODD(ZPODD) Date: Thu, 26 Jul 2012 18:05:25 +0800 Message-ID: <1343297129-28174-4-git-send-email-aaron.lu@amd.com> X-Mailer: git-send-email 1.7.11.3 In-Reply-To: <1343297129-28174-1-git-send-email-aaron.lu@amd.com> References: <1343297129-28174-1-git-send-email-aaron.lu@amd.com> X-OriginalArrivalTime: 26 Jul 2012 10:05:58.0487 (UTC) FILETIME=[40D69670:01CD6B16] MIME-Version: 1.0 X-OriginatorOrg: amd.com Sender: linux-pm-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-pm@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, 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 Signed-off-by: Aaron Lu --- drivers/scsi/sr.c | 130 ++++++++++++++++++++++++++++++++++++++++++++- drivers/scsi/sr.h | 2 + include/scsi/scsi_device.h | 2 + 3 files changed, 133 insertions(+), 1 deletion(-) diff --git a/drivers/scsi/sr.c b/drivers/scsi/sr.c index abfefab..2573a42 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 *dev, pm_message_t msg); +static int sr_resume(struct device *dev); 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 (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,61 @@ 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); + scsi_autopm_put_device(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 unsigned int sr_get_events(struct scsi_device *sdev) { u8 buf[8]; @@ -201,6 +262,38 @@ static unsigned int sr_get_events(struct scsi_device *sdev) 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; +} + + /* * This function checks to see if the media has been changed or eject * button has been pressed. It is possible that we have already @@ -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(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,10 @@ static int sr_remove(struct device *dev) { struct scsi_cd *cd = dev_get_drvdata(dev); + /* disable runtime pm and possibly resume the device */ + if (!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 7539f52..9d11981 100644 --- a/include/scsi/scsi_device.h +++ b/include/scsi/scsi_device.h @@ -155,6 +155,8 @@ 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 wce_default_on:1; /* Cache is ON by default */ + unsigned can_power_off:1; /* Device supports runtime power off */ + unsigned wakeup_by_user:1; /* user wakes up the ODD */ DECLARE_BITMAP(supported_events, SDEV_EVT_MAXBITS); /* supported events */ struct list_head event_list; /* asserted events */