mirror of
https://github.com/FreeRTOS/FreeRTOS-Kernel.git
synced 2025-08-11 13:54:16 -04:00
Add vTaskGetTaskInfo() function that allows a TaskStatus_t structure to be returned for an individual task (previously information could only be obtained for all the tasks at once).
Add a member to the TaskStatus_t structure that is used to return the base address of the stack used by the task being queried. Add xTaskGetTaskHandle() that allows the handle of a task to be looked up from the task's text name. Continue to document the macros that allow RTOS objects to be created using statically allocated memory. Introduced vApplicationDaemonTaskStartupHook(), which allows initialisation that that needs to be executed after the scheduler has been started to be executed from the RTOS daemon task. Call prvResetNextTaskUnblockTime() in xTaskResumeAll() if a task is moved from the pending ready list - this can prevent an unnecessary wake from sleep mode if a task is unblocked by an interrupt while in a low power tickless state.
This commit is contained in:
parent
b514f4fa4e
commit
802af0150c
11 changed files with 838 additions and 147 deletions
|
@ -171,6 +171,10 @@ extern "C" {
|
|||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef configUSE_DAEMON_TASK_STARTUP_HOOK
|
||||
#define configUSE_DAEMON_TASK_STARTUP_HOOK 0
|
||||
#endif
|
||||
|
||||
#ifndef INCLUDE_xTaskGetIdleTaskHandle
|
||||
#define INCLUDE_xTaskGetIdleTaskHandle 0
|
||||
#endif
|
||||
|
@ -191,6 +195,10 @@ extern "C" {
|
|||
#define INCLUDE_pcTaskGetTaskName 0
|
||||
#endif
|
||||
|
||||
#ifndef INCLUDE_xTaskGetTaskHandle
|
||||
#define INCLUDE_xTaskGetTaskHandle 0
|
||||
#endif
|
||||
|
||||
#ifndef configUSE_APPLICATION_TASK_TAG
|
||||
#define configUSE_APPLICATION_TASK_TAG 0
|
||||
#endif
|
||||
|
|
|
@ -229,9 +229,13 @@ typedef void * QueueSetMemberHandle_t;
|
|||
* hold the queue's data structure, removing the need for the memory to be
|
||||
* allocated dynamically.
|
||||
*
|
||||
* @return If the queue is successfully create then a handle to the newly
|
||||
* created queue is returned. If the queue cannot be created then 0 is
|
||||
* returned.
|
||||
* @return If neither pucQueueStorageBuffer or pxQueueBuffer are NULL, then the
|
||||
* function will not attempt any dynamic memory allocation, and a handle to the
|
||||
* created queue will always be returned. If pucQueueStorageBuffer or
|
||||
* pxQueueBuffer is NULL then the function will attempt to dynamically allocate
|
||||
* one of both buffers. In this case, if the allocation succeeds then a handle
|
||||
* to the created queue will be returned, and if one of the the allocation fails
|
||||
* NULL will be returned.
|
||||
*
|
||||
* Example usage:
|
||||
<pre>
|
||||
|
@ -245,7 +249,7 @@ typedef void * QueueSetMemberHandle_t;
|
|||
#define ITEM_SIZE sizeof( uint32_t )
|
||||
|
||||
// xQueueBuffer will hold the queue structure.
|
||||
StaticQueue_t xQueueBuffer;
|
||||
StaticQueue_t xQueueBuffer;
|
||||
|
||||
// ucQueueStorage will hold the items posted to the queue. Must be at least
|
||||
// [(queue length) * ( queue item size)] bytes long.
|
||||
|
@ -267,7 +271,7 @@ typedef void * QueueSetMemberHandle_t;
|
|||
// ... Rest of task code.
|
||||
}
|
||||
</pre>
|
||||
* \defgroup xQueueCreate xQueueCreate
|
||||
* \defgroup xQueueCreateStatic xQueueCreateStatic
|
||||
* \ingroup QueueManagement
|
||||
*/
|
||||
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
|
||||
|
@ -1649,7 +1653,7 @@ BaseType_t xQueueGiveMutexRecursive( QueueHandle_t pxMutex ) PRIVILEGED_FUNCTION
|
|||
* returned.
|
||||
*/
|
||||
#if( configQUEUE_REGISTRY_SIZE > 0 )
|
||||
const char *pcQueueGetQueueName( QueueHandle_t xQueue );
|
||||
const char *pcQueueGetQueueName( QueueHandle_t xQueue ); /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
|
||||
#endif
|
||||
|
||||
/*
|
||||
|
|
|
@ -145,10 +145,23 @@ typedef QueueHandle_t SemaphoreHandle_t;
|
|||
* semphr. h
|
||||
* <pre>SemaphoreHandle_t xSemaphoreCreateBinary( void )</pre>
|
||||
*
|
||||
* Creates a new binary semaphore instance, and returns a handle by which the
|
||||
* new semaphore can be referenced.
|
||||
*
|
||||
* In many usage scenarios it is faster and more memory efficient to use a
|
||||
* direct to task notification in place of a binary semaphore!
|
||||
* http://www.freertos.org/RTOS-task-notifications.html
|
||||
*
|
||||
* Internally, within the FreeRTOS implementation, binary semaphores use a block
|
||||
* of memory, in which the semaphore structure is stored. If a binary semaphore
|
||||
* is created using xSemaphoreCreateBinary() then the required memory is
|
||||
* automatically dynamically allocated inside the xSemaphoreCreateBinary()
|
||||
* function. (see http://www.freertos.org/a00111.html). If a binary semaphore
|
||||
* is created using xSemaphoreCreateBinaryStatic() then the application writer
|
||||
* can instead optionally provide the memory that will get used by the binary
|
||||
* semaphore. xSemaphoreCreateBinaryStatic() therefore allows a binary
|
||||
* semaphore to be created without using any dynamic memory allocation.
|
||||
*
|
||||
* The old vSemaphoreCreateBinary() macro is now deprecated in favour of this
|
||||
* xSemaphoreCreateBinary() function. Note that binary semaphores created using
|
||||
* the vSemaphoreCreateBinary() macro are created in a state such that the
|
||||
|
@ -156,11 +169,6 @@ typedef QueueHandle_t SemaphoreHandle_t;
|
|||
* created using xSemaphoreCreateBinary() are created in a state such that the
|
||||
* the semaphore must first be 'given' before it can be 'taken'.
|
||||
*
|
||||
* Function that creates a semaphore by using the existing queue mechanism.
|
||||
* The queue length is 1 as this is a binary semaphore. The data size is 0
|
||||
* as nothing is actually stored - all that is important is whether the queue is
|
||||
* empty or full (the binary semaphore is available or not).
|
||||
*
|
||||
* This type of semaphore can be used for pure synchronisation between tasks or
|
||||
* between an interrupt and a task. The semaphore need not be given back once
|
||||
* obtained, so one task/interrupt can continuously 'give' the semaphore while
|
||||
|
@ -168,7 +176,8 @@ typedef QueueHandle_t SemaphoreHandle_t;
|
|||
* semaphore does not use a priority inheritance mechanism. For an alternative
|
||||
* that does use priority inheritance see xSemaphoreCreateMutex().
|
||||
*
|
||||
* @return Handle to the created semaphore.
|
||||
* @return Handle to the created semaphore, or NULL if the memory required to
|
||||
* hold the semaphore's data structures could not be allocated.
|
||||
*
|
||||
* Example usage:
|
||||
<pre>
|
||||
|
@ -176,7 +185,7 @@ typedef QueueHandle_t SemaphoreHandle_t;
|
|||
|
||||
void vATask( void * pvParameters )
|
||||
{
|
||||
// Semaphore cannot be used before a call to vSemaphoreCreateBinary ().
|
||||
// Semaphore cannot be used before a call to xSemaphoreCreateBinary().
|
||||
// This is a macro so pass the variable in directly.
|
||||
xSemaphore = xSemaphoreCreateBinary();
|
||||
|
||||
|
@ -187,13 +196,78 @@ typedef QueueHandle_t SemaphoreHandle_t;
|
|||
}
|
||||
}
|
||||
</pre>
|
||||
* \defgroup vSemaphoreCreateBinary vSemaphoreCreateBinary
|
||||
* \defgroup xSemaphoreCreateBinary xSemaphoreCreateBinary
|
||||
* \ingroup Semaphores
|
||||
*/
|
||||
#define xSemaphoreCreateBinary() xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, NULL, NULL, queueQUEUE_TYPE_BINARY_SEMAPHORE )
|
||||
|
||||
/**
|
||||
* semphr. h
|
||||
* <pre>SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuffer )</pre>
|
||||
*
|
||||
* Creates a new binary semaphore instance, and returns a handle by which the
|
||||
* new semaphore can be referenced.
|
||||
*
|
||||
* NOTE: In many usage scenarios it is faster and more memory efficient to use a
|
||||
* direct to task notification in place of a binary semaphore!
|
||||
* http://www.freertos.org/RTOS-task-notifications.html
|
||||
*
|
||||
* Internally, within the FreeRTOS implementation, binary semaphores use a block
|
||||
* of memory, in which the semaphore structure is stored. If a binary semaphore
|
||||
* is created using xSemaphoreCreateBinary() then the required memory is
|
||||
* automatically dynamically allocated inside the xSemaphoreCreateBinary()
|
||||
* function. (see http://www.freertos.org/a00111.html). If a binary semaphore
|
||||
* is created using xSemaphoreCreateBinaryStatic() then the application writer
|
||||
* can instead optionally provide the memory that will get used by the binary
|
||||
* semaphore. xSemaphoreCreateBinaryStatic() therefore allows a binary
|
||||
* semaphore to be created without using any dynamic memory allocation.
|
||||
*
|
||||
* This type of semaphore can be used for pure synchronisation between tasks or
|
||||
* between an interrupt and a task. The semaphore need not be given back once
|
||||
* obtained, so one task/interrupt can continuously 'give' the semaphore while
|
||||
* another continuously 'takes' the semaphore. For this reason this type of
|
||||
* semaphore does not use a priority inheritance mechanism. For an alternative
|
||||
* that does use priority inheritance see xSemaphoreCreateMutex().
|
||||
*
|
||||
* @param pxSemaphoreBuffer If pxSemaphoreBuffer is NULL then the memory
|
||||
* required to hold the semaphore's data structures will be allocated
|
||||
* dynamically, just as when a semaphore is created using
|
||||
* xSemaphoreCreateBinary(). If pxSemaphoreBuffer is not NULL then it must
|
||||
* point to a variable of type StaticSemaphore_t, which will then be used to
|
||||
* hold the semaphore's data structure, removing the need for the memory to be
|
||||
* allocated dynamically.
|
||||
*
|
||||
* @return If pxSemaphoreBuffer is not NULL then the function will not attempt
|
||||
* any dynamic memory allocation, and a handle to the created semaphore will
|
||||
* always be returned. If pxSemaphoreBuffer is NULL then the function will
|
||||
* attempt to dynamically allocate the memory required to hold the semaphore's
|
||||
* data structures. In this case, if the allocation succeeds then a handle to
|
||||
* the created semaphore will be returned, and if the allocation fails NULL will
|
||||
* be returned.
|
||||
*
|
||||
* Example usage:
|
||||
<pre>
|
||||
SemaphoreHandle_t xSemaphore = NULL;
|
||||
StaticSemaphore_t xSemaphoreBuffer;
|
||||
|
||||
void vATask( void * pvParameters )
|
||||
{
|
||||
// Semaphore cannot be used before a call to xSemaphoreCreateBinary().
|
||||
// The semaphore's data structures will be placed in the xSemaphoreBuffer
|
||||
// variable, the address of which is passed into the function. The
|
||||
// function's parameter is not NULL, so the function will not attempt any
|
||||
// dynamic memory allocation, and therefore the function will not return
|
||||
// return NULL.
|
||||
xSemaphore = xSemaphoreCreateBinary( &xSemaphoreBuffer );
|
||||
|
||||
// Rest of task code goes here.
|
||||
}
|
||||
</pre>
|
||||
* \defgroup xSemaphoreCreateBinaryStatic xSemaphoreCreateBinaryStatic
|
||||
* \ingroup Semaphores
|
||||
*/
|
||||
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
|
||||
#define xSemaphoreCreateBinaryStatic( pxStaticQueue ) xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, NULL, pxStaticQueue, queueQUEUE_TYPE_BINARY_SEMAPHORE )
|
||||
#define xSemaphoreCreateBinaryStatic( pxStaticSemaphore ) xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, NULL, pxStaticSemaphore, queueQUEUE_TYPE_BINARY_SEMAPHORE )
|
||||
#endif /* configSUPPORT_STATIC_ALLOCATION */
|
||||
|
||||
/**
|
||||
|
@ -204,7 +278,7 @@ typedef QueueHandle_t SemaphoreHandle_t;
|
|||
* )</pre>
|
||||
*
|
||||
* <i>Macro</i> to obtain a semaphore. The semaphore must have previously been
|
||||
* created with a call to vSemaphoreCreateBinary(), xSemaphoreCreateMutex() or
|
||||
* created with a call to xSemaphoreCreateBinary(), xSemaphoreCreateMutex() or
|
||||
* xSemaphoreCreateCounting().
|
||||
*
|
||||
* @param xSemaphore A handle to the semaphore being taken - obtained when
|
||||
|
@ -227,7 +301,7 @@ typedef QueueHandle_t SemaphoreHandle_t;
|
|||
void vATask( void * pvParameters )
|
||||
{
|
||||
// Create the semaphore to guard a shared resource.
|
||||
vSemaphoreCreateBinary( xSemaphore );
|
||||
xSemaphore = xSemaphoreCreateBinary();
|
||||
}
|
||||
|
||||
// A task that uses the semaphore.
|
||||
|
@ -376,7 +450,7 @@ typedef QueueHandle_t SemaphoreHandle_t;
|
|||
* <pre>xSemaphoreGive( SemaphoreHandle_t xSemaphore )</pre>
|
||||
*
|
||||
* <i>Macro</i> to release a semaphore. The semaphore must have previously been
|
||||
* created with a call to vSemaphoreCreateBinary(), xSemaphoreCreateMutex() or
|
||||
* created with a call to xSemaphoreCreateBinary(), xSemaphoreCreateMutex() or
|
||||
* xSemaphoreCreateCounting(). and obtained using sSemaphoreTake().
|
||||
*
|
||||
* This macro must not be used from an ISR. See xSemaphoreGiveFromISR () for
|
||||
|
@ -400,7 +474,7 @@ typedef QueueHandle_t SemaphoreHandle_t;
|
|||
void vATask( void * pvParameters )
|
||||
{
|
||||
// Create the semaphore to guard a shared resource.
|
||||
vSemaphoreCreateBinary( xSemaphore );
|
||||
xSemaphore = vSemaphoreCreateBinary();
|
||||
|
||||
if( xSemaphore != NULL )
|
||||
{
|
||||
|
@ -541,7 +615,7 @@ typedef QueueHandle_t SemaphoreHandle_t;
|
|||
)</pre>
|
||||
*
|
||||
* <i>Macro</i> to release a semaphore. The semaphore must have previously been
|
||||
* created with a call to vSemaphoreCreateBinary() or xSemaphoreCreateCounting().
|
||||
* created with a call to xSemaphoreCreateBinary() or xSemaphoreCreateCounting().
|
||||
*
|
||||
* Mutex type semaphores (those created using a call to xSemaphoreCreateMutex())
|
||||
* must not be used with this macro.
|
||||
|
@ -632,7 +706,7 @@ typedef QueueHandle_t SemaphoreHandle_t;
|
|||
)</pre>
|
||||
*
|
||||
* <i>Macro</i> to take a semaphore from an ISR. The semaphore must have
|
||||
* previously been created with a call to vSemaphoreCreateBinary() or
|
||||
* previously been created with a call to xSemaphoreCreateBinary() or
|
||||
* xSemaphoreCreateCounting().
|
||||
*
|
||||
* Mutex type semaphores (those created using a call to xSemaphoreCreateMutex())
|
||||
|
@ -661,12 +735,22 @@ typedef QueueHandle_t SemaphoreHandle_t;
|
|||
* semphr. h
|
||||
* <pre>SemaphoreHandle_t xSemaphoreCreateMutex( void )</pre>
|
||||
*
|
||||
* <i>Macro</i> that implements a mutex semaphore by using the existing queue
|
||||
* mechanism.
|
||||
* Creates a new mutex type semaphore instance, and returns a handle by which
|
||||
* the new mutex can be referenced.
|
||||
*
|
||||
* Mutexes created using this macro can be accessed using the xSemaphoreTake()
|
||||
* Internally, within the FreeRTOS implementation, mutex semaphores use a block
|
||||
* of memory, in which the mutex structure is stored. If a mutex is created
|
||||
* using xSemaphoreCreateMutex() then the required memory is automatically
|
||||
* dynamically allocated inside the xSemaphoreCreateMutex() function. (see
|
||||
* http://www.freertos.org/a00111.html). If a mutex is created using
|
||||
* xSemaphoreCreateMutexStatic() then the application writer can instead
|
||||
* optionally provide the memory that will get used by the mutex.
|
||||
* xSemaphoreCreateMutexStatic() therefore allows a mutex to be created without
|
||||
* using any dynamic memory allocation.
|
||||
*
|
||||
* Mutexes created using this function can be accessed using the xSemaphoreTake()
|
||||
* and xSemaphoreGive() macros. The xSemaphoreTakeRecursive() and
|
||||
* xSemaphoreGiveRecursive() macros should not be used.
|
||||
* xSemaphoreGiveRecursive() macros must not be used.
|
||||
*
|
||||
* This type of semaphore uses a priority inheritance mechanism so a task
|
||||
* 'taking' a semaphore MUST ALWAYS 'give' the semaphore back once the
|
||||
|
@ -674,13 +758,14 @@ typedef QueueHandle_t SemaphoreHandle_t;
|
|||
*
|
||||
* Mutex type semaphores cannot be used from within interrupt service routines.
|
||||
*
|
||||
* See vSemaphoreCreateBinary() for an alternative implementation that can be
|
||||
* See xSemaphoreCreateBinary() for an alternative implementation that can be
|
||||
* used for pure synchronisation (where one task or interrupt always 'gives' the
|
||||
* semaphore and another always 'takes' the semaphore) and from within interrupt
|
||||
* service routines.
|
||||
*
|
||||
* @return xSemaphore Handle to the created mutex semaphore. Should be of type
|
||||
* SemaphoreHandle_t.
|
||||
* @return If the mutex was successfully created then a handle to the created
|
||||
* semaphore is returned. If there was not enough heap to allocate the mutex
|
||||
* data structures then NULL is returned.
|
||||
*
|
||||
* Example usage:
|
||||
<pre>
|
||||
|
@ -699,13 +784,75 @@ typedef QueueHandle_t SemaphoreHandle_t;
|
|||
}
|
||||
}
|
||||
</pre>
|
||||
* \defgroup vSemaphoreCreateMutex vSemaphoreCreateMutex
|
||||
* \defgroup xSemaphoreCreateMutex xSemaphoreCreateMutex
|
||||
* \ingroup Semaphores
|
||||
*/
|
||||
#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX, NULL )
|
||||
|
||||
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
|
||||
#define xSemaphoreCreateMutexStatic( pxStaticQueue ) xQueueCreateMutex( queueQUEUE_TYPE_MUTEX, ( pxStaticQueue ) )
|
||||
/**
|
||||
* semphr. h
|
||||
* <pre>SemaphoreHandle_t xSemaphoreCreateMutexStatic( StaticSemaphore_t *pxMutexBuffer )</pre>
|
||||
*
|
||||
* Creates a new mutex type semaphore instance, and returns a handle by which
|
||||
* the new mutex can be referenced.
|
||||
*
|
||||
* Internally, within the FreeRTOS implementation, mutex semaphores use a block
|
||||
* of memory, in which the mutex structure is stored. If a mutex is created
|
||||
* using xSemaphoreCreateMutex() then the required memory is automatically
|
||||
* dynamically allocated inside the xSemaphoreCreateMutex() function. (see
|
||||
* http://www.freertos.org/a00111.html). If a mutex is created using
|
||||
* xSemaphoreCreateMutexStatic() then the application writer can instead
|
||||
* optionally provide the memory that will get used by the mutex.
|
||||
* xSemaphoreCreateMutexStatic() therefore allows a mutex to be created without
|
||||
* using any dynamic memory allocation.
|
||||
*
|
||||
* Mutexes created using this function can be accessed using the xSemaphoreTake()
|
||||
* and xSemaphoreGive() macros. The xSemaphoreTakeRecursive() and
|
||||
* xSemaphoreGiveRecursive() macros must not be used.
|
||||
*
|
||||
* This type of semaphore uses a priority inheritance mechanism so a task
|
||||
* 'taking' a semaphore MUST ALWAYS 'give' the semaphore back once the
|
||||
* semaphore it is no longer required.
|
||||
*
|
||||
* Mutex type semaphores cannot be used from within interrupt service routines.
|
||||
*
|
||||
* See xSemaphoreCreateBinary() for an alternative implementation that can be
|
||||
* used for pure synchronisation (where one task or interrupt always 'gives' the
|
||||
* semaphore and another always 'takes' the semaphore) and from within interrupt
|
||||
* service routines.
|
||||
*
|
||||
* @param pxMutexBuffer If pxMutexBuffer is NULL then the memory required to
|
||||
* hold the mutex's data structures will be allocated dynamically, just as when
|
||||
* a mutex is created using xSemaphoreCreateMutex(). If pxMutexBuffer is not
|
||||
* NULL then it must point to a variable of type StaticSemaphore_t, which will
|
||||
* then be used to hold the mutex's data structure, removing the need for
|
||||
* the memory to be allocated dynamically.
|
||||
*
|
||||
* @return If the mutex was successfully created then a handle to the created
|
||||
* mutex is returned. If pxMutexBuffer was NULL, and there was not enough
|
||||
* heap to allocate the mutex data structures, then NULL is returned.
|
||||
*
|
||||
* Example usage:
|
||||
<pre>
|
||||
SemaphoreHandle_t xSemaphore;
|
||||
StaticSemaphore_t xMutexBuffer;
|
||||
|
||||
void vATask( void * pvParameters )
|
||||
{
|
||||
// A mutex cannot be used before it has been created. xMutexBuffer is
|
||||
// into xSemaphoreCreateMutexStatic() so no dynamic memory allocation is
|
||||
// attempted.
|
||||
xSemaphore = xSemaphoreCreateMutexStatic( &xMutexBuffer );
|
||||
|
||||
// As no dynamic memory allocation was performed, xSemaphore cannot be NULL,
|
||||
// so there is no need to check it.
|
||||
}
|
||||
</pre>
|
||||
* \defgroup xSemaphoreCreateMutexStatic xSemaphoreCreateMutexStatic
|
||||
* \ingroup Semaphores
|
||||
*/
|
||||
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
|
||||
#define xSemaphoreCreateMutexStatic( pxMutexBuffer ) xQueueCreateMutex( queueQUEUE_TYPE_MUTEX, ( pxMutexBuffer ) )
|
||||
#endif /* configSUPPORT_STATIC_ALLOCATION */
|
||||
|
||||
|
||||
|
@ -713,12 +860,23 @@ typedef QueueHandle_t SemaphoreHandle_t;
|
|||
* semphr. h
|
||||
* <pre>SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void )</pre>
|
||||
*
|
||||
* <i>Macro</i> that implements a recursive mutex by using the existing queue
|
||||
* mechanism.
|
||||
* Creates a new recursive mutex type semaphore instance, and returns a handle
|
||||
* by which the new recursive mutex can be referenced.
|
||||
*
|
||||
* Internally, within the FreeRTOS implementation, recursive mutexs use a block
|
||||
* of memory, in which the mutex structure is stored. If a recursive mutex is
|
||||
* created using xSemaphoreCreateRecursiveMutex() then the required memory is
|
||||
* automatically dynamically allocated inside the
|
||||
* xSemaphoreCreateRecursiveMutex() function. (see
|
||||
* http://www.freertos.org/a00111.html). If a recursive mutex is created using
|
||||
* xSemaphoreCreateRecursiveMutexStatic() then the application writer can
|
||||
* instead optionally provide the memory that will get used by the mutex.
|
||||
* xSemaphoreCreateRecursiveMutexStatic() therefore allows a recursive mutex to
|
||||
* be created without using any dynamic memory allocation.
|
||||
*
|
||||
* Mutexes created using this macro can be accessed using the
|
||||
* xSemaphoreTakeRecursive() and xSemaphoreGiveRecursive() macros. The
|
||||
* xSemaphoreTake() and xSemaphoreGive() macros should not be used.
|
||||
* xSemaphoreTake() and xSemaphoreGive() macros must not be used.
|
||||
*
|
||||
* A mutex used recursively can be 'taken' repeatedly by the owner. The mutex
|
||||
* doesn't become available again until the owner has called
|
||||
|
@ -733,13 +891,13 @@ typedef QueueHandle_t SemaphoreHandle_t;
|
|||
*
|
||||
* Mutex type semaphores cannot be used from within interrupt service routines.
|
||||
*
|
||||
* See vSemaphoreCreateBinary() for an alternative implementation that can be
|
||||
* See xSemaphoreCreateBinary() for an alternative implementation that can be
|
||||
* used for pure synchronisation (where one task or interrupt always 'gives' the
|
||||
* semaphore and another always 'takes' the semaphore) and from within interrupt
|
||||
* service routines.
|
||||
*
|
||||
* @return xSemaphore Handle to the created mutex semaphore. Should be of type
|
||||
* SemaphoreHandle_t.
|
||||
* SemaphoreHandle_t.
|
||||
*
|
||||
* Example usage:
|
||||
<pre>
|
||||
|
@ -758,11 +916,85 @@ typedef QueueHandle_t SemaphoreHandle_t;
|
|||
}
|
||||
}
|
||||
</pre>
|
||||
* \defgroup vSemaphoreCreateMutex vSemaphoreCreateMutex
|
||||
* \defgroup xSemaphoreCreateRecursiveMutex xSemaphoreCreateRecursiveMutex
|
||||
* \ingroup Semaphores
|
||||
*/
|
||||
#define xSemaphoreCreateRecursiveMutex() xQueueCreateMutex( queueQUEUE_TYPE_RECURSIVE_MUTEX, NULL )
|
||||
|
||||
/**
|
||||
* semphr. h
|
||||
* <pre>SemaphoreHandle_t xSemaphoreCreateRecursiveMutexStatic( StaticSemaphore_t *pxMutexBuffer )</pre>
|
||||
*
|
||||
* Creates a new recursive mutex type semaphore instance, and returns a handle
|
||||
* by which the new recursive mutex can be referenced.
|
||||
*
|
||||
* Internally, within the FreeRTOS implementation, recursive mutexs use a block
|
||||
* of memory, in which the mutex structure is stored. If a recursive mutex is
|
||||
* created using xSemaphoreCreateRecursiveMutex() then the required memory is
|
||||
* automatically dynamically allocated inside the
|
||||
* xSemaphoreCreateRecursiveMutex() function. (see
|
||||
* http://www.freertos.org/a00111.html). If a recursive mutex is created using
|
||||
* xSemaphoreCreateRecursiveMutexStatic() then the application writer can
|
||||
* instead optionally provide the memory that will get used by the mutex.
|
||||
* xSemaphoreCreateRecursiveMutexStatic() therefore allows a recursive mutex to
|
||||
* be created without using any dynamic memory allocation.
|
||||
*
|
||||
* Mutexes created using this macro can be accessed using the
|
||||
* xSemaphoreTakeRecursive() and xSemaphoreGiveRecursive() macros. The
|
||||
* xSemaphoreTake() and xSemaphoreGive() macros must not be used.
|
||||
*
|
||||
* A mutex used recursively can be 'taken' repeatedly by the owner. The mutex
|
||||
* doesn't become available again until the owner has called
|
||||
* xSemaphoreGiveRecursive() for each successful 'take' request. For example,
|
||||
* if a task successfully 'takes' the same mutex 5 times then the mutex will
|
||||
* not be available to any other task until it has also 'given' the mutex back
|
||||
* exactly five times.
|
||||
*
|
||||
* This type of semaphore uses a priority inheritance mechanism so a task
|
||||
* 'taking' a semaphore MUST ALWAYS 'give' the semaphore back once the
|
||||
* semaphore it is no longer required.
|
||||
*
|
||||
* Mutex type semaphores cannot be used from within interrupt service routines.
|
||||
*
|
||||
* See xSemaphoreCreateBinary() for an alternative implementation that can be
|
||||
* used for pure synchronisation (where one task or interrupt always 'gives' the
|
||||
* semaphore and another always 'takes' the semaphore) and from within interrupt
|
||||
* service routines.
|
||||
*
|
||||
* @param pxMutexBuffer If pxMutexBuffer is NULL then the memory required to
|
||||
* hold the recursive mutex's data structures will be allocated dynamically,
|
||||
* just as when a recursive mutex is created using
|
||||
* xSemaphoreCreateRecursiveMutex(). If pxMutexBuffer is not NULL then it must
|
||||
* point to a variable of type StaticSemaphore_t, which will then be used to
|
||||
* hold the recursive mutex's data structure, removing the need for the memory
|
||||
* to be allocated dynamically.
|
||||
*
|
||||
* @return If the recursive mutex was successfully created then a handle to the
|
||||
* created recursive mutex is returned. If pxMutexBuffer was NULL, and there
|
||||
* was not enough heap to allocate the mutex data structures, then NULL is
|
||||
* returned.
|
||||
*
|
||||
* Example usage:
|
||||
<pre>
|
||||
SemaphoreHandle_t xSemaphore;
|
||||
StaticSemaphore_t xMutexBuffer;
|
||||
|
||||
void vATask( void * pvParameters )
|
||||
{
|
||||
// A recursive semaphore cannot be used before it is created. Here a
|
||||
// recursive mutex is created using xSemaphoreCreateRecursiveMutexStatic().
|
||||
// The address of xMutexBuffer is passed into the function, and will hold
|
||||
// the mutexes data structures - so no dynamic memory allocation will be
|
||||
// attempted.
|
||||
xSemaphore = xSemaphoreCreateRecursiveMutexStatic( &xMutexBuffer );
|
||||
|
||||
// As no dynamic memory allocation was performed, xSemaphore cannot be NULL,
|
||||
// so there is no need to check it.
|
||||
}
|
||||
</pre>
|
||||
* \defgroup xSemaphoreCreateRecursiveMutexStatic xSemaphoreCreateRecursiveMutexStatic
|
||||
* \ingroup Semaphores
|
||||
*/
|
||||
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
|
||||
#define xSemaphoreCreateRecursiveMutexStatic( pxStaticSemaphore ) xQueueCreateMutex( queueQUEUE_TYPE_RECURSIVE_MUTEX, pxStaticSemaphore )
|
||||
#endif /* configSUPPORT_STATIC_ALLOCATION */
|
||||
|
@ -771,8 +1003,19 @@ typedef QueueHandle_t SemaphoreHandle_t;
|
|||
* semphr. h
|
||||
* <pre>SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount, UBaseType_t uxInitialCount )</pre>
|
||||
*
|
||||
* <i>Macro</i> that creates a counting semaphore by using the existing
|
||||
* queue mechanism.
|
||||
* Creates a new counting semaphore instance, and returns a handle by which the
|
||||
* new counting semaphore can be referenced.
|
||||
*
|
||||
* Internally, within the FreeRTOS implementation, counting semaphores use a
|
||||
* block of memory, in which the counting semaphore structure is stored. If a
|
||||
* counting semaphore is created using xSemaphoreCreateCounting() then the
|
||||
* required memory is automatically dynamically allocated inside the
|
||||
* xSemaphoreCreateCounting() function. (see
|
||||
* http://www.freertos.org/a00111.html). If a counting semaphore is created
|
||||
* using xSemaphoreCreateCountingStatic() then the application writer can
|
||||
* instead optionally provide the memory that will get used by the counting
|
||||
* semaphore. xSemaphoreCreateCountingStatic() therefore allows a counting
|
||||
* semaphore to be created without using any dynamic memory allocation.
|
||||
*
|
||||
* Counting semaphores are typically used for two things:
|
||||
*
|
||||
|
@ -830,8 +1073,91 @@ typedef QueueHandle_t SemaphoreHandle_t;
|
|||
*/
|
||||
#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount ) xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ), ( NULL ) )
|
||||
|
||||
/**
|
||||
* semphr. h
|
||||
* <pre>SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount, UBaseType_t uxInitialCount, StaticSemaphore_t *pxSemaphoreBuffer )</pre>
|
||||
*
|
||||
* Creates a new counting semaphore instance, and returns a handle by which the
|
||||
* new counting semaphore can be referenced.
|
||||
*
|
||||
* Internally, within the FreeRTOS implementation, counting semaphores use a
|
||||
* block of memory, in which the counting semaphore structure is stored. If a
|
||||
* counting semaphore is created using xSemaphoreCreateCounting() then the
|
||||
* required memory is automatically dynamically allocated inside the
|
||||
* xSemaphoreCreateCounting() function. (see
|
||||
* http://www.freertos.org/a00111.html). If a counting semaphore is created
|
||||
* using xSemaphoreCreateCountingStatic() then the application writer can
|
||||
* instead optionally provide the memory that will get used by the counting
|
||||
* semaphore. xSemaphoreCreateCountingStatic() therefore allows a counting
|
||||
* semaphore to be created without using any dynamic memory allocation.
|
||||
*
|
||||
* Counting semaphores are typically used for two things:
|
||||
*
|
||||
* 1) Counting events.
|
||||
*
|
||||
* In this usage scenario an event handler will 'give' a semaphore each time
|
||||
* an event occurs (incrementing the semaphore count value), and a handler
|
||||
* task will 'take' a semaphore each time it processes an event
|
||||
* (decrementing the semaphore count value). The count value is therefore
|
||||
* the difference between the number of events that have occurred and the
|
||||
* number that have been processed. In this case it is desirable for the
|
||||
* initial count value to be zero.
|
||||
*
|
||||
* 2) Resource management.
|
||||
*
|
||||
* In this usage scenario the count value indicates the number of resources
|
||||
* available. To obtain control of a resource a task must first obtain a
|
||||
* semaphore - decrementing the semaphore count value. When the count value
|
||||
* reaches zero there are no free resources. When a task finishes with the
|
||||
* resource it 'gives' the semaphore back - incrementing the semaphore count
|
||||
* value. In this case it is desirable for the initial count value to be
|
||||
* equal to the maximum count value, indicating that all resources are free.
|
||||
*
|
||||
* @param uxMaxCount The maximum count value that can be reached. When the
|
||||
* semaphore reaches this value it can no longer be 'given'.
|
||||
*
|
||||
* @param uxInitialCount The count value assigned to the semaphore when it is
|
||||
* created.
|
||||
*
|
||||
* @param pxSemaphoreBuffer If pxSemaphoreBuffer is NULL then the memory
|
||||
* required to hold the semaphore's data structures will be allocated
|
||||
* dynamically, just as when a counting semaphore is created using
|
||||
* xSemaphoreCreateCounting(). If pxSemaphoreBuffer is not NULL then it must
|
||||
* point to a variable of type StaticSemaphore_t, which will then be used to
|
||||
* hold the semaphore's data structure, removing the need for the memory
|
||||
* to be allocated dynamically.
|
||||
*
|
||||
* @return If the counting semaphore was successfully created then a handle to
|
||||
* the created counting semaphore is returned. If pxSemaphoreBuffer was NULL,
|
||||
* and there was not enough heap to allocate the counting semaphore data
|
||||
* structures, then NULL is returned.
|
||||
*
|
||||
* Example usage:
|
||||
<pre>
|
||||
SemaphoreHandle_t xSemaphore;
|
||||
StaticSemaphore_t xSemaphoreBuffer;
|
||||
|
||||
void vATask( void * pvParameters )
|
||||
{
|
||||
SemaphoreHandle_t xSemaphore = NULL;
|
||||
|
||||
// Counting semaphore cannot be used before they have been created. Create
|
||||
// a counting semaphore using xSemaphoreCreateCountingStatic(). The max
|
||||
// value to which the semaphore can count is 10, and the initial value
|
||||
// assigned to the count will be 0. The address of xSemaphoreBuffer is
|
||||
// passed in and will be used to hold the semaphore structure, so no dynamic
|
||||
// memory allocation will be used.
|
||||
xSemaphore = xSemaphoreCreateCounting( 10, 0, &xSemaphoreBuffer );
|
||||
|
||||
// No memory allocation was attempted so xSemaphore cannot be NULL, so there
|
||||
// is no need to check its value.
|
||||
}
|
||||
</pre>
|
||||
* \defgroup xSemaphoreCreateCountingStatic xSemaphoreCreateCountingStatic
|
||||
* \ingroup Semaphores
|
||||
*/
|
||||
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
|
||||
#define xSemaphoreCreateCountingStatic( uxMaxCount, uxInitialCount, pxStaticSemaphore ) xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ), ( pxStaticSemaphore ) )
|
||||
#define xSemaphoreCreateCountingStatic( uxMaxCount, uxInitialCount, pxSemaphoreBuffer ) xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ), ( pxSemaphoreBuffer ) )
|
||||
#endif /* configSUPPORT_STATIC_ALLOCATION */
|
||||
|
||||
/**
|
||||
|
|
|
@ -115,7 +115,8 @@ typedef enum
|
|||
eReady, /* The task being queried is in a read or pending ready list. */
|
||||
eBlocked, /* The task being queried is in the Blocked state. */
|
||||
eSuspended, /* The task being queried is in the Suspended state, or is in the Blocked state with an infinite time out. */
|
||||
eDeleted /* The task being queried has been deleted, but its TCB has not yet been freed. */
|
||||
eDeleted, /* The task being queried has been deleted, but its TCB has not yet been freed. */
|
||||
eInvalid /* Used as an 'invalid state' value. */
|
||||
} eTaskState;
|
||||
|
||||
/* Actions that can be performed when vTaskNotify() is called. */
|
||||
|
@ -172,6 +173,7 @@ typedef struct xTASK_STATUS
|
|||
UBaseType_t uxCurrentPriority; /* The priority at which the task was running (may be inherited) when the structure was populated. */
|
||||
UBaseType_t uxBasePriority; /* The priority to which the task will return if the task's current priority has been inherited to avoid unbounded priority inversion when obtaining a mutex. Only valid if configUSE_MUTEXES is defined as 1 in FreeRTOSConfig.h. */
|
||||
uint32_t ulRunTimeCounter; /* The total run time allocated to the task so far, as defined by the run time stats clock. See http://www.freertos.org/rtos-run-time-stats.html. Only valid when configGENERATE_RUN_TIME_STATS is defined as 1 in FreeRTOSConfig.h. */
|
||||
StackType_t *pxStackBase; /* Points to the lowest address of the task's stack area. */
|
||||
uint16_t usStackHighWaterMark; /* The minimum amount of stack space that has remained for the task since the task was created. The closer this value is to zero the closer the task has come to overflowing its stack. */
|
||||
} TaskStatus_t;
|
||||
|
||||
|
@ -426,8 +428,12 @@ is used in assert() statements. */
|
|||
* task's data structures, removing the need for the memory to be allocated
|
||||
* dynamically.
|
||||
*
|
||||
* @return pdPASS if the task was successfully created and added to a ready
|
||||
* list, otherwise an error code defined in the file projdefs.h
|
||||
* @return If neither pxStackBuffer or pxTaskBuffer are NULL, then the function
|
||||
* will not attempt any dynamic memory allocation, and pdPASS will always be
|
||||
* returned. If pxStackBuffer or pxTaskBuffer is NULL then the function will
|
||||
* attempt to dynamically allocate one of both buffers. In this case, if the
|
||||
* allocation succeeds then pdPASS will be returned, and if the allocation fails
|
||||
* then an error code defined in projdefs.h is returned.
|
||||
*
|
||||
* Example usage:
|
||||
<pre>
|
||||
|
@ -819,6 +825,62 @@ UBaseType_t uxTaskPriorityGetFromISR( TaskHandle_t xTask ) PRIVILEGED_FUNCTION;
|
|||
*/
|
||||
eTaskState eTaskGetState( TaskHandle_t xTask ) PRIVILEGED_FUNCTION;
|
||||
|
||||
/**
|
||||
* task. h
|
||||
* <pre>void vTaskGetTaskInfo( TaskHandle_t xTask, TaskStatus_t *pxTaskStatus, BaseType_t xGetFreeStackSpace, eTaskState eState );</pre>
|
||||
*
|
||||
* configUSE_TRACE_FACILITY must be defined as 1 for this function to be
|
||||
* available. See the configuration section for more information.
|
||||
*
|
||||
* Populates a TaskStatus_t structure with information about a task.
|
||||
*
|
||||
* @param xTask Handle of the task being queried. If xTask is NULL then
|
||||
* information will be returned about the calling task.
|
||||
*
|
||||
* @param pxTaskStatus A pointer to the TaskStatus_t structure that will be
|
||||
* filled with information about the task referenced by the handle passed using
|
||||
* the xTask parameter.
|
||||
*
|
||||
* @xGetFreeStackSpace The TaskStatus_t structure contains a member to report
|
||||
* the stack high water mark of the task being queried. Calculating the stack
|
||||
* high water mark takes a relatively long time, and can make the system
|
||||
* temporarily unresponsive - so the xGetFreeStackSpace parameter is provided to
|
||||
* allow the high water mark checking to be skipped. The high watermark value
|
||||
* will only be written to the TaskStatus_t structure if xGetFreeStackSpace is
|
||||
* not set to pdFALSE;
|
||||
*
|
||||
* @param eState The TaskStatus_t structure contains a member to report the
|
||||
* state of the task being queried. Obtaining the task state is not as fast as
|
||||
* a simple assignment - so the eState parameter is provided to allow the state
|
||||
* information to be omitted from the TaskStatus_t structure. To obtain state
|
||||
* information then set eState to eInvalid - otherwise the value passed in
|
||||
* eState will be reported as the task state in the TaskStatus_t structure.
|
||||
*
|
||||
* Example usage:
|
||||
<pre>
|
||||
void vAFunction( void )
|
||||
{
|
||||
TaskHandle_t xHandle;
|
||||
TaskStatus_t xTaskDetails;
|
||||
|
||||
// Obtain the handle of a task from its name.
|
||||
xHandle = xTaskGetTaskHandle( "Task_Name" );
|
||||
|
||||
// Check the handle is not NULL.
|
||||
configASSERT( xHandle );
|
||||
|
||||
// Use the handle to obtain further information about the task.
|
||||
vTaskGetTaskInfo( xHandle,
|
||||
&xTaskDetails,
|
||||
pdTRUE, // Include the high water mark in xTaskDetails.
|
||||
eInvalid ); // Include the task state in xTaskDetails.
|
||||
}
|
||||
</pre>
|
||||
* \defgroup vTaskGetTaskInfo vTaskGetTaskInfo
|
||||
* \ingroup TaskCtrl
|
||||
*/
|
||||
void vTaskGetTaskInfo( TaskHandle_t xTask, TaskStatus_t *pxTaskStatus, BaseType_t xGetFreeStackSpace, eTaskState eState );
|
||||
|
||||
/**
|
||||
* task. h
|
||||
* <pre>void vTaskPrioritySet( TaskHandle_t xTask, UBaseType_t uxNewPriority );</pre>
|
||||
|
@ -1243,6 +1305,22 @@ UBaseType_t uxTaskGetNumberOfTasks( void ) PRIVILEGED_FUNCTION;
|
|||
*/
|
||||
char *pcTaskGetTaskName( TaskHandle_t xTaskToQuery ) PRIVILEGED_FUNCTION; /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
|
||||
|
||||
/**
|
||||
* task. h
|
||||
* <PRE>TaskHandle_t xTaskGetTaskHandle( const char *pcNameToQuery );</PRE>
|
||||
*
|
||||
* NOTE: This function takes a relatively long time to complete and should be
|
||||
* used sparingly.
|
||||
*
|
||||
* @return The handle of the task that has the human readable name pcNameToQuery.
|
||||
* NULL is returned if no matching name is found. INCLUDE_xTaskGetTaskHandle
|
||||
* must be set to 1 in FreeRTOSConfig.h for pcTaskGetTaskHandle() to be available.
|
||||
*
|
||||
* \defgroup pcTaskGetTaskHandle pcTaskGetTaskHandle
|
||||
* \ingroup TaskUtils
|
||||
*/
|
||||
TaskHandle_t xTaskGetTaskHandle( const char *pcNameToQuery ) PRIVILEGED_FUNCTION; /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
|
||||
|
||||
/**
|
||||
* task.h
|
||||
* <PRE>UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask );</PRE>
|
||||
|
|
|
@ -145,8 +145,8 @@ typedef void (*PendedFunction_t)( void *, uint32_t );
|
|||
* http://www.freertos.org/a00111.html). If a software timer is created using
|
||||
* xTimerCreateStatic() then the application writer can instead optionally
|
||||
* provide the memory that will get used by the software timer.
|
||||
* xTimerCreateStatic() therefore allows a software to be created without using
|
||||
* any dynamic memory allocation.
|
||||
* xTimerCreateStatic() therefore allows a software timer to be created without
|
||||
* using any dynamic memory allocation.
|
||||
*
|
||||
* Timers are created in the dormant state. The xTimerStart(), xTimerReset(),
|
||||
* xTimerStartFromISR(), xTimerResetFromISR(), xTimerChangePeriod() and
|
||||
|
@ -327,10 +327,12 @@ typedef void (*PendedFunction_t)( void *, uint32_t );
|
|||
* will be then be used to hold the software timer's data structures, removing
|
||||
* the need for the memory to be allocated dynamically.
|
||||
*
|
||||
* @return If the timer is successfully created then a handle to the newly
|
||||
* created timer is returned. If the timer cannot be created (because either
|
||||
* there is insufficient FreeRTOS heap remaining to allocate the timer
|
||||
* structures, or the timer period was set to 0) then NULL is returned.
|
||||
* @return If pxTimerBuffer is not NULL then the function will not attempt
|
||||
* any dynamic memory allocation, and a handle to the created timer will always
|
||||
* be returned. If pxTimerBuffer is NULL then the function will attempt to
|
||||
* dynamically allocate the memory required to hold the timer's data structures.
|
||||
* In this case, if the allocation succeeds then a handle to the created timer
|
||||
* will be returned, and if the allocation fails NULL will be returned.
|
||||
*
|
||||
* Example usage:
|
||||
* @verbatim
|
||||
|
@ -1277,7 +1279,7 @@ const char * pcTimerGetTimerName( TimerHandle_t xTimer ) PRIVILEGED_FUNCTION; /*
|
|||
*/
|
||||
BaseType_t xTimerCreateTimerTask( void ) PRIVILEGED_FUNCTION;
|
||||
BaseType_t xTimerGenericCommand( TimerHandle_t xTimer, const BaseType_t xCommandID, const TickType_t xOptionalValue, BaseType_t * const pxHigherPriorityTaskWoken, const TickType_t xTicksToWait ) PRIVILEGED_FUNCTION;
|
||||
TimerHandle_t xTimerGenericCreate( const char * const pcTimerName, const TickType_t xTimerPeriodInTicks, const UBaseType_t uxAutoReload, void * const pvTimerID, TimerCallbackFunction_t pxCallbackFunction, StaticTimer_t *pxTimerBuffer ) PRIVILEGED_FUNCTION;
|
||||
TimerHandle_t xTimerGenericCreate( const char * const pcTimerName, const TickType_t xTimerPeriodInTicks, const UBaseType_t uxAutoReload, void * const pvTimerID, TimerCallbackFunction_t pxCallbackFunction, StaticTimer_t *pxTimerBuffer ) PRIVILEGED_FUNCTION; /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue