From patchwork Sun Nov 4 12:50:12 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jiang Liu X-Patchwork-Id: 1694031 X-Patchwork-Delegate: bhelgaas@google.com Return-Path: X-Original-To: patchwork-linux-pci@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 D0F123FD2B for ; Sun, 4 Nov 2012 12:53:06 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753886Ab2KDMvy (ORCPT ); Sun, 4 Nov 2012 07:51:54 -0500 Received: from mail-pb0-f46.google.com ([209.85.160.46]:46858 "EHLO mail-pb0-f46.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1754095Ab2KDMvs (ORCPT ); Sun, 4 Nov 2012 07:51:48 -0500 Received: by mail-pb0-f46.google.com with SMTP id rr4so3339156pbb.19 for ; Sun, 04 Nov 2012 04:51:48 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:cc:subject:date:message-id:x-mailer:in-reply-to:references; bh=dscwUh6LmHlQtR8EbQWYlGLyo7vAyfulVjrbk8U8IKE=; b=E+wL1DwAEb+yfUHjfe0UThyfr9gRl/ja/wbTwRVSgmBdyzHFUN9VGhEARREFxUlfgR /jcrEI5Ueq5WVebP9Ki7hQDQtUMfUiiotLwBeC+Gpa4oPuvdj+haxnf2ZAJBFBk0v3tF EXSpLHwJWwOa6xQkLiEBEvuxRK8zkZjG9ka7h0BqhFNQofqx0bfvL17JDHKz6CX8qEdF rqj+vmGDL9gAvj+sQuzmN1By6dRQGmzzfVHGBV+X2q7X2pDSG+tOyLuWUUHUAL9VFkPN kIUkgTjLSmpN6JcL1eldKjBpg1aIl1/T9TvbuzUhUM70K9Asa7copimbSxilzs0of8VC eHeQ== Received: by 10.68.248.10 with SMTP id yi10mr22040707pbc.39.1352033508024; Sun, 04 Nov 2012 04:51:48 -0800 (PST) Received: from localhost.localdomain ([120.196.98.117]) by mx.google.com with ESMTPS id a4sm8922768pax.12.2012.11.04.04.51.39 (version=TLSv1/SSLv3 cipher=OTHER); Sun, 04 Nov 2012 04:51:47 -0800 (PST) From: Jiang Liu To: "Rafael J . Wysocki" , Yinghai Lu , Tony Luck , Yasuaki Ishimatsu , Wen Congyang , Tang Chen , Taku Izumi , Bjorn Helgaas Cc: Jiang Liu , Kenji Kaneshige , Huang Ying , Bob Moore , Len Brown , "Srivatsa S . Bhat" , Yijing Wang , Hanjun Guo , Jiang Liu , linux-kernel@vger.kernel.org, linux-acpi@vger.kernel.org, linux-pci@vger.kernel.org, linux-mm@kvack.org Subject: [ACPIHP PATCH part2 10/13] ACPIHP: implement the core state machine to manage hotplug slots Date: Sun, 4 Nov 2012 20:50:12 +0800 Message-Id: <1352033415-5606-11-git-send-email-jiang.liu@huawei.com> X-Mailer: git-send-email 1.7.9.5 In-Reply-To: <1352033415-5606-1-git-send-email-jiang.liu@huawei.com> References: <1352033415-5606-1-git-send-email-jiang.liu@huawei.com> Sender: linux-pci-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-pci@vger.kernel.org This patch implements the core of the new ACPI hotplug framework, a state machine for ACPI hotplug slots. The state machine is: (plug in) (power on) (connect) (configure) [ABSENT] <-> [PRESENT] <-> [POWERED] <-> [CONNECTED] <-> [CONFIGURED] (plug out) (power off) (disconnect) (unconfigure) [...]: state (...): action (connect): create ACPI devices and bind ACPI device drivers (disconnect): unbind ACPI device drivers and destroy ACPI devices (configure): allocate resources and add system device into running system (unconfigure): remove system device from running system and free resources It provides a simple interface to drive the state machine: int acpihp_drv_change_state(struct acpihp_slot *slot, enum acpihp_drv_cmd cmd); It glues all hotplug logic together, including resolving dependencies among slots, cancalling inprogress hotplug operations and configuring/ unconfigure system devices etc. Signed-off-by: Jiang Liu Signed-off-by: Hanjun Guo --- drivers/acpi/hotplug/Makefile | 1 + drivers/acpi/hotplug/acpihp_drv.h | 7 + drivers/acpi/hotplug/drv_main.c | 1 + drivers/acpi/hotplug/state_machine.c | 639 ++++++++++++++++++++++++++++++++++ 4 files changed, 648 insertions(+) create mode 100644 drivers/acpi/hotplug/state_machine.c diff --git a/drivers/acpi/hotplug/Makefile b/drivers/acpi/hotplug/Makefile index 6cb6aa1..0f43933 100644 --- a/drivers/acpi/hotplug/Makefile +++ b/drivers/acpi/hotplug/Makefile @@ -15,3 +15,4 @@ acpihp_drv-y = drv_main.o acpihp_drv-y += dependency.o acpihp_drv-y += cancel.o acpihp_drv-y += configure.o +acpihp_drv-y += state_machine.o diff --git a/drivers/acpi/hotplug/acpihp_drv.h b/drivers/acpi/hotplug/acpihp_drv.h index aa239f6..175ef81 100644 --- a/drivers/acpi/hotplug/acpihp_drv.h +++ b/drivers/acpi/hotplug/acpihp_drv.h @@ -49,6 +49,7 @@ enum acpihp_drv_cancel_state { struct acpihp_slot_drv { struct mutex op_mutex; + struct list_head depend_list; atomic_t cancel_state; atomic_t cancel_users; struct acpihp_cancel_context cancel_ctx; @@ -61,6 +62,9 @@ struct acpihp_slot_dependency { u32 execute_stages; }; +extern struct mutex state_machine_mutex; +extern wait_queue_head_t acpihp_drv_event_wq; + void acpihp_drv_get_data(struct acpihp_slot *slot, struct acpihp_slot_drv **data); int acpihp_drv_enumerate_devices(struct acpihp_slot *slot); @@ -85,4 +89,7 @@ int acpihp_drv_cancel_wait(struct list_head *list); int acpihp_drv_configure(struct list_head *list); int acpihp_drv_unconfigure(struct list_head *list); +/* The heart of the ACPI system device hotplug driver */ +int acpihp_drv_change_state(struct acpihp_slot *slot, enum acpihp_drv_cmd cmd); + #endif /* __ACPIHP_DRV_H__ */ diff --git a/drivers/acpi/hotplug/drv_main.c b/drivers/acpi/hotplug/drv_main.c index 8ab298a..5a919e7 100644 --- a/drivers/acpi/hotplug/drv_main.c +++ b/drivers/acpi/hotplug/drv_main.c @@ -273,6 +273,7 @@ static int acpihp_drv_slot_add(struct device *dev, struct class_interface *intf) drv_data = kzalloc(sizeof(*drv_data), GFP_KERNEL); if (drv_data) { mutex_init(&drv_data->op_mutex); + INIT_LIST_HEAD(&drv_data->depend_list); } if (drv_data == NULL || acpihp_slot_attach_drv_data(slot, intf, (void *)drv_data)) { diff --git a/drivers/acpi/hotplug/state_machine.c b/drivers/acpi/hotplug/state_machine.c new file mode 100644 index 0000000..7604da1 --- /dev/null +++ b/drivers/acpi/hotplug/state_machine.c @@ -0,0 +1,639 @@ +/* + * Copyright (C) 2012 Huawei Tech. Co., Ltd. + * Copyright (C) 2012 Jiang Liu + * Copyright (C) 2012 Hanjun Guo + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#include +#include +#include +#include "acpihp_drv.h" + +/* + * Global lock to serialize manipulating of dependency list among hotplug slots + * to avoid deadlock among slots. The lock order is: + * 1) acquire state_machine_mutex + * 2) acquire drv_data->op_mutex + * 3) acquire slot's device lock + */ +DEFINE_MUTEX(state_machine_mutex); +DECLARE_WAIT_QUEUE_HEAD(acpihp_drv_event_wq); + +static int acpihp_drv_lock_slot(struct acpihp_slot *slot) +{ + int retval = 0; + struct acpihp_slot_drv *drv_data; + + acpihp_drv_get_data(slot, &drv_data); + if (!mutex_trylock(&drv_data->op_mutex)) { + ACPIHP_SLOT_DEBUG(slot, "slot is busy in state %d.\n", + slot->state); + retval = -EBUSY; + } + + return retval; +} + +static void acpihp_drv_unlock_slot(struct acpihp_slot *slot) +{ + struct acpihp_slot_drv *drv_data; + + acpihp_drv_get_data(slot, &drv_data); + BUG_ON(!mutex_is_locked(&drv_data->op_mutex)); + mutex_unlock(&drv_data->op_mutex); +} + +/* + * Lock all slots in the dependency list to serialize concurrent operations. + * Caller must hold state_machine_mutex. + */ +static int acpihp_drv_lock_slots(struct list_head *list, + struct acpihp_slot *slot) +{ + struct acpihp_slot_dependency *dep; + + list_for_each_entry(dep, list, node) + if (acpihp_drv_lock_slot(dep->slot)) + goto unlock; + + return 0; + +unlock: + list_for_each_entry_continue_reverse(dep, list, node) + acpihp_drv_unlock_slot(dep->slot); + + return -EBUSY; +} + +static void acpihp_drv_unlock_slots(struct list_head *list) +{ + struct acpihp_slot_dependency *dep; + + list_for_each_entry(dep, list, node) + acpihp_drv_unlock_slot(dep->slot); +} + +static bool acpihp_drv_is_ancestor(struct acpihp_slot *ancestor, + struct acpihp_slot *slot) +{ + while (slot) { + if (slot->parent == ancestor) + return true; + slot = slot->parent; + }; + + return false; +} + +/* + * Check whether the command is valid according to current slot state, + * and get required operations for this slot if command is valid. + */ +static int acpihp_drv_validate_transition(struct acpihp_slot_dependency *dep, + struct acpihp_slot *target, + enum acpihp_drv_cmd cmd) +{ + u32 op1, op2; + struct acpihp_slot *slot = dep->slot; + + if (slot->state <= ACPIHP_SLOT_STATE_UNKNOWN || + slot->state >= ACPIHP_SLOT_STATE_MAX) { + ACPIHP_SLOT_DEBUG(slot, "invalid state %d.\n", slot->state); + return -EINVAL; + } else if (slot->state >= ACPIHP_SLOT_STATE_POWERING_ON) { + /* + * It shouldn't happen, transcendant states are protected + * by slot->op_mutex. + */ + BUG_ON(slot->state); + return -EINVAL; + } + + op1 = op2 = ACPIHP_DRV_CMD_NOOP; + dep->opcodes = ACPIHP_DRV_CMD_NOOP; + + /* + * To be compatible with legacy OSes, the PCI host bridges built into + * physical processor may be hosted directly under \\__SB instead of + * under the CONTAINER device corresponding to physical processor. + * That's really a corner case to deal with. + */ + switch (cmd) { + case ACPIHP_DRV_CMD_POWERON: + if (slot->state == ACPIHP_SLOT_STATE_ABSENT) + return -ENODEV; + else if (slot->state == ACPIHP_SLOT_STATE_PRESENT) + dep->opcodes = ACPIHP_DRV_CMD_POWERON; + break; + + case ACPIHP_DRV_CMD_CONNECT: + /* + * Its parent must have already been connected when connecting + * a slot, otherwise the device tree topology becomes incorrect. + */ + if (target == slot || acpihp_drv_is_ancestor(slot, target)) + op2 = ACPIHP_DRV_CMD_CONNECT; + + if (slot->state == ACPIHP_SLOT_STATE_ABSENT) + return -ENODEV; + else if (slot->state == ACPIHP_SLOT_STATE_PRESENT) + dep->opcodes = ACPIHP_DRV_CMD_POWERON | op2; + else if (slot->state == ACPIHP_SLOT_STATE_POWERED) + dep->opcodes = op2; + break; + + case ACPIHP_DRV_CMD_CONFIGURE: + /* Only CONFIGURE the requested slot */ + if (slot == target) + op1 = ACPIHP_DRV_CMD_CONFIGURE; + /* + * Its parent must have already been connected when configuring + * a slot, otherwise the device tree topology becomes incorrect. + */ + if (target == slot || acpihp_drv_is_ancestor(slot, target)) + op2 = ACPIHP_DRV_CMD_CONNECT; + + if (slot->state == ACPIHP_SLOT_STATE_ABSENT) + return -ENODEV; + else if (slot->state == ACPIHP_SLOT_STATE_PRESENT) + dep->opcodes = ACPIHP_DRV_CMD_POWERON | op1 | op2; + else if (slot->state == ACPIHP_SLOT_STATE_POWERED) + dep->opcodes = op1 | op2; + else if (slot->state == ACPIHP_SLOT_STATE_CONNECTED) + dep->opcodes = op1; + break; + + case ACPIHP_DRV_CMD_UNCONFIGURE: + /* Only UNCONFIGURE the requested slot */ + if (slot->state == ACPIHP_SLOT_STATE_CONFIGURED && + slot == target) + dep->opcodes = ACPIHP_DRV_CMD_UNCONFIGURE; + break; + + case ACPIHP_DRV_CMD_DISCONNECT: + /* + * all descedant slots must be unconfigured/disconnected + * when disconnecting a slot. + */ + if (target == slot || acpihp_drv_is_ancestor(target, slot)) { + op1 = ACPIHP_DRV_CMD_UNCONFIGURE; + op2 = ACPIHP_DRV_CMD_DISCONNECT; + } + + if (slot->state == ACPIHP_SLOT_STATE_CONFIGURED) + dep->opcodes = op1 | op2; + else if (slot->state == ACPIHP_SLOT_STATE_CONNECTED) + dep->opcodes = op2; + break; + + case ACPIHP_DRV_CMD_POWEROFF: + /* + * All slots have dependency on the target slot must be + * powered off when powering the target slot off. + */ + if (slot->state == ACPIHP_SLOT_STATE_CONFIGURED) + dep->opcodes = ACPIHP_DRV_CMD_UNCONFIGURE | + ACPIHP_DRV_CMD_DISCONNECT | + ACPIHP_DRV_CMD_POWEROFF; + else if (slot->state == ACPIHP_SLOT_STATE_CONNECTED) + dep->opcodes = ACPIHP_DRV_CMD_DISCONNECT | + ACPIHP_DRV_CMD_POWEROFF; + else if (slot->state == ACPIHP_SLOT_STATE_POWERED) + dep->opcodes = ACPIHP_DRV_CMD_POWEROFF; + break; + + default: + ACPIHP_SLOT_DEBUG(slot, "invalid command %d.\n", cmd); + return -EINVAL; + } + + return 0; +} + +static int acpihp_drv_validate_command(struct list_head *list, + struct acpihp_slot *target, enum acpihp_drv_cmd cmd) +{ + int retval; + struct acpihp_slot *slot; + struct acpihp_slot_dependency *dep; + + list_for_each_entry(dep, list, node) { + slot = dep->slot; + acpihp_drv_update_slot_status(slot); + + retval = acpihp_drv_validate_transition(dep, target, cmd); + if (retval) { + ACPIHP_SLOT_DEBUG(slot, + "invalid transition for slot in state %d.\n", + slot->state); + return retval; + } + + /* Check whether the slot is in good shape */ + if (dep->opcodes && + acpihp_slot_get_flag(slot, ACPIHP_SLOT_FLAG_FAULT)) { + ACPIHP_SLOT_WARN(slot, + "slot has been marked as faulty.\n"); + return -EINVAL; + } else if ((dep->opcodes & ACPIHP_DRV_CMD_UNCONFIGURE) && + acpihp_slot_get_flag(slot, ACPIHP_SLOT_FLAG_IRREMOVABLE)) { + ACPIHP_SLOT_WARN(slot, "slot is busy.\n"); + return -EBUSY; + } + } + + return 0; +} + +static int acpihp_drv_pre_execute(struct acpihp_slot *slot, + enum acpihp_drv_cmd cmd, + struct list_head **head) +{ + int retval; + struct list_head *list; + struct acpihp_slot_drv *drv_data; + + acpihp_drv_get_data(slot, &drv_data); + mutex_lock(&state_machine_mutex); + *head = list = &drv_data->depend_list; + + /* + * Set cancellation flags on all affected slots. + * All affected slots should already be on drv_data->depend_list + * if there's inprogress operation for the slot. + * + * state_machine_mutex must be held to serialize calls to + * acpihp_drv_cancel_init(), + * acpihp_drv_cancel_start(), + * acpihp_drv_cancel_fini(), + */ + if (cmd == ACPIHP_DRV_CMD_CANCEL) { + retval = acpihp_drv_cancel_start(list); + goto out; + } + + /* bail out if slot has already been locked to avoid deadlock */ + if (mutex_is_locked(&drv_data->op_mutex)) { + ACPIHP_SLOT_DEBUG(slot, "is busy.\n"); + retval = -EBUSY; + goto out; + } + + BUG_ON(!list_empty(list)); + retval = acpihp_drv_generate_dependency_list(slot, list, cmd); + if (retval) { + ACPIHP_SLOT_DEBUG(slot, "fails to get dependency lists.\n"); + goto out; + } + + retval = acpihp_drv_lock_slots(list, slot); + if (retval) { + ACPIHP_SLOT_DEBUG(slot, "fails to lock slots.\n"); + acpihp_drv_destroy_dependency_list(list); + goto out; + } + + retval = acpihp_drv_validate_command(list, slot, cmd); + if (retval) { + ACPIHP_SLOT_DEBUG(slot, "invalid command.\n"); + acpihp_drv_unlock_slots(list); + acpihp_drv_destroy_dependency_list(list); + } else { + acpihp_drv_cancel_init(list); + } + +out: + mutex_unlock(&state_machine_mutex); + + return retval; +} + +static void acpihp_drv_post_execute(struct list_head *list, + enum acpihp_drv_cmd cmd) +{ + if (cmd == ACPIHP_DRV_CMD_CANCEL) + return; + + mutex_lock(&state_machine_mutex); + if (list && !list_empty(list)) { + acpihp_drv_cancel_fini(list); + acpihp_drv_unlock_slots(list); + acpihp_drv_destroy_dependency_list(list); + } + mutex_unlock(&state_machine_mutex); +} + +static int acpihp_drv_poweron_slot(struct acpihp_slot *slot) +{ + acpi_status status; + struct acpihp_slot_drv *drv_data; + + if (acpihp_slot_powered(slot)) + return 0; + + acpihp_drv_get_data(slot, &drv_data); + + status = acpihp_slot_poweron(slot); + if (ACPI_FAILURE(status)) { + if (status == AE_SUPPORT) + return -ENOSYS; + else { + ACPIHP_SLOT_WARN(slot, "fails to power on slot.\n"); + acpihp_slot_set_flag(slot, ACPIHP_SLOT_FLAG_FAULT); + return -ENXIO; + } + } + + if (!acpihp_slot_powered(slot)) { + ACPIHP_SLOT_WARN(slot, "fails to power on.\n"); + acpihp_slot_set_flag(slot, ACPIHP_SLOT_FLAG_FAULT); + return -ENXIO; + } + + return 0; +} + +static int acpihp_drv_poweron(struct list_head *list) +{ + int retval = 0; + struct acpihp_slot_dependency *dep; + + list_for_each_entry(dep, list, node) { + if (!(dep->opcodes & ACPIHP_DRV_CMD_POWERON)) + continue; + + acpihp_slot_change_state(dep->slot, + ACPIHP_SLOT_STATE_POWERING_ON); + retval = acpihp_drv_poweron_slot(dep->slot); + if (!retval) { + ACPIHP_SLOT_DEBUG(dep->slot, "succeed to power on!\n"); + acpihp_slot_change_state(dep->slot, + ACPIHP_SLOT_STATE_POWERED); + } else { + acpihp_slot_change_state(dep->slot, + ACPIHP_SLOT_STATE_PRESENT); + break; + } + } + + return retval; +} + +static int acpihp_drv_poweroff_slot(struct acpihp_slot *slot) +{ + acpi_status status; + + if (acpihp_slot_powered(slot)) + return 0; + + status = acpihp_slot_poweroff(slot); + if (ACPI_FAILURE(status)) { + if (status == AE_SUPPORT) + return -ENOSYS; + else { + ACPIHP_SLOT_WARN(slot, "fails to power off slot.\n"); + acpihp_slot_set_flag(slot, ACPIHP_SLOT_FLAG_FAULT); + return -ENXIO; + } + } + + if (acpihp_slot_powered(slot)) { + ACPIHP_SLOT_WARN(slot, "fails to power off slot.\n"); + acpihp_slot_set_flag(slot, ACPIHP_SLOT_FLAG_FAULT); + return -ENXIO; + } + + return 0; +} + +static int acpihp_drv_poweroff(struct list_head *list) +{ + int retval = 0; + struct acpihp_slot_dependency *dep; + + list_for_each_entry(dep, list, node) { + if (!(dep->opcodes & ACPIHP_DRV_CMD_POWEROFF)) + continue; + + acpihp_slot_change_state(dep->slot, + ACPIHP_SLOT_STATE_POWERING_OFF); + retval = acpihp_drv_poweroff_slot(dep->slot); + if (!retval) { + ACPIHP_SLOT_DEBUG(dep->slot, + "succeed to power off slot!\n"); + acpihp_slot_change_state(dep->slot, + ACPIHP_SLOT_STATE_PRESENT); + } else { + acpihp_slot_change_state(dep->slot, + ACPIHP_SLOT_STATE_POWERED); + break; + } + } + + return retval; +} + +static int acpihp_drv_connect_slot(struct acpihp_slot *slot) +{ + int retval; + + retval = acpihp_add_devices(slot->handle, NULL); + if (retval) + ACPIHP_SLOT_DEBUG(slot, + "fails to add ACPI devices for slot.\n"); + else { + retval = acpihp_drv_enumerate_devices(slot); + if (retval) + ACPIHP_SLOT_DEBUG(slot, + "fails to enumerate device for slot.\n"); + } + + return retval; +} + +static int acpihp_drv_connect(struct list_head *list) +{ + int retval = 0; + struct acpihp_slot_dependency *dep; + + list_for_each_entry(dep, list, node) { + if (!(dep->opcodes & ACPIHP_DRV_CMD_CONNECT)) + continue; + + acpihp_slot_change_state(dep->slot, + ACPIHP_SLOT_STATE_CONNECTING); + retval = acpihp_drv_connect_slot(dep->slot); + if (!retval) { + ACPIHP_SLOT_DEBUG(dep->slot, + "succeed to connect slot!\n"); + acpihp_slot_change_state(dep->slot, + ACPIHP_SLOT_STATE_CONNECTED); + } else { + acpihp_slot_set_flag(dep->slot, ACPIHP_SLOT_FLAG_FAULT); + acpihp_drv_update_slot_state(dep->slot); + break; + } + } + + return retval; +} + +static int acpihp_drv_disconnect_slot(struct acpihp_slot *slot) +{ + int retval = 0; + enum acpihp_dev_type i; + struct acpi_device *device = NULL; + + /* remove all devices attached to a slot */ + for (i = ACPIHP_DEV_TYPE_UNKNOWN; i < ACPIHP_DEV_TYPE_MAX; i++) { + retval = acpihp_remove_device_list(&slot->dev_lists[i]); + if (retval) { + ACPIHP_SLOT_DEBUG(slot, + "fails to remove ACPI devices.\n"); + return retval; + } + } + + /* remove ACPI devices */ + retval = acpi_bus_get_device(slot->handle, &device); + if (!retval && device) { + retval = acpi_bus_trim(device, 1); + if (retval) + ACPIHP_SLOT_WARN(slot, + "fails to remove ACPI devices.\n"); + } else { + ACPIHP_SLOT_WARN(slot, "fails to get ACPI device for slot.\n"); + retval = -ENXIO; + } + + return retval; +} + +static int acpihp_drv_disconnect(struct list_head *list) +{ + int retval = 0; + struct acpihp_slot_dependency *dep; + + list_for_each_entry(dep, list, node) { + if (!(dep->opcodes & ACPIHP_DRV_CMD_DISCONNECT)) + continue; + + acpihp_slot_change_state(dep->slot, + ACPIHP_SLOT_STATE_DISCONNECTING); + retval = acpihp_drv_disconnect_slot(dep->slot); + if (!retval) { + ACPIHP_SLOT_DEBUG(dep->slot, + "succeed to disconnect slot!\n"); + acpihp_slot_change_state(dep->slot, + ACPIHP_SLOT_STATE_POWERED); + } else { + acpihp_slot_set_flag(dep->slot, ACPIHP_SLOT_FLAG_FAULT); + acpihp_drv_update_slot_state(dep->slot); + break; + } + } + + return retval; +} + +static int acpihp_drv_execute(struct list_head *list, enum acpihp_drv_cmd cmd) +{ + int retval = 0; + bool connect = false, configure = false; + bool disconnect = false, poweroff = false; + + if (!list || list_empty(list)) { + ACPIHP_DEBUG("slot dependency list is NULL or empty!\n"); + retval = -EINVAL; + goto out; + } + + switch (cmd) { + case ACPIHP_DRV_CMD_CONFIGURE: + configure = true; + /* fall through */ + case ACPIHP_DRV_CMD_CONNECT: + connect = true; + /* fall through */ + case ACPIHP_DRV_CMD_POWERON: + retval = acpihp_drv_poweron(list); + if (!retval && connect) + retval = acpihp_drv_connect(list); + if (!retval && configure) + retval = acpihp_drv_configure(list); + break; + + case ACPIHP_DRV_CMD_POWEROFF: + poweroff = true; + /* fall through */ + case ACPIHP_DRV_CMD_DISCONNECT: + disconnect = true; + /* fall through */ + case ACPIHP_DRV_CMD_UNCONFIGURE: + retval = acpihp_drv_unconfigure(list); + if (!retval && disconnect) + retval = acpihp_drv_disconnect(list); + if (!retval && poweroff) + retval = acpihp_drv_poweroff(list); + break; + + case ACPIHP_DRV_CMD_CANCEL: + retval = acpihp_drv_cancel_wait(list); + break; + + default: + ACPIHP_DEBUG("unsupported command %d.\n", cmd); + retval = -EINVAL; + break; + } + +out: + return retval; +} + +/* + * The heart of ACPI based system device hotplug driver, which implements + * a state machine as below for hotplug slots. + * + * (plug in) (power on) (connect) (configure) + * [ABSENT] <-> [PRESENT] <-> [POWERED] <-> [CONNECTED] <-> [CONFIGURED] + * (plug out) (power off) (disconnect) (unconfigure) + * + * [...]: state + * (...): action + * (connect): create ACPI devices and bind ACPI device drivers + * (disconnect): unbind ACPI device drivers and destroy ACPI devices + * (configure): allocate resources and add system device into running system + * (unconfigure): remove system device from running system and free resources + */ +int acpihp_drv_change_state(struct acpihp_slot *slot, enum acpihp_drv_cmd cmd) +{ + int retval; + struct list_head *list = NULL; + + retval = acpihp_drv_pre_execute(slot, cmd, &list); + if (!retval) { + retval = acpihp_drv_execute(list, cmd); + acpihp_drv_post_execute(list, cmd); + } + + return retval; +}