diff options
Diffstat (limited to 'drivers/ata/libata-acpi.c')
-rw-r--r-- | drivers/ata/libata-acpi.c | 167 |
1 files changed, 133 insertions, 34 deletions
diff --git a/drivers/ata/libata-acpi.c b/drivers/ata/libata-acpi.c index 70b77e0899a8..9330b7922f62 100644 --- a/drivers/ata/libata-acpi.c +++ b/drivers/ata/libata-acpi.c @@ -29,14 +29,16 @@ enum { ATA_ACPI_FILTER_SETXFER = 1 << 0, ATA_ACPI_FILTER_LOCK = 1 << 1, + ATA_ACPI_FILTER_DIPM = 1 << 2, ATA_ACPI_FILTER_DEFAULT = ATA_ACPI_FILTER_SETXFER | - ATA_ACPI_FILTER_LOCK, + ATA_ACPI_FILTER_LOCK | + ATA_ACPI_FILTER_DIPM, }; static unsigned int ata_acpi_gtf_filter = ATA_ACPI_FILTER_DEFAULT; module_param_named(acpi_gtf_filter, ata_acpi_gtf_filter, int, 0644); -MODULE_PARM_DESC(acpi_gtf_filter, "filter mask for ACPI _GTF commands, set to filter out (0x1=set xfermode, 0x2=lock/freeze lock)"); +MODULE_PARM_DESC(acpi_gtf_filter, "filter mask for ACPI _GTF commands, set to filter out (0x1=set xfermode, 0x2=lock/freeze lock, 0x4=DIPM)"); #define NO_PORT_MULT 0xffff #define SATA_ADR(root, pmp) (((root) << 16) | (pmp)) @@ -118,19 +120,86 @@ static void ata_acpi_associate_ide_port(struct ata_port *ap) ap->pflags |= ATA_PFLAG_INIT_GTM_VALID; } +static void ata_acpi_eject_device(acpi_handle handle) +{ + struct acpi_object_list arg_list; + union acpi_object arg; + + arg_list.count = 1; + arg_list.pointer = &arg; + arg.type = ACPI_TYPE_INTEGER; + arg.integer.value = 1; + + if (ACPI_FAILURE(acpi_evaluate_object(handle, "_EJ0", + &arg_list, NULL))) + printk(KERN_ERR "Failed to evaluate _EJ0!\n"); +} + +/* @ap and @dev are the same as ata_acpi_handle_hotplug() */ +static void ata_acpi_detach_device(struct ata_port *ap, struct ata_device *dev) +{ + if (dev) + dev->flags |= ATA_DFLAG_DETACH; + else { + struct ata_link *tlink; + struct ata_device *tdev; + + ata_port_for_each_link(tlink, ap) + ata_link_for_each_dev(tdev, tlink) + tdev->flags |= ATA_DFLAG_DETACH; + } + + ata_port_schedule_eh(ap); +} + +/** + * ata_acpi_handle_hotplug - ACPI event handler backend + * @ap: ATA port ACPI event occurred + * @dev: ATA device ACPI event occurred (can be NULL) + * @event: ACPI event which occurred + * @is_dock_event: boolean indicating whether the event was a dock one + * + * All ACPI bay / device realted events end up in this function. If + * the event is port-wide @dev is NULL. If the event is specific to a + * device, @dev points to it. + * + * Hotplug (as opposed to unplug) notification is always handled as + * port-wide while unplug only kills the target device on device-wide + * event. + * + * LOCKING: + * ACPI notify handler context. May sleep. + */ static void ata_acpi_handle_hotplug(struct ata_port *ap, struct ata_device *dev, - u32 event) + u32 event, int is_dock_event) { char event_string[12]; char *envp[] = { event_string, NULL }; - struct ata_eh_info *ehi; + struct ata_eh_info *ehi = &ap->link.eh_info; struct kobject *kobj = NULL; int wait = 0; unsigned long flags; + acpi_handle handle, tmphandle; + unsigned long sta; + acpi_status status; - if (!ap) - ap = dev->link->ap; - ehi = &ap->link.eh_info; + if (dev) { + if (dev->sdev) + kobj = &dev->sdev->sdev_gendev.kobj; + handle = dev->acpi_handle; + } else { + kobj = &ap->dev->kobj; + handle = ap->acpi_handle; + } + + status = acpi_get_handle(handle, "_EJ0", &tmphandle); + if (ACPI_FAILURE(status)) + /* This device does not support hotplug */ + return; + + if (event == ACPI_NOTIFY_BUS_CHECK || + event == ACPI_NOTIFY_DEVICE_CHECK) + status = acpi_evaluate_integer(handle, "_STA", NULL, &sta); spin_lock_irqsave(ap->lock, flags); @@ -138,57 +207,79 @@ static void ata_acpi_handle_hotplug(struct ata_port *ap, struct ata_device *dev, case ACPI_NOTIFY_BUS_CHECK: case ACPI_NOTIFY_DEVICE_CHECK: ata_ehi_push_desc(ehi, "ACPI event"); - ata_ehi_hotplugged(ehi); - ata_port_freeze(ap); - break; + if (ACPI_FAILURE(status)) { + ata_port_printk(ap, KERN_ERR, + "acpi: failed to determine bay status (0x%x)\n", + status); + break; + } + + if (sta) { + ata_ehi_hotplugged(ehi); + ata_port_freeze(ap); + } else { + /* The device has gone - unplug it */ + ata_acpi_detach_device(ap, dev); + wait = 1; + } + break; case ACPI_NOTIFY_EJECT_REQUEST: ata_ehi_push_desc(ehi, "ACPI event"); - if (dev) - dev->flags |= ATA_DFLAG_DETACH; - else { - struct ata_link *tlink; - struct ata_device *tdev; - - ata_port_for_each_link(tlink, ap) - ata_link_for_each_dev(tdev, tlink) - tdev->flags |= ATA_DFLAG_DETACH; - } - ata_port_schedule_eh(ap); + if (!is_dock_event) + break; + + /* undock event - immediate unplug */ + ata_acpi_detach_device(ap, dev); wait = 1; break; } - if (dev) { - if (dev->sdev) - kobj = &dev->sdev->sdev_gendev.kobj; - } else - kobj = &ap->dev->kobj; + /* make sure kobj doesn't go away while ap->lock is released */ + kobject_get(kobj); + + spin_unlock_irqrestore(ap->lock, flags); - if (kobj) { + if (wait) { + ata_port_wait_eh(ap); + ata_acpi_eject_device(handle); + } + + if (kobj && !is_dock_event) { sprintf(event_string, "BAY_EVENT=%d", event); kobject_uevent_env(kobj, KOBJ_CHANGE, envp); } - spin_unlock_irqrestore(ap->lock, flags); + kobject_put(kobj); +} - if (wait) - ata_port_wait_eh(ap); +static void ata_acpi_dev_notify_dock(acpi_handle handle, u32 event, void *data) +{ + struct ata_device *dev = data; + + ata_acpi_handle_hotplug(dev->link->ap, dev, event, 1); +} + +static void ata_acpi_ap_notify_dock(acpi_handle handle, u32 event, void *data) +{ + struct ata_port *ap = data; + + ata_acpi_handle_hotplug(ap, NULL, event, 1); } static void ata_acpi_dev_notify(acpi_handle handle, u32 event, void *data) { struct ata_device *dev = data; - ata_acpi_handle_hotplug(NULL, dev, event); + ata_acpi_handle_hotplug(dev->link->ap, dev, event, 0); } static void ata_acpi_ap_notify(acpi_handle handle, u32 event, void *data) { struct ata_port *ap = data; - ata_acpi_handle_hotplug(ap, NULL, event); + ata_acpi_handle_hotplug(ap, NULL, event, 0); } /** @@ -229,7 +320,7 @@ void ata_acpi_associate(struct ata_host *host) ata_acpi_ap_notify, ap); /* we might be on a docking station */ register_hotplug_dock_device(ap->acpi_handle, - ata_acpi_ap_notify, ap); + ata_acpi_ap_notify_dock, ap); } for (j = 0; j < ata_link_max_devices(&ap->link); j++) { @@ -241,7 +332,7 @@ void ata_acpi_associate(struct ata_host *host) ata_acpi_dev_notify, dev); /* we might be on a docking station */ register_hotplug_dock_device(dev->acpi_handle, - ata_acpi_dev_notify, dev); + ata_acpi_dev_notify_dock, dev); } } } @@ -604,6 +695,14 @@ static int ata_acpi_filter_tf(const struct ata_taskfile *tf, return 1; } + if (ata_acpi_gtf_filter & ATA_ACPI_FILTER_DIPM) { + /* inhibit enabling DIPM */ + if (tf->command == ATA_CMD_SET_FEATURES && + tf->feature == SETFEATURES_SATA_ENABLE && + tf->nsect == SATA_DIPM) + return 1; + } + return 0; } |