diff options
Diffstat (limited to 'drivers/ata/libata-acpi.c')
| -rw-r--r-- | drivers/ata/libata-acpi.c | 148 | 
1 files changed, 117 insertions, 31 deletions
diff --git a/drivers/ata/libata-acpi.c b/drivers/ata/libata-acpi.c index 70b77e0899a..3ff8b14420d 100644 --- a/drivers/ata/libata-acpi.c +++ b/drivers/ata/libata-acpi.c @@ -118,19 +118,82 @@ 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 (dev) { +		if (dev->sdev) +			kobj = &dev->sdev->sdev_gendev.kobj; +		handle = dev->acpi_handle; +	} else { +		kobj = &ap->dev->kobj; +		handle = ap->acpi_handle; +	} -	if (!ap) -		ap = dev->link->ap; -	ehi = &ap->link.eh_info; +	status = acpi_get_handle(handle, "_EJ0", &tmphandle); +	if (ACPI_FAILURE(status)) +		/* This device does not support hotplug */ +		return;  	spin_lock_irqsave(ap->lock, flags); @@ -138,57 +201,80 @@ 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; +		status = acpi_evaluate_integer(handle, "_STA", NULL, &sta); +		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; -		} +		if (!is_dock_event) +			break; -		ata_port_schedule_eh(ap); +		/* 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 (wait) { +		ata_port_wait_eh(ap); +		ata_acpi_eject_device(handle); +	} -	if (kobj) { +	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 +315,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 +327,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);  			}  		}  	}  |