From patchwork Mon Jun 18 06:26:05 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lin Ming X-Patchwork-Id: 165390 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 37803B70ED for ; Mon, 18 Jun 2012 16:27:22 +1000 (EST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1754959Ab2FRG0t (ORCPT ); Mon, 18 Jun 2012 02:26:49 -0400 Received: from mga11.intel.com ([192.55.52.93]:63878 "EHLO mga11.intel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753603Ab2FRG0r (ORCPT ); Mon, 18 Jun 2012 02:26:47 -0400 Received: from fmsmga002.fm.intel.com ([10.253.24.26]) by fmsmga102.fm.intel.com with ESMTP; 17 Jun 2012 23:26:47 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="4.71,315,1320652800"; d="scan'208";a="181387385" Received: from bee.sh.intel.com (HELO bee.lkp.intel.com) ([10.239.97.14]) by fmsmga002.fm.intel.com with ESMTP; 17 Jun 2012 23:26:45 -0700 From: Lin Ming To: Jeff Garzik , Aaron Lu , Holger Macht , Matthew Garrett , Alan Cox , David Woodhouse Cc: linux-kernel@vger.kernel.org, linux-pm@vger.kernel.org, linux-scsi@vger.kernel.org, linux-ide@vger.kernel.org, linux-acpi@vger.kernel.org Subject: [PATCH v5 11/12] [SCSI] sr: support zero power ODD Date: Mon, 18 Jun 2012 14:26:05 +0800 Message-Id: <1340000766-129148-12-git-send-email-ming.m.lin@intel.com> X-Mailer: git-send-email 1.7.10 In-Reply-To: <1340000766-129148-1-git-send-email-ming.m.lin@intel.com> References: <1340000766-129148-1-git-send-email-ming.m.lin@intel.com> Sender: linux-ide-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-ide@vger.kernel.org From: Aaron Lu If there is no media inside the ODD and the ODD's tray is closed, it's safe to omit power. When user ejects the tray by pressing the button or inserts a disc into the slot, the ODD will gets resumed from acpi notifier handler. Signed-off-by: Aaron Lu Signed-off-by: Lin Ming --- drivers/ata/libata-acpi.c | 4 +- drivers/scsi/sr.c | 128 +++++++++++++++++++++++++++++++++++++++++++- drivers/scsi/sr.h | 2 + include/scsi/scsi_device.h | 1 + 4 files changed, 133 insertions(+), 2 deletions(-) diff --git a/drivers/ata/libata-acpi.c b/drivers/ata/libata-acpi.c index 0c54d1e..08edebf 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..72488c2 100644 --- a/drivers/scsi/sr.c +++ b/drivers/scsi/sr.c @@ -46,6 +46,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, }; @@ -167,6 +172,58 @@ static void scsi_cd_put(struct scsi_cd *cd) 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 suspend */ + if (msg.event == PM_EVENT_SUSPEND) + 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 +258,37 @@ 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 +303,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 +367,22 @@ static unsigned int sr_check_events(struct cdrom_device_info *cdi, 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)) { + pm_runtime_mark_last_busy(&cd->device->sdev_gendev); + pm_runtime_put_autosuspend(&cd->device->sdev_gendev); + } + } + if (cd->ignore_get_event) return events; @@ -704,6 +817,15 @@ static int sr_probe(struct device *dev) 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); disk->private_data = &cd->driver; @@ -988,6 +1110,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)) + pm_runtime_get_sync(dev); + 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..fd5c550 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; /* we should request autosuspend only once */ + 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 1237fac..65b9732 100644 --- a/include/scsi/scsi_device.h +++ b/include/scsi/scsi_device.h @@ -153,6 +153,7 @@ struct scsi_device { unsigned no_read_capacity_16:1; /* Avoid READ_CAPACITY_16 cmds */ 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 */ DECLARE_BITMAP(supported_events, SDEV_EVT_MAXBITS); /* supported events */ struct list_head event_list; /* asserted events */