Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/suspend-2.6
* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/suspend-2.6: PM / Runtime: Add runtime PM statistics (v3) PM / Runtime: Make runtime_status attribute not debug-only (v. 2) PM: Do not use dynamically allocated objects in pm_wakeup_event() PM / Suspend: Fix ordering of calls in suspend error paths PM / Hibernate: Fix snapshot error code path PM / Hibernate: Fix hibernation_platform_enter() pm_qos: Get rid of the allocation in pm_qos_add_request() pm_qos: Reimplement using plists plist: Add plist_last PM: Make it possible to avoid races between wakeup and system sleep PNPACPI: Add support for remote wakeup PM: describe kernel policy regarding wakeup defaults (v. 2) PM / Hibernate: Fix typos in comments in kernel/power/swap.c
This commit is contained in:
@@ -48,59 +48,49 @@
|
||||
* or pm_qos_object list and pm_qos_objects need to happen with pm_qos_lock
|
||||
* held, taken with _irqsave. One lock to rule them all
|
||||
*/
|
||||
struct pm_qos_request_list {
|
||||
struct list_head list;
|
||||
union {
|
||||
s32 value;
|
||||
s32 usec;
|
||||
s32 kbps;
|
||||
};
|
||||
int pm_qos_class;
|
||||
enum pm_qos_type {
|
||||
PM_QOS_MAX, /* return the largest value */
|
||||
PM_QOS_MIN /* return the smallest value */
|
||||
};
|
||||
|
||||
static s32 max_compare(s32 v1, s32 v2);
|
||||
static s32 min_compare(s32 v1, s32 v2);
|
||||
|
||||
struct pm_qos_object {
|
||||
struct pm_qos_request_list requests;
|
||||
struct plist_head requests;
|
||||
struct blocking_notifier_head *notifiers;
|
||||
struct miscdevice pm_qos_power_miscdev;
|
||||
char *name;
|
||||
s32 default_value;
|
||||
atomic_t target_value;
|
||||
s32 (*comparitor)(s32, s32);
|
||||
enum pm_qos_type type;
|
||||
};
|
||||
|
||||
static DEFINE_SPINLOCK(pm_qos_lock);
|
||||
|
||||
static struct pm_qos_object null_pm_qos;
|
||||
static BLOCKING_NOTIFIER_HEAD(cpu_dma_lat_notifier);
|
||||
static struct pm_qos_object cpu_dma_pm_qos = {
|
||||
.requests = {LIST_HEAD_INIT(cpu_dma_pm_qos.requests.list)},
|
||||
.requests = PLIST_HEAD_INIT(cpu_dma_pm_qos.requests, pm_qos_lock),
|
||||
.notifiers = &cpu_dma_lat_notifier,
|
||||
.name = "cpu_dma_latency",
|
||||
.default_value = 2000 * USEC_PER_SEC,
|
||||
.target_value = ATOMIC_INIT(2000 * USEC_PER_SEC),
|
||||
.comparitor = min_compare
|
||||
.type = PM_QOS_MIN,
|
||||
};
|
||||
|
||||
static BLOCKING_NOTIFIER_HEAD(network_lat_notifier);
|
||||
static struct pm_qos_object network_lat_pm_qos = {
|
||||
.requests = {LIST_HEAD_INIT(network_lat_pm_qos.requests.list)},
|
||||
.requests = PLIST_HEAD_INIT(network_lat_pm_qos.requests, pm_qos_lock),
|
||||
.notifiers = &network_lat_notifier,
|
||||
.name = "network_latency",
|
||||
.default_value = 2000 * USEC_PER_SEC,
|
||||
.target_value = ATOMIC_INIT(2000 * USEC_PER_SEC),
|
||||
.comparitor = min_compare
|
||||
.type = PM_QOS_MIN
|
||||
};
|
||||
|
||||
|
||||
static BLOCKING_NOTIFIER_HEAD(network_throughput_notifier);
|
||||
static struct pm_qos_object network_throughput_pm_qos = {
|
||||
.requests = {LIST_HEAD_INIT(network_throughput_pm_qos.requests.list)},
|
||||
.requests = PLIST_HEAD_INIT(network_throughput_pm_qos.requests, pm_qos_lock),
|
||||
.notifiers = &network_throughput_notifier,
|
||||
.name = "network_throughput",
|
||||
.default_value = 0,
|
||||
.target_value = ATOMIC_INIT(0),
|
||||
.comparitor = max_compare
|
||||
.type = PM_QOS_MAX,
|
||||
};
|
||||
|
||||
|
||||
@@ -111,8 +101,6 @@ static struct pm_qos_object *pm_qos_array[] = {
|
||||
&network_throughput_pm_qos
|
||||
};
|
||||
|
||||
static DEFINE_SPINLOCK(pm_qos_lock);
|
||||
|
||||
static ssize_t pm_qos_power_write(struct file *filp, const char __user *buf,
|
||||
size_t count, loff_t *f_pos);
|
||||
static int pm_qos_power_open(struct inode *inode, struct file *filp);
|
||||
@@ -124,46 +112,55 @@ static const struct file_operations pm_qos_power_fops = {
|
||||
.release = pm_qos_power_release,
|
||||
};
|
||||
|
||||
/* static helper functions */
|
||||
static s32 max_compare(s32 v1, s32 v2)
|
||||
/* unlocked internal variant */
|
||||
static inline int pm_qos_get_value(struct pm_qos_object *o)
|
||||
{
|
||||
return max(v1, v2);
|
||||
if (plist_head_empty(&o->requests))
|
||||
return o->default_value;
|
||||
|
||||
switch (o->type) {
|
||||
case PM_QOS_MIN:
|
||||
return plist_last(&o->requests)->prio;
|
||||
|
||||
case PM_QOS_MAX:
|
||||
return plist_first(&o->requests)->prio;
|
||||
|
||||
default:
|
||||
/* runtime check for not using enum */
|
||||
BUG();
|
||||
}
|
||||
}
|
||||
|
||||
static s32 min_compare(s32 v1, s32 v2)
|
||||
static void update_target(struct pm_qos_object *o, struct plist_node *node,
|
||||
int del, int value)
|
||||
{
|
||||
return min(v1, v2);
|
||||
}
|
||||
|
||||
|
||||
static void update_target(int pm_qos_class)
|
||||
{
|
||||
s32 extreme_value;
|
||||
struct pm_qos_request_list *node;
|
||||
unsigned long flags;
|
||||
int call_notifier = 0;
|
||||
int prev_value, curr_value;
|
||||
|
||||
spin_lock_irqsave(&pm_qos_lock, flags);
|
||||
extreme_value = pm_qos_array[pm_qos_class]->default_value;
|
||||
list_for_each_entry(node,
|
||||
&pm_qos_array[pm_qos_class]->requests.list, list) {
|
||||
extreme_value = pm_qos_array[pm_qos_class]->comparitor(
|
||||
extreme_value, node->value);
|
||||
}
|
||||
if (atomic_read(&pm_qos_array[pm_qos_class]->target_value) !=
|
||||
extreme_value) {
|
||||
call_notifier = 1;
|
||||
atomic_set(&pm_qos_array[pm_qos_class]->target_value,
|
||||
extreme_value);
|
||||
pr_debug(KERN_ERR "new target for qos %d is %d\n", pm_qos_class,
|
||||
atomic_read(&pm_qos_array[pm_qos_class]->target_value));
|
||||
prev_value = pm_qos_get_value(o);
|
||||
/* PM_QOS_DEFAULT_VALUE is a signal that the value is unchanged */
|
||||
if (value != PM_QOS_DEFAULT_VALUE) {
|
||||
/*
|
||||
* to change the list, we atomically remove, reinit
|
||||
* with new value and add, then see if the extremal
|
||||
* changed
|
||||
*/
|
||||
plist_del(node, &o->requests);
|
||||
plist_node_init(node, value);
|
||||
plist_add(node, &o->requests);
|
||||
} else if (del) {
|
||||
plist_del(node, &o->requests);
|
||||
} else {
|
||||
plist_add(node, &o->requests);
|
||||
}
|
||||
curr_value = pm_qos_get_value(o);
|
||||
spin_unlock_irqrestore(&pm_qos_lock, flags);
|
||||
|
||||
if (call_notifier)
|
||||
blocking_notifier_call_chain(
|
||||
pm_qos_array[pm_qos_class]->notifiers,
|
||||
(unsigned long) extreme_value, NULL);
|
||||
if (prev_value != curr_value)
|
||||
blocking_notifier_call_chain(o->notifiers,
|
||||
(unsigned long)curr_value,
|
||||
NULL);
|
||||
}
|
||||
|
||||
static int register_pm_qos_misc(struct pm_qos_object *qos)
|
||||
@@ -196,10 +193,23 @@ static int find_pm_qos_object_by_minor(int minor)
|
||||
*/
|
||||
int pm_qos_request(int pm_qos_class)
|
||||
{
|
||||
return atomic_read(&pm_qos_array[pm_qos_class]->target_value);
|
||||
unsigned long flags;
|
||||
int value;
|
||||
|
||||
spin_lock_irqsave(&pm_qos_lock, flags);
|
||||
value = pm_qos_get_value(pm_qos_array[pm_qos_class]);
|
||||
spin_unlock_irqrestore(&pm_qos_lock, flags);
|
||||
|
||||
return value;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm_qos_request);
|
||||
|
||||
int pm_qos_request_active(struct pm_qos_request_list *req)
|
||||
{
|
||||
return req->pm_qos_class != 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm_qos_request_active);
|
||||
|
||||
/**
|
||||
* pm_qos_add_request - inserts new qos request into the list
|
||||
* @pm_qos_class: identifies which list of qos request to us
|
||||
@@ -211,27 +221,23 @@ EXPORT_SYMBOL_GPL(pm_qos_request);
|
||||
* element as a handle for use in updating and removal. Call needs to save
|
||||
* this handle for later use.
|
||||
*/
|
||||
struct pm_qos_request_list *pm_qos_add_request(int pm_qos_class, s32 value)
|
||||
void pm_qos_add_request(struct pm_qos_request_list *dep,
|
||||
int pm_qos_class, s32 value)
|
||||
{
|
||||
struct pm_qos_request_list *dep;
|
||||
unsigned long flags;
|
||||
struct pm_qos_object *o = pm_qos_array[pm_qos_class];
|
||||
int new_value;
|
||||
|
||||
dep = kzalloc(sizeof(struct pm_qos_request_list), GFP_KERNEL);
|
||||
if (dep) {
|
||||
if (value == PM_QOS_DEFAULT_VALUE)
|
||||
dep->value = pm_qos_array[pm_qos_class]->default_value;
|
||||
else
|
||||
dep->value = value;
|
||||
dep->pm_qos_class = pm_qos_class;
|
||||
|
||||
spin_lock_irqsave(&pm_qos_lock, flags);
|
||||
list_add(&dep->list,
|
||||
&pm_qos_array[pm_qos_class]->requests.list);
|
||||
spin_unlock_irqrestore(&pm_qos_lock, flags);
|
||||
update_target(pm_qos_class);
|
||||
if (pm_qos_request_active(dep)) {
|
||||
WARN(1, KERN_ERR "pm_qos_add_request() called for already added request\n");
|
||||
return;
|
||||
}
|
||||
|
||||
return dep;
|
||||
if (value == PM_QOS_DEFAULT_VALUE)
|
||||
new_value = o->default_value;
|
||||
else
|
||||
new_value = value;
|
||||
plist_node_init(&dep->list, new_value);
|
||||
dep->pm_qos_class = pm_qos_class;
|
||||
update_target(o, &dep->list, 0, PM_QOS_DEFAULT_VALUE);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm_qos_add_request);
|
||||
|
||||
@@ -246,27 +252,28 @@ EXPORT_SYMBOL_GPL(pm_qos_add_request);
|
||||
* Attempts are made to make this code callable on hot code paths.
|
||||
*/
|
||||
void pm_qos_update_request(struct pm_qos_request_list *pm_qos_req,
|
||||
s32 new_value)
|
||||
s32 new_value)
|
||||
{
|
||||
unsigned long flags;
|
||||
int pending_update = 0;
|
||||
s32 temp;
|
||||
struct pm_qos_object *o;
|
||||
|
||||
if (pm_qos_req) { /*guard against callers passing in null */
|
||||
spin_lock_irqsave(&pm_qos_lock, flags);
|
||||
if (new_value == PM_QOS_DEFAULT_VALUE)
|
||||
temp = pm_qos_array[pm_qos_req->pm_qos_class]->default_value;
|
||||
else
|
||||
temp = new_value;
|
||||
if (!pm_qos_req) /*guard against callers passing in null */
|
||||
return;
|
||||
|
||||
if (temp != pm_qos_req->value) {
|
||||
pending_update = 1;
|
||||
pm_qos_req->value = temp;
|
||||
}
|
||||
spin_unlock_irqrestore(&pm_qos_lock, flags);
|
||||
if (pending_update)
|
||||
update_target(pm_qos_req->pm_qos_class);
|
||||
if (!pm_qos_request_active(pm_qos_req)) {
|
||||
WARN(1, KERN_ERR "pm_qos_update_request() called for unknown object\n");
|
||||
return;
|
||||
}
|
||||
|
||||
o = pm_qos_array[pm_qos_req->pm_qos_class];
|
||||
|
||||
if (new_value == PM_QOS_DEFAULT_VALUE)
|
||||
temp = o->default_value;
|
||||
else
|
||||
temp = new_value;
|
||||
|
||||
if (temp != pm_qos_req->list.prio)
|
||||
update_target(o, &pm_qos_req->list, 0, temp);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm_qos_update_request);
|
||||
|
||||
@@ -280,19 +287,20 @@ EXPORT_SYMBOL_GPL(pm_qos_update_request);
|
||||
*/
|
||||
void pm_qos_remove_request(struct pm_qos_request_list *pm_qos_req)
|
||||
{
|
||||
unsigned long flags;
|
||||
int qos_class;
|
||||
struct pm_qos_object *o;
|
||||
|
||||
if (pm_qos_req == NULL)
|
||||
return;
|
||||
/* silent return to keep pcm code cleaner */
|
||||
|
||||
qos_class = pm_qos_req->pm_qos_class;
|
||||
spin_lock_irqsave(&pm_qos_lock, flags);
|
||||
list_del(&pm_qos_req->list);
|
||||
kfree(pm_qos_req);
|
||||
spin_unlock_irqrestore(&pm_qos_lock, flags);
|
||||
update_target(qos_class);
|
||||
if (!pm_qos_request_active(pm_qos_req)) {
|
||||
WARN(1, KERN_ERR "pm_qos_remove_request() called for unknown object\n");
|
||||
return;
|
||||
}
|
||||
|
||||
o = pm_qos_array[pm_qos_req->pm_qos_class];
|
||||
update_target(o, &pm_qos_req->list, 1, PM_QOS_DEFAULT_VALUE);
|
||||
memset(pm_qos_req, 0, sizeof(*pm_qos_req));
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm_qos_remove_request);
|
||||
|
||||
@@ -340,8 +348,12 @@ static int pm_qos_power_open(struct inode *inode, struct file *filp)
|
||||
|
||||
pm_qos_class = find_pm_qos_object_by_minor(iminor(inode));
|
||||
if (pm_qos_class >= 0) {
|
||||
filp->private_data = (void *) pm_qos_add_request(pm_qos_class,
|
||||
PM_QOS_DEFAULT_VALUE);
|
||||
struct pm_qos_request_list *req = kzalloc(GFP_KERNEL, sizeof(*req));
|
||||
if (!req)
|
||||
return -ENOMEM;
|
||||
|
||||
pm_qos_add_request(req, pm_qos_class, PM_QOS_DEFAULT_VALUE);
|
||||
filp->private_data = req;
|
||||
|
||||
if (filp->private_data)
|
||||
return 0;
|
||||
@@ -353,8 +365,9 @@ static int pm_qos_power_release(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct pm_qos_request_list *req;
|
||||
|
||||
req = (struct pm_qos_request_list *)filp->private_data;
|
||||
req = filp->private_data;
|
||||
pm_qos_remove_request(req);
|
||||
kfree(req);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -277,7 +277,7 @@ static int create_image(int platform_mode)
|
||||
goto Enable_irqs;
|
||||
}
|
||||
|
||||
if (hibernation_test(TEST_CORE))
|
||||
if (hibernation_test(TEST_CORE) || !pm_check_wakeup_events())
|
||||
goto Power_up;
|
||||
|
||||
in_suspend = 1;
|
||||
@@ -288,8 +288,10 @@ static int create_image(int platform_mode)
|
||||
error);
|
||||
/* Restore control flow magically appears here */
|
||||
restore_processor_state();
|
||||
if (!in_suspend)
|
||||
if (!in_suspend) {
|
||||
events_check_enabled = false;
|
||||
platform_leave(platform_mode);
|
||||
}
|
||||
|
||||
Power_up:
|
||||
sysdev_resume();
|
||||
@@ -328,7 +330,7 @@ int hibernation_snapshot(int platform_mode)
|
||||
|
||||
error = platform_begin(platform_mode);
|
||||
if (error)
|
||||
return error;
|
||||
goto Close;
|
||||
|
||||
/* Preallocate image memory before shutting down devices. */
|
||||
error = hibernate_preallocate_memory();
|
||||
@@ -511,18 +513,24 @@ int hibernation_platform_enter(void)
|
||||
|
||||
local_irq_disable();
|
||||
sysdev_suspend(PMSG_HIBERNATE);
|
||||
if (!pm_check_wakeup_events()) {
|
||||
error = -EAGAIN;
|
||||
goto Power_up;
|
||||
}
|
||||
|
||||
hibernation_ops->enter();
|
||||
/* We should never get here */
|
||||
while (1);
|
||||
|
||||
/*
|
||||
* We don't need to reenable the nonboot CPUs or resume consoles, since
|
||||
* the system is going to be halted anyway.
|
||||
*/
|
||||
Power_up:
|
||||
sysdev_resume();
|
||||
local_irq_enable();
|
||||
enable_nonboot_cpus();
|
||||
|
||||
Platform_finish:
|
||||
hibernation_ops->finish();
|
||||
|
||||
dpm_suspend_noirq(PMSG_RESTORE);
|
||||
dpm_resume_noirq(PMSG_RESTORE);
|
||||
|
||||
Resume_devices:
|
||||
entering_platform_hibernation = false;
|
||||
|
||||
@@ -204,6 +204,60 @@ static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
|
||||
power_attr(state);
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
/*
|
||||
* The 'wakeup_count' attribute, along with the functions defined in
|
||||
* drivers/base/power/wakeup.c, provides a means by which wakeup events can be
|
||||
* handled in a non-racy way.
|
||||
*
|
||||
* If a wakeup event occurs when the system is in a sleep state, it simply is
|
||||
* woken up. In turn, if an event that would wake the system up from a sleep
|
||||
* state occurs when it is undergoing a transition to that sleep state, the
|
||||
* transition should be aborted. Moreover, if such an event occurs when the
|
||||
* system is in the working state, an attempt to start a transition to the
|
||||
* given sleep state should fail during certain period after the detection of
|
||||
* the event. Using the 'state' attribute alone is not sufficient to satisfy
|
||||
* these requirements, because a wakeup event may occur exactly when 'state'
|
||||
* is being written to and may be delivered to user space right before it is
|
||||
* frozen, so the event will remain only partially processed until the system is
|
||||
* woken up by another event. In particular, it won't cause the transition to
|
||||
* a sleep state to be aborted.
|
||||
*
|
||||
* This difficulty may be overcome if user space uses 'wakeup_count' before
|
||||
* writing to 'state'. It first should read from 'wakeup_count' and store
|
||||
* the read value. Then, after carrying out its own preparations for the system
|
||||
* transition to a sleep state, it should write the stored value to
|
||||
* 'wakeup_count'. If that fails, at least one wakeup event has occured since
|
||||
* 'wakeup_count' was read and 'state' should not be written to. Otherwise, it
|
||||
* is allowed to write to 'state', but the transition will be aborted if there
|
||||
* are any wakeup events detected after 'wakeup_count' was written to.
|
||||
*/
|
||||
|
||||
static ssize_t wakeup_count_show(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
unsigned long val;
|
||||
|
||||
return pm_get_wakeup_count(&val) ? sprintf(buf, "%lu\n", val) : -EINTR;
|
||||
}
|
||||
|
||||
static ssize_t wakeup_count_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t n)
|
||||
{
|
||||
unsigned long val;
|
||||
|
||||
if (sscanf(buf, "%lu", &val) == 1) {
|
||||
if (pm_save_wakeup_count(val))
|
||||
return n;
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
power_attr(wakeup_count);
|
||||
#endif /* CONFIG_PM_SLEEP */
|
||||
|
||||
#ifdef CONFIG_PM_TRACE
|
||||
int pm_trace_enabled;
|
||||
|
||||
@@ -236,6 +290,7 @@ static struct attribute * g[] = {
|
||||
#endif
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
&pm_async_attr.attr,
|
||||
&wakeup_count_attr.attr,
|
||||
#ifdef CONFIG_PM_DEBUG
|
||||
&pm_test_attr.attr,
|
||||
#endif
|
||||
|
||||
@@ -136,19 +136,19 @@ static int suspend_enter(suspend_state_t state)
|
||||
if (suspend_ops->prepare) {
|
||||
error = suspend_ops->prepare();
|
||||
if (error)
|
||||
return error;
|
||||
goto Platform_finish;
|
||||
}
|
||||
|
||||
error = dpm_suspend_noirq(PMSG_SUSPEND);
|
||||
if (error) {
|
||||
printk(KERN_ERR "PM: Some devices failed to power down\n");
|
||||
goto Platfrom_finish;
|
||||
goto Platform_finish;
|
||||
}
|
||||
|
||||
if (suspend_ops->prepare_late) {
|
||||
error = suspend_ops->prepare_late();
|
||||
if (error)
|
||||
goto Power_up_devices;
|
||||
goto Platform_wake;
|
||||
}
|
||||
|
||||
if (suspend_test(TEST_PLATFORM))
|
||||
@@ -163,8 +163,10 @@ static int suspend_enter(suspend_state_t state)
|
||||
|
||||
error = sysdev_suspend(PMSG_SUSPEND);
|
||||
if (!error) {
|
||||
if (!suspend_test(TEST_CORE))
|
||||
if (!suspend_test(TEST_CORE) && pm_check_wakeup_events()) {
|
||||
error = suspend_ops->enter(state);
|
||||
events_check_enabled = false;
|
||||
}
|
||||
sysdev_resume();
|
||||
}
|
||||
|
||||
@@ -178,10 +180,9 @@ static int suspend_enter(suspend_state_t state)
|
||||
if (suspend_ops->wake)
|
||||
suspend_ops->wake();
|
||||
|
||||
Power_up_devices:
|
||||
dpm_resume_noirq(PMSG_RESUME);
|
||||
|
||||
Platfrom_finish:
|
||||
Platform_finish:
|
||||
if (suspend_ops->finish)
|
||||
suspend_ops->finish();
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
/*
|
||||
* The swap map is a data structure used for keeping track of each page
|
||||
* written to a swap partition. It consists of many swap_map_page
|
||||
* structures that contain each an array of MAP_PAGE_SIZE swap entries.
|
||||
* structures that contain each an array of MAP_PAGE_ENTRIES swap entries.
|
||||
* These structures are stored on the swap and linked together with the
|
||||
* help of the .next_swap member.
|
||||
*
|
||||
@@ -148,7 +148,7 @@ sector_t alloc_swapdev_block(int swap)
|
||||
|
||||
/**
|
||||
* free_all_swap_pages - free swap pages allocated for saving image data.
|
||||
* It also frees the extents used to register which swap entres had been
|
||||
* It also frees the extents used to register which swap entries had been
|
||||
* allocated.
|
||||
*/
|
||||
|
||||
|
||||
Reference in New Issue
Block a user