diff --git a/core/mbed-rtos/TESTS/mbed-rtos/threads/main.cpp b/core/mbed-rtos/TESTS/mbed-rtos/threads/main.cpp new file mode 100644 index 0000000000..030beec2da --- /dev/null +++ b/core/mbed-rtos/TESTS/mbed-rtos/threads/main.cpp @@ -0,0 +1,110 @@ +#include "mbed.h" +#include "test_env.h" +#include "unity.h" +#include "utest.h" +#include "rtos.h" + + +using namespace utest::v1; + + +// Tasks with different functions to test on threads +void increment(const void *var) { + (*(int *)var)++; +} + +void increment_with_yield(const void *var) { + Thread::yield(); + (*(int *)var)++; +} + +void increment_with_wait(const void *var) { + Thread::wait(100); + (*(int *)var)++; +} + +void increment_with_child(const void *var) { + Thread child(increment, (void*)var); + child.join(); +} + +void increment_with_murder(const void *var) { + Thread child(increment_with_wait, (void*)var); + // Kill child before it can increment var + child.terminate(); + (*(int *)var)++; +} + + +// Tests that spawn tasks in different configurations +template +void test_single_thread() { + int var = 0; + Thread thread(F, &var); + thread.join(); + TEST_ASSERT_EQUAL(var, 1); +} + +template +void test_parallel_threads() { + int var = 0; + Thread *threads[N]; + + for (int i = 0; i < N; i++) { + threads[i] = new Thread(F, &var); + } + + for (int i = 0; i < N; i++) { + threads[i]->join(); + delete threads[i]; + } + + TEST_ASSERT_EQUAL(var, N); +} + +template +void test_serial_threads() { + int var = 0; + + for (int i = 0; i < N; i++) { + Thread thread(F, &var); + thread.join(); + } + + TEST_ASSERT_EQUAL(var, N); +} + + +status_t test_setup(const size_t number_of_cases) { + GREENTEA_SETUP(40, "default_auto"); + return verbose_test_setup_handler(number_of_cases); +} + +// Test cases +Case cases[] = { + Case("Testing single thread", test_single_thread), + Case("Testing parallel threads", test_parallel_threads<3, increment>), + Case("Testing serial threads", test_serial_threads<10, increment>), + + Case("Testing single thread with yield", test_single_thread), + Case("Testing parallel threads with yield", test_parallel_threads<3, increment_with_yield>), + Case("Testing serial threads with yield", test_serial_threads<10, increment_with_yield>), + + Case("Testing single thread with wait", test_single_thread), + Case("Testing parallel threads with wait", test_parallel_threads<3, increment_with_wait>), + Case("Testing serial threads with wait", test_serial_threads<10, increment_with_wait>), + + Case("Testing single thread with child", test_single_thread), + Case("Testing parallel threads with child", test_parallel_threads<3, increment_with_child>), + Case("Testing serial threads with child", test_serial_threads<10, increment_with_child>), + + Case("Testing single thread with murder", test_single_thread), + Case("Testing parallel threads with murder", test_parallel_threads<3, increment_with_murder>), + Case("Testing serial threads with murder", test_serial_threads<10, increment_with_murder>), +}; + +Specification specification(test_setup, cases); + +int main() { + return !Harness::run(specification); +} diff --git a/core/mbed-rtos/rtos/Thread.cpp b/core/mbed-rtos/rtos/Thread.cpp index ed8270a36a..402295e8d7 100644 --- a/core/mbed-rtos/rtos/Thread.cpp +++ b/core/mbed-rtos/rtos/Thread.cpp @@ -26,34 +26,82 @@ namespace rtos { -Thread::Thread(void (*task)(void const *argument), void *argument, - osPriority priority, uint32_t stack_size, unsigned char *stack_pointer) { +Thread::Thread(osPriority priority, + uint32_t stack_size, unsigned char *stack_pointer): + _tid(NULL), _dynamic_stack(stack_pointer == NULL) { #ifdef __MBED_CMSIS_RTOS_CM - _thread_def.pthread = task; _thread_def.tpriority = priority; _thread_def.stacksize = stack_size; - if (stack_pointer != NULL) { - _thread_def.stack_pointer = (uint32_t*)stack_pointer; - _dynamic_stack = false; - } else { - _thread_def.stack_pointer = new uint32_t[stack_size/sizeof(uint32_t)]; - if (_thread_def.stack_pointer == NULL) + _thread_def.stack_pointer = (uint32_t*)stack_pointer; +#endif +} + +Thread::Thread(void (*task)(void const *argument), void *argument, + osPriority priority, uint32_t stack_size, unsigned char *stack_pointer): + _tid(NULL), _dynamic_stack(stack_pointer == NULL) { +#ifdef __MBED_CMSIS_RTOS_CM + _thread_def.tpriority = priority; + _thread_def.stacksize = stack_size; + _thread_def.stack_pointer = (uint32_t*)stack_pointer; +#endif + switch(start(task, argument)) { + case osErrorResource: + error("OS ran out of threads!\n"); + break; + case osErrorParameter: + error("Thread already running!\n"); + break; + case osErrorNoMemory: error("Error allocating the stack memory\n"); - _dynamic_stack = true; + default: + break; } - +} + +osStatus Thread::start(void (*task)(void const *argument), void *argument) { + if (_tid != NULL) { + return osErrorParameter; + } + +#ifdef __MBED_CMSIS_RTOS_CM + _thread_def.pthread = task; + if (_thread_def.stack_pointer == NULL) { + _thread_def.stack_pointer = new uint32_t[_thread_def.stacksize/sizeof(uint32_t)]; + if (_thread_def.stack_pointer == NULL) + return osErrorNoMemory; + } + //Fill the stack with a magic word for maximum usage checking - for (uint32_t i = 0; i < (stack_size / sizeof(uint32_t)); i++) { + for (uint32_t i = 0; i < (_thread_def.stacksize / sizeof(uint32_t)); i++) { _thread_def.stack_pointer[i] = 0xE25A2EA5; } #endif _tid = osThreadCreate(&_thread_def, argument); + if (_tid == NULL) { + if (_dynamic_stack) delete[] (_thread_def.stack_pointer); + return osErrorResource; + } + return osOK; } osStatus Thread::terminate() { return osThreadTerminate(_tid); } +osStatus Thread::join() { + while (true) { + uint8_t state = get_state(); + if (state == Thread::Inactive || state == osErrorParameter) { + return osOK; + } + + osStatus status = yield(); + if (status != osOK) { + return status; + } + } +} + osStatus Thread::set_priority(osPriority priority) { return osThreadSetPriority(_tid, priority); } diff --git a/core/mbed-rtos/rtos/Thread.h b/core/mbed-rtos/rtos/Thread.h index 3e787983ab..275f6fe5c9 100644 --- a/core/mbed-rtos/rtos/Thread.h +++ b/core/mbed-rtos/rtos/Thread.h @@ -30,6 +30,15 @@ namespace rtos { /** The Thread class allow defining, creating, and controlling thread functions in the system. */ class Thread { public: + /** Allocate a new thread without starting execution + @param priority initial priority of the thread function. (default: osPriorityNormal). + @param stack_size stack size (in bytes) requirements for the thread function. (default: DEFAULT_STACK_SIZE). + @param stack_pointer pointer to the stack area to be used by this thread (default: NULL). + */ + Thread(osPriority priority=osPriorityNormal, + uint32_t stack_size=DEFAULT_STACK_SIZE, + unsigned char *stack_pointer=NULL); + /** Create a new thread, and start it executing the specified function. @param task function to be executed by this thread. @param argument pointer that is passed to the thread function as start argument. (default: NULL). @@ -42,6 +51,19 @@ public: uint32_t stack_size=DEFAULT_STACK_SIZE, unsigned char *stack_pointer=NULL); + /** Starts a thread executing the specified function. + @param task function to be executed by this thread. + @param argument pointer that is passed to the thread function as start argument. (default: NULL). + @return status code that indicates the execution status of the function. + */ + osStatus start(void (*task)(void const *argument), void *argument=NULL); + + /** Wait for thread to terminate + @return status code that indicates the execution status of the function. + @note not callable from interrupt + */ + osStatus join(); + /** Terminate execution of a thread and remove it from Active Threads @return status code that indicates the execution status of the function. */ @@ -113,17 +135,20 @@ public: @param signals wait until all specified signal flags set or 0 for any single signal flag. @param millisec timeout value or 0 in case of no time-out. (default: osWaitForever). @return event flag information or error code. + @note not callable from interrupt */ static osEvent signal_wait(int32_t signals, uint32_t millisec=osWaitForever); /** Wait for a specified time period in millisec: @param millisec time delay value @return status code that indicates the execution status of the function. + @note not callable from interrupt */ static osStatus wait(uint32_t millisec); /** Pass control to next thread that is in state READY. @return status code that indicates the execution status of the function. + @note not callable from interrupt */ static osStatus yield(); diff --git a/tools/test.py b/tools/test.py index 136a55fda6..e79c1dc6ac 100644 --- a/tools/test.py +++ b/tools/test.py @@ -27,7 +27,7 @@ sys.path.insert(0, ROOT) from tools.test_api import test_path_to_name, find_tests, print_tests, build_tests, test_spec_from_test_build from tools.options import get_default_options_parser -from tools.build_api import build_project +from tools.build_api import build_project, build_library from tools.targets import TARGET_MAP from tools.utils import mkdir @@ -115,10 +115,17 @@ if __name__ == '__main__': if not base_source_paths: base_source_paths = ['.'] + target = TARGET_MAP[options.mcu] + lib_build_res = build_library(base_source_paths, options.build_dir, target, options.tool, + options=options.options, + jobs=options.jobs, + clean=options.clean, + archive=False) + # Build all the tests - test_build = build_tests(tests, base_source_paths, options.build_dir, target, options.tool, + test_build = build_tests(tests, [options.build_dir], options.build_dir, target, options.tool, options=options.options, clean=options.clean, jobs=options.jobs) diff --git a/tools/test_api.py b/tools/test_api.py index 44a6b6aed6..812d5667d3 100644 --- a/tools/test_api.py +++ b/tools/test_api.py @@ -2041,13 +2041,13 @@ def build_tests(tests, base_source_paths, build_path, target, toolchain_name, } for test_name, test_path in tests.iteritems(): + test_build_path = os.path.join(build_path, test_path) src_path = base_source_paths + [test_path] - artifact_name = os.path.join(test_path, test_name) - bin_file = build_project(src_path, build_path, target, toolchain_name, + bin_file = build_project(src_path, test_build_path, target, toolchain_name, options=options, jobs=jobs, clean=clean, - name=artifact_name, + name=test_name, report=report, properties=properties, verbose=verbose) diff --git a/tools/toolchains/__init__.py b/tools/toolchains/__init__.py index 71cad7ec10..f550ad5a43 100644 --- a/tools/toolchains/__init__.py +++ b/tools/toolchains/__init__.py @@ -29,6 +29,7 @@ from multiprocessing import Pool, cpu_count from tools.utils import run_cmd, mkdir, rel_path, ToolException, NotSupportedException, split_path from tools.settings import BUILD_OPTIONS, MBED_ORG_USER import tools.hooks as hooks +from hashlib import md5 #Disables multiprocessing if set to higher number than the host machine CPUs @@ -210,6 +211,7 @@ class mbedToolchain: self.has_config = False self.build_all = False + self.build_dir = None self.timestamp = time() self.jobs = 1 @@ -476,19 +478,35 @@ class mbedToolchain: mkdir(obj_dir) return join(obj_dir, name + '.o') + def get_inc_file(self, includes): + include_file = join(self.build_dir, ".includes_%s.txt" % self.inc_md5) + if not exists(include_file): + with open(include_file, "wb") as f: + cmd_list = [] + for c in includes: + if c: + cmd_list.append(('-I%s' % c).replace("\\", "/")) + string = " ".join(cmd_list) + f.write(string) + return include_file + def compile_sources(self, resources, build_path, inc_dirs=None): # Web IDE progress bar for project build files_to_compile = resources.s_sources + resources.c_sources + resources.cpp_sources self.to_be_compiled = len(files_to_compile) self.compiled = 0 - #for i in self.build_params: - # self.debug(i) - # self.debug("%s" % self.build_params[i]) - inc_paths = resources.inc_dirs if inc_dirs is not None: inc_paths.extend(inc_dirs) + # De-duplicate include paths + inc_paths = set(inc_paths) + # Sort include paths for consistency + inc_paths = sorted(set(inc_paths)) + # Unique id of all include paths + self.inc_md5 = md5(' '.join(inc_paths)).hexdigest() + # Where to store response files + self.build_dir = build_path objects = [] queue = [] @@ -496,6 +514,7 @@ class mbedToolchain: # The dependency checking for C/C++ is delegated to the compiler base_path = resources.base_path + # Sort compile queue for consistency files_to_compile.sort() work_dir = getcwd() @@ -641,28 +660,6 @@ class mbedToolchain: else: raise ToolException(_stderr) - def compile(self, cc, source, object, includes): - _, ext = splitext(source) - ext = ext.lower() - - command = cc + ['-D%s' % s for s in self.get_symbols()] + ["-I%s" % i for i in includes] + ["-o", object, source] - - if hasattr(self, "get_dep_opt"): - base, _ = splitext(object) - dep_path = base + '.d' - command.extend(self.get_dep_opt(dep_path)) - - if hasattr(self, "cc_extra"): - command.extend(self.cc_extra(base)) - - return [command] - - def compile_c(self, source, object, includes): - return self.compile(self.cc, source, object, includes) - - def compile_cpp(self, source, object, includes): - return self.compile(self.cppc, source, object, includes) - def build_library(self, objects, dir, name): needed_update = False lib = self.STD_LIB_NAME % name @@ -712,12 +709,12 @@ class mbedToolchain: return bin, needed_update def default_cmd(self, command): + self.debug("Command: %s"% ' '.join(command)) _stdout, _stderr, _rc = run_cmd(command) # Print all warning / erros from stderr to console output for error_line in _stderr.splitlines(): print error_line - self.debug("Command: %s"% ' '.join(command)) self.debug("Return: %s"% _rc) for output_line in _stdout.splitlines(): diff --git a/tools/toolchains/arm.py b/tools/toolchains/arm.py index 1e524a7b03..c4633debf7 100644 --- a/tools/toolchains/arm.py +++ b/tools/toolchains/arm.py @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. """ import re -from os.path import join, dirname, basename +from os.path import join, dirname, splitext, basename, exists from tools.toolchains import mbedToolchain from tools.settings import ARM_BIN, ARM_INC, ARM_LIB, MY_ARM_CLIB, ARM_CPPLIB, GOANNA_PATH @@ -78,11 +78,6 @@ class ARM(mbedToolchain): self.ar = join(ARM_BIN, "armar") self.elf2bin = join(ARM_BIN, "fromelf") - def remove_option(self, option): - for tool in [self.asm, self.cc, self.cppc]: - if option in tool: - tool.remove(option) - def parse_dependencies(self, dep_path): dependencies = [] for line in open(dep_path).readlines(): @@ -112,9 +107,14 @@ class ARM(mbedToolchain): match.group('message') ) - def get_dep_opt(self, dep_path): + def get_dep_option(self, object): + base, _ = splitext(object) + dep_path = base + '.d' return ["--depend", dep_path] + def get_compile_options(self, defines, includes): + return ['-D%s' % d for d in defines] + ['--via', self.get_inc_file(includes)] + @hook_tool def assemble(self, source, object, includes): # Preprocess first, then assemble @@ -123,8 +123,8 @@ class ARM(mbedToolchain): tempfile = join(dir, basename(object) + '.E.s') # Build preprocess assemble command - cmd_pre = self.asm + ['-D%s' % s for s in self.get_symbols() + self.macros] + ["-I%s" % i for i in includes] + ["-E", "-o", tempfile, source] - + cmd_pre = self.asm + self.get_compile_options(self.get_symbols(), includes) + ["-E", "-o", tempfile, source] + # Build main assemble command cmd = self.asm + ["-o", object, tempfile] @@ -135,6 +135,25 @@ class ARM(mbedToolchain): # Return command array, don't execute return [cmd_pre, cmd] + @hook_tool + def compile(self, cc, source, object, includes): + # Build compile command + cmd = cc + self.get_compile_options(self.get_symbols(), includes) + + cmd.extend(self.get_dep_option(object)) + + cmd.extend(["-o", object, source]) + + # Call cmdline hook + cmd = self.hook.get_cmdline_compiler(cmd) + + return [cmd] + + def compile_c(self, source, object, includes): + return self.compile(self.cc, source, object, includes) + + def compile_cpp(self, source, object, includes): + return self.compile(self.cppc, source, object, includes) @hook_tool def link(self, output, objects, libraries, lib_dirs, mem_map): diff --git a/tools/toolchains/gcc.py b/tools/toolchains/gcc.py index 4bc81a7b8e..df4be262ac 100644 --- a/tools/toolchains/gcc.py +++ b/tools/toolchains/gcc.py @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. """ import re -from os.path import join, basename, splitext, dirname +from os.path import join, basename, splitext, dirname, exists from tools.toolchains import mbedToolchain from tools.settings import GCC_ARM_PATH, GCC_CR_PATH @@ -68,7 +68,7 @@ class GCC(mbedToolchain): "-Wno-unused-parameter", "-Wno-missing-field-initializers", "-fmessage-length=0", "-fno-exceptions", "-fno-builtin", "-ffunction-sections", "-fdata-sections", - "-MMD", "-fno-delete-null-pointer-checks", "-fomit-frame-pointer" + "-fno-delete-null-pointer-checks", "-fomit-frame-pointer" ] + self.cpu if "save-asm" in self.options: @@ -161,10 +161,18 @@ class GCC(mbedToolchain): message + match.group('message') ) + def get_dep_option(self, object): + base, _ = splitext(object) + dep_path = base + '.d' + return ["-MD", "-MF", dep_path] + + def get_compile_options(self, defines, includes): + return ['-D%s' % d for d in defines] + ['@%s' % self.get_inc_file(includes)] + @hook_tool def assemble(self, source, object, includes): # Build assemble command - cmd = self.asm + ['-D%s' % s for s in self.get_symbols() + self.macros] + ["-I%s" % i for i in includes] + ["-o", object, source] + cmd = self.asm + self.get_compile_options(self.get_symbols(), includes) + ["-o", object, source] # Call cmdline hook cmd = self.hook.get_cmdline_assembler(cmd) @@ -172,6 +180,26 @@ class GCC(mbedToolchain): # Return command array, don't execute return [cmd] + @hook_tool + def compile(self, cc, source, object, includes): + # Build compile command + cmd = cc + self.get_compile_options(self.get_symbols(), includes) + + cmd.extend(self.get_dep_option(object)) + + cmd.extend(["-o", object, source]) + + # Call cmdline hook + cmd = self.hook.get_cmdline_compiler(cmd) + + return [cmd] + + def compile_c(self, source, object, includes): + return self.compile(self.cc, source, object, includes) + + def compile_cpp(self, source, object, includes): + return self.compile(self.cppc, source, object, includes) + @hook_tool def link(self, output, objects, libraries, lib_dirs, mem_map): libs = [] diff --git a/tools/toolchains/iar.py b/tools/toolchains/iar.py index 3d297a80f3..7d2fb60992 100644 --- a/tools/toolchains/iar.py +++ b/tools/toolchains/iar.py @@ -16,7 +16,7 @@ limitations under the License. """ import re from os import remove -from os.path import join, exists, dirname +from os.path import join, exists, dirname, splitext, exists from tools.toolchains import mbedToolchain from tools.settings import IAR_PATH @@ -72,6 +72,10 @@ class IAR(mbedToolchain): self.ar = join(IAR_BIN, "iarchive") self.elf2bin = join(IAR_BIN, "ielftool") + def parse_dependencies(self, dep_path): + return [path.strip() for path in open(dep_path).readlines() + if (path and not path.isspace())] + def parse_output(self, output): for line in output.splitlines(): match = IAR.DIAGNOSTIC_PATTERN.match(line) @@ -93,20 +97,22 @@ class IAR(mbedToolchain): match.group('message') ) - def get_dep_opt(self, dep_path): + def get_dep_option(self, object): + base, _ = splitext(object) + dep_path = base + '.d' return ["--dependencies", dep_path] - def cc_extra(self, base): + def cc_extra(self, object): + base, _ = splitext(object) return ["-l", base + '.s'] - def parse_dependencies(self, dep_path): - return [path.strip() for path in open(dep_path).readlines() - if (path and not path.isspace())] + def get_compile_options(self, defines, includes): + return ['-D%s' % d for d in defines] + ['-f', self.get_inc_file(includes)] @hook_tool def assemble(self, source, object, includes): # Build assemble command - cmd = self.asm + ['-D%s' % s for s in self.get_symbols() + self.macros] + ["-I%s" % i for i in includes] + ["-o", object, source] + cmd = self.asm + self.get_compile_options(self.get_symbols(), includes) + ["-o", object, source] # Call cmdline hook cmd = self.hook.get_cmdline_assembler(cmd) @@ -114,6 +120,28 @@ class IAR(mbedToolchain): # Return command array, don't execute return [cmd] + @hook_tool + def compile(self, cc, source, object, includes): + # Build compile command + cmd = cc + self.get_compile_options(self.get_symbols(), includes) + + cmd.extend(self.get_dep_option(object)) + + cmd.extend(self.cc_extra(object)) + + cmd.extend(["-o", object, source]) + + # Call cmdline hook + cmd = self.hook.get_cmdline_compiler(cmd) + + return [cmd] + + def compile_c(self, source, object, includes): + return self.compile(self.cc, source, object, includes) + + def compile_cpp(self, source, object, includes): + return self.compile(self.cppc, source, object, includes) + @hook_tool def link(self, output, objects, libraries, lib_dirs, mem_map): # Build linker command diff --git a/tools/utils.py b/tools/utils.py index 21f0e1496b..a03558a019 100644 --- a/tools/utils.py +++ b/tools/utils.py @@ -34,8 +34,12 @@ def cmd(l, check=True, verbose=False, shell=False, cwd=None): def run_cmd(command, wd=None, redirect=False): assert is_cmd_valid(command[0]) - p = Popen(command, stdout=PIPE, stderr=STDOUT if redirect else PIPE, cwd=wd) - _stdout, _stderr = p.communicate() + try: + p = Popen(command, stdout=PIPE, stderr=STDOUT if redirect else PIPE, cwd=wd) + _stdout, _stderr = p.communicate() + except: + print "[OS ERROR] Command: "+(' '.join(command)) + raise return _stdout, _stderr, p.returncode