Commit 29d124d0 by Michael Schmid

added FreeRTOS implementation

parent dd88ea03
......@@ -26,36 +26,51 @@
#
function(SetGNUCompilerFlags compiler_libs)
if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
set(compiler_libs pthread rt PARENT_SCOPE)
# -Wall -> All warnings
# -Wextra -> Even more warnings
# -Werror -> Warnings are errors
set(warning_flags "-Wall -Wextra")
if (WARNINGS_ARE_ERRORS STREQUAL ON)
set(warning_flags "${warning_flags} -Werror")
endif()
if(CMAKE_COMPILER_IS_GNUCC)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread -std=c99 ${warning_flags}"
PARENT_SCOPE)
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -DEMBB_DEBUG"
PARENT_SCOPE)
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -DNDEBUG"
PARENT_SCOPE)
set(CMAKE_C_FLAGS_COVERAGE
"${CMAKE_C_FLAGS_COVERAGE} -O0 -fprofile-arcs -ftest-coverage"
PARENT_SCOPE)
endif()
if(CMAKE_COMPILER_IS_GNUCXX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread -std=c++03 ${warning_flags}"
PARENT_SCOPE)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DEMBB_DEBUG"
PARENT_SCOPE)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DNDEBUG"
PARENT_SCOPE)
set(CMAKE_CXX_FLAGS_COVERAGE
"${CMAKE_CXX_FLAGS_COVERAGE} -O0 -fprofile-arcs -ftest-coverage"
PARENT_SCOPE)
endif()
if(DEFINED CMAKE_SYSTEM_PROCESSOR)
set(compiler_libs PARENT_SCOPE)
set(common_flags "-fno-common -Os")
set(warning_flags "-W -Wall -Wextra -Wdiv-by-zero -Warray-bounds -Wcast-align -Wignored-qualifiers -Wformat -Wformat-security")
set(target_flags "-DAPPKIT_TC277TFT -fshort-double -mcpu=tc27xx -mversion-info")
if (WARNINGS_ARE_ERRORS STREQUAL ON)
set(warning_flags "${warning_flags} -Werror")
endif()
if(CMAKE_COMPILER_IS_GNUCC)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${common_flags} ${warning_flags} ${target_flags}"
PARENT_SCOPE)
endif()
else()
set(compiler_libs pthread rt PARENT_SCOPE)
# -Wall -> All warnings
# -Wextra -> Even more warnings
# -Werror -> Warnings are errors
set(warning_flags "-Wall -Wextra")
if (WARNINGS_ARE_ERRORS STREQUAL ON)
set(warning_flags "${warning_flags} -Werror")
endif()
if(CMAKE_COMPILER_IS_GNUCC)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread -std=c99 ${warning_flags}"
PARENT_SCOPE)
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -DEMBB_DEBUG"
PARENT_SCOPE)
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -DNDEBUG"
PARENT_SCOPE)
set(CMAKE_C_FLAGS_COVERAGE
"${CMAKE_C_FLAGS_COVERAGE} -O0 -fprofile-arcs -ftest-coverage"
PARENT_SCOPE)
endif()
if(CMAKE_COMPILER_IS_GNUCXX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread -std=c++03 ${warning_flags}"
PARENT_SCOPE)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DEMBB_DEBUG"
PARENT_SCOPE)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DNDEBUG"
PARENT_SCOPE)
set(CMAKE_CXX_FLAGS_COVERAGE
"${CMAKE_CXX_FLAGS_COVERAGE} -O0 -fprofile-arcs -ftest-coverage"
PARENT_SCOPE)
endif()
endif()
endif()
endfunction()
......
......@@ -76,7 +76,7 @@ endif()
# The set option will be converted to uppercase letters by cmake!! --> ON/OFF
# Note that the help string (second argument) cannot be printed by cmake.
#
option(BUILD_TESTS "Specify whether tests should be built" ON)
option(BUILD_TESTS "Specify whether tests should be built" OFF)
option(BUILD_EXAMPLES "Specify whether examples should be built" OFF)
option(USE_EXCEPTIONS "Specify whether exceptions should be activated in C++" ON)
option(INSTALL_DOCS "Specify whether Doxygen docs should be installed" ON)
......@@ -104,6 +104,12 @@ else()
endif()
message(" (set with command line option -DUSE_AUTOMATIC_INITIALIZATION=ON/OFF)")
set(CMAKE_CXX_STANDARD_LIBRARIES ${FREE_RTOS_LIB_PATH}/iRom/libFreeRTOS_Lib.a)
set(CMAKE_C_STANDARD_LIBRARIES ${FREE_RTOS_LIB_PATH}/iRom/libFreeRTOS_Lib.a)
set(CMAKE_INSTALL_RPATH FREE_RTOS_LIB_PATH)
set(__TriCore__ 1)
include(CMakeCommon/SetCompilerFlags.cmake)
SetGNUCompilerFlags(compiler_libs compiler_flags)
SetVisualStudioCompilerFlags(compiler_libs compiler_flags)
......
......@@ -2,7 +2,8 @@
SET(CMAKE_SYSTEM_NAME Generic)
#this one not so much
SET(CMAKE_SYSTEM_VERSION 1)
SET(CMAKE_SYSTEM_PROCESSOR TriCore)
SET(CMAKE_SYSTEM_PROCESSOR __TriCore__)
SET(__TriCore__ 1)
# specify the cross compiler
SET(CMAKE_C_COMPILER C:/HighTec/toolchains/tricore/v4.6.6.0-infineon-1.1/bin/tricore-gcc.exe)
......@@ -11,6 +12,9 @@ SET(CMAKE_CXX_COMPILER C:/HighTec/toolchains/tricore/v4.6.6.0-infineon-1.1/bin/t
# where is the target environment
SET(CMAKE_FIND_ROOT_PATH C:/HighTec/toolchains/tricore/v4.6.6.0-infineon-1.1)
SET(FREE_RTOS_LIB_PATH C:/data/projekte/freeRTOS_lib/)
# search for programs in the build host directories
SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH)
# for libraries and headers in the target directories
......
......@@ -46,6 +46,8 @@
*/
#if defined(_M_IA64) || defined(_IA64)
#define EMBB_PLATFORM_CACHE_LINE_SIZE 128
#elif defined(__TriCore__)
#define EMBB_PLATFORM_CACHE_LINE_SIZE 32
#else
#define EMBB_PLATFORM_CACHE_LINE_SIZE 64
#endif
......@@ -86,6 +88,8 @@
#define EMBB_PLATFORM_ARCH_X86
#elif defined(__arm__)
#define EMBB_PLATFORM_ARCH_ARM
#elif defined(__TriCore__)
#define EMBB_PLATFORM_ARCH_TC
#else
#define EMBB_PLATFORM_ARCH_UNKNOWN
#endif
......@@ -93,7 +97,11 @@
#if defined(EMBB_PLATFORM_COMPILER_MSVC)
#define EMBB_PLATFORM_THREADING_WINTHREADS
#elif defined(EMBB_PLATFORM_COMPILER_GNUC)
#define EMBB_PLATFORM_THREADING_POSIXTHREADS
#if defined(EMBB_PLATFORM_ARCH_TC)
#define EMBB_PLATFORM_THREADING_RTOSTASKS
#else
#define EMBB_PLATFORM_THREADING_POSIXTHREADS
#endif
#else
#error "No thread implementation could be determined"
#endif
......
......@@ -71,6 +71,7 @@ typedef CONDITION_VARIABLE embb_condition_t;
#define EMBB_THREAD_SPECIFIC static __declspec(thread)
/* EMBB_PLATFORM_THREADING_WINTHREADS */
#elif defined EMBB_PLATFORM_THREADING_POSIXTHREADS
#include <pthread.h>
......@@ -95,7 +96,31 @@ typedef pthread_cond_t embb_condition_t;
#define EMBB_THREAD_SPECIFIC __thread
#else /* EMBB_PLATFORM_THREADING_POSIXTHREADS */
/* EMBB_PLATFORM_THREADING_POSIXTHREADS */
#elif defined EMBB_PLATFORM_THREADING_RTOSTASKS
#include <FreeRTOS/task.h>
#include <FreeRTOS/semphr.h>
struct embb_internal_thread_arg_t;
/**
* Opaque handle for a thread.
*/
typedef struct embb_thread_t {
TaskHandle_t embb_internal_handle;
struct embb_internal_thread_arg_t* embb_internal_arg;
} embb_thread_t;
typedef TaskStatus_t embb_thread_id_t;
typedef SemaphoreHandle_t embb_mutex_t;
typedef SemaphoreHandle_t embb_condition_t; // scm34681: implement FreeRTOS condition variable
#define EMBB_DURATION_MIN_NANOSECONDS 1000
#define EMBB_THREAD_SPECIFIC
#else /* EMBB_PLATFORM_THREADING_RTOSTASKS */
#error "No threading platform defined!"
......
......@@ -185,3 +185,7 @@ int embb_condition_destroy(embb_condition_t* condition_var) {
}
#endif /* EMBB_PLATFORM_THREADING_POSIXTHREADS */
#ifdef EMBB_PLATFORM_THREADING_RTOSTASKS
#endif /* EMBB_PLATFORM_THREADING_RTOSTASKS */
......@@ -125,6 +125,32 @@ void embb_core_set_init(embb_core_set_t* core_set, int initializer) {
#endif /* EMBB_PLATFORM_THREADING_POSIXTHREADS */
#ifdef EMBB_PLATFORM_THREADING_RTOSTASKS
#ifdef __TriCore__
#define CORE_COUNT 3
#endif
unsigned int embb_core_count_available() {
return CORE_COUNT;
}
void embb_core_set_init(embb_core_set_t* core_set, int initializer) { // scm34681: rework
assert(core_set != NULL);
assert(embb_core_count_available() < 64 &&
"Core sets are only supported up to 64 processors!");
if (initializer == 0) {
embb_bitset_clear_all(&core_set->rep);
} else {
embb_bitset_set_n(&core_set->rep, embb_core_count_available());
}
}
#endif EMBB_PLATFORM_THREADING_RTOSTASKS
void embb_core_set_add(embb_core_set_t* core_set, unsigned int core_number) {
assert(core_set != NULL);
assert(core_number < embb_core_count_available());
......
......@@ -178,6 +178,14 @@ void *embb_alloc_aligned(size_t alignment, size_t size) {
*/
malloc_addr = _aligned_malloc(size, alignment);
#elif defined EMBB_PLATFORM_COMPILER_GNUC
#ifdef __TRICORE__
/*
* The TriCore Toolchain uses obsolete function memalign() for
* aligned memory allocation.
*/
#include <malloc.h> // scm3681: rework
malloc_addr = memalign(alignment, size);
#else /* __TRICORE__ */
/*
* From the Documentation:
* The posix_memalign() function shall allocate size bytes aligned on a
......@@ -190,6 +198,7 @@ void *embb_alloc_aligned(size_t alignment, size_t size) {
int status = posix_memalign(&malloc_addr, alignment, size);
EMBB_UNUSED(status);
#endif
#endif
return malloc_addr;
}
......
/*
* Copyright (c) 2014-2016, Siemens AG. All rights reserved.
*
* 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.
*
* 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 HOLDER 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.
*/
* Copyright (c) 2014-2016, Siemens AG. All rights reserved.
*
* 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.
*
* 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 HOLDER 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.
*/
#include <embb/base/c/mutex.h>
#include <embb/base/c/thread.h>
......@@ -33,44 +33,44 @@
#ifdef EMBB_PLATFORM_THREADING_WINTHREADS
int embb_mutex_init(embb_mutex_t* mutex, int type) {
if (NULL == mutex) {
return EMBB_ERROR;
}
/* Critical sections in Windows are always recursive */
InitializeCriticalSection(mutex);
EMBB_UNUSED(type);
return EMBB_SUCCESS;
if (NULL == mutex) {
return EMBB_ERROR;
}
/* Critical sections in Windows are always recursive */
InitializeCriticalSection(mutex);
EMBB_UNUSED(type);
return EMBB_SUCCESS;
}
int embb_mutex_lock(embb_mutex_t* mutex) {
if (NULL == mutex) {
return EMBB_ERROR;
}
EnterCriticalSection(mutex);
return EMBB_SUCCESS;
if (NULL == mutex) {
return EMBB_ERROR;
}
EnterCriticalSection(mutex);
return EMBB_SUCCESS;
}
int embb_mutex_try_lock(embb_mutex_t* mutex) {
if (NULL == mutex) {
return EMBB_ERROR;
}
BOOL success;
success = TryEnterCriticalSection(mutex);
if (success == FALSE) return EMBB_ERROR;
return EMBB_SUCCESS;
if (NULL == mutex) {
return EMBB_ERROR;
}
BOOL success;
success = TryEnterCriticalSection(mutex);
if (success == FALSE) return EMBB_ERROR;
return EMBB_SUCCESS;
}
int embb_mutex_unlock(embb_mutex_t* mutex) {
if (NULL == mutex) {
return EMBB_ERROR;
}
LeaveCriticalSection(mutex);
return EMBB_SUCCESS;
if (NULL == mutex) {
return EMBB_ERROR;
}
LeaveCriticalSection(mutex);
return EMBB_SUCCESS;
}
void embb_mutex_destroy(embb_mutex_t* mutex) {
assert(NULL != mutex);
DeleteCriticalSection(mutex);
assert(NULL != mutex);
DeleteCriticalSection(mutex);
}
#endif /* EMBB_PLATFORM_THREADING_WINTHREADS */
......@@ -78,136 +78,208 @@ void embb_mutex_destroy(embb_mutex_t* mutex) {
#ifdef EMBB_PLATFORM_THREADING_POSIXTHREADS
int embb_mutex_init(embb_mutex_t* mutex, int type) {
if (NULL == mutex) {
return EMBB_ERROR;
}
if (type == EMBB_MUTEX_PLAIN) {
if (pthread_mutex_init(mutex, NULL) != 0) return EMBB_ERROR;
} else {
assert(type == EMBB_MUTEX_RECURSIVE);
pthread_mutexattr_t attributes;
if (pthread_mutexattr_init(&attributes) != 0) return EMBB_ERROR;
if (pthread_mutexattr_settype(&attributes, PTHREAD_MUTEX_RECURSIVE) != 0) {
pthread_mutexattr_destroy(&attributes);
return EMBB_ERROR;
}
if (pthread_mutex_init(mutex, &attributes) != 0) {
pthread_mutexattr_destroy(&attributes);
return EMBB_ERROR;
}
if (pthread_mutexattr_destroy(&attributes) != 0) return EMBB_ERROR;
}
return EMBB_SUCCESS;
if (NULL == mutex) {
return EMBB_ERROR;
}
if (type == EMBB_MUTEX_PLAIN) {
if (pthread_mutex_init(mutex, NULL) != 0) return EMBB_ERROR;
} else {
assert(type == EMBB_MUTEX_RECURSIVE);
pthread_mutexattr_t attributes;
if (pthread_mutexattr_init(&attributes) != 0) return EMBB_ERROR;
if (pthread_mutexattr_settype(&attributes, PTHREAD_MUTEX_RECURSIVE) != 0) {
pthread_mutexattr_destroy(&attributes);
return EMBB_ERROR;
}
if (pthread_mutex_init(mutex, &attributes) != 0) {
pthread_mutexattr_destroy(&attributes);
return EMBB_ERROR;
}
if (pthread_mutexattr_destroy(&attributes) != 0) return EMBB_ERROR;
}
return EMBB_SUCCESS;
}
int embb_mutex_lock(embb_mutex_t* mutex) {
if (NULL == mutex) {
return EMBB_ERROR;
}
int result = pthread_mutex_lock(mutex);
if (result != 0) {
return EMBB_ERROR;
}
return EMBB_SUCCESS;
if (NULL == mutex) {
return EMBB_ERROR;
}
int result = pthread_mutex_lock(mutex);
if (result != 0) {
return EMBB_ERROR;
}
return EMBB_SUCCESS;
}
int embb_mutex_try_lock(embb_mutex_t* mutex) {
if (NULL == mutex) {
return EMBB_ERROR;
}
int result = pthread_mutex_trylock(mutex);
if (result == 0) {
return EMBB_SUCCESS;
}
if (result == EBUSY) {
return EMBB_BUSY;
}
return EMBB_ERROR;
if (NULL == mutex) {
return EMBB_ERROR;
}
int result = pthread_mutex_trylock(mutex);
if (result == 0) {
return EMBB_SUCCESS;
}
if (result == EBUSY) {
return EMBB_BUSY;
}
return EMBB_ERROR;
}
int embb_mutex_unlock(embb_mutex_t* mutex) {
if (NULL == mutex) {
return EMBB_ERROR;
}
int result = pthread_mutex_unlock(mutex);
if (result != 0) {
return EMBB_ERROR;
}
return EMBB_SUCCESS;
if (NULL == mutex) {
return EMBB_ERROR;
}
int result = pthread_mutex_unlock(mutex);
if (result != 0) {
return EMBB_ERROR;
}
return EMBB_SUCCESS;
}
void embb_mutex_destroy(embb_mutex_t* mutex) {
assert(NULL != mutex);
pthread_mutex_destroy(mutex);
assert(NULL != mutex);
pthread_mutex_destroy(mutex);
}
#endif /* EMBB_PLATFORM_THREADING_POSIXTHREADS */
#ifdef EMBB_PLATFORM_THREADING_RTOSTASKS
#include <FreeRTOS/semphr.h>
#include <FreeRTOS/portmacro.h>
int embb_mutex_init(embb_mutex_t* mutex, int type) {
if (NULL == mutex) {
return EMBB_ERROR;
}
/*
* scm34681:
* Type of mutex cannot be determined from SemaphoreHandle_t,
* but mutex gets locked with two different functions.
* ==> only recursive mutexes are used.
* Check Queue_t for type checking. Queue_t not defined in queue.h
* but queue.c...
*/
/*if(type == EMBB_MUTEX_PLAIN) {
mutex = xSemaphoreCreateMutex();
}
else {
assert(type == EMBB_MUTEX_RECURSIVE);*/
mutex = xSemaphoreCreateRecursiveMutex();
/*}*/
EMBB_UNUSED(type);
if(mutex == NULL) {
return EMBB_ERROR;
}
return EMBB_SUCCESS;
}
int embb_mutex_lock(embb_mutex_t* mutex) {
if (NULL == mutex) {
return EMBB_ERROR;
}
int result = xSemaphoreTakeRecursive(mutex, portMAX_DELAY);
if (result != 1) return EMBB_ERROR;
return EMBB_SUCCESS;
}
int embb_mutex_try_lock(embb_mutex_t* mutex) {
if (NULL == mutex) {
return EMBB_ERROR;
}
int result = xSemaphoreTakeRecursive(mutex, 0);
if (result != 1) return EMBB_ERROR;
return EMBB_SUCCESS;
}
int embb_mutex_unlock(embb_mutex_t* mutex) {
if (NULL == mutex) {
return EMBB_ERROR;
}
int result = xSemaphoreGiveRecursive(mutex);
if(result != 1) return EMBB_ERROR;
return EMBB_SUCCESS;
}
void embb_mutex_destroy(embb_mutex_t* mutex) {
assert(NULL != mutex);
vSemaphoreDelete(mutex);
}
#endif /* EMBB_PLATFORM_THREADING_RTOSTASKS */
int embb_spin_init(embb_spinlock_t* spinlock) {
if (NULL == spinlock) {
return EMBB_ERROR;
}
// For now, store the initial value. In the future will use atomic init
// function (as soon as available).
embb_atomic_store_int(&spinlock->atomic_spin_variable_, 0);
return EMBB_SUCCESS;
if (NULL == spinlock) {
return EMBB_ERROR;
}
// For now, store the initial value. In the future will use atomic init
// function (as soon as available).
embb_atomic_store_int(&spinlock->atomic_spin_variable_, 0);
return EMBB_SUCCESS;
}
int embb_spin_lock(embb_spinlock_t* spinlock) {
if (NULL == spinlock) {
return EMBB_ERROR;
}
int expected = 0;
int spins = 1;
// try to swap the
while (0 == embb_atomic_compare_and_swap_int(
&spinlock->atomic_spin_variable_, &expected, 1)) {
if (0 == (spins & 1023)) {
embb_thread_yield();
}
spins++;
// reset expected, as CAS might change it...
expected = 0;
}
return EMBB_SUCCESS;
if (NULL == spinlock) {
return EMBB_ERROR;
}
int expected = 0;
int spins = 1;
// try to swap the
while (0 == embb_atomic_compare_and_swap_int(
&spinlock->atomic_spin_variable_, &expected, 1)) {
if (0 == (spins & 1023)) {
embb_thread_yield();
}
spins++;
// reset expected, as CAS might change it...
expected = 0;
}
return EMBB_SUCCESS;
}
int embb_spin_try_lock(embb_spinlock_t* spinlock,
unsigned int max_number_spins) {
if (NULL == spinlock) {
return EMBB_ERROR;
}
if (max_number_spins == 0)
return EMBB_BUSY;
int expected = 0;
while (0 == embb_atomic_compare_and_swap_int(
&spinlock->atomic_spin_variable_,
&expected, 1)) {
max_number_spins--;
if (0 == max_number_spins) {
return EMBB_BUSY;
}
expected = 0;
}
return EMBB_SUCCESS;
unsigned int max_number_spins) {
if (NULL == spinlock) {
return EMBB_ERROR;
}
if (max_number_spins == 0)
return EMBB_BUSY;
int expected = 0;
while (0 == embb_atomic_compare_and_swap_int(
&spinlock->atomic_spin_variable_,
&expected, 1)) {
max_number_spins--;
if (0 == max_number_spins) {
return EMBB_BUSY;
}
expected = 0;
}
return EMBB_SUCCESS;
}
int embb_spin_unlock(embb_spinlock_t* spinlock) {
if (NULL == spinlock) {
return EMBB_ERROR;
}
int expected = 1;
return embb_atomic_compare_and_swap_int(&spinlock->atomic_spin_variable_,
&expected, 0) ?
EMBB_SUCCESS : EMBB_ERROR;
if (NULL == spinlock) {
return EMBB_ERROR;
}
int expected = 1;
return embb_atomic_compare_and_swap_int(&spinlock->atomic_spin_variable_,
&expected, 0) ?
EMBB_SUCCESS : EMBB_ERROR;
}
void embb_spin_destroy(embb_spinlock_t* spinlock) {
assert(NULL != spinlock);
// for now, doing nothing here... in future, will call the respective
// destroy function for atomics...
EMBB_UNUSED(spinlock);
assert(NULL != spinlock);
// for now, doing nothing here... in future, will call the respective
// destroy function for atomics...
EMBB_UNUSED(spinlock);
}
/*
* Copyright (c) 2014-2016, Siemens AG. All rights reserved.
*
* 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.
*
* 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 HOLDER 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.
*/
* Copyright (c) 2014-2016, Siemens AG. All rights reserved.
*
* 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.
*
* 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 HOLDER 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.
*/
#include <embb/base/c/thread.h>
#include <embb/base/c/internal/thread_index.h>
......@@ -33,135 +33,135 @@
#include <assert.h>
unsigned int embb_thread_get_max_count() {
return (unsigned int)(embb_internal_thread_index_max());
return (unsigned int)(embb_internal_thread_index_max());
}
void embb_thread_set_max_count(unsigned int max) {
embb_internal_thread_index_set_max(max);
embb_internal_thread_index_set_max(max);
}
#ifdef EMBB_PLATFORM_THREADING_WINTHREADS
/**
* Used to wrap client thread start function and argument when calling internal
* thread start function embb_internal_thread_start.
*/
* Used to wrap client thread start function and argument when calling internal
* thread start function embb_internal_thread_start.
*/
typedef struct embb_internal_thread_arg_t {
embb_thread_start_t func;
void* arg;
embb_thread_start_t func;
void* arg;
} embb_internal_thread_arg_t;
/**
* Used to offer a consistent thread start function signature. Windows threads
* have a different signature than Pthreads and C11. This internal start
* function for Windows threads just calls the client start function with the
* given argument.
*/
* Used to offer a consistent thread start function signature. Windows threads
* have a different signature than Pthreads and C11. This internal start
* function for Windows threads just calls the client start function with the
* given argument.
*/
DWORD WINAPI embb_internal_thread_start(LPVOID internalArg) {
int result = ((embb_internal_thread_arg_t*)internalArg)->func(
((embb_internal_thread_arg_t*)internalArg)->arg);
int result = ((embb_internal_thread_arg_t*)internalArg)->func(
((embb_internal_thread_arg_t*)internalArg)->arg);
#if !defined(__cplusplus)
ExitThread((DWORD)result); /* In C, returning the result code doesn't work */
ExitThread((DWORD)result); /* In C, returning the result code doesn't work */
#else
return (DWORD)result;
return (DWORD)result;
#endif
}
embb_thread_t embb_thread_current() {
embb_thread_t thread;
thread.embb_internal_handle = GetCurrentThread();
thread.embb_internal_arg = NULL;
return thread;
embb_thread_t thread;
thread.embb_internal_handle = GetCurrentThread();
thread.embb_internal_arg = NULL;
return thread;
}
void embb_thread_yield() {
SwitchToThread();
SwitchToThread();
}
int embb_thread_create(embb_thread_t* thread, const embb_core_set_t* core_set,
embb_thread_start_t func, void *arg) {
if (thread == NULL) {
return EMBB_ERROR;
}
thread->embb_internal_arg = (embb_internal_thread_arg_t*)
embb_alloc(sizeof(embb_internal_thread_arg_t));
if (thread->embb_internal_arg == NULL) {
thread->embb_internal_handle = NULL;
return EMBB_NOMEM;
}
thread->embb_internal_arg->func = func;
thread->embb_internal_arg->arg = arg;
thread->embb_internal_handle = CreateThread(
0, /* no security */
0, /* default stack size */
embb_internal_thread_start, /* entry function */
(LPVOID)thread->embb_internal_arg, /* parameters */
0, /* no creation arguments */
0); /* no system thread ID */
if (thread->embb_internal_handle == NULL) {
embb_free(thread->embb_internal_arg);
thread->embb_internal_arg = NULL;
return EMBB_ERROR;
}
if (core_set != NULL) { /* Set thread affinity, if a core set is given */
DWORD_PTR core_mask = 0;
DWORD bit_mask = 1;
assert(embb_core_count_available() < 64);
for (unsigned int i = 0; i < embb_core_count_available(); i++) {
if (embb_core_set_contains(core_set, i)) {
core_mask |= bit_mask;
}
bit_mask <<= 1;
}
if (SetThreadAffinityMask(thread->embb_internal_handle, core_mask)
== (DWORD_PTR)NULL) {
return EMBB_ERROR;
}
}
return EMBB_SUCCESS;
embb_thread_start_t func, void *arg) {
if (thread == NULL) {
return EMBB_ERROR;
}
thread->embb_internal_arg = (embb_internal_thread_arg_t*)
embb_alloc(sizeof(embb_internal_thread_arg_t));
if (thread->embb_internal_arg == NULL) {
thread->embb_internal_handle = NULL;
return EMBB_NOMEM;
}
thread->embb_internal_arg->func = func;
thread->embb_internal_arg->arg = arg;
thread->embb_internal_handle = CreateThread(
0, /* no security */
0, /* default stack size */
embb_internal_thread_start, /* entry function */
(LPVOID)thread->embb_internal_arg, /* parameters */
0, /* no creation arguments */
0); /* no system thread ID */
if (thread->embb_internal_handle == NULL) {
embb_free(thread->embb_internal_arg);
thread->embb_internal_arg = NULL;
return EMBB_ERROR;
}
if (core_set != NULL) { /* Set thread affinity, if a core set is given */
DWORD_PTR core_mask = 0;
DWORD bit_mask = 1;
assert(embb_core_count_available() < 64);
for (unsigned int i = 0; i < embb_core_count_available(); i++) {
if (embb_core_set_contains(core_set, i)) {
core_mask |= bit_mask;
}
bit_mask <<= 1;
}
if (SetThreadAffinityMask(thread->embb_internal_handle, core_mask)
== (DWORD_PTR)NULL) {
return EMBB_ERROR;
}
}
return EMBB_SUCCESS;
}
int embb_thread_join(embb_thread_t* thread, int* result_code) {
if (thread == NULL) {
return EMBB_ERROR;
}
BOOL success;
DWORD result;
result = WaitForSingleObject(thread->embb_internal_handle, INFINITE);
embb_free(thread->embb_internal_arg);
if (result != WAIT_OBJECT_0) {
/* WAIT_OBJECT_0 indicates successful waiting */
return EMBB_ERROR;
}
if (result_code != NULL) { /* != NULL means the client wants a result code */
if (GetExitCodeThread(thread->embb_internal_handle, &result) != 0) {
*result_code = (int)result;
} else {
*result_code = 0; /* Error on obtaining result code */
return EMBB_ERROR;
}
}
success = CloseHandle(thread->embb_internal_handle);
if (success == FALSE) {
return EMBB_ERROR;
}
/*return embb_internal_thread_counter_try_decrement();*/
return EMBB_SUCCESS;
if (thread == NULL) {
return EMBB_ERROR;
}
BOOL success;
DWORD result;
result = WaitForSingleObject(thread->embb_internal_handle, INFINITE);
embb_free(thread->embb_internal_arg);
if (result != WAIT_OBJECT_0) {
/* WAIT_OBJECT_0 indicates successful waiting */
return EMBB_ERROR;
}
if (result_code != NULL) { /* != NULL means the client wants a result code */
if (GetExitCodeThread(thread->embb_internal_handle, &result) != 0) {
*result_code = (int)result;
} else {
*result_code = 0; /* Error on obtaining result code */
return EMBB_ERROR;
}
}
success = CloseHandle(thread->embb_internal_handle);
if (success == FALSE) {
return EMBB_ERROR;
}
/*return embb_internal_thread_counter_try_decrement();*/
return EMBB_SUCCESS;
}
int embb_thread_equal(const embb_thread_t* lhs, const embb_thread_t* rhs) {
if (lhs == NULL || rhs == NULL) {
return 0;
}
embb_thread_id_t idLhs = GetThreadId(lhs->embb_internal_handle);
embb_thread_id_t idRhs = GetThreadId(rhs->embb_internal_handle);
if (idLhs == idRhs) {
return 1;
}
return 0;
if (lhs == NULL || rhs == NULL) {
return 0;
}
embb_thread_id_t idLhs = GetThreadId(lhs->embb_internal_handle);
embb_thread_id_t idRhs = GetThreadId(rhs->embb_internal_handle);
if (idLhs == idRhs) {
return 1;
}
return 0;
}
#endif /* EMBB_PLATFORM_THREADING_WINTHREADS */
......@@ -181,122 +181,247 @@ int embb_thread_equal(const embb_thread_t* lhs, const embb_thread_t* rhs) {
#endif /* EMBB_PLATFORM_HAS_HEADER_SYSINFO */
/**
* Used to wrap client thread start function and argument when calling internal
* thread start function embb_internal_thread_start.
*/
* Used to wrap client thread start function and argument when calling internal
* thread start function embb_internal_thread_start.
*/
typedef struct embb_internal_thread_arg_t {
embb_thread_start_t func;
void* arg;
int result;
embb_thread_start_t func;
void* arg;
int result;
} embb_internal_thread_arg_t;
/**
* Used to offer a consistent thread start function signature. POSIX threads
* have a different signature than C11 threads. This internal start function
* for POSIX threads just calls the client start function with the given
* argument.
*/
* Used to offer a consistent thread start function signature. POSIX threads
* have a different signature than C11 threads. This internal start function
* for POSIX threads just calls the client start function with the given
* argument.
*/
void* embb_internal_thread_start(void* internalArg) {
((embb_internal_thread_arg_t*)internalArg)->result =
((embb_internal_thread_arg_t*)internalArg)->func(
((struct embb_internal_thread_arg_t*)internalArg)->arg);
return NULL;
((embb_internal_thread_arg_t*)internalArg)->result =
((embb_internal_thread_arg_t*)internalArg)->func(
((struct embb_internal_thread_arg_t*)internalArg)->arg);
return NULL;
}
embb_thread_t embb_thread_current() {
embb_thread_t thread;
thread.embb_internal_handle = pthread_self();
thread.embb_internal_arg = NULL;
return thread;
embb_thread_t thread;
thread.embb_internal_handle = pthread_self();
thread.embb_internal_arg = NULL;
return thread;
}
void embb_thread_yield() {
pthread_yield();
pthread_yield();
}
int embb_thread_create(embb_thread_t* thread, const embb_core_set_t* core_set,
embb_thread_start_t func, void* arg) {
if (thread == NULL) {
return EMBB_ERROR;
}
pthread_attr_t attr; /* Used to set thread affinities */
int status = pthread_attr_init(&attr);
if (status != 0) return EMBB_ERROR;
if (core_set != NULL) {
embb_thread_start_t func, void* arg) {
if (thread == NULL) {
return EMBB_ERROR;
}
pthread_attr_t attr; /* Used to set thread affinities */
int status = pthread_attr_init(&attr);
if (status != 0) return EMBB_ERROR;
if (core_set != NULL) {
#if defined(EMBB_PLATFORM_HAS_GLIB_CPU) || \
defined(EMBB_PLATFORM_HAS_HEADER_CPUSET)
assert(embb_core_count_available() < CPU_SETSIZE &&
"Core sets are only supported up to CPU_SETSIZE processors!");
defined(EMBB_PLATFORM_HAS_HEADER_CPUSET)
assert(embb_core_count_available() < CPU_SETSIZE &&
"Core sets are only supported up to CPU_SETSIZE processors!");
#ifdef EMBB_PLATFORM_HAS_GLIB_CPU
cpu_set_t cpuset;
cpu_set_t cpuset;
#else
cpuset_t cpuset;
cpuset_t cpuset;
#endif
CPU_ZERO(&cpuset); /* Disable all processors */
for (unsigned int i = 0; i < embb_core_count_available(); i++) {
if (embb_core_set_contains(core_set, i)) {
CPU_SET(i, &cpuset);
}
}
status = pthread_attr_setaffinity_np(&attr, sizeof(cpuset), &cpuset);
if (status != 0) {
thread->embb_internal_arg = NULL;
thread->embb_internal_handle = 0;
return EMBB_ERROR;
}
CPU_ZERO(&cpuset); /* Disable all processors */
for (unsigned int i = 0; i < embb_core_count_available(); i++) {
if (embb_core_set_contains(core_set, i)) {
CPU_SET(i, &cpuset);
}
}
status = pthread_attr_setaffinity_np(&attr, sizeof(cpuset), &cpuset);
if (status != 0) {
thread->embb_internal_arg = NULL;
thread->embb_internal_handle = 0;
return EMBB_ERROR;
}
#else
embb_log_write("base_c", EMBB_LOG_LEVEL_WARNING, "Could not set thread "
"affinity, since no implementation available!\n");
embb_log_write("base_c", EMBB_LOG_LEVEL_WARNING, "Could not set thread "
"affinity, since no implementation available!\n");
#endif
}
/* Dynamic allocation of thread arguments. Freed on call of join. */
thread->embb_internal_arg = (embb_internal_thread_arg_t*)
embb_alloc(sizeof(embb_internal_thread_arg_t));
if (thread->embb_internal_arg == NULL) {
thread->embb_internal_handle = 0;
pthread_attr_destroy(&attr);
return EMBB_NOMEM;
}
thread->embb_internal_arg->func = func;
thread->embb_internal_arg->arg = arg;
status = pthread_create(
&(thread->embb_internal_handle), /* pthread handle */
&attr, /* additional attributes,
e.g., affinities */
embb_internal_thread_start, /* thread start function */
(void*)(thread->embb_internal_arg)); /* arguments to thread start func. */
if (status != 0) return EMBB_ERROR;
status = pthread_attr_destroy(&attr);
if (status != 0) return EMBB_ERROR;
return EMBB_SUCCESS;
}
/* Dynamic allocation of thread arguments. Freed on call of join. */
thread->embb_internal_arg = (embb_internal_thread_arg_t*)
embb_alloc(sizeof(embb_internal_thread_arg_t));
if (thread->embb_internal_arg == NULL) {
thread->embb_internal_handle = 0;
pthread_attr_destroy(&attr);
return EMBB_NOMEM;
}
thread->embb_internal_arg->func = func;
thread->embb_internal_arg->arg = arg;
status = pthread_create(
&(thread->embb_internal_handle), /* pthread handle */
&attr, /* additional attributes,
e.g., affinities */
embb_internal_thread_start, /* thread start function */
(void*)(thread->embb_internal_arg)); /* arguments to thread start func. */
if (status != 0) return EMBB_ERROR;
status = pthread_attr_destroy(&attr);
if (status != 0) return EMBB_ERROR;
return EMBB_SUCCESS;
}
int embb_thread_join(embb_thread_t* thread, int *result_code) {
if (thread == NULL) {
return EMBB_ERROR;
}
int status = 0;
status = pthread_join(thread->embb_internal_handle, NULL);
if (thread->embb_internal_arg != NULL) {
if (result_code != NULL) {
*result_code = thread->embb_internal_arg->result;
}
embb_free(thread->embb_internal_arg);
}
if (status != 0) {
return EMBB_ERROR;
}
return EMBB_SUCCESS;
if (thread == NULL) {
return EMBB_ERROR;
}
int status = 0;
status = pthread_join(thread->embb_internal_handle, NULL);
if (thread->embb_internal_arg != NULL) {
if (result_code != NULL) {
*result_code = thread->embb_internal_arg->result;
}
embb_free(thread->embb_internal_arg);
}
if (status != 0) {
return EMBB_ERROR;
}
return EMBB_SUCCESS;
}
int embb_thread_equal(const embb_thread_t* lhs, const embb_thread_t* rhs) {
if (lhs == NULL || rhs == NULL) {
return 0;
}
return pthread_equal(lhs->embb_internal_handle, rhs->embb_internal_handle);
if (lhs == NULL || rhs == NULL) {
return 0;
}
return pthread_equal(lhs->embb_internal_handle, rhs->embb_internal_handle);
}
#endif /* EMBB_PLATFORM_THREADING_POSIXTHREADS */
#ifdef EMBB_PLATFORM_THREADING_RTOSTASKS
#include <FreeRTOS/task.h>
/**
* Used to wrap client thread start function and argument when calling internal
* thread start function embb_internal_thread_start.
*/
typedef struct embb_internal_thread_arg_t {
embb_thread_start_t func;
void* arg;
int result;
} embb_internal_thread_arg_t;
/**
* Used to offer a consistent thread start function signature. POSIX threads
* have a different signature than C11 threads. This internal start function
* for POSIX threads just calls the client start function with the given
* argument.
*/
void* embb_internal_thread_start(void* internalArg) {
for(;;) {
((embb_internal_thread_arg_t*)internalArg)->result =
((embb_internal_thread_arg_t*)internalArg)->func(
((struct embb_internal_thread_arg_t*)internalArg)->arg);
vTaskDelete(NULL);
}
}
embb_thread_t embb_thread_current() {
embb_thread_t thread;
thread.embb_internal_handle = xTaskGetCurrentTaskHandle();
thread.embb_internal_arg = NULL;
return thread;
}
void embb_thread_yield() {
taskYIELD();
}
int embb_thread_create(embb_thread_t* thread, const embb_core_set_t* core_set,
embb_thread_start_t func, void *arg) {
int status;
if (thread == NULL) {
return EMBB_ERROR;
}
thread->embb_internal_arg = (embb_internal_thread_arg_t*)
embb_alloc(sizeof(embb_internal_thread_arg_t));
if (thread->embb_internal_arg == NULL) {
thread->embb_internal_handle = NULL;
return EMBB_NOMEM;
}
thread->embb_internal_arg->func = func;
thread->embb_internal_arg->arg = arg;
status = xTaskCreate( embb_internal_thread_start, // entry function
0, // task name (not needed)
128, // stack size
thread->embb_internal_arg, // parameters
1, // priority
thread->embb_internal_handle); // thread handle
if (status != 1) {
embb_free(thread->embb_internal_arg);
thread->embb_internal_arg = NULL;
return EMBB_ERROR;
}
/*if (core_set != NULL) { // scm34681: implement core affinity
DWORD_PTR core_mask = 0;
DWORD bit_mask = 1;
assert(embb_core_count_available() < 64);
for (unsigned int i = 0; i < embb_core_count_available(); i++) {
if (embb_core_set_contains(core_set, i)) {
core_mask |= bit_mask;
}
bit_mask <<= 1;
}
if (SetThreadAffinityMask(thread->embb_internal_handle, core_mask)
== (DWORD_PTR)NULL) {
return EMBB_ERROR;
}
}*/
return EMBB_SUCCESS;
}
int embb_thread_join(embb_thread_t* thread, int *result_code) {
if (thread == NULL) {
return EMBB_ERROR;
}
int status = 0;
while(thread->embb_internal_handle != NULL && status++ < 10000); // scm34681: rework
if (thread->embb_internal_arg != NULL) {
if (result_code != NULL) {
*result_code = thread->embb_internal_arg->result;
}
embb_free(thread->embb_internal_arg);
}
return EMBB_SUCCESS;
}
int embb_thread_equal(const embb_thread_t* lhs, const embb_thread_t* rhs) {
if (lhs == NULL || rhs == NULL) {
return 0;
}
TaskStatus_t lhs_info;
TaskStatus_t rhs_info;
vTaskGetInfo(lhs->embb_internal_handle, &lhs_info, 0, 0);
vTaskGetInfo(lhs->embb_internal_handle, &rhs_info, 0, 0);
return lhs_info.xTaskNumber == rhs_info.xTaskNumber;
}
#endif /* EMBB_PLATFORM_THREADING_RTOSTASKS */
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment