|  | @@ -0,0 +1,267 @@
 | 
	
		
			
				|  |  | +<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
 | 
	
		
			
				|  |  | +<html><head><meta content="text/html; charset=ISO-8859-1" http-equiv="content-type"><title>PTE Porting Guide</title></head><body>
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	<meta http-equiv="CONTENT-TYPE" content="text/html; charset=utf-8"><title></title><meta name="GENERATOR" content="OpenOffice.org 2.3  (Linux)"><meta name="CHANGEDBY" content="Jason"><meta name="CHANGEDBY" content="Jason"><meta name="CHANGEDBY" content="Jason"><meta name="CHANGEDBY" content="Jason">
 | 
	
		
			
				|  |  | +	
 | 
	
		
			
				|  |  | +	
 | 
	
		
			
				|  |  | +	
 | 
	
		
			
				|  |  | +	
 | 
	
		
			
				|  |  | +	
 | 
	
		
			
				|  |  | +	
 | 
	
		
			
				|  |  | +	<style type="text/css">
 | 
	
		
			
				|  |  | +	<!--
 | 
	
		
			
				|  |  | +		@page { size: 8.5in 11in; margin: 0.79in }
 | 
	
		
			
				|  |  | +		P { margin-bottom: 0.08in }
 | 
	
		
			
				|  |  | +		H1 { margin-bottom: 0.08in }
 | 
	
		
			
				|  |  | +		H1.western { font-family: "Helvetica"; font-size: 16pt }
 | 
	
		
			
				|  |  | +		H1.cjk { font-family: "AR PL ShanHeiSun Uni"; font-size: 16pt }
 | 
	
		
			
				|  |  | +		H1.ctl { font-family: "Tahoma"; font-size: 16pt }
 | 
	
		
			
				|  |  | +		H2 { margin-bottom: 0.08in }
 | 
	
		
			
				|  |  | +		H3 { margin-bottom: 0.08in }
 | 
	
		
			
				|  |  | +		P.code-western { font-family: "Courier New", monospace }
 | 
	
		
			
				|  |  | +	-->
 | 
	
		
			
				|  |  | +	</style>
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +<p style="margin-bottom: 0in;"><br>
 | 
	
		
			
				|  |  | +</p>
 | 
	
		
			
				|  |  | +<h1 class="western" align="center">Pthreads-Embedded (PTE) Porting
 | 
	
		
			
				|  |  | +Guide</h1>
 | 
	
		
			
				|  |  | +<p>The PTE library consists of both a platform independent component,
 | 
	
		
			
				|  |  | +which contains the bulk of the pthreads functionality, and a platform
 | 
	
		
			
				|  |  | +specific component which connects the platform independent component
 | 
	
		
			
				|  |  | +to the underlying OS. Naturally, the platform specific layer is the
 | 
	
		
			
				|  |  | +only layer that needs to be modified when porting PTE.</p>
 | 
	
		
			
				|  |  | +<p>The OS adaptation layer (OSAL) must provide the following
 | 
	
		
			
				|  |  | +functionality:</p>
 | 
	
		
			
				|  |  | +<ol><li><p>Threads</p>
 | 
	
		
			
				|  |  | +	</li><li><p>Semaphores</p>
 | 
	
		
			
				|  |  | +	</li><li><p>Mutexes</p>
 | 
	
		
			
				|  |  | +	</li><li><p>Atomic operations</p>
 | 
	
		
			
				|  |  | +	</li><li><p>Thread local storage</p>
 | 
	
		
			
				|  |  | +</li></ol>
 | 
	
		
			
				|  |  | +<p>The underlying OS does not necessarily have to provide all of this
 | 
	
		
			
				|  |  | +functionality – it is possible for the OSAL to emulate behavior.
 | 
	
		
			
				|  |  | +For instance, mutexes can be emulated using semaphores provided by
 | 
	
		
			
				|  |  | +the OS. The sections below present a high level of the required
 | 
	
		
			
				|  |  | +functionality as well as how it fits into a “typical” embedded
 | 
	
		
			
				|  |  | +OS. Specifics such as function parameters, etc are covered in the
 | 
	
		
			
				|  |  | +OSAL API reference.</p>
 | 
	
		
			
				|  |  | +<h2 align="center">Threads</h2>
 | 
	
		
			
				|  |  | +<h3>Thread Initialization</h3>
 | 
	
		
			
				|  |  | +<p class="code-western"><b>OsThreadCreate, OsThreadStart</b></p>
 | 
	
		
			
				|  |  | +<p>Thread initialization is separated into two steps: create and
 | 
	
		
			
				|  |  | +start. When <font face="Courier New, monospace">OSThreadCreate</font>
 | 
	
		
			
				|  |  | +is called, the OS should create a new thread, but it must be started
 | 
	
		
			
				|  |  | +in a suspended state. The thread should not start executing until
 | 
	
		
			
				|  |  | +<font face="Courier New, monospace">OSThreadStart</font> is called.
 | 
	
		
			
				|  |  | +This separation in functionality is due necessary to avoid race
 | 
	
		
			
				|  |  | +condFor instance, if the target OS supports TLS but only allows a
 | 
	
		
			
				|  |  | +single TLS value, this single value could contain a pointer to a
 | 
	
		
			
				|  |  | +structure that contains multiple TLS values.  The PTE distribution
 | 
	
		
			
				|  |  | +includes a helper file to implement this functionality
 | 
	
		
			
				|  |  | +(/platform/helper/tls-helper.c).  See the DSP/BIOS port for an
 | 
	
		
			
				|  |  | +example of using tls-helper.c.itions in the PTE library<span style="font-style: normal;">.</span></p>
 | 
	
		
			
				|  |  | +<p><span style="font-style: normal;">Since the actual prototype of an
 | 
	
		
			
				|  |  | +thread entry point varies between OS and thus will more than likely
 | 
	
		
			
				|  |  | +</span><i>not</i> <span style="font-style: normal;">match that used by
 | 
	
		
			
				|  |  | +</span><font face="Courier New, monospace"><span style="font-style: normal;">OsThreadCreate</span></font><span style="font-style: normal;">,
 | 
	
		
			
				|  |  | +it will usually be necessary to create a stub function that matches
 | 
	
		
			
				|  |  | +your OS's entry point prototype. This stub function simply calls the
 | 
	
		
			
				|  |  | +entry point specified by </span><font face="Courier New, monospace"><span style="font-style: normal;">OsThreadCreate</span></font>
 | 
	
		
			
				|  |  | +<span style="font-style: normal;">(see DSP/BIOS and PSP-OS ports).</span></p>
 | 
	
		
			
				|  |  | +<p>Typically, OsThreadCreate will also perform other initialization;
 | 
	
		
			
				|  |  | +for instance to initialize TLS structures, allocate other control
 | 
	
		
			
				|  |  | +resources. This of course varies depending on the target OS. 
 | 
	
		
			
				|  |  | +</p>
 | 
	
		
			
				|  |  | +<p>Some OS's require additional parameters to start a thread.  For
 | 
	
		
			
				|  |  | +instance, DSP/BIOS requires the priority in order to start the
 | 
	
		
			
				|  |  | +thread.  Rather than pass these parameters to both <font face="Courier New, monospace">OsThreadCreate</font>
 | 
	
		
			
				|  |  | +and <font face="Courier New, monospace">OsThreadStart</font>, the
 | 
	
		
			
				|  |  | +OSAL should store any necessary information as thread specific values
 | 
	
		
			
				|  |  | +during <font face="Courier New, monospace">OsThreadCreate</font> and
 | 
	
		
			
				|  |  | +then retrieve them as necessary in <font face="Courier New, monospace">OsThreadStart</font>.</p>
 | 
	
		
			
				|  |  | +<p><br><br>
 | 
	
		
			
				|  |  | +</p>
 | 
	
		
			
				|  |  | +<h3>Thread Destruction</h3>
 | 
	
		
			
				|  |  | +<p class="code-western"><b>OsThreadExit, OsThreadDelete,
 | 
	
		
			
				|  |  | +OsThreadExitAndDelete, OsThreadWaitForEnd</b></p>
 | 
	
		
			
				|  |  | +<p>Thread destruction is broken into three API calls to support the
 | 
	
		
			
				|  |  | +different use cases of the pthreads API.  <font face="Courier New, monospace">OsThreadExit</font>
 | 
	
		
			
				|  |  | +should cause the currently executing thread to stop execution;
 | 
	
		
			
				|  |  | +resources should not yet be freed.  This is called when a thread
 | 
	
		
			
				|  |  | +exits but the thread is not detached – resource deallocation must
 | 
	
		
			
				|  |  | +wait until the user calls <font face="Courier New, monospace">pthread_join</font>
 | 
	
		
			
				|  |  | +(or <font face="Courier New, monospace">pthread_detach</font>) at
 | 
	
		
			
				|  |  | +which point <font face="Courier New, monospace">OsThreadDelete</font>
 | 
	
		
			
				|  |  | +will be called.</p>
 | 
	
		
			
				|  |  | +<p>Alternatively, if a detached thread exits, thread resource
 | 
	
		
			
				|  |  | +deallocation and thread termination can occur simultaneously.  In
 | 
	
		
			
				|  |  | +this case, <font face="Courier New, monospace">OsThreadExitAndDelete</font>
 | 
	
		
			
				|  |  | +will be called.</p>
 | 
	
		
			
				|  |  | +<p><font face="Courier New, monospace">OsThreadWaitForEnd</font>
 | 
	
		
			
				|  |  | +should block until the specified thread exists. For OS's that do not
 | 
	
		
			
				|  |  | +directly support this functionality, a semaphore can be used to
 | 
	
		
			
				|  |  | +emulate this behavior (see DSP/BIOS port).  Note that this call
 | 
	
		
			
				|  |  | +should be cancellable – that is, it should return (even if the
 | 
	
		
			
				|  |  | +target thread has not exited) if <font face="Courier New, monospace">OsThreadCancel</font>
 | 
	
		
			
				|  |  | +is called.</p>
 | 
	
		
			
				|  |  | +<h3>Thread Priority</h3>
 | 
	
		
			
				|  |  | +<p class="code-western"><b>OsThreadSetPriority, OsThreadGetPriority,
 | 
	
		
			
				|  |  | +OsThreadGetMaxPriority, OsThreadGetMinPriority</b></p>
 | 
	
		
			
				|  |  | +<p>The OSAL provides the upper and lower bounds of it's priority
 | 
	
		
			
				|  |  | +range when <font face="Courier New, monospace">OsThreadGetMaxPriority</font>
 | 
	
		
			
				|  |  | +and <font face="Courier New, monospace">OsThreadGetMinPriority</font>
 | 
	
		
			
				|  |  | +are called.  The PTE library will ensure that all priorities passed
 | 
	
		
			
				|  |  | +to the OSAL (e.g. through <font face="Courier New, monospace">OsThreadCreate</font>)
 | 
	
		
			
				|  |  | +are within these bounds.</p>
 | 
	
		
			
				|  |  | +<h3>Thread Cancellation</h3>
 | 
	
		
			
				|  |  | +<p class="code-western"><b>OsThreadCancel, OsThreadCheckCancel</b></p>
 | 
	
		
			
				|  |  | +<p>Currently, the PTE library only supports deferred cancellation
 | 
	
		
			
				|  |  | +(see PTE notes). While the PTE library handles most of the
 | 
	
		
			
				|  |  | +complexities of cancellation, there are three hooks required from the
 | 
	
		
			
				|  |  | +OSAL. When <font face="Courier New, monospace">OsThreadCancel</font>
 | 
	
		
			
				|  |  | +is called, it must cause <font face="Courier New, monospace">OsSemaphorePendCancellable</font>
 | 
	
		
			
				|  |  | +and <font face="Courier New, monospace">OsThreadWaitForEnd </font>to
 | 
	
		
			
				|  |  | +return (this function is used by the PTE library to implement pthread
 | 
	
		
			
				|  |  | +cancellation points). Since most embedded OS's do not support this
 | 
	
		
			
				|  |  | +kind of functionality, it can be implemented using semaphores (see
 | 
	
		
			
				|  |  | +DSP/BIOS and PSP-OS ports). <font face="Courier New, monospace">OsThreadCheckCancel</font>
 | 
	
		
			
				|  |  | +simply returns whether <font face="Courier New, monospace">OsThreadCancel</font>
 | 
	
		
			
				|  |  | +has been called for this thread.</p>
 | 
	
		
			
				|  |  | +<h3>Miscellaneous Thread Functionality</h3>
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +<p class="code-western"><b>OsThreadGetHandle, OsThreadSleep,
 | 
	
		
			
				|  |  | +OsThreadGetMaxPriority, OsThreadGetMinPriority,
 | 
	
		
			
				|  |  | +OsThreadGetDefaultPriority</b><br><br>
 | 
	
		
			
				|  |  | +</p>
 | 
	
		
			
				|  |  | +<h2 align="center"></h2>
 | 
	
		
			
				|  |  | +<h2 style="page-break-before: always;" align="center">Semaphores</h2>
 | 
	
		
			
				|  |  | +<p align="center"><br><br>
 | 
	
		
			
				|  |  | +</p>
 | 
	
		
			
				|  |  | +<p class="code-western"><b>OsSemaphoreCreate, OsSemaphoreDelete,
 | 
	
		
			
				|  |  | +OsSemaphorePend, OsSemaphorePort</b></p>
 | 
	
		
			
				|  |  | +<p>This basic semaphore functionality should map directly to almost
 | 
	
		
			
				|  |  | +all embedded OS's. 
 | 
	
		
			
				|  |  | +</p>
 | 
	
		
			
				|  |  | +<p class="code-western"><b>OsSemaphoreCancellablePend</b></p>
 | 
	
		
			
				|  |  | +<p>In order to implement deferred cancellation, a “cancellable”
 | 
	
		
			
				|  |  | +pend (<font face="Courier New, monospace">OsSemaphorePendCancellable</font>)
 | 
	
		
			
				|  |  | +must also be supported. As discussed above,
 | 
	
		
			
				|  |  | +<font face="Courier New, monospace">OsSemaphorePendCancellable</font>
 | 
	
		
			
				|  |  | +must return when <font face="Courier New, monospace">OsThreadCancel</font>
 | 
	
		
			
				|  |  | +has been called on the thread that is currently pending, regardless
 | 
	
		
			
				|  |  | +of whether the semaphore has been posted to or not. The way that this
 | 
	
		
			
				|  |  | +is implemented in other ports (e.g. DSP/BIOS and PSP-OS) is to use an
 | 
	
		
			
				|  |  | +additional semaphore, and then poll on both semaphores, as shown in
 | 
	
		
			
				|  |  | +the pseudo-code below:</p>
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +<p><font face="Courier New, monospace">loop forever:</font></p>
 | 
	
		
			
				|  |  | +<p><font face="Courier New, monospace">poll main semaphore</font></p>
 | 
	
		
			
				|  |  | +<p><font face="Courier New, monospace">if semaphore was posted to,
 | 
	
		
			
				|  |  | +return OK</font></p>
 | 
	
		
			
				|  |  | +<p><font face="Courier New, monospace">else</font></p>
 | 
	
		
			
				|  |  | +<p><font face="Courier New, monospace">check timeout</font></p>
 | 
	
		
			
				|  |  | +<p><font face="Courier New, monospace">if timeout has expired, return
 | 
	
		
			
				|  |  | +'timed out'</font></p>
 | 
	
		
			
				|  |  | +<p><font face="Courier New, monospace">else</font></p>
 | 
	
		
			
				|  |  | +<p><font face="Courier New, monospace">poll cancellation semaphore </font>
 | 
	
		
			
				|  |  | +</p>
 | 
	
		
			
				|  |  | +<p><font face="Courier New, monospace">if cancellation semaphore has
 | 
	
		
			
				|  |  | +been posted to, return 'canceled'</font></p>
 | 
	
		
			
				|  |  | +<p><font face="Courier New, monospace">else</font></p>
 | 
	
		
			
				|  |  | +<p><font face="Courier New, monospace">sleep for small amount of time</font></p>
 | 
	
		
			
				|  |  | +<p>For instance, if the target OS supports TLS but only allows a
 | 
	
		
			
				|  |  | +single TLS value, this single value could contain a pointer to a
 | 
	
		
			
				|  |  | +structure that contains multiple TLS values.  The PTE distribution
 | 
	
		
			
				|  |  | +includes a helper file to implement this functionality
 | 
	
		
			
				|  |  | +(/platform/helper/tls-helper.c).  See the DSP/BIOS port for an
 | 
	
		
			
				|  |  | +example of using tls-helper.c.</p>
 | 
	
		
			
				|  |  | +<h2 align="center">Mutexes</h2>
 | 
	
		
			
				|  |  | +<p>Mutexes are only included as an optimization as some OS's mutex
 | 
	
		
			
				|  |  | +operation is much faster than semaphore operations. If the target OS
 | 
	
		
			
				|  |  | +does not support mutexes, they can easily be implemented using
 | 
	
		
			
				|  |  | +semaphores.</p>
 | 
	
		
			
				|  |  | +<p><br><br>
 | 
	
		
			
				|  |  | +</p>
 | 
	
		
			
				|  |  | +<h2 align="center">Atomic operations</h2>
 | 
	
		
			
				|  |  | +<p class="code-western"><b>OsAtomicExchange, OsAtomicCompareExchange,
 | 
	
		
			
				|  |  | +OsAtomicExchangeIncrement, OsAtomicDecrement, OsAtomicIncrement</b></p>
 | 
	
		
			
				|  |  | +<p align="left">The PTE library requires five atomic operations to be
 | 
	
		
			
				|  |  | +supplied by the OSAL.  Macros are used in case the target platform
 | 
	
		
			
				|  |  | +supports direct assembly instructions to perform some or all of these
 | 
	
		
			
				|  |  | +operations.  However, under most OS's these macros will simply refer
 | 
	
		
			
				|  |  | +to functions that disable interrupts and then perform the required
 | 
	
		
			
				|  |  | +operations.</p>
 | 
	
		
			
				|  |  | +<p><br><br>
 | 
	
		
			
				|  |  | +</p>
 | 
	
		
			
				|  |  | +<h2 align="center">Thread local storage</h2>
 | 
	
		
			
				|  |  | +<p class="code-western"><b>OsTlsInit, OsTlsAlloc, OsTlsFree,
 | 
	
		
			
				|  |  | +OsTlsSetValue, OsTlsGetValue</b></p>
 | 
	
		
			
				|  |  | +<p>The OSAL must be able to allocate and free TLS keys, and retrieve
 | 
	
		
			
				|  |  | +thread specific data.  If the target OS does not support this level
 | 
	
		
			
				|  |  | +of TLS functionality, but does have limited TLS support, it is
 | 
	
		
			
				|  |  | +possible to emulate the behavior required by the PTE library.</p>
 | 
	
		
			
				|  |  | +<p>For instance, if the target OS supports TLS but only allows a
 | 
	
		
			
				|  |  | +single TLS value, this single value could contain a pointer to a
 | 
	
		
			
				|  |  | +structure that contains multiple TLS values.  The PTE distribution
 | 
	
		
			
				|  |  | +includes a helper file to implement this functionality
 | 
	
		
			
				|  |  | +(/platform/helper/tls-helper.c).  See the DSP/BIOS port for an
 | 
	
		
			
				|  |  | +example of using tls-helper.c.</p>
 | 
	
		
			
				|  |  | +<p>If the OS contains no TLS support, it might still be possible to
 | 
	
		
			
				|  |  | +emulate TLS functionality.  See the PSP-OS port for an example of how
 | 
	
		
			
				|  |  | +this can be accomplished.  Be warned – it is not a pretty solution,
 | 
	
		
			
				|  |  | +but it works.</p>
 | 
	
		
			
				|  |  | +<p>It is important to note that TLS functionality is <i>required </i><span style="font-style: normal;">by
 | 
	
		
			
				|  |  | +the PTE library – it is used for more than just the pthread TLS
 | 
	
		
			
				|  |  | +functions, but is used extensively throughout the library.</span></p>
 | 
	
		
			
				|  |  | +<p style="font-style: normal;">One potentially tricky issue with TLS
 | 
	
		
			
				|  |  | +is how to handle the case of when pthread TLS functions are called
 | 
	
		
			
				|  |  | +from non-pthread threads (i.e. pure native threads that were not
 | 
	
		
			
				|  |  | +created through pthread_create).  Technically, according to the
 | 
	
		
			
				|  |  | +pthread spec, this should work.  However, it is problematic in that
 | 
	
		
			
				|  |  | +OsTlsInit would not have been called for that thread, since it is
 | 
	
		
			
				|  |  | +called in response to pthread_create().  Different ports handle this
 | 
	
		
			
				|  |  | +differently – see the notes for a particular ports.</p>
 | 
	
		
			
				|  |  | +<h2 align="center">Miscellaneous Functionality</h2>
 | 
	
		
			
				|  |  | +<p class="code-western"><b>ftime</b></p>
 | 
	
		
			
				|  |  | +<p>Since pthreads uses absolute time for timeouts, the PTE library
 | 
	
		
			
				|  |  | +requires the OS to supply the current time.  Note that this does not
 | 
	
		
			
				|  |  | +have to be the actual world time, but can be an internal timebase
 | 
	
		
			
				|  |  | +(for example, since the unit started up).   However, the time source
 | 
	
		
			
				|  |  | +should be the same one that the caller to pthread would use.</p>
 | 
	
		
			
				|  |  | +<h2 align="center">Types and Constants</h2>
 | 
	
		
			
				|  |  | +<p align="left">The OSAL layer must declare a number of types and
 | 
	
		
			
				|  |  | +constants.  
 | 
	
		
			
				|  |  | +</p>
 | 
	
		
			
				|  |  | +<p align="left">The following types must be defined to map to the
 | 
	
		
			
				|  |  | +appropriate OS constructs:</p>
 | 
	
		
			
				|  |  | +<p class="code-western">OsThreadHandle</p>
 | 
	
		
			
				|  |  | +<p class="code-western">OsSemaphoreHandle</p>
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +<p class="code-western">OsMutexHandle<br><br><br>
 | 
	
		
			
				|  |  | +</p>
 | 
	
		
			
				|  |  | +<p>The following constants must be defined:</p>
 | 
	
		
			
				|  |  | +<p align="left"><font face="Courier New, monospace">OS_DEFAULT_PRIO</font>
 | 
	
		
			
				|  |  | +– default priority for a created thread.</p>
 | 
	
		
			
				|  |  | +<p align="left"><font face="Courier New, monospace">OS_MIN_PRIO</font>
 | 
	
		
			
				|  |  | +– minimum thread priority.</p>
 | 
	
		
			
				|  |  | +<p align="left"><font face="Courier New, monospace">OS_MAX_PRIO</font>
 | 
	
		
			
				|  |  | +– maximum thread priority.</p>
 | 
	
		
			
				|  |  | +<p align="left"><font face="Courier New, monospace">OS_MAX_SIMUL_THREADS</font>
 | 
	
		
			
				|  |  | +– maximum number of threads that may be active simultaneously.</p>
 | 
	
		
			
				|  |  | +<p align="left"><br><br>
 | 
	
		
			
				|  |  | +</p>
 | 
	
		
			
				|  |  | +<p align="left">Each port must also include a file, pte_types.h, that
 | 
	
		
			
				|  |  | +defines all of the following types.  This may be done by explicitly
 | 
	
		
			
				|  |  | +typedef'ing the structure (for OS's that do not natively support the
 | 
	
		
			
				|  |  | +type) or simply by including the appropriate header file:</p>
 | 
	
		
			
				|  |  | +<p align="left"><font face="Courier New, monospace">pid_t</font></p>
 | 
	
		
			
				|  |  | +<p align="left"><font face="Courier New, monospace">struct timespec</font></p>
 | 
	
		
			
				|  |  | +<p align="left"><font face="Courier New, monospace">mode_t</font></p>
 | 
	
		
			
				|  |  | +<p align="left"><font face="Courier New, monospace">struct timeb</font></p>
 | 
	
		
			
				|  |  | +<h2 align="center">File structure</h2>
 | 
	
		
			
				|  |  | +<p align="left">The OSAL layer must include a file named pte_osal.h. 
 | 
	
		
			
				|  |  | +This file must include pte_generic_osal.h as well as the platform
 | 
	
		
			
				|  |  | +specific header file (e.g. dspbios_osal.h).</p>
 | 
	
		
			
				|  |  | +</body></html>
 |