summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xnuttx/Documentation/NuttXDemandPaging.html178
-rwxr-xr-xnuttx/include/nuttx/page.h126
-rw-r--r--nuttx/sched/Makefile4
-rw-r--r--nuttx/sched/os_internal.h8
-rw-r--r--nuttx/sched/os_start.c26
-rwxr-xr-xnuttx/sched/pg_internal.h33
-rw-r--r--nuttx/sched/pg_miss.c2
-rwxr-xr-xnuttx/sched/pg_worker.c537
-rwxr-xr-xnuttx/sched/work_internal.h2
9 files changed, 830 insertions, 86 deletions
diff --git a/nuttx/Documentation/NuttXDemandPaging.html b/nuttx/Documentation/NuttXDemandPaging.html
index cf4bb0ce4..fb0894d6b 100755
--- a/nuttx/Documentation/NuttXDemandPaging.html
+++ b/nuttx/Documentation/NuttXDemandPaging.html
@@ -72,7 +72,7 @@
<tr>
<td>&nbsp;</td>
<td>
- <a href="#Fillnitiation">Fill Initiation</a>
+ <a href="#FillInitiation">Fill Initiation</a>
</td>
</tr>
<tr>
@@ -160,14 +160,14 @@
<dl>
<dt><code>g_waitingforfill</code></dt>
<dd>An OS list that is used to hold the TCBs of tasks that are waiting for a page fill.</dd>
- <dt><code>g_pendingfill</code></dt>
+ <dt><code>g_pendingfilltcb</code></dt>
<dd>A variable that holds a reference to the TCB of the thread that is currently be re-filled.</dd>
<dt><code>g_pgworker</code></dt>
<dd>The <i>process</i> ID of of the thread that will perform the page fills.</dd>
<dt><code>pg_callback()</code></dt>
<dd>The callback function that is invoked from a driver when the fill is complete.</dd>
<dt><code>pg_miss()</code></dt>
- <dd>The function that is called from chip-specific code to handle a page fault.</dd>
+ <dd>The function that is called from architecture-specific code to handle a page fault.</dd>
<dt><code>TCB</code></dt>
<dd>Task Control Block</dd>
</dl>
@@ -215,8 +215,10 @@
<p>
Declarations for <code>g_waitingforfill</code>, <code>g_pgworker</code>, and other
internal, private definitions will be provided in <code>sched/pg_internal.h</code>.
- All public definitions that should be used by the chip-specific code will be available
- in <code>include/nuttx/page.h</code> and <code>include/nuttx/arch.h</code>.
+ All public definitions that should be used by the architecture-specific code will be available
+ in <code>include/nuttx/page.h</code>.
+ Most architecture-specific functions are declared in <code>include/nuttx/arch.h</code>,
+ but for the case of this paging logic, those architecture specific functions are instead declared in <code>include/nuttx/page.h</code>.
</p>
<a name="PageFaults"><h2>Page Faults</h2></a>
@@ -244,6 +246,7 @@
<b>Boost the page fill worker thread priority</b>.
Check the priority of the task at the head of the <code>g_waitingforfill</code> list.
If the priority of that task is higher than the current priority of the page fill worker thread, then boost the priority of the page fill worker thread to that priority.
+ Thus, the page fill worker thread will always run at the priority of the highest priority task that is waiting for a fill.
</li>
<li>
<b>Signal the page fill worker thread</b>.
@@ -277,51 +280,46 @@
Interrupt handling logic must always be available and &quot;<a href="#MemoryOrg">locked</a>&quot; into memory so that page faults never come from interrupt handling.
</li>
<li>
- The chip-specific page fault exception handling has already verified that the exception did not occur from interrupt/exception handling logic.
+ The architecture-specific page fault exception handling has already verified that the exception did not occur from interrupt/exception handling logic.
</li>
<li>
As mentioned above, the task causing the page fault must not be the page fill worker thread because that is the only way to complete the page fill.
</li>
</ul>
</p>
-<p>
- <b>Locking code in Memory</b>.
- One way to accomplish this would be a two phase link:
- <ul>
- <li>
- In the first phase, create a partially linked objected containing all interrupt/exception handling logic, the page fill worker thread plus all parts of the IDLE thread (which must always be available for execution).
- </li>
- <li>
- All of the <code>.text</code> and <code>.rodata</code> sections of this partial link should be collected into a single section.
- </li>
- <li>
- The second link would link the partially linked object along with the remaining object to produce the final binary.
- The linker script should position the &quot;special&quot; section so that it lies in a reserved, &quot;non-swappable&quot; region.
- </ul>
-</p>
<a name="FillInitiation"><h2>Fill Initiation</h2></a>
<p>
- The page fill worker thread will be awakened on one of two conditions:
+ The page fill worker thread will be awakened on one of three conditions:
<ul>
<li>
- When signaled by <code>pg_miss()</code>, the page fill worker thread will be awakenend (see above), or
+ When signaled by <code>pg_miss()</code>, the page fill worker thread will be awakenend (see above),
+ </li>
+ <li>
+ From <code>pg_callback()</code> after completing last fill (when <code>CONFIG_PAGING_BLOCKINGFILL</code> is defined... see below), or
</li>
<li>
- From <code>pg_fillcomplete()</code> after completing last fill (see below).
+ A configurable timeout expires with no activity.
+ This timeout can be used to detect failure conditions such things as fills that never complete.
</li>
</ul>
</p>
<p>
- The page fill worker thread will maintain a static variable called <code>_TCB *g_pendingfill</code>.
- If not fill is in progress, <code>g_pendingfill</code> will be NULL.
- Otherwise, will point to the TCB of the task which is receiving the fill that is in progess.
+ The page fill worker thread will maintain a static variable called <code>_TCB *g_pendingfilltcb</code>.
+ If no fill is in progress, <code>g_pendingfilltcb</code> will be NULL.
+ Otherwise, it will point to the TCB of the task which is receiving the fill that is in progess.
</p>
+<ul><small>
+ <b>NOTE</b>:
+ I think that this is the only state in which a TCB does not reside in some list.
+ Here is it in limbo, outside of the normally queuing while the page file is in progress.
+ While here, it will be marked with TSTATE_TASK_INVALID.
+</small></ul>
<p>
- When awakened from <code>pg_miss()</code>, no fill will be in progress and <code>g_pendingfill</code> will be NULL.
+ When awakened from <code>pg_miss()</code>, no fill will be in progress and <code>g_pendingfilltcb</code> will be NULL.
In this case, the page fill worker thread will call <code>pg_startfill()</code>.
That function will perform the following operations:
<ul>
@@ -334,50 +332,73 @@
</li>
<li>
Call <code>up_allocpage(tcb, &vpage)</code>.
- This chip-specific function will set aside page in memory and map to virtual address (vpage).
- If all pages available pages are in-use (the typical case),
+ This architecture-specific function will set aside page in memory and map to virtual address (vpage).
+ If all available pages are in-use (the typical case),
this function will select a page in-use, un-map it, and make it available.
</li>
<li>
- Call the chip-specific function <code>up_fillpage(page, pg_callback)</code>.
- This will start asynchronous page fill.
- The page fill worker thread will provide a callback function, <code>pg_callback</code>,
- that will be called when the page fill is finished (or an error occurs).
- This callback will probably from interrupt level.
- </li>
- <li>
- Restore default priority of the page fill worker thread's default priority and wait to be signaled for the next event -- the fill completion event.
+ Call the architecture-specific function <code>up_fillpage()</code>.
+ Two versions of the up_fillpage function are supported -- a blocking and a non-blocking version based upon the configuratin setting <code>CONFIG_PAGING_BLOCKINGFILL</code>.
+ <ul>
+ <li>
+ If <code>CONFIG_PAGING_BLOCKINGFILL</code> is defined, then up_fillpage is blocking call.
+ In this case, <code>up_fillpage()</code> will accept only (1) a reference to the TCB that requires the fill.
+ Architecture-specific context information within the TCB will be sufficient to perform the fill.
+ And (2) the (virtual) address of the allocated page to be filled.
+ The resulting status of the fill will be provided by return value from <code>up_fillpage()</code>.
+ </li>
+ <li>
+ If <code>CONFIG_PAGING_BLOCKINGFILL</code> is defined, then up_fillpage is non-blocking call.
+ In this case <code>up_fillpage()</code> will accept an additional argument:
+ The page fill worker thread will provide a callback function, <code>pg_callback</code>.
+ This function is non-blocking, it will start an asynchronous page fill.
+ After calling the non-blocking <code>up_fillpage()</code>, the page fill worker thread will wait to be signaled for the next event -- the fill completion event.
+ The callback function will be called when the page fill is finished (or an error occurs).
+ The resulting status of the fill will be providing as an argument to the callback functions.
+ This callback will probably occur from interrupt level.
+ </ul>
</li>
</ul>
</p>
<p>
- While the fill is in progress, other tasks may execute.
- If another page fault occurs during this time, the faulting task will be blocked and its TCB will be added (in priority order) to <code>g_waitingforfill</code>.
+ In any case, while the fill is in progress, other tasks may execute.
+ If another page fault occurs during this time, the faulting task will be blocked, its TCB will be added (in priority order) to <code>g_waitingforfill</code>, and the priority of the page worker task may be boosted.
But no action will be taken until the current page fill completes.
NOTE: The IDLE task must also be fully <a href="#MemoryOrg">locked</a> in memory.
The IDLE task cannot be blocked.
It the case where all tasks are blocked waiting for a page fill, the IDLE task must still be available to run.
<p>
- The chip-specific functions, <code>up_allocpage(tcb, &vpage)</code> and <code>up_fillpage(page, pg_callback)</code>
+ The architecture-specific functions, <code>up_checkmapping()</code>, <code>up_allocpage(tcb, &vpage)</code> and <code>up_fillpage(page, pg_callback)</code>
will be prototyped in <code>include/nuttx/arch.h</code>
</p>
<a name="FillComplete"><h2>Fill Complete</h2></a>
<p>
- When the chip-specific driver completes the page fill, it will call the <code>pg_callback()</code> that was provided to <code>up_fillpage</code>.
- <code>pg_callback()</code> will probably be called from driver interrupt-level logic.
- The driver ill provide the result of the fill as an argument.
+ For the blocking <code>up_fillpage()</code>, the result of the fill will be returned directly from the call to <code>up_fillpage</code>.
+</p>
+<p>
+ For the non-blocking <code>up_fillpage()</code>, the architecture-specific driver call the <code>pg_callback()</code> that was provided to <code>up_fillpage()</code> when the fill completes.
+ In this case, the <code>pg_callback()</code> will probably be called from driver interrupt-level logic.
+ The driver will provide the result of the fill as an argument to the callback function.
NOTE: <code>pg_callback()</code> must also be <a href="#MemoryOrg">locked</a> in memory.
</p>
<p>
- When <code>pg_callback()</code> is called, it will perform the following operations:
+ In this non-blocking case, the callback <code>pg_callback()</code> will perform the following operations when it is notified that the fill has completed:
<ul>
<li>
- Verify that <code>g_pendingfill</code> is non-NULL.
+ Verify that <code>g_pendingfilltcb</code> is non-NULL.
+ </li>
+ <li>
+ Find the higher priority between the task waiting for the fill to complete in <code>g_pendingfilltcb</code> and the task waiting at the head of the <code>g_waitingforfill</code> list.
+ That will be the priority of he highest priority task waiting for a fill.
+ </li>
+ <li>
+ If this higher priority is higher than current page fill worker thread, then boost worker thread's priority to that level.
+ Thus, the page fill worker thread will always run at the priority of the highest priority task that is waiting for a fill.
</li>
<li>
- If the priority of thread in <code>g_pendingfill</code> is higher than page fill worker thread, boost work thread to that level.
+ Save the result of the fill operation.
</li>
<li>
Signal the page fill worker thread.
@@ -388,18 +409,20 @@
<a name="TaskResumption"><h2>Task Resumption</h2></a>
<p>
- When the page fill worker thread is awakened and <code>g_pendingfill</code> is non-NULL (and other state variables are in concurrence),
- the page fill thread will know that is was awakened because of a page fill completion event.
- In this case, the page fill worker thread will:
+ For the non-blocking <code>up_fillpage()</code>, the page fill worker thread will detect that the page fill is complete when it is awakened with <code>g_pendingfilltcb</code> non-NULL and fill completion status from <code>pg_callback</code>.
+ In the non-blocking case, the page fill worker thread will know that the page fill is complete when <code>up_fillpage()</code> returns.
+</p>
+<p>
+ In this either, the page fill worker thread will:
<ul>
<li>
- Verify consistency of state information and <code>g_pendingfill</code>.
+ Verify consistency of state information and <code>g_pendingfilltcb</code>.
</li>
<li>
Verify that the page fill completed successfully, and if so,
</li>
<li>
- Call <code>up_unblocktask(g_pendingfill)</code> to make the task that just received the fill ready-to-run.
+ Call <code>up_unblocktask(g_pendingfilltcb)</code> to make the task that just received the fill ready-to-run.
</li>
<li>
Check if the <code>g_waitingforfill</code> list is empty.
@@ -409,10 +432,10 @@
Remove the highest priority task waiting for a page fill from <code>g_waitingforfill</code>,
</li>
<li>
- Save the task's TCB in <code>g_pendingfill</code>,
+ Save the task's TCB in <code>g_pendingfilltcb</code>,
</li>
<li>
- If the priority of the thread in <code>g_pendingfill</code>, is higher in priority than the default priority of the page fill worker thread, then set the priority of the page fill worker thread to that priority.
+ If the priority of the thread in <code>g_pendingfilltcb</code>, is higher in priority than the default priority of the page fill worker thread, then set the priority of the page fill worker thread to that priority.
</li>
<li>
Call <code>pg_startfill()</code> which will start the next fill (as described above).
@@ -423,7 +446,7 @@
Otherwise,
<ul>
<li>
- Set <code>g_pendingfill</code> to NULL.
+ Set <code>g_pendingfilltcb</code> to NULL.
</li>
<li>
Restore the default priority of the page fill worker thread.
@@ -447,6 +470,7 @@
<a name="MemoryOrg"><h2>Memory Organization</h2></a>
<p>
+ <b>Memory Regions</b>.
Chip specific logic will map the virtual and physical address spaces into three general regions:
<ol>
<li>
@@ -459,7 +483,7 @@
All interrupt logic must be locked in memory because the design present here will not support page faults from interrupt handlers.
This includes the page fault handling logic and <a href="#PageFaults"><code>pg_miss()</code></a> that is called from the page fault handler.
It also includes the <a href="#FillComplete"><code>pg_callback()</code></a> function that wakes up the page fill worker thread
- and whatever chip-specific logic that calls <code>pg_callback()</code>.
+ and whatever architecture-specific logic that calls <code>pg_callback()</code>.
</li>
<li>
All logic for the IDLE thread.
@@ -547,6 +571,7 @@
</table></center>
<p>
+ <b>Example</b>.
As an example, suppose that the size of the SRAM is 192Kb (as in the NXP LPC3131). And suppose further that:
<ul>
<li>
@@ -563,16 +588,35 @@
</li>
</ul>
<p>
- Then, the size of the locked, memory resident code is 32Kb (32 pages).
+ Then, the size of the locked, memory resident code is 32Kb (<i>m</i>=32 pages).
The size of the physical page region is 96Kb (96 pages), and the
size of the data region is 64 pages.
- And the size of the virtual paged region must then be greater than or equal to (1024-32) or 992 pages (<i>m</i>).
+ And the size of the virtual paged region must then be greater than or equal to (1024-32) or 992 pages (<i>n</i>).
+</p>
+
+<p>
+ <b>Building the Locked, In-Memory Image</b>.
+ One way to accomplish this would be a two phase link:
+ <ul>
+ <li>
+ In the first phase, create a partially linked objected containing all interrupt/exception handling logic, the page fill worker thread plus all parts of the IDLE thread (which must always be available for execution).
+ </li>
+ <li>
+ All of the <code>.text</code> and <code>.rodata</code> sections of this partial link should be collected into a single section.
+ </li>
+ <li>
+ The second link would link the partially linked object along with the remaining object to produce the final binary.
+ The linker script should position the &quot;special&quot; section so that it lies in a reserved, &quot;non-swappable&quot; region.
+ </ul>
</p>
<a name="ArchFuncs"><h2>Architecture-Specific Functions</h2></a>
<p>
- Standard functions that should already be provided in the architecture port:
+ Most standard, architecture-specific functions are declared in <code>include/nuttx/arch.h</code>.
+ However, for the case of this paging logic, the architecture specific functions are declared in <code>include/nuttx/page.h</code>.
+ Standard, architecture-specific functions that should already be provided in the architecture port.
+ The following are used by the common paging logic:
</p>
<ul><dl>
<dt>
@@ -603,7 +647,7 @@
<code>int up_checkmapping(FAR _TCB *tcb);</code>
</dt>
<dd>
- The function <code>up_checkmapping()</code> returns an indication that checks if the page fill still needs to performed or not.
+ The function <code>up_checkmapping()</code> returns an indication if the page fill still needs to performed or not.
In certain conditions, the page fault may occur on several threads and be queued multiple times.
This function will prevent the same page from be filled multiple times.
</dd>
@@ -611,18 +655,18 @@
<code>int up_allocpage(FAR _TCB *tcb, FAR void *vpage);</code>
</dt>
<dd>
- This chip-specific function will set aside page in memory and map to its correct virtual address.
- Architecture-specific context information saved within the TCB will provide the function with the information need to identify the virtual miss address.
- This function will return the allocated physical page address in <code>paddr</code>.
- The size of a physical page is determined by the configuration setting <code>CONFIG_PAGING_PAGESIZE</code>.
+ This architecture-specific function will set aside page in memory and map to its correct virtual address.
+ Architecture-specific context information saved within the TCB will provide the function with the information needed to identify the virtual miss address.
+ This function will return the allocated physical page address in <code>vpage</code>.
+ The size of the underlying physical page is determined by the configuration setting <code>CONFIG_PAGING_PAGESIZE</code>.
NOTE: This function must <i>always</i> return a page allocation.
- If all pages available pages are in-use (the typical case), then this function will select a page in-use, un-map it, and make it available.
+ If all available pages are in-use (the typical case), then this function will select a page in-use, un-map it, and make it available.
</dd>
<dt><code>int up_fillpage(FAR _TCB *tcb, FAR const void *vpage, void (*pg_callback)(FAR _TCB *tcb, int result));</code>
</dt>
- The actual filling of the page with data from the non-volatile, be performed by a separate call to the architecture-specific function, <code>up_fillpage()</code>.
+ The actual filling of the page with data from the non-volatile, must be performed by a separate call to the architecture-specific function, <code>up_fillpage()</code>.
This will start asynchronous page fill.
- The common logic will provide a callback function, <code>pg_callback</code>, that will be called when the page fill is finished (or an error occurs).
+ The common paging logic will provide a callback function, <code>pg_callback</code>, that will be called when the page fill is finished (or an error occurs).
This callback is assumed to occur from an interrupt level when the device driver completes the fill operation.
</dt>
</dl></ul>
diff --git a/nuttx/include/nuttx/page.h b/nuttx/include/nuttx/page.h
index 09a17f5a0..147a3b6c8 100755
--- a/nuttx/include/nuttx/page.h
+++ b/nuttx/include/nuttx/page.h
@@ -69,7 +69,8 @@ extern "C" {
#endif
/****************************************************************************
- * Public Functions
+ * Public Functions -- Provided by common paging logic to architecture-
+ * specific logic.
****************************************************************************/
/****************************************************************************
@@ -140,6 +141,129 @@ extern "C" {
EXTERN void pg_miss(void);
+/****************************************************************************
+ * Public Functions -- Provided by architecture-specific logic to common
+ * paging logic.
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: up_checkmapping()
+ *
+ * Description:
+ *
+ * The function up_checkmapping() returns an indication if the page fill
+ * still needs to performed or not. In certain conditions, the page fault
+ * may occur on several threads and be queued multiple times. This function
+ * will prevent the same page from be filled multiple times.
+ *
+ * Input Parameters:
+ * tcb - A reference to the task control block of the task that we believe
+ * needs to have a page fill. Architecture-specific logic can
+ * retrieve page fault information from the architecture-specific
+ * context information in this TCB and can consult processor resources
+ * (page tables or TLBs or ???) to determine if the fill still needs
+ * to be performed or not.
+ *
+ * Returned Value:
+ * This function will return zero (OK) if the mapping is in place and
+ * the negated errn ENXIO if the mapping is still needed. Other errors
+ * may also be returned but these will be interpreted as fatal error
+ * conditions.
+ *
+ * Assumptions:
+ * - This function is called from the normal tasking context (but with
+ * interrupts disabled). The implementation must take whatever actions
+ * are necessary to assure that the operation is safe within this
+ * context.
+ *
+ ****************************************************************************/
+
+EXTERN int up_checkmapping(FAR _TCB *tcb);
+
+/****************************************************************************
+ * Name: up_allocpage()
+ *
+ * Description:
+ * This architecture-specific function will set aside page in memory and map
+ * the page to its correct virtual address. Architecture-specific context
+ * information saved within the TCB will provide the function with the
+ * information needed to identify the virtual miss address.
+ *
+ * This function will return the allocated physical page address in vpage.
+ * The size of the underlying physical page is determined by the
+ * configuration setting CONFIG_PAGING_PAGESIZE.
+ *
+ * NOTE: This function must always return a page allocation. If all
+ * available pages are in-use (the typical case), then this function will
+ * select a page in-use, un-map it, and make it available.
+ *
+ * Input Parameters:
+ * tcb - A reference to the task control block of the task that needs to
+ * have a page fill. Architecture-specific logic can retrieve page
+ * fault information from the architecture-specific context
+ * information in this TCB to perform the mapping.
+ *
+ * Returned Value:
+ * This function will return zero (OK) if the allocation was successful.
+ * A negated errno value may be returned if an error occurs. All errors,
+ * however, are fatal.
+ *
+ * Assumptions:
+ * - This function is called from the normal tasking context (but with
+ * interrupts disabled). The implementation must take whatever actions
+ * are necessary to assure that the operation is safe within this
+ * context.
+ *
+ ****************************************************************************/
+
+EXTERN int up_allocpage(FAR _TCB *tcb, FAR void **vpage);
+
+/****************************************************************************
+ * Name: up_fillpage()
+ *
+ * Description:
+ * After a page is allocated and mapped by up_allocpage(), the actual
+ * filling of the page with data from the non-volatile, must be performed
+ * by a separate call to the architecture-specific function, up_fillpage().
+ * This function is non-blocking, it will start an asynchronous page fill.
+ * The common paging logic will provide a callback function, pg_callback,
+ * that will be called when the page fill is finished (or an error occurs).
+ * This callback is assumed to occur from an interrupt level when the
+ * device driver completes the fill operation.
+ *
+ * Input Parameters:
+ * tcb - A reference to the task control block of the task that needs to
+ * have a page fill. Architecture-specific logic can retrieve page
+ * fault information from the architecture-specific context
+ * information in this TCB to perform the fill.
+ * pg_callbck - The function to be called when the page fill is complete.
+ *
+ * Returned Value:
+ * This function will return zero (OK) if the page fill was successfully
+ * started (the result of the page fill is passed to the callback function
+ * as the result argument). A negated errno value may be returned if an
+ * error occurs. All errors, however, are fatal.
+ *
+ * NOTE: -EBUSY has a special meaning. It is used internally to mean that
+ * the callback function has not executed. Therefore, -EBUSY should
+ * never be provided in the result argument of pg_callback.
+ *
+ * Assumptions:
+ * - This function is called from the normal tasking context (but
+ * interrupts siabled). The implementation must take whatever actions
+ * are necessary to assure that the operation is safe within this context.
+ * - Upon return, the caller will sleep waiting for the page fill callback
+ * to occur. The callback function will perform the wakeup.
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_PAGING_BLOCKINGFILL
+EXTERN int up_fillpage(FAR _TCB *tcb, FAR void *vpage);
+#else
+typedef void (*up_pgcallback_t)(FAR _TCB *tcb, int result);
+EXTERN int up_fillpage(FAR _TCB *tcb, FAR void *vpage, up_pgcallback_t pg_callback);
+#endif
+
#undef EXTERN
#if defined(__cplusplus)
}
diff --git a/nuttx/sched/Makefile b/nuttx/sched/Makefile
index 40392504c..b11511b65 100644
--- a/nuttx/sched/Makefile
+++ b/nuttx/sched/Makefile
@@ -1,7 +1,7 @@
############################################################################
# sched/Makefile
#
-# Copyright (C) 2007-2009 Gregory Nutt. All rights reserved.
+# Copyright (C) 2007-2010 Gregory Nutt. All rights reserved.
# Author: Gregory Nutt <spudmonkey@racsa.co.cr>
#
# Redistribution and use in source and binary forms, with or without
@@ -140,7 +140,7 @@ WORK_SRCS = work_thread.c work_queue.c work_cancel.c
endif
ifeq ($(CONFIG_PAGING),y)
-PGFILL_SRCS = pg_miss.c
+PGFILL_SRCS = pg_miss.c pg_worker.c
endif
IRQ_SRCS = irq_initialize.c irq_attach.c irq_dispatch.c irq_unexpectedisr.c
diff --git a/nuttx/sched/os_internal.h b/nuttx/sched/os_internal.h
index d37000b6a..f2cbbf863 100644
--- a/nuttx/sched/os_internal.h
+++ b/nuttx/sched/os_internal.h
@@ -1,7 +1,7 @@
/****************************************************************************
* sched/os_internal.h
*
- * Copyright (C) 2007-2009 Gregory Nutt. All rights reserved.
+ * Copyright (C) 2007-2010 Gregory Nutt. All rights reserved.
* Author: Gregory Nutt <spudmonkey@racsa.co.cr>
*
* Redistribution and use in source and binary forms, with or without
@@ -199,6 +199,12 @@ extern volatile dq_queue_t g_waitingformqnotempty;
extern volatile dq_queue_t g_waitingformqnotfull;
#endif
+/* This is the list of all tasks that are blocking waiting for a page fill */
+
+#ifdef CONFIG_PAGING
+extern volatile dq_queue_t g_waitingforfill;
+#endif
+
/* This the list of all tasks that have been initialized, but not yet
* activated. NOTE: This is the only list that is not prioritized.
*/
diff --git a/nuttx/sched/os_start.c b/nuttx/sched/os_start.c
index ac52c4d66..9e299a946 100644
--- a/nuttx/sched/os_start.c
+++ b/nuttx/sched/os_start.c
@@ -62,6 +62,9 @@
#include "clock_internal.h"
#include "timer_internal.h"
#include "irq_internal.h"
+#ifdef CONFIG_PAGING
+# include "pg_internal.h"
+#endif
#ifdef CONFIG_SCHED_WORKQUEUE
# include "work_internal.h"
#endif
@@ -172,12 +175,6 @@ pidhash_t g_pidhash[CONFIG_MAX_TASKS];
pid_t g_worker;
#endif
-/* The task ID of the page fill worker thread */
-
-#ifdef CONFIG_PAGING
-pid_t g_pgworker;
-#endif
-
/* This is a table of task lists. This table is indexed by
* the task state enumeration type (tstate_t) and provides
* a pointer to the associated static task list (if there
@@ -451,6 +448,23 @@ void os_start(void)
(void)sched_setupidlefiles(&g_idletcb);
+ /* Start the page fill worker thread that will resolve page faults.
+ * This should always be the first thread started because it may
+ * have to resolve page faults in other threads
+ */
+
+#ifdef CONFIG_PAGING
+#ifndef CONFIG_CUSTOM_STACK
+ g_pgworker = task_create("pgfill", CONFIG_PAGING_DEFPRIO,
+ CONFIG_PAGING_STACKSIZE,
+ (main_t)pg_worker, (const char **)NULL);
+#else
+ g_pgworker = task_create("pgfill", CONFIG_PAGING_DEFPRIO,
+ (main_t)pg_worker, (const char **)NULL);
+#endif
+ ASSERT(g_pgworker != ERROR);
+#endif
+
/* Start the worker thread that will perform misc garbage clean-up */
#ifdef CONFIG_SCHED_WORKQUEUE
diff --git a/nuttx/sched/pg_internal.h b/nuttx/sched/pg_internal.h
index 5a22daad1..15f7bbb18 100755
--- a/nuttx/sched/pg_internal.h
+++ b/nuttx/sched/pg_internal.h
@@ -51,12 +51,20 @@
/* Configuration ************************************************************/
-#ifndef CONFIG_PAGING_WORKPRIORITY
-# define CONFIG_PAGING_WORKPRIORITY 50
+/* Supply reasonable (but probably non-optimal) default settings if
+ * configuration items are omitted.
+ */
+
+#ifndef CONFIG_PAGING_DEFPRIO
+# define CONFIG_PAGING_DEFPRIO 50
+#endif
+
+#ifndef CONFIG_PAGING_WORKPERIOD
+# define CONFIG_PAGING_WORKPERIOD (500*1000) /* 1/2 second */
#endif
-#ifndef CONFIG_PAGING_WORKSTACKSIZE
-# define CONFIG_PAGING_WORKSTACKSIZE CONFIG_IDLETHREAD_STACKSIZE
+#ifndef CONFIG_PAGING_STACKSIZE
+# define CONFIG_PAGING_STACKSIZE CONFIG_IDLETHREAD_STACKSIZE
#endif
#ifdef CONFIG_DISABLE_SIGNALS
@@ -73,13 +81,24 @@
#ifndef __ASSEMBLY
-/* If is the PID of the page fill worker thread */
+/* This is the task IDof the page fill worker thread. This value was set in
+ * os_start when the page fill worker thread was started.
+ */
extern pid_t g_pgworker;
-/* If non-NULL, this points to the TCB of the task currently being filled */
+/* The page fill worker thread maintains a static variable called
+ * g_pendingfilltcb. If no fill is in progress, g_pendingfilltcb will be NULL.
+ * Otherwise, g_pendingfile will point to the TCB of the task which is
+ * receiving the fill that is in progess.
+ *
+ * NOTE: I think that this is the only state in which a TCB does not reside
+ * in some list. Here is it in limbo, outside of the normally queuing while
+ * the page file is in progress. Where here, it will be marked with
+ * TSTATE_TASK_INVALID.
+ */
-extern FAR _TCB *g_filltcb;
+extern FAR _TCB *g_pendingfilltcb;
/****************************************************************************
* Public Function Prototypes
diff --git a/nuttx/sched/pg_miss.c b/nuttx/sched/pg_miss.c
index d1fdf8b31..10680365b 100644
--- a/nuttx/sched/pg_miss.c
+++ b/nuttx/sched/pg_miss.c
@@ -166,7 +166,7 @@ void pg_miss(void)
* thread to start working on the queued page fill requests.
*/
- if (!g_filltcb)
+ if (!g_pendingfilltcb)
{
kill(g_pgworker, SIGWORK);
}
diff --git a/nuttx/sched/pg_worker.c b/nuttx/sched/pg_worker.c
new file mode 100755
index 000000000..51975b05a
--- /dev/null
+++ b/nuttx/sched/pg_worker.c
@@ -0,0 +1,537 @@
+/****************************************************************************
+ * sched/pg_worker.c
+ * Page fill worker thread implementation.
+ *
+ * Copyright (C) 2010 Gregory Nutt. All rights reserved.
+ * Author: Gregory Nutt <spudmonkey@racsa.co.cr>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * 3. Neither the name NuttX nor the names of its contributors may be
+ * used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+#include <unistd.h>
+#include <queue.h>
+#include <assert.h>
+#include <errno.h>
+#include <debug.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/page.h>
+#include <nuttx/clock.h>
+
+#include "os_internal.h"
+#include "pg_internal.h"
+
+#ifdef CONFIG_PAGING
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Configuration ************************************************************/
+
+#ifdef CONFIG_DISABLE_SIGNALS
+# warning "Signals needed by this function (CONFIG_DISABLE_SIGNALS=n)"
+#endif
+
+/****************************************************************************
+ * Private Type Declarations
+ ****************************************************************************/
+
+/****************************************************************************
+ * Public Variables
+ ****************************************************************************/
+
+/* This is the task ID of the page fill worker thread. This value was set in
+ * os_start when the page fill worker thread was started.
+ */
+
+pid_t g_pgworker;
+
+/* The page fill worker thread maintains a static variable called
+ * g_pendingfilltcb. If no fill is in progress, g_pendingfilltcb will be NULL.
+ * Otherwise, g_pendingfile will point to the TCB of the task which is
+ * receiving the fill that is in progess.
+ *
+ * NOTE: I think that this is the only state in which a TCB does not reside
+ * in some list. Here is it in limbo, outside of the normally queuing while
+ * the page file is in progress. Where here, it will be marked with
+ * TSTATE_TASK_INVALID.
+ */
+
+FAR _TCB *g_pendingfilltcb;
+
+/****************************************************************************
+ * Private Variables
+ ****************************************************************************/
+
+#ifndef CONFIG_PAGING_BLOCKINGFILL
+
+/* When a page fill completes, the result of the fill is stored here. The
+ * value -EBUSY means that the page fill callback has not yet been received.
+ */
+
+static int g_fillresult;
+
+/* A configurable timeout period (in clock ticks) may be select to detect
+ * page fill failures.
+ */
+
+#ifdef CONFIG_PAGING_TIMEOUT_TICKS
+status uint32_t g_starttime;
+#endif
+#endif
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: pg_callback
+ *
+ * Description:
+ * This function is called from the architecture-specific, page fill logic
+ * when the page fill completes (with or without an error). A reference to
+ * this function was provided to up_fillpage(). The driver will provide
+ * the result of the fill as an argument.
+ *
+ * NOTE: pg_callback() must also be locked in memory.
+ *
+ * When pg_callback() is called, it will perform the following operations:
+ *
+ * - Verify that g_pendingfilltcb is non-NULL.
+ * - Find the higher priority between the task waiting for the fill to
+ * complete in g_pendingfilltcb and the task waiting at the head of the
+ * g_waitingforfill list. That will be the priority of he highest priority
+ * task waiting for a fill.
+ * - If this higher priority is higher than current page fill worker thread,
+ * then boost worker thread's priority to that level. Thus, the page fill
+ * worker thread will always run at the priority of the highest priority
+ * task that is waiting for a fill.
+ * - Signal the page fill worker thread.
+ *
+ * Input parameters:
+ * tcb - The TCB of the task that just received the fill.
+ * result - The result of the page fill operation.
+ *
+ * Returned Value:
+ * None
+ *
+ * Assumptions:
+ * Possibly executing in the context of a driver interrupt handler???
+ *
+ ****************************************************************************/
+
+#ifndef CONFIG_PAGING_BLOCKINGFILL
+static void pg_callback(FAR _TCB *tcb, int result)
+{
+ /* Verify that g_pendingfilltcb is non-NULL */
+
+ if (g_pendingfilltcb)
+ {
+ FAR _TCB *htcb = (FAR _TCB *)g_waitingforfill.head;
+ FAR _TCB *wtcb = sched_gettcb(g_pgworker);
+
+ /* Find the higher priority between the task waiting for the fill to
+ * complete in g_pendingfilltcb and the task waiting at the head of the
+ * g_waitingforfill list. That will be the priority of he highest
+ * priority task waiting for a fill.
+ */
+
+ int priority = g_pendingfilltcb->sched_priority;
+ if (htcb && priority < htcb->sched_priority)
+ {
+ priority = htcb->sched_priority;
+ }
+
+ /* If this higher priority is higher than current page fill worker
+ * thread, then boost worker thread's priority to that level. Thus,
+ * the page fill worker thread will always run at the priority of
+ * the highest priority task that is waiting for a fill.
+ */
+
+ if (priority > wtcb->sched_priority)
+ {
+ sched_setpriority(wtcb, priority);
+ }
+
+ /* Save the page fill result (don't permit the value -EBUSY) */
+
+ if (result == -EBUSY)
+ {
+ result = -ENOSYS;
+ }
+ g_fillresult = result;
+ }
+
+ /* Signal the page fill worker thread (in any event) */
+
+ kill(g_pgworker, SIGWORK);
+}
+#endif
+
+/****************************************************************************
+ * Name: pg_startfill
+ *
+ * Description:
+ * Start a page fill operation on the thread whose TCB is at the head of
+ * of the g_waitingforfill task list. That is a prioritized list so that will
+ * be the highest priority task waiting for a page fill (in the event that
+ * are multiple tasks waiting for a page fill).
+ *
+ * This function may be called either (1) when the page fill worker thread
+ * is notified that there is a new page fill TCB in the g_waitingforfill
+ * prioritized list, or (2) when a page fill completes and there are more
+ * pages to be filled in g_waitingforfill list.
+ *
+ * Input parameters:
+ * None
+ *
+ * Returned Value:
+ * None
+ *
+ * Assumptions:
+ * Executing in the context of the page fill worker thread with all
+ * interrupts disabled.
+ *
+ ****************************************************************************/
+
+static inline void pg_startfill(void)
+{
+ FAR void *vpage;
+ int result;
+
+ /* Remove the TCB at the head of the g_waitfor fill list */
+
+ g_pendingfilltcb = (FAR _TCB *)dq_remfirst((dq_queue_t*)&g_waitingforfill);
+ if (g_pendingfilltcb != NULL)
+ {
+ /* Call the architecture-specific function up_checkmapping() to see if the
+ * page fill still needs to be performed. In certain conditions, the page
+ * fault may occur on several threads and be queued multiple times. In this
+ * corner case, the blocked task will simply be restarted.
+ */
+
+ result = up_checkmapping(g_pendingfilltcb);
+ if (result == OK)
+ {
+ up_unblock_task(g_pendingfilltcb);
+ g_pendingfilltcb = NULL;
+ return;
+ }
+
+ /* Call up_allocpage(tcb, &vpage). This architecture-specific function will
+ * set aside page in memory and map to virtual address (vpage). If all
+ * available pages are in-use (the typical case), this function will select
+ * a page in-use, un-map it, and make it available.
+ */
+
+ result = up_allocpage(g_pendingfilltcb, &vpage);
+ DEBUGASSERT(result == OK);
+
+ /* Start the fill. The exact way that the fill is started depends upon
+ * the nature of the architecture-specific up_fillpage() function -- Is it
+ * a blocking or a non-blocking call?
+ */
+#ifdef CONFIG_PAGING_BLOCKINGFILL
+ /* If CONFIG_PAGING_BLOCKINGFILL is defined, then up_fillpage is blocking
+ * call. In this case, up_fillpage() will accept only (1) a reference to
+ * the TCB that requires the fill. Architecture-specific context information
+ * within the TCB will be sufficient to perform the fill. And (2) the
+ * (virtual) address of the allocated page to be filled. The resulting
+ * status of the fill will be provided by return value from up_fillpage().
+ */
+
+ result = up_fillpage(g_pendingfilltcb, vpage);
+ DEBUGASSERT(result == OK);
+#else
+ /* If CONFIG_PAGING_BLOCKINGFILL is defined, then up_fillpage is non-blocking
+ * call. In this case up_fillpage() will accept an additional argument: The page
+ * fill worker thread will provide a callback function, pg_callback.
+ *
+ * Calling up_fillpage will start an asynchronous page fill. pg_callback
+ * ill be called when the page fill is finished (or an error occurs). This
+ * This callback will probably from interrupt level.
+ */
+
+ result = up_fillpage(g_pendingfilltcb, vpage, pg_callback);
+ DEBUGASSERT(result == OK);
+
+ /* Save the time that the fill was started. These will be used to check for
+ * timeouts.
+ */
+
+#ifdef CONFIG_PAGING_TIMEOUT_TICKS
+ g_starttime = g_system_timer;
+#endif
+
+ /* Return and wait to be signaled for the next event -- the fill completion
+ * event. While the fill is in progress, other tasks may execute. If
+ * another page fault occurs during this time, the faulting task will be
+ * blocked, its TCB will be added (in priority order) to g_waitingforfill
+ * and the priority of the page worker task may be boosted. But no action
+ * will be taken until the current page fill completes. NOTE: The IDLE task
+ * must also be fully locked in memory. The IDLE task cannot be blocked. It
+ * the case where all tasks are blocked waiting for a page fill, the IDLE
+ * task must still be available to run.
+ */
+#endif /* CONFIG_PAGING_BLOCKINGFILL */
+ }
+}
+
+/****************************************************************************
+ * Name: pg_alldone
+ *
+ * Description:
+ * Called by the page fill worker thread when all pending page fill
+ * operations have been completed and the g_waitingforfill list is empty.
+ *
+ * This functin will perform the following operations:
+ *
+ * - Set g_pendingfilltcb to NULL.
+ * - Restore the default priority of the page fill worker thread.
+ *
+ * Input parameters:
+ * None.
+ *
+ * Returned Value:
+ * None
+ *
+ * Assumptions:
+ * Executing in the context of the page fill worker thread with interrupts
+ * disabled.
+ *
+ ****************************************************************************/
+
+static inline void pg_alldone(void)
+{
+ FAR _TCB *wtcb = (FAR _TCB *)g_readytorun.head;
+ g_pendingfilltcb = NULL;
+ sched_setpriority(wtcb, CONFIG_PAGING_DEFPRIO);
+}
+
+/****************************************************************************
+ * Name: pg_fillcomplete
+ *
+ * Description:
+ * Called by the page fill worker thread when a page fill completes.
+ * Either (1) in the non-blocking up_fillpage(), after the architecture-
+ * specific driver call the pg_callback() to wake up the page fill worker
+ * thread, or (2) after the blocking up_fillpage() returens (when
+ * CONFIG_PAGING_BLOCKINGFILL is defined).
+ *
+ * This function is just a dumb wrapper around up_unblocktask(). This
+ * function simply makes the task that just received the fill ready-to-run.
+ *
+ * Input parameters:
+ * None.
+ *
+ * Returned Value:
+ * None
+ *
+ * Assumptions:
+ * Executing in the context of the page fill worker thread with interrupts
+ * disabled.
+ *
+ ****************************************************************************/
+
+static inline void pg_fillcomplete(void)
+{
+ /* Call up_unblocktask(g_pendingfilltcb) to make the task that just
+ * received the fill ready-to-run.
+ */
+
+ up_unblock_task(g_pendingfilltcb);
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+/****************************************************************************
+ * Name: pg_worker
+ *
+ * Description:
+ * This is the page fill worker thread that performs pages fills for tasks
+ * that have received a pag fault and are blocked in the g_waitingforfill
+ * task queue.
+ *
+ * The page fill worker thread will be awakened on one of three conditions:
+ * - When signaled by pg_miss(), the page fill worker thread will be
+ * awakenend, or
+ * - if CONFIG_PAGING_BLOCKINGFILL is not defined, from pg_fillcomplete()
+ * after completing a page fill.
+ * - A configurable timeout with no activity.
+ *
+ * Input parameters:
+ * argc, argv (not used)
+ *
+ * Returned Value:
+ * Does not return
+ *
+ ****************************************************************************/
+
+int pg_worker(int argc, char *argv[])
+{
+ irqstate_t flags;
+
+ /* Loop forever -- Notice that interrupts will be disable at all times that
+ * this thread runs. That is so that we con't lose signals or have
+ * asynchronous page faults.
+ *
+ * All interrupt logic as well as all page fill worker thread logic must
+ * be locked in memory. Therefore, keeping interrupts disabled here
+ * should prevent any concurrent page faults. Any page faults or page
+ * fill completions should occur while this thread sleeps.
+ */
+
+ flags = irqsave();
+ for (;;)
+ {
+ /* Wait awhile. We will wait here until either the configurable timeout
+ * elapses or until we are awakened by a signal (which terminates the
+ * usleep with an EINTR error). Note that interrupts will be re-enabled
+ * while this task sleeps.
+ *
+ * The timeout is a failsafe that will handle any cases where a single
+ * is lost (that would really be a bug and shouldn't happen!) and also
+ * supports timeouts for case of non-blocking, asynchronous fills.
+ */
+
+ usleep(CONFIG_PAGING_WORKPERIOD);
+
+ /* The page fill worker thread will be awakened on one of three conditions:
+ *
+ * - When signaled by pg_miss(), the page fill worker thread will be awakenend,
+ * - if CONFIG_PAGING_BLOCKINGFILL is not defined, from pg_callback()
+ * after completing a page fill, or
+ * - On a configurable timeout expires with no activity.
+ *
+ * Interrupts are still disabled.
+ */
+
+#ifdef CONFIG_PAGING_BLOCKINGFILL
+ /* For the non-blocking up_fillpage(), the page fill worker thread will detect
+ * that the page fill is complete when it is awakened with g_pendingfilltcb non-NULL
+ * and fill completion status from pg_callback.
+ */
+
+ if (g_pendingfilltcb != NULL)
+ {
+ /* If it is a real page fill completion event, then the result of the page
+ * fill will be in g_fillresult and will not be equal to -EBUSY.
+ */
+
+ if (g_fillresult != -EBUSY)
+ {
+ /* Any value other than OK, brings the system down */
+
+ ASSERT(g_fillresult == OK);
+
+ /* Handle the page fill complete event */
+
+ pg_fillcomplete();
+
+ /* Check if there are are more pending page fills */
+
+ if (g_waitingforfill.head != NULL)
+ {
+ /* Yes .. Start the next asynchronous fill */
+
+ pg_startfill();
+ }
+ else
+ {
+ /* Otherwise, there is nothing more to do */
+
+ pg_alldone();
+ }
+ }
+
+ /* If a configurable timeout period expires with no page fill completion
+ * event, then declare a failure.
+ */
+
+#if defined() && defined(CONFIG_PAGING_TIMEOUT_TICKS)
+ else
+ {
+ dbg("Timeout!\n");
+ ASSERT(g_system_timer - g_starttime < CONFIG_PAGING_TIMEOUT_TICKS);
+ }
+#endif
+ }
+
+ /* Otherwise, this might be a page fill initiation event. When
+ * awakened from pg_miss(), no fill will be in progress and
+ * g_pendingfilltcb will be NULL.
+ */
+
+ else
+ {
+ /* Are there tasks blocked and waiting for a fill? */
+
+ if (g_waitingforfill.head != NULL)
+ {
+ /* Yes .. Start the asynchronous fill */
+
+ pg_startfill();
+ }
+ }
+#else
+ /* Are there tasks blocked and waiting for a fill? Loop until all
+ * pending fills have been processed
+ */
+
+ while (g_waitingforfill.head != NULL)
+ {
+ /* Yes .. Start the fill and block until the fill completes */
+
+ pg_startfill();
+
+ /* Handle the page fill complete event. In the non-blocking case,
+ * the page fill worker thread will know that the page fill is
+ * complete when pg_startfill() returns.
+ */
+
+ pg_fillcomplete();
+ }
+
+ /* All queued fills have been processed */
+
+ pg_alldone();
+#endif
+ }
+ return OK; /* To keep some compilers happy */
+}
+#endif /* CONFIG_PAGING */
diff --git a/nuttx/sched/work_internal.h b/nuttx/sched/work_internal.h
index 840577859..a25c9615b 100755
--- a/nuttx/sched/work_internal.h
+++ b/nuttx/sched/work_internal.h
@@ -1,7 +1,7 @@
/****************************************************************************
* sched/work_internal.h
*
- * Copyright (C) 2009 Gregory Nutt. All rights reserved.
+ * Copyright (C) 2009-2010 Gregory Nutt. All rights reserved.
* Author: Gregory Nutt <spudmonkey@racsa.co.cr>
*
* Redistribution and use in source and binary forms, with or without