Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 664d080c authored by Holger Macht's avatar Holger Macht Committed by Jeff Garzik
Browse files

[libata] ACPI: Properly handle bay devices in dock stations



* Differentiate between bay devices in dock stations and others:

 - When an ACPI_NOTIFY_EJECT_REQUEST appears, just signal uevent to
   userspace (that is when the optional eject button on a bay device is
   pressed/pulled) giving the possibility to unmount file systems and to
   clean up. Also, only send uevent in case we get an EJECT_REQUEST
   without doing anything else. In other cases, you'll get an add/remove
   event because libata attaches/detaches the device.

 - In case of a dock event, which in turn signals an
   ACPI_NOTIFY_EJECT_REQUEST, immediately detach the device, because it
   may already have been gone

* In case of an ACPI_NOTIFY_DEVICE/BUS_CHECK, evaluate _STA to check if
  the device has been plugged or unplugged. If plugged, hotplug it, if
  unplugged, just signal event to userspace
  (initial patch by Matthew Garrett <mjg59@srcf.ucam.org>)

* Call ACPI _EJ0 for detached devices

Signed-off-by: default avatarHolger Macht <hmacht@suse.de>
Signed-off-by: default avatarJeff Garzik <jgarzik@redhat.com>
parent c3b25b32
Loading
Loading
Loading
Loading
+114 −51
Original line number Diff line number Diff line
@@ -118,12 +118,62 @@ static void ata_acpi_associate_ide_port(struct ata_port *ap)
		ap->pflags |= ATA_PFLAG_INIT_GTM_VALID;
}

static void ata_acpi_handle_hotplug(struct ata_port *ap, struct ata_device
				    *dev, u32 event)
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, 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;
@@ -131,87 +181,100 @@ static void ata_acpi_handle_hotplug(struct ata_port *ap, struct ata_device
	unsigned long sta;
	acpi_status status;

	if (!ap)
		ap = dev->link->ap;
	ehi = &ap->link.eh_info;

	spin_lock_irqsave(ap->lock, flags);

	if (dev)
	if (dev) {
		if (dev->sdev)
			kobj = &dev->sdev->sdev_gendev.kobj;
		handle = dev->acpi_handle;
	else
	} else {
		kobj = &ap->dev->kobj;
		handle = ap->acpi_handle;
	}

	status = acpi_get_handle(handle, "_EJ0", &tmphandle);
	if (ACPI_FAILURE(status)) {
		/* This device is not ejectable */
		spin_unlock_irqrestore(ap->lock, flags);
	if (ACPI_FAILURE(status))
		/* This device does not support hotplug */
		return;
	}

	status = acpi_evaluate_integer(handle, "_STA", NULL, &sta);
	if (ACPI_FAILURE(status)) {
		printk ("Unable to determine bay status\n");
		spin_unlock_irqrestore(ap->lock, flags);
		return;
	}
	spin_lock_irqsave(ap->lock, flags);

	switch (event) {
	case ACPI_NOTIFY_BUS_CHECK:
	case ACPI_NOTIFY_DEVICE_CHECK:
		ata_ehi_push_desc(ehi, "ACPI event");
		if (!sta) {
                /* Device has been unplugged */
			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;
					}
				}
		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;
		}
			ata_port_schedule_eh(ap);
			wait = 1;
		} else {

		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 (!is_dock_event)
			break;

		/* undock event - immediate unplug */
		ata_acpi_detach_device(ap, dev);
		wait = 1;
		break;
	}

	/* make sure kobj doesn't go away while ap->lock is released */
	kobject_get(kobj);

	spin_unlock_irqrestore(ap->lock, flags);

	if (wait)
	if (wait) {
		ata_port_wait_eh(ap);
		ata_acpi_eject_device(handle);
	}

	if (dev) {
		if (dev->sdev)
			kobj = &dev->sdev->sdev_gendev.kobj;
	} else
		kobj = &ap->dev->kobj;

	if (kobj) {
	if (kobj && !is_dock_event) {
		sprintf(event_string, "BAY_EVENT=%d", event);
		kobject_uevent_env(kobj, KOBJ_CHANGE, envp);
	}

	kobject_put(kobj);
}

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);
}

/**
@@ -252,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++) {
@@ -264,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);
			}
		}
	}