* RE: Concurrency in the execution of ACPI control methods
@ 2006-03-07 20:47 Moore, Robert
2006-04-08 10:52 ` Linux does not correctly implement the ACPI specification Peter Wainwright
0 siblings, 1 reply; 4+ messages in thread
From: Moore, Robert @ 2006-03-07 20:47 UTC (permalink / raw)
To: Peter Wainwright, linux-acpi
Looking at method _L19, the Sleep() operator should relinquish the
processor and allow the Notify dispatch to occur.
AcpiOsQueueForExecution should be using a different thread than the
thread that executes the AML interpreter. The Notify dispatcher does not
enter the AML interpreter mutex, so there should not be a deadlock. The
actual handler for the Notify should be in a driver somewhere.
I have found that the best method to determine exactly what is going on
is to enable full debug tracing in the ACPICA subsystem (via
acpi_dbg_level) and analyze the sequence of events.
Perhaps the ACPICA reference uses a bad choice of words. The code
supports the concurrency model defined in the ACPICA spec.
Bob
> -----Original Message-----
> From: linux-acpi-owner@vger.kernel.org [mailto:linux-acpi-
> owner@vger.kernel.org] On Behalf Of Peter Wainwright
> Sent: Tuesday, March 07, 2006 12:23 PM
> To: linux-acpi@vger.kernel.org
> Subject: Concurrency in the execution of ACPI control methods
>
> Hello,
>
> this is my first posting to this list. I recently purchased a HP
nx6125
> laptop. This
> laptop is known to suffer from a number of bugs when used with linux;
> these have
> been reported on Suse, Gentoo and Debian lists as well as on kernel
> bugzilla.
> The kernel bug is http://bugzilla.kernel.org/show_bug.cgi?id=5534.
>
> I posted my own analysis of this bug on bugzilla, but there has been
no
> reply
> so far. I am posting here because I think it may raise an issue which
is
> wider than
> the scope of that bug, which seemingly affects only nx6125 owners. I
> think the ACPI
> spec is unclear on certain points so that the linux implementation may
> not be adequate
> for some machines.
>
> <gory_technical_details>
>
> In brief, the bug is that thermal Notify events are never processed
> until
> someone explicitly reads the temperature
> (e.g. /proc/acpi/thermal_zone/TZ1/temperature).
> According to my analysis, thermal events on the nx6125 are handled by
a
> control method
> (_L19) which issues a Notify event to the OS and then Sleeps and loops
> waiting for the
> OS to reset the trip points and clear the exception condition.
>
> It seems to me that this causes a deadlock, because the GPE events and
> the Notify
> events are queued in a single-threaded workqueue and the Notify events
> cannot be
> processed until after _L19 returns.
>
> </gory_technical_details>
>
> This is described in
http://bugzilla.kernel.org/show_bug.cgi?id=5534#c65
> and
> http://bugzilla.kernel.org/show_bug.cgi?id=5534#c66.
>
> My question is this: If this analysis is true, it means that the HP
BIOS
> is
> expecting some type of concurrent execution of ACPI control methods.
> This seems to
> go against (one interpretation of) the ACPI spec. However, the ACPI
spec
> is not
> entirely clear on the issue. The ACPI CA Reference Manual by Intel
(one
> of the
> authors of the ACPI spec) says "If a control method blocks (an event
> that can occur
> only under a few limited conditions), another method may begin
> execution. However, ???it can be said that the specification precludes
> the concurrent execution of control methods???. Therefore, the AML
> interpreter itself is essentially a single-threaded component of the
> ACPI subsystem". This looks like equivocation to me - surely it's
either
> single-threaded or not! "essentially" - "it can be said" - what are
they
> saying
> here? Anyway, it seems to me that to successfully interpret this DSDT
> (http://acpi.sourceforge.net/dsdt/view.php?id=558) it would be
necessary
> to use
> multiple threads in kacpid. Are there any other machines which do this
> kind
> of clever stuff? What is the correct interpretation of the ACPI spec
here?
>
>
>
> Peter Wainwright
>
>
> -
> To unsubscribe from this list: send the line "unsubscribe linux-acpi"
in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 4+ messages in thread
* Linux does not correctly implement the ACPI specification
2006-03-07 20:47 Concurrency in the execution of ACPI control methods Moore, Robert
@ 2006-04-08 10:52 ` Peter Wainwright
2006-04-12 16:56 ` Andi Kleen
0 siblings, 1 reply; 4+ messages in thread
From: Peter Wainwright @ 2006-04-08 10:52 UTC (permalink / raw)
To: linux-acpi
[-- Attachment #1: Type: text/plain, Size: 2998 bytes --]
Reference:
http://bugzilla.kernel.org/show_bug.cgi?id=5534
There has been no significant movement on this bug for months.
I can only conclude that the problem is only triggered by a few
very sophisticated DSDTs (the HP nx6125 among them) or has remained
unrecognized on other platforms. Nonetheless it seems to me that
Linux does not correctly implement the ACPI spec.
Therefore, I propose a solution with the patch attached.
Interpretation of control methods called in response to GPE events or
Notify events is confined to one single-threaded workqueue.
It is true that the AML interpreter is essentially single-threaded,
because it is protected by a mutex and therefore only one kernel thread
can be executing AML code at one time. However, this does NOT mean that
the execution of different control methods should not overlap. The ACPI
spec allows for the transfer of control between one method and another
when the AML calls Sleep, Acquire, Wait etc. (see the ACPI-CA
reference).
The way Sleep() is implemented in Linux, it calls schedule() and transfers
control to other kernel threads: but any other control method which is queued
in the kacpid thread itself will not be able to run until the currently
executing control method is finished.
On the HP nx6125 laptop this is essential, otherwise the ACPI subsystem
will block, thermal events will not be processed, and the system will
overheat. http://bugzilla.kernel.org/show_bug.cgi?id=5534
So, if any of you have an nx6125, or an ACPI bug which you think may be
caused by the mechanism I have described, please try this patch.
This is my first attempt at a serious kernel hack, so please forgive
the state of it: this is work in progress. At least it should show one
approach to the problem. I'm sure it has loads of problems with respect
to locking, SMP, etc. which you will point out.
In order to enable the new behaviour you need to write a positive
integer (e.g. 10) to /proc/acpi/poolsize.
Instead of executing all the GPE and Notify handlers in the single
kacpid thread, we create a pool of worker threads and hand over the
work to them. These can now execute concurrently, though access to the
interpreter is still serialized by the use of a mutex. The thread pool
is allowed to grow dynamically up to the maximum size which is set by
the user by writing an integer to /proc/acpi/poolsize. If this integer
is 0 the thread pool is not used at all and the old behaviour is used.
You can also read /proc/acpi/poolsize to see the maximum pool size and
the currently allocated threads. There is a field "jiffies" in the
thread pool entry structure which is written when a thread finishes
execution of a control method. My intention is that in future this will
be used to reap unused threads.
Of course, the user-configurable pool size may not be necessary. We
might hard-code it. Or even allow the AML to create as many threads as
necessary (assuming we trust the BIOS).
Peter Wainwright (P.S. not the Apache/Perl expert).
[-- Attachment #2: acpi-osl.patch --]
[-- Type: text/x-patch, Size: 7137 bytes --]
diff -U3 -r linux-2.6.16-prw11-old/drivers/acpi/bus.c linux-2.6.16-prw11/drivers/acpi/bus.c
--- linux-2.6.16-prw11-old/drivers/acpi/bus.c 2006-01-03 03:21:10.000000000 +0000
+++ linux-2.6.16-prw11/drivers/acpi/bus.c 2006-04-08 08:25:09.000000000 +0100
@@ -664,6 +664,7 @@
int result = 0;
acpi_status status = AE_OK;
extern acpi_status acpi_os_initialize1(void);
+ extern acpi_status acpi_os_initialize2(void);
ACPI_FUNCTION_TRACE("acpi_bus_init");
@@ -727,6 +728,8 @@
*/
acpi_root_dir = proc_mkdir(ACPI_BUS_FILE_ROOT, NULL);
+ status = acpi_os_initialize2();
+
return_VALUE(0);
/* Mimic structured exception handling */
diff -U3 -r linux-2.6.16-prw11-old/drivers/acpi/osl.c linux-2.6.16-prw11/drivers/acpi/osl.c
--- linux-2.6.16-prw11-old/drivers/acpi/osl.c 2006-04-08 07:50:57.000000000 +0100
+++ linux-2.6.16-prw11/drivers/acpi/osl.c 2006-04-08 09:10:05.000000000 +0100
@@ -46,6 +46,10 @@
#include <linux/efi.h>
+#include <linux/kthread.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+
#define _COMPONENT ACPI_OS_SERVICES
ACPI_MODULE_NAME("osl")
#define PREFIX "ACPI: "
@@ -76,6 +80,165 @@
static void *acpi_irq_context;
static struct workqueue_struct *kacpid_wq;
+/* This parameter may be configured at runtime by writing to
+ /proc/acpi/poolsize */
+static int max_thread_pool_size = 0;
+
+/* Hopefully we need no special locking here, since this always
+ executes in one thread: kacpid */
+static int thread_pool_size = 0;
+static struct list_head thread_pool = LIST_HEAD_INIT(thread_pool);
+
+struct thread_pool_entry {
+ struct task_struct *thread;
+ int id;
+ int active;
+ acpi_osd_exec_callback function;
+ void *context;
+ unsigned long jiffies;
+ wait_queue_head_t more_work;
+ wait_queue_head_t work_done;
+ struct list_head list;
+};
+
+static int acpi_osl_poolsize_read(struct seq_file *seq, void *offset)
+{
+ struct list_head *l;
+
+ ACPI_FUNCTION_TRACE("acpi_osl_poolsize_read");
+
+ seq_printf(seq, "poolsize=%d\n", max_thread_pool_size);
+ /* FIXME: Need lock? */
+ list_for_each(l, &thread_pool) {
+ struct thread_pool_entry *e = list_entry(l, struct thread_pool_entry, list);
+ seq_printf(seq, "entry=(%p,%d,%p,%p)\n",
+ e->thread, e->active, e->function, e->context);
+ }
+ return_VALUE(0);
+}
+
+static int acpi_osl_poolsize_open_fs(struct inode *inode, struct file *file)
+{
+ return single_open(file, acpi_osl_poolsize_read, PDE(inode)->data);
+}
+
+static ssize_t
+acpi_osl_poolsize_write(struct file *file,
+ const char __user * buffer,
+ size_t count, loff_t * ppos)
+{
+ char *size_string;
+
+ ACPI_FUNCTION_TRACE("acpi_osl_poolsize_write");
+
+ size_string = kmalloc(4096, GFP_KERNEL);
+ if (!size_string)
+ return_VALUE(-ENOMEM);
+
+ memset(size_string, 0, 4096);
+
+ if (copy_from_user(size_string, buffer, count)) {
+ ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid data\n"));
+ count = -EFAULT;
+ goto end;
+ }
+
+ size_string[count] = '\0';
+ max_thread_pool_size = simple_strtol(size_string, NULL, 10);
+ ACPI_DEBUG_PRINT((ACPI_DB_WARN, "Poolsize now %d\n", max_thread_pool_size));
+
+end:
+ kfree(size_string);
+ return_VALUE(count);
+}
+
+static struct file_operations acpi_osl_poolsize_ops = {
+ .open = acpi_osl_poolsize_open_fs,
+ .read = seq_read,
+ .write = acpi_osl_poolsize_write,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+/* This is based on workqueue worker_thread */
+static int acpi_os_execute_worker(void *context)
+{
+ struct thread_pool_entry *e = (struct thread_pool_entry *)context;
+ DECLARE_WAITQUEUE(wait, current);
+
+ set_user_nice(current, -5);
+
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ ACPI_DEBUG_PRINT((ACPI_DB_WARN, "[%d:%p] before loop\n", e->id, e));
+ while (!kthread_should_stop()) {
+ /* Wait until there is work to do in this thread */
+ add_wait_queue(&e->more_work, &wait);
+ if (!e->function) {
+ ACPI_DEBUG_PRINT((ACPI_DB_WARN, "[%d:%p] sleeping\n", e->id, e));
+ schedule();
+ ACPI_DEBUG_PRINT((ACPI_DB_WARN, "[%d:%p] waking (function=%p)\n", e->id, e, e->function));
+ }
+ else {
+ ACPI_DEBUG_PRINT((ACPI_DB_WARN, "[%d:%p] running (function=%p)\n", e->id, e, e->function));
+ __set_current_state(TASK_RUNNING);
+ }
+ try_to_freeze();
+ remove_wait_queue(&e->more_work, &wait);
+ ACPI_DEBUG_PRINT((ACPI_DB_WARN, "[%d:%p] ready (function=%p)\n", e->id, e, e->function));
+ if (e->function) {
+ acpi_osd_exec_callback function = e->function;
+ void *context = e->context;
+ e->function = NULL;
+ e->context = NULL;
+ ACPI_DEBUG_PRINT((ACPI_DB_WARN, "[%d:%p] executing function=%p context=%p\n",
+ e->id, e, function, context));
+ function(context);
+
+ e->active = 0;
+ e->jiffies = jiffies;
+ ACPI_DEBUG_PRINT((ACPI_DB_WARN, "[%d:%p] function=%p context=%p exit at %lx\n",
+ e->id, e, function, context, e->jiffies));
+
+ }
+ set_current_state(TASK_INTERRUPTIBLE);
+ }
+
+ return 0;
+}
+
+static struct thread_pool_entry *get_pool_thread(void)
+{
+ struct list_head *l;
+ list_for_each(l, &thread_pool) {
+ struct thread_pool_entry *e = list_entry(l, struct thread_pool_entry, list);
+ if (!e->active) {
+ e->active = 1;
+ return e;
+ }
+ }
+ /* N.B. For the time being we have a user-configurable limit
+ on the thread_pool_size. We don't want a bad BIOS to create
+ unlimited kthreads. */
+ if (thread_pool_size < max_thread_pool_size) {
+ struct thread_pool_entry *e;
+ e = kmalloc(sizeof(struct thread_pool_entry), GFP_KERNEL);
+ list_add_tail(&e->list, &thread_pool);
+ e->id = thread_pool_size;
+ init_waitqueue_head(&e->more_work);
+ init_waitqueue_head(&e->work_done);
+ e->function = NULL;
+ e->context = NULL;
+ e->active = 1;
+ /* kthread_run does kthread_create and wake_up_process */
+ e->thread = kthread_run(acpi_os_execute_worker, e, "kacpid-work-%d",
+ thread_pool_size);
+ thread_pool_size++;
+ return e;
+ }
+ return NULL;
+}
+
acpi_status acpi_os_initialize(void)
{
return AE_OK;
@@ -98,6 +261,16 @@
return AE_OK;
}
+/* enable poolsize modification by /proc filesystem */
+acpi_status acpi_os_initialize2(void)
+{
+ struct proc_dir_entry *entry;
+ entry = create_proc_entry("poolsize", S_IRUSR|S_IWUSR, acpi_root_dir);
+ if (entry)
+ entry->proc_fops = &acpi_osl_poolsize_ops;
+ return AE_OK;
+}
+
acpi_status acpi_os_terminate(void)
{
if (acpi_irq_handler) {
@@ -661,6 +834,7 @@
static void acpi_os_execute_deferred(void *context)
{
struct acpi_os_dpc *dpc = NULL;
+ struct thread_pool_entry *e = NULL;
ACPI_FUNCTION_TRACE("os_execute_deferred");
@@ -670,8 +844,18 @@
return_VOID;
}
- dpc->function(dpc->context);
-
+ if (max_thread_pool_size > 0 && (e = get_pool_thread())) {
+ ACPI_DEBUG_PRINT((ACPI_DB_WARN, "[%d:%p] function=%p context=%p\n",
+ e->id, e, dpc->function, dpc->context));
+ e->context = dpc->context;
+ e->function = dpc->function;
+ wake_up(&e->more_work);
+ ACPI_DEBUG_PRINT((ACPI_DB_WARN, "[%d:%p] wake finished\n", e->id, e));
+ }
+ else {
+ ACPI_DEBUG_PRINT((ACPI_DB_WARN, "(now) function=%p context=%p\n", dpc->function, dpc->context));
+ dpc->function(dpc->context);
+ }
kfree(dpc);
return_VOID;
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: Linux does not correctly implement the ACPI specification
2006-04-08 10:52 ` Linux does not correctly implement the ACPI specification Peter Wainwright
@ 2006-04-12 16:56 ` Andi Kleen
2006-04-27 19:23 ` Peter Wainwright
0 siblings, 1 reply; 4+ messages in thread
From: Andi Kleen @ 2006-04-12 16:56 UTC (permalink / raw)
To: Peter Wainwright; +Cc: linux-acpi
Peter Wainwright <prw@ceiriog.eclipse.co.uk> writes:
> Instead of executing all the GPE and Notify handlers in the single
> kacpid thread, we create a pool of worker threads and hand over the
> work to them.
You do this at boot - this means you waste a lot of memory for this
obscure case. Not a good idea. If anything create/destroy them
dynamically.
Also externs belong in header files.
-Andi
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: Linux does not correctly implement the ACPI specification
2006-04-12 16:56 ` Andi Kleen
@ 2006-04-27 19:23 ` Peter Wainwright
0 siblings, 0 replies; 4+ messages in thread
From: Peter Wainwright @ 2006-04-27 19:23 UTC (permalink / raw)
To: Andi Kleen; +Cc: linux-acpi
[-- Attachment #1.1: Type: text/plain, Size: 2637 bytes --]
On Wed, 2006-04-12 at 18:56 +0200, Andi Kleen wrote:
> Peter Wainwright <prw@ceiriog.eclipse.co.uk> writes:
> > Instead of executing all the GPE and Notify handlers in the single
> > kacpid thread, we create a pool of worker threads and hand over the
> > work to them.
>
> You do this at boot - this means you waste a lot of memory for this
> obscure case. Not a good idea. If anything create/destroy them
> dynamically.
Eh? the threads *are* created dynamically - and only if the user first
enables this behaviour by writing to a /proc filesystem entry (e.g. in
an init script). NO additional memory (except for the code itself) is
used unless the user explicitly enables this behaviour.
Also, I don't regard this as an "obscure case". Even if this bug were
never triggered by any current hardware, it represents a divergence
from the ACPI spec which could potentially bite any new hardware.
We can't claim that Linux supports the full ACPI spec without some
similar
patch.
>
> Also externs belong in header files.
>
You're thinking of the declaration of acpi_os_initialize2? Well, there
was an existing extern declaration: acpi_os_initialize1 - I just added
another one :-). I don't want to expose this in a header because it is
a purely private interface. The neatest solution would be to make an
__initcall entry. However, this wouldn't work at the moment, because
acpi_os_initialize2 creates an entry in /proc/acpi which does not exist
until the bus.c initialization stuff. Is it possible to
swap the order
of bus.c and osl.c in the link line (I assume this swaps the order of
the
__initcalls)?
Anyway, here is the second version of my patch. It has been tested
pretty thoroughly against 2.6.16.4, and applies cleanly to 2.6.16.11.
As promised, the thread pool entries are now destroyed dynamically; if
no ACPI events occur in 10 seconds, all the extra threads will
disappear.
I have added a kernel boot option "acpi_pool_size=". This is necessary
because
a thermal trip may occur between booting and running any init script
which
writes the /proc/acpi/pool_size (particularly if the system was warm to
start
with). Therefore, the thread pool needs to be enabled at boot time.
I renamed /proc/acpi/poolsize to /proc/acpi/pool_size for greater
readability and
compatibility with other filenames.
I downgraded the debugging messages to ACPI_DB_INFO level, so they are
not printed
by default. The patch is very robust for me now, so I don't think we
should
need them.
Peter
> -Andi
Signed-off-by: Peter R Wainwright <prw@ceiriog.eclipse.co.uk>
[-- Attachment #1.2: acpi-osl2.patch --]
[-- Type: text/x-patch, Size: 8814 bytes --]
diff -u -r linux-2.6.16.4-prw3-old/drivers/acpi/bus.c linux-2.6.16.4-prw3/drivers/acpi/bus.c
--- linux-2.6.16.4-prw3-old/drivers/acpi/bus.c 2006-04-21 12:18:11.000000000 +0100
+++ linux-2.6.16.4-prw3/drivers/acpi/bus.c 2006-04-21 12:18:24.000000000 +0100
@@ -664,6 +664,7 @@
int result = 0;
acpi_status status = AE_OK;
extern acpi_status acpi_os_initialize1(void);
+ extern acpi_status acpi_os_initialize2(void);
ACPI_FUNCTION_TRACE("acpi_bus_init");
@@ -727,6 +728,8 @@
*/
acpi_root_dir = proc_mkdir(ACPI_BUS_FILE_ROOT, NULL);
+ status = acpi_os_initialize2();
+
return_VALUE(0);
/* Mimic structured exception handling */
diff -u -r linux-2.6.16.4-prw3-old/drivers/acpi/osl.c linux-2.6.16.4-prw3/drivers/acpi/osl.c
--- linux-2.6.16.4-prw3-old/drivers/acpi/osl.c 2006-04-21 12:18:11.000000000 +0100
+++ linux-2.6.16.4-prw3/drivers/acpi/osl.c 2006-04-27 18:58:07.000000000 +0100
@@ -46,6 +46,10 @@
#include <linux/efi.h>
+#include <linux/kthread.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+
#define _COMPONENT ACPI_OS_SERVICES
ACPI_MODULE_NAME("osl")
#define PREFIX "ACPI: "
@@ -75,6 +79,213 @@
static acpi_osd_handler acpi_irq_handler;
static void *acpi_irq_context;
static struct workqueue_struct *kacpid_wq;
+struct timer_list acpi_reap_timer;
+
+static int reap_interval = 5000;
+
+/* This parameter may be configured at runtime by writing to
+ /proc/acpi/pool_size */
+static int max_pool_size = 0;
+static int __init acpi_set_pool_size(char *str)
+{
+ get_option(&str, &max_pool_size);
+ return 0;
+}
+__setup("acpi_pool_size=", acpi_set_pool_size);
+
+/* Hopefully we need no special locking here, since this always
+ executes in one thread: kacpid */
+static int current_pool_size = 0;
+static struct list_head thread_pool = LIST_HEAD_INIT(thread_pool);
+
+struct thread_pool_entry {
+ struct task_struct *thread;
+ int id;
+ int active;
+ acpi_osd_exec_callback function;
+ void *context;
+ unsigned long jiffies;
+ wait_queue_head_t more_work;
+ wait_queue_head_t work_done;
+ struct list_head list;
+};
+
+static int acpi_osl_pool_size_read(struct seq_file *seq, void *offset)
+{
+ struct list_head *l;
+
+ ACPI_FUNCTION_TRACE("acpi_osl_pool_size_read");
+
+ seq_printf(seq, "max_pool_size=%d\n", max_pool_size);
+ seq_printf(seq, "current_pool_size=%d\n", current_pool_size);
+ /* FIXME: Need lock? */
+ list_for_each(l, &thread_pool) {
+ struct thread_pool_entry *e = list_entry(l, struct thread_pool_entry, list);
+ seq_printf(seq, "entry=(%p,%d,%p,%p,%lu)\n",
+ e->thread, e->active, e->function, e->context, e->jiffies);
+ }
+ return_VALUE(0);
+}
+
+static int acpi_osl_pool_size_open_fs(struct inode *inode, struct file *file)
+{
+ return single_open(file, acpi_osl_pool_size_read, PDE(inode)->data);
+}
+
+static ssize_t
+acpi_osl_pool_size_write(struct file *file,
+ const char __user * buffer,
+ size_t count, loff_t * ppos)
+{
+ char *size_string;
+
+ ACPI_FUNCTION_TRACE("acpi_osl_pool_size_write");
+
+ size_string = kmalloc(4096, GFP_KERNEL);
+ if (!size_string)
+ return_VALUE(-ENOMEM);
+
+ memset(size_string, 0, 4096);
+
+ if (copy_from_user(size_string, buffer, count)) {
+ ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid data\n"));
+ count = -EFAULT;
+ goto end;
+ }
+
+ size_string[count] = '\0';
+ max_pool_size = simple_strtol(size_string, NULL, 10);
+ ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Pool_Size now %d\n", max_pool_size));
+
+end:
+ kfree(size_string);
+ return_VALUE(count);
+}
+
+static struct file_operations acpi_osl_pool_size_ops = {
+ .open = acpi_osl_pool_size_open_fs,
+ .read = seq_read,
+ .write = acpi_osl_pool_size_write,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+/* This is based on workqueue worker_thread */
+static int acpi_os_execute_worker(void *context)
+{
+ struct thread_pool_entry *e = (struct thread_pool_entry *)context;
+ DECLARE_WAITQUEUE(wait, current);
+
+ set_user_nice(current, -5);
+
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ ACPI_DEBUG_PRINT((ACPI_DB_INFO, "[%d:%p] before loop\n", e->id, e));
+ while (!kthread_should_stop()) {
+ /* Wait until there is work to do in this thread */
+ add_wait_queue(&e->more_work, &wait);
+ if (!e->function) {
+ ACPI_DEBUG_PRINT((ACPI_DB_INFO, "[%d:%p] sleeping\n", e->id, e));
+ schedule();
+ }
+ else {
+ __set_current_state(TASK_RUNNING);
+ }
+ try_to_freeze();
+ remove_wait_queue(&e->more_work, &wait);
+ if (e->function) {
+ acpi_osd_exec_callback function = e->function;
+ void *context = e->context;
+ e->function = NULL;
+ e->context = NULL;
+ ACPI_DEBUG_PRINT((ACPI_DB_INFO, "[%d:%p] executing function=%p context=%p\n",
+ e->id, e, function, context));
+ function(context);
+
+ e->active = 0;
+ e->jiffies = jiffies;
+ ACPI_DEBUG_PRINT((ACPI_DB_INFO, "[%d:%p] exit function=%p context=%p at %lx\n",
+ e->id, e, function, context, e->jiffies));
+
+ }
+ set_current_state(TASK_INTERRUPTIBLE);
+ }
+ ACPI_DEBUG_PRINT((ACPI_DB_INFO, "[%d:%p] stopped\n", e->id, e));
+
+ return 0;
+}
+
+static struct work_struct acpi_reap_task;
+
+static void acpi_queue_reap_threads(unsigned long data);
+
+static void acpi_reap_threads(void *data)
+{
+ struct list_head *l, *n;
+
+ /* FIXME: Need lock? */
+ list_for_each_safe(l, n, &thread_pool) {
+ struct thread_pool_entry *e = list_entry(l, struct thread_pool_entry, list);
+ if (!e->active && (jiffies - e->jiffies) > 10*HZ) {
+ int status;
+ ACPI_DEBUG_PRINT((ACPI_DB_INFO, "acpi_reap_threads REAP [%d:%p]\n", e->id, e));
+ list_del(l);
+ current_pool_size--;
+ status = kthread_stop(e->thread);
+ ACPI_DEBUG_PRINT((ACPI_DB_INFO, "acpi_reap_threads kthread_stop returned %d\n", status));
+ kfree(e);
+ }
+ }
+ if (!timer_pending(&acpi_reap_timer)) {
+ acpi_reap_timer.function = acpi_queue_reap_threads;
+ acpi_reap_timer.data = 0;
+ acpi_reap_timer.expires = jiffies + (HZ * reap_interval) / 1000;
+ add_timer(&(acpi_reap_timer));
+ }
+}
+
+static void acpi_queue_reap_threads(unsigned long data)
+{
+ queue_work(kacpid_wq, &acpi_reap_task);
+}
+
+static struct thread_pool_entry *get_pool_thread(void)
+{
+ struct list_head *l;
+ list_for_each(l, &thread_pool) {
+ struct thread_pool_entry *e = list_entry(l, struct thread_pool_entry, list);
+ if (!e->active) {
+ e->active = 1;
+ return e;
+ }
+ }
+ /* N.B. For the time being we have a user-configurable limit
+ on the current_pool_size. We don't want a bad BIOS to create
+ unlimited kthreads. */
+ if (current_pool_size < max_pool_size) {
+ struct thread_pool_entry *e;
+ e = kmalloc(sizeof(struct thread_pool_entry), GFP_KERNEL);
+ list_add_tail(&e->list, &thread_pool);
+ e->id = current_pool_size;
+ init_waitqueue_head(&e->more_work);
+ init_waitqueue_head(&e->work_done);
+ e->function = NULL;
+ e->context = NULL;
+ e->active = 1;
+ /* kthread_run does kthread_create and wake_up_process */
+ e->thread = kthread_run(acpi_os_execute_worker, e, "kacpid-work-%d",
+ current_pool_size);
+ current_pool_size++;
+ if (!timer_pending(&acpi_reap_timer)) {
+ acpi_reap_timer.function = acpi_queue_reap_threads;
+ acpi_reap_timer.data = 0;
+ acpi_reap_timer.expires = jiffies + (HZ * reap_interval) / 1000;
+ add_timer(&(acpi_reap_timer));
+ }
+ return e;
+ }
+ return NULL;
+}
acpi_status acpi_os_initialize(void)
{
@@ -98,6 +309,18 @@
return AE_OK;
}
+/* enable pool_size modification by /proc filesystem */
+acpi_status acpi_os_initialize2(void)
+{
+ struct proc_dir_entry *entry;
+ entry = create_proc_entry("pool_size", S_IRUSR|S_IWUSR, acpi_root_dir);
+ if (entry)
+ entry->proc_fops = &acpi_osl_pool_size_ops;
+ INIT_WORK(&acpi_reap_task, acpi_reap_threads, NULL);
+ init_timer(&acpi_reap_timer);
+ return AE_OK;
+}
+
acpi_status acpi_os_terminate(void)
{
if (acpi_irq_handler) {
@@ -661,6 +884,7 @@
static void acpi_os_execute_deferred(void *context)
{
struct acpi_os_dpc *dpc = NULL;
+ struct thread_pool_entry *e = NULL;
ACPI_FUNCTION_TRACE("os_execute_deferred");
@@ -670,8 +894,17 @@
return_VOID;
}
- dpc->function(dpc->context);
-
+ if (max_pool_size > 0 && (e = get_pool_thread())) {
+ ACPI_DEBUG_PRINT((ACPI_DB_INFO, "[%d:%p] function=%p context=%p\n",
+ e->id, e, dpc->function, dpc->context));
+ e->context = dpc->context;
+ e->function = dpc->function;
+ wake_up(&e->more_work);
+ }
+ else {
+ ACPI_DEBUG_PRINT((ACPI_DB_INFO, "[inline] function=%p context=%p\n", dpc->function, dpc->context));
+ dpc->function(dpc->context);
+ }
kfree(dpc);
return_VOID;
[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 191 bytes --]
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2006-04-27 19:23 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2006-03-07 20:47 Concurrency in the execution of ACPI control methods Moore, Robert
2006-04-08 10:52 ` Linux does not correctly implement the ACPI specification Peter Wainwright
2006-04-12 16:56 ` Andi Kleen
2006-04-27 19:23 ` Peter Wainwright
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox