s390/zcrypt: fix suspend/resume of AP bus devices
If there are no devices on the AP bus there will not be a single call to the per-device ap_bus_suspend function. Even worse, there will not be a call to the per-device ap_bus_resume either and the AP will fail so resume correctly. Introduce a bus specific dev_pm_ops to suspend / resume the AP bus related things. While we are at it, simplify the power management code of the AP bus. Reviewd-by: Ingo Tuchscherer <ingo.tuchscherer@linux.vnet.ibm.com> Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
This commit is contained in:
@ -37,6 +37,7 @@
|
|||||||
#include <linux/notifier.h>
|
#include <linux/notifier.h>
|
||||||
#include <linux/kthread.h>
|
#include <linux/kthread.h>
|
||||||
#include <linux/mutex.h>
|
#include <linux/mutex.h>
|
||||||
|
#include <linux/suspend.h>
|
||||||
#include <asm/reset.h>
|
#include <asm/reset.h>
|
||||||
#include <asm/airq.h>
|
#include <asm/airq.h>
|
||||||
#include <linux/atomic.h>
|
#include <linux/atomic.h>
|
||||||
@ -52,8 +53,6 @@
|
|||||||
static void ap_scan_bus(struct work_struct *);
|
static void ap_scan_bus(struct work_struct *);
|
||||||
static void ap_poll_all(unsigned long);
|
static void ap_poll_all(unsigned long);
|
||||||
static enum hrtimer_restart ap_poll_timeout(struct hrtimer *);
|
static enum hrtimer_restart ap_poll_timeout(struct hrtimer *);
|
||||||
static int ap_poll_thread_start(void);
|
|
||||||
static void ap_poll_thread_stop(void);
|
|
||||||
static void ap_request_timeout(unsigned long);
|
static void ap_request_timeout(unsigned long);
|
||||||
static inline void ap_schedule_poll_timer(void);
|
static inline void ap_schedule_poll_timer(void);
|
||||||
static int __ap_poll_device(struct ap_device *ap_dev, unsigned long *flags);
|
static int __ap_poll_device(struct ap_device *ap_dev, unsigned long *flags);
|
||||||
@ -614,6 +613,78 @@ static void ap_decrease_queue_count(struct ap_device *ap_dev)
|
|||||||
ap_dev->reset = AP_RESET_IGNORE;
|
ap_dev->reset = AP_RESET_IGNORE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ap_poll_thread(): Thread that polls for finished requests.
|
||||||
|
* @data: Unused pointer
|
||||||
|
*
|
||||||
|
* AP bus poll thread. The purpose of this thread is to poll for
|
||||||
|
* finished requests in a loop if there is a "free" cpu - that is
|
||||||
|
* a cpu that doesn't have anything better to do. The polling stops
|
||||||
|
* as soon as there is another task or if all messages have been
|
||||||
|
* delivered.
|
||||||
|
*/
|
||||||
|
static int ap_poll_thread(void *data)
|
||||||
|
{
|
||||||
|
DECLARE_WAITQUEUE(wait, current);
|
||||||
|
unsigned long flags;
|
||||||
|
struct ap_device *ap_dev;
|
||||||
|
|
||||||
|
set_user_nice(current, MAX_NICE);
|
||||||
|
set_freezable();
|
||||||
|
while (!kthread_should_stop()) {
|
||||||
|
add_wait_queue(&ap_poll_wait, &wait);
|
||||||
|
set_current_state(TASK_INTERRUPTIBLE);
|
||||||
|
if (ap_suspend_flag ||
|
||||||
|
atomic_read(&ap_poll_requests) <= 0) {
|
||||||
|
schedule();
|
||||||
|
try_to_freeze();
|
||||||
|
}
|
||||||
|
set_current_state(TASK_RUNNING);
|
||||||
|
remove_wait_queue(&ap_poll_wait, &wait);
|
||||||
|
|
||||||
|
if (need_resched()) {
|
||||||
|
schedule();
|
||||||
|
try_to_freeze();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
flags = 0;
|
||||||
|
spin_lock_bh(&ap_device_list_lock);
|
||||||
|
list_for_each_entry(ap_dev, &ap_device_list, list) {
|
||||||
|
spin_lock(&ap_dev->lock);
|
||||||
|
__ap_poll_device(ap_dev, &flags);
|
||||||
|
spin_unlock(&ap_dev->lock);
|
||||||
|
}
|
||||||
|
spin_unlock_bh(&ap_device_list_lock);
|
||||||
|
} while (!kthread_should_stop());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ap_poll_thread_start(void)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
if (ap_using_interrupts() || ap_poll_kthread)
|
||||||
|
return 0;
|
||||||
|
mutex_lock(&ap_poll_thread_mutex);
|
||||||
|
ap_poll_kthread = kthread_run(ap_poll_thread, NULL, "appoll");
|
||||||
|
rc = PTR_RET(ap_poll_kthread);
|
||||||
|
if (rc)
|
||||||
|
ap_poll_kthread = NULL;
|
||||||
|
mutex_unlock(&ap_poll_thread_mutex);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ap_poll_thread_stop(void)
|
||||||
|
{
|
||||||
|
if (!ap_poll_kthread)
|
||||||
|
return;
|
||||||
|
mutex_lock(&ap_poll_thread_mutex);
|
||||||
|
kthread_stop(ap_poll_kthread);
|
||||||
|
ap_poll_kthread = NULL;
|
||||||
|
mutex_unlock(&ap_poll_thread_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* AP device related attributes.
|
* AP device related attributes.
|
||||||
*/
|
*/
|
||||||
@ -827,25 +898,11 @@ static int ap_uevent (struct device *dev, struct kobj_uevent_env *env)
|
|||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ap_bus_suspend(struct device *dev, pm_message_t state)
|
static int ap_dev_suspend(struct device *dev, pm_message_t state)
|
||||||
{
|
{
|
||||||
struct ap_device *ap_dev = to_ap_dev(dev);
|
struct ap_device *ap_dev = to_ap_dev(dev);
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
|
|
||||||
if (!ap_suspend_flag) {
|
|
||||||
ap_suspend_flag = 1;
|
|
||||||
|
|
||||||
/* Disable scanning for devices, thus we do not want to scan
|
|
||||||
* for them after removing.
|
|
||||||
*/
|
|
||||||
del_timer_sync(&ap_config_timer);
|
|
||||||
if (ap_work_queue != NULL) {
|
|
||||||
destroy_workqueue(ap_work_queue);
|
|
||||||
ap_work_queue = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
tasklet_disable(&ap_tasklet);
|
|
||||||
}
|
|
||||||
/* Poll on the device until all requests are finished. */
|
/* Poll on the device until all requests are finished. */
|
||||||
do {
|
do {
|
||||||
flags = 0;
|
flags = 0;
|
||||||
@ -854,72 +911,86 @@ static int ap_bus_suspend(struct device *dev, pm_message_t state)
|
|||||||
spin_unlock_bh(&ap_dev->lock);
|
spin_unlock_bh(&ap_dev->lock);
|
||||||
} while ((flags & 1) || (flags & 2));
|
} while ((flags & 1) || (flags & 2));
|
||||||
|
|
||||||
spin_lock_bh(&ap_dev->lock);
|
|
||||||
ap_dev->unregistered = 1;
|
|
||||||
spin_unlock_bh(&ap_dev->lock);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ap_bus_resume(struct device *dev)
|
static int ap_dev_resume(struct device *dev)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ap_bus_suspend(void)
|
||||||
|
{
|
||||||
|
ap_suspend_flag = 1;
|
||||||
|
/*
|
||||||
|
* Disable scanning for devices, thus we do not want to scan
|
||||||
|
* for them after removing.
|
||||||
|
*/
|
||||||
|
del_timer_sync(&ap_config_timer);
|
||||||
|
flush_workqueue(ap_work_queue);
|
||||||
|
tasklet_disable(&ap_tasklet);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __ap_devices_unregister(struct device *dev, void *dummy)
|
||||||
|
{
|
||||||
|
device_unregister(dev);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ap_bus_resume(void)
|
||||||
{
|
{
|
||||||
struct ap_device *ap_dev = to_ap_dev(dev);
|
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
if (ap_suspend_flag) {
|
/* Unconditionally remove all AP devices */
|
||||||
ap_suspend_flag = 0;
|
bus_for_each_dev(&ap_bus_type, NULL, NULL, __ap_devices_unregister);
|
||||||
if (ap_interrupts_available()) {
|
/* Reset thin interrupt setting */
|
||||||
if (!ap_using_interrupts()) {
|
if (ap_interrupts_available() && !ap_using_interrupts()) {
|
||||||
rc = register_adapter_interrupt(&ap_airq);
|
rc = register_adapter_interrupt(&ap_airq);
|
||||||
ap_airq_flag = (rc == 0);
|
ap_airq_flag = (rc == 0);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (ap_using_interrupts()) {
|
|
||||||
unregister_adapter_interrupt(&ap_airq);
|
|
||||||
ap_airq_flag = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ap_query_configuration();
|
|
||||||
if (!user_set_domain) {
|
|
||||||
ap_domain_index = -1;
|
|
||||||
ap_select_domain();
|
|
||||||
}
|
|
||||||
init_timer(&ap_config_timer);
|
|
||||||
ap_config_timer.function = ap_config_timeout;
|
|
||||||
ap_config_timer.data = 0;
|
|
||||||
ap_config_timer.expires = jiffies + ap_config_time * HZ;
|
|
||||||
add_timer(&ap_config_timer);
|
|
||||||
ap_work_queue = create_singlethread_workqueue("kapwork");
|
|
||||||
if (!ap_work_queue)
|
|
||||||
return -ENOMEM;
|
|
||||||
tasklet_enable(&ap_tasklet);
|
|
||||||
if (!ap_using_interrupts())
|
|
||||||
ap_schedule_poll_timer();
|
|
||||||
else
|
|
||||||
tasklet_schedule(&ap_tasklet);
|
|
||||||
if (ap_thread_flag)
|
|
||||||
rc = ap_poll_thread_start();
|
|
||||||
else
|
|
||||||
rc = 0;
|
|
||||||
} else
|
|
||||||
rc = 0;
|
|
||||||
if (AP_QID_QUEUE(ap_dev->qid) != ap_domain_index) {
|
|
||||||
spin_lock_bh(&ap_dev->lock);
|
|
||||||
ap_dev->qid = AP_MKQID(AP_QID_DEVICE(ap_dev->qid),
|
|
||||||
ap_domain_index);
|
|
||||||
spin_unlock_bh(&ap_dev->lock);
|
|
||||||
}
|
}
|
||||||
|
if (!ap_interrupts_available() && ap_using_interrupts()) {
|
||||||
|
unregister_adapter_interrupt(&ap_airq);
|
||||||
|
ap_airq_flag = 0;
|
||||||
|
}
|
||||||
|
/* Reset domain */
|
||||||
|
if (!user_set_domain)
|
||||||
|
ap_domain_index = -1;
|
||||||
|
/* Get things going again */
|
||||||
|
ap_suspend_flag = 0;
|
||||||
|
if (ap_airq_flag)
|
||||||
|
xchg(ap_airq.lsi_ptr, 0);
|
||||||
|
tasklet_enable(&ap_tasklet);
|
||||||
queue_work(ap_work_queue, &ap_config_work);
|
queue_work(ap_work_queue, &ap_config_work);
|
||||||
|
wake_up(&ap_poll_wait);
|
||||||
return rc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int ap_power_event(struct notifier_block *this, unsigned long event,
|
||||||
|
void *ptr)
|
||||||
|
{
|
||||||
|
switch (event) {
|
||||||
|
case PM_HIBERNATION_PREPARE:
|
||||||
|
case PM_SUSPEND_PREPARE:
|
||||||
|
ap_bus_suspend();
|
||||||
|
break;
|
||||||
|
case PM_POST_HIBERNATION:
|
||||||
|
case PM_POST_SUSPEND:
|
||||||
|
ap_bus_resume();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return NOTIFY_DONE;
|
||||||
|
}
|
||||||
|
static struct notifier_block ap_power_notifier = {
|
||||||
|
.notifier_call = ap_power_event,
|
||||||
|
};
|
||||||
|
|
||||||
static struct bus_type ap_bus_type = {
|
static struct bus_type ap_bus_type = {
|
||||||
.name = "ap",
|
.name = "ap",
|
||||||
.match = &ap_bus_match,
|
.match = &ap_bus_match,
|
||||||
.uevent = &ap_uevent,
|
.uevent = &ap_uevent,
|
||||||
.suspend = ap_bus_suspend,
|
.suspend = ap_dev_suspend,
|
||||||
.resume = ap_bus_resume
|
.resume = ap_dev_resume,
|
||||||
};
|
};
|
||||||
|
|
||||||
static int ap_device_probe(struct device *dev)
|
static int ap_device_probe(struct device *dev)
|
||||||
@ -1017,6 +1088,8 @@ EXPORT_SYMBOL(ap_driver_unregister);
|
|||||||
|
|
||||||
void ap_bus_force_rescan(void)
|
void ap_bus_force_rescan(void)
|
||||||
{
|
{
|
||||||
|
if (ap_suspend_flag)
|
||||||
|
return;
|
||||||
/* reconfigure the AP bus rescan timer. */
|
/* reconfigure the AP bus rescan timer. */
|
||||||
mod_timer(&ap_config_timer, jiffies + ap_config_time * HZ);
|
mod_timer(&ap_config_timer, jiffies + ap_config_time * HZ);
|
||||||
/* processing a asynchronous bus rescan */
|
/* processing a asynchronous bus rescan */
|
||||||
@ -1102,9 +1175,8 @@ static ssize_t ap_poll_thread_store(struct bus_type *bus,
|
|||||||
if (flag) {
|
if (flag) {
|
||||||
rc = ap_poll_thread_start();
|
rc = ap_poll_thread_start();
|
||||||
if (rc)
|
if (rc)
|
||||||
return rc;
|
count = rc;
|
||||||
}
|
} else
|
||||||
else
|
|
||||||
ap_poll_thread_stop();
|
ap_poll_thread_stop();
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
@ -1309,7 +1381,8 @@ out:
|
|||||||
static void ap_interrupt_handler(struct airq_struct *airq)
|
static void ap_interrupt_handler(struct airq_struct *airq)
|
||||||
{
|
{
|
||||||
inc_irq_stat(IRQIO_APB);
|
inc_irq_stat(IRQIO_APB);
|
||||||
tasklet_schedule(&ap_tasklet);
|
if (!ap_suspend_flag)
|
||||||
|
tasklet_schedule(&ap_tasklet);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1341,9 +1414,8 @@ static void ap_scan_bus(struct work_struct *unused)
|
|||||||
int rc, i;
|
int rc, i;
|
||||||
|
|
||||||
ap_query_configuration();
|
ap_query_configuration();
|
||||||
if (ap_select_domain() != 0) {
|
if (ap_select_domain() != 0)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < AP_DEVICES; i++) {
|
for (i = 0; i < AP_DEVICES; i++) {
|
||||||
qid = AP_MKQID(i, ap_domain_index);
|
qid = AP_MKQID(i, ap_domain_index);
|
||||||
@ -1422,9 +1494,10 @@ static void ap_scan_bus(struct work_struct *unused)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void ap_config_timeout(unsigned long ptr)
|
||||||
ap_config_timeout(unsigned long ptr)
|
|
||||||
{
|
{
|
||||||
|
if (ap_suspend_flag)
|
||||||
|
return;
|
||||||
queue_work(ap_work_queue, &ap_config_work);
|
queue_work(ap_work_queue, &ap_config_work);
|
||||||
ap_config_timer.expires = jiffies + ap_config_time * HZ;
|
ap_config_timer.expires = jiffies + ap_config_time * HZ;
|
||||||
add_timer(&ap_config_timer);
|
add_timer(&ap_config_timer);
|
||||||
@ -1706,7 +1779,8 @@ EXPORT_SYMBOL(ap_cancel_message);
|
|||||||
*/
|
*/
|
||||||
static enum hrtimer_restart ap_poll_timeout(struct hrtimer *unused)
|
static enum hrtimer_restart ap_poll_timeout(struct hrtimer *unused)
|
||||||
{
|
{
|
||||||
tasklet_schedule(&ap_tasklet);
|
if (!ap_suspend_flag)
|
||||||
|
tasklet_schedule(&ap_tasklet);
|
||||||
return HRTIMER_NORESTART;
|
return HRTIMER_NORESTART;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1777,84 +1851,6 @@ static void ap_poll_all(unsigned long dummy)
|
|||||||
__ap_schedule_poll_timer();
|
__ap_schedule_poll_timer();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* ap_poll_thread(): Thread that polls for finished requests.
|
|
||||||
* @data: Unused pointer
|
|
||||||
*
|
|
||||||
* AP bus poll thread. The purpose of this thread is to poll for
|
|
||||||
* finished requests in a loop if there is a "free" cpu - that is
|
|
||||||
* a cpu that doesn't have anything better to do. The polling stops
|
|
||||||
* as soon as there is another task or if all messages have been
|
|
||||||
* delivered.
|
|
||||||
*/
|
|
||||||
static int ap_poll_thread(void *data)
|
|
||||||
{
|
|
||||||
DECLARE_WAITQUEUE(wait, current);
|
|
||||||
unsigned long flags;
|
|
||||||
int requests;
|
|
||||||
struct ap_device *ap_dev;
|
|
||||||
|
|
||||||
set_user_nice(current, MAX_NICE);
|
|
||||||
while (1) {
|
|
||||||
if (ap_suspend_flag)
|
|
||||||
return 0;
|
|
||||||
if (need_resched()) {
|
|
||||||
schedule();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
add_wait_queue(&ap_poll_wait, &wait);
|
|
||||||
set_current_state(TASK_INTERRUPTIBLE);
|
|
||||||
if (kthread_should_stop())
|
|
||||||
break;
|
|
||||||
requests = atomic_read(&ap_poll_requests);
|
|
||||||
if (requests <= 0)
|
|
||||||
schedule();
|
|
||||||
set_current_state(TASK_RUNNING);
|
|
||||||
remove_wait_queue(&ap_poll_wait, &wait);
|
|
||||||
|
|
||||||
flags = 0;
|
|
||||||
spin_lock_bh(&ap_device_list_lock);
|
|
||||||
list_for_each_entry(ap_dev, &ap_device_list, list) {
|
|
||||||
spin_lock(&ap_dev->lock);
|
|
||||||
__ap_poll_device(ap_dev, &flags);
|
|
||||||
spin_unlock(&ap_dev->lock);
|
|
||||||
}
|
|
||||||
spin_unlock_bh(&ap_device_list_lock);
|
|
||||||
}
|
|
||||||
set_current_state(TASK_RUNNING);
|
|
||||||
remove_wait_queue(&ap_poll_wait, &wait);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int ap_poll_thread_start(void)
|
|
||||||
{
|
|
||||||
int rc;
|
|
||||||
|
|
||||||
if (ap_using_interrupts() || ap_suspend_flag)
|
|
||||||
return 0;
|
|
||||||
mutex_lock(&ap_poll_thread_mutex);
|
|
||||||
if (!ap_poll_kthread) {
|
|
||||||
ap_poll_kthread = kthread_run(ap_poll_thread, NULL, "appoll");
|
|
||||||
rc = PTR_RET(ap_poll_kthread);
|
|
||||||
if (rc)
|
|
||||||
ap_poll_kthread = NULL;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
rc = 0;
|
|
||||||
mutex_unlock(&ap_poll_thread_mutex);
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ap_poll_thread_stop(void)
|
|
||||||
{
|
|
||||||
mutex_lock(&ap_poll_thread_mutex);
|
|
||||||
if (ap_poll_kthread) {
|
|
||||||
kthread_stop(ap_poll_kthread);
|
|
||||||
ap_poll_kthread = NULL;
|
|
||||||
}
|
|
||||||
mutex_unlock(&ap_poll_thread_mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ap_request_timeout(): Handling of request timeouts
|
* ap_request_timeout(): Handling of request timeouts
|
||||||
* @data: Holds the AP device.
|
* @data: Holds the AP device.
|
||||||
@ -1865,6 +1861,8 @@ static void ap_request_timeout(unsigned long data)
|
|||||||
{
|
{
|
||||||
struct ap_device *ap_dev = (struct ap_device *) data;
|
struct ap_device *ap_dev = (struct ap_device *) data;
|
||||||
|
|
||||||
|
if (ap_suspend_flag)
|
||||||
|
return;
|
||||||
if (ap_dev->reset == AP_RESET_ARMED) {
|
if (ap_dev->reset == AP_RESET_ARMED) {
|
||||||
ap_dev->reset = AP_RESET_DO;
|
ap_dev->reset = AP_RESET_DO;
|
||||||
|
|
||||||
@ -1990,8 +1988,14 @@ int __init ap_module_init(void)
|
|||||||
goto out_work;
|
goto out_work;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rc = register_pm_notifier(&ap_power_notifier);
|
||||||
|
if (rc)
|
||||||
|
goto out_pm;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
out_pm:
|
||||||
|
ap_poll_thread_stop();
|
||||||
out_work:
|
out_work:
|
||||||
del_timer_sync(&ap_config_timer);
|
del_timer_sync(&ap_config_timer);
|
||||||
hrtimer_cancel(&ap_poll_timer);
|
hrtimer_cancel(&ap_poll_timer);
|
||||||
@ -2010,11 +2014,6 @@ out:
|
|||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int __ap_match_all(struct device *dev, void *data)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ap_modules_exit(): The module termination code
|
* ap_modules_exit(): The module termination code
|
||||||
*
|
*
|
||||||
@ -2023,7 +2022,6 @@ static int __ap_match_all(struct device *dev, void *data)
|
|||||||
void ap_module_exit(void)
|
void ap_module_exit(void)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
struct device *dev;
|
|
||||||
|
|
||||||
ap_reset_domain();
|
ap_reset_domain();
|
||||||
ap_poll_thread_stop();
|
ap_poll_thread_stop();
|
||||||
@ -2031,14 +2029,10 @@ void ap_module_exit(void)
|
|||||||
hrtimer_cancel(&ap_poll_timer);
|
hrtimer_cancel(&ap_poll_timer);
|
||||||
destroy_workqueue(ap_work_queue);
|
destroy_workqueue(ap_work_queue);
|
||||||
tasklet_kill(&ap_tasklet);
|
tasklet_kill(&ap_tasklet);
|
||||||
while ((dev = bus_find_device(&ap_bus_type, NULL, NULL,
|
bus_for_each_dev(&ap_bus_type, NULL, NULL, __ap_devices_unregister);
|
||||||
__ap_match_all)))
|
|
||||||
{
|
|
||||||
device_unregister(dev);
|
|
||||||
put_device(dev);
|
|
||||||
}
|
|
||||||
for (i = 0; ap_bus_attrs[i]; i++)
|
for (i = 0; ap_bus_attrs[i]; i++)
|
||||||
bus_remove_file(&ap_bus_type, ap_bus_attrs[i]);
|
bus_remove_file(&ap_bus_type, ap_bus_attrs[i]);
|
||||||
|
unregister_pm_notifier(&ap_power_notifier);
|
||||||
root_device_unregister(ap_root_device);
|
root_device_unregister(ap_root_device);
|
||||||
bus_unregister(&ap_bus_type);
|
bus_unregister(&ap_bus_type);
|
||||||
kfree(ap_configuration);
|
kfree(ap_configuration);
|
||||||
|
Reference in New Issue
Block a user