/*
* %CopyrightBegin%
*
* Copyright Ericsson AB 2010-2011. All Rights Reserved.
*
* The contents of this file are subject to the Erlang Public License,
* Version 1.1, (the "License"); you may not use this file except in
* compliance with the License. You should have received a copy of the
* Erlang Public License along with this software. If not, it can be
* retrieved online at http://www.erlang.org/.
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
* %CopyrightEnd%
*/
/*
* Description: OSE implementation of the ethread library
* Author: Lukas Larsson
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "stdio.h"
#ifdef ETHR_TIME_WITH_SYS_TIME
# include "time.h"
# include "sys/time.h"
#else
# ifdef ETHR_HAVE_SYS_TIME_H
# include "sys/time.h"
# else
# include "time.h"
# endif
#endif
#include "sys/types.h"
#include "unistd.h"
#include "limits.h"
#define ETHR_INLINE_FUNC_NAME_(X) X ## __
#define ETHREAD_IMPL__
#include "ethread.h"
#include "ethr_internal.h"
#include "erl_printf.h"
#include "efs.h"
#include "ose.h"
#include "ose_spi.h"
#include "string.h"
#include "ctype.h"
#include "stdlib.h"
#ifndef ETHR_HAVE_ETHREAD_DEFINES
#error Missing configure defines
#endif
#define ETHR_INVALID_TID_ID -1
#define DEFAULT_PRIO_NAME "ERTS_ETHR_DEFAULT_PRIO"
/* Set the define to 1 to get some logging */
#if 0
#include "ramlog.h"
#define LOG(output) ramlog_printf output
#else
#define LOG(output)
#endif
static ethr_tid main_thr_tid;
static const char* own_tid_key = "ethread_own_tid";
ethr_tsd_key ethr_ts_event_key__;
#define ETHREADWRAPDATASIG 1
/* Init data sent to thr_wrapper() */
typedef struct {
SIGSELECT sig_no;
ethr_ts_event *tse;
ethr_tid *tid;
ethr_sint32_t result;
void *(*thr_func)(void *);
void *arg;
void *prep_func_res;
const char *name;
} ethr_thr_wrap_data__;
union SIGNAL {
SIGSELECT sig_no;
ethr_thr_wrap_data__ data;
};
#define ETHR_GET_OWN_TID__ ((ethr_tid *) get_envp(current_process(),\
own_tid_key))
/*
* --------------------------------------------------------------------------
* Static functions
* --------------------------------------------------------------------------
*/
/* Will retrive the instrinsic name by removing the 'prefix' and the
* suffix from 'name'.
* The 'prefix' is given as an inparameter. If NULL or an empty string no
* prefix will be removed.
* If 'strip_suffix' is 1 suffixes in the form of '_123' will be removed.
* Will return a pointer to a newly allocated buffer containing the intrinsic
* name in uppercase characters.
* The caller must remember to free this buffer when no lnger needed.
*/
static char *
ethr_intrinsic_name(const char *name, const char *prefix, int strip_suffix)
{
const char *start = name;
const char *end = name + strlen(name);
char *intrinsic_name = NULL;
int i;
if (name == NULL) {
LOG(("ERTS - ethr_intrinsic_namNo input name.\n"));
return NULL;
}
/* take care of the prefix */
if ((prefix != NULL) && (*prefix != '\0')) {
const char *found = strstr(name, prefix);
if (found == name) {
/* found the prefix at the beginning */
start += strlen(prefix);
}
}
/* take care of the suffix */
if (strip_suffix) {
const char *suffix_start = strrchr(start, '_');
if (suffix_start != NULL) {
const char *ch;
int only_numbers = 1;
for (ch = suffix_start + 1; *ch != '\0'; ch++) {
if (strchr("0123456789", *ch) == NULL) {
only_numbers = 0;
break;
}
}
if (only_numbers) {
end = suffix_start;
}
}
}
intrinsic_name = malloc(end - start + 1);
for (i = 0; (start + i) < end; i++) {
intrinsic_name[i] = toupper(start[i]);
}
intrinsic_name[i] = '\0';
return intrinsic_name;
}
static char *
ethr_get_amended_env(const char *name, const char *prefix, const char *suffix)
{
unsigned len;
char *env_name = NULL;
char *env_value = NULL;
if (name == NULL) {
return NULL;
}
len = strlen(name);
if (prefix != NULL) {
len += strlen(prefix);
}
if (suffix != NULL) {
len += strlen(suffix);
}
env_name = malloc(len + 1);
sprintf(env_name, "%s%s%s", (prefix != NULL) ? prefix : "",
name,
(suffix != NULL) ? suffix : "");
env_value = get_env(get_bid(current_process()), env_name);
if (env_value == NULL) {
LOG(("ERTS - ethr_get_amended_env(): %s environment variable not present\n", env_name));
} else {
LOG(("ERTS - ethr_get_amended_env(): Found %s environment variable: %s.\n", env_name, env_value));
}
free(env_name);
return env_value;
}
/* Reads the environment variable derived from 'name' and interprets it as as an
* OSE priority. If successfull it will update 'out_prio'.
* Returns: 0 if successfull
* -1 orherwise.
*/
static int
ethr_get_prio(const char *name, OSPRIORITY *out_prio)
{
int rc = -1;
char *intrinsic_name = NULL;
char *prio_env = NULL;
long prio;
char *endptr = NULL;
LOG(("ERTS - ethr_get_prio(): name: %s.\n", name));
intrinsic_name = ethr_intrinsic_name(name, NULL, 1);
LOG(("ERTS - ethr_get_prio(): Intrinsic name: %s.\n", intrinsic_name));
prio_env = ethr_get_amended_env(intrinsic_name, "ERTS_", "_PRIO");
if (prio_env == NULL) {
goto fini;
}
prio = efs_str_to_long(prio_env, (const char **)&endptr);
if (endptr != NULL) {
LOG(("ERTS - ethr_get_prio(): Environment varible for '%s' includes "
"non-numerical characters: '%s'.\n", intrinsic_name, prio_env));
goto fini;
}
if ((prio < 0) || (prio > 32)) {
LOG(("ERTS - ethr_get_prio(): prio for '%s' (%d) is out of bounds (0-32).\n",
intrinsic_name, prio));
goto fini;
}
/* Success */
*out_prio = (OSPRIORITY)prio;
rc = 0;
fini:
if (intrinsic_name != NULL) {
free(intrinsic_name);
}
if (prio_env != NULL) {
free_buf((union SIGNAL **) &prio_env);
}
return rc;
}
static PROCESS blockId(void) {
static PROCESS bid = (PROCESS)0;
/* For now we only use the same block. */
/* if (bid == 0) {
bid = create_block("Erlang-VM", 0, 0, 0, 0);
}
return bid; */
return 0;
}
static void thr_exit_cleanup(ethr_tid *tid, void *res)
{
ETHR_ASSERT(tid == ETHR_GET_OWN_TID__);
tid->res = res;
ethr_run_exit_handlers__();
ethr_ts_event_destructor__((void *) ethr_get_tse__());
}
//static OS_PROCESS(thr_wrapper);
static OS_PROCESS(thr_wrapper)
{
ethr_tid my_tid;
ethr_sint32_t result;
void *res;
void *(*thr_func)(void *);
void *arg;
ethr_ts_event *tsep = NULL;
#ifdef DEBUG
{
PROCESS pid = current_process();
const char *execMode;
PROCESS bid = get_bid(pid);
/* In the call below, 16 is a secret number provided by frbr that makes
* the function return current domain. */
OSADDRESS domain = get_pid_info(current_process(), 16);
#ifdef HAVE_OSE_SPI_H
execMode = get_pid_info(pid, OSE_PI_SUPERVISOR)
? "Supervisor"
: "User";
#else
execMode = "unknown";
#endif
fprintf(stderr,"[0x%x] New process. Bid:0x%x, domain:%d, exec mode:%s\n",
current_process(), bid, domain, execMode);
}
#endif
{
SIGSELECT sigsel[] = {1,ETHREADWRAPDATASIG};
union SIGNAL *init_msg = receive(sigsel);
thr_func = init_msg->data.thr_func;
arg = init_msg->data.arg;
result = (ethr_sint32_t) ethr_make_ts_event__(&tsep);
if (result == 0) {
tsep->iflgs |= ETHR_TS_EV_ETHREAD;
my_tid = *init_msg->data.tid;
set_envp(current_process(), own_tid_key, (OSADDRESS)&my_tid);
if (ethr_thr_child_func__)
ethr_thr_child_func__(init_msg->data.prep_func_res);
}
init_msg->data.result = result;
send(&init_msg,sender(&init_msg));
}
/* pthread mutex api says we have to do this */
signal_fsem(current_process());
ETHR_ASSERT(get_fsem(current_process()) == 0);
res = result == 0 ? (*thr_func)(arg) : NULL;
ethr_thr_exit(&res);
}
/* internal exports */
int ethr_set_tse__(ethr_ts_event *tsep)
{
return ethr_tsd_set(ethr_ts_event_key__,(void *) tsep);
}
ethr_ts_event *ethr_get_tse__(void)
{
return (ethr_ts_event *) ethr_tsd_get(ethr_ts_event_key__);
}
#if defined(ETHR_PPC_RUNTIME_CONF__)
static int
ppc_init__(void)
{
int pid;
ethr_runtime__.conf.have_lwsync = 0;
return 0;
}
#endif
#if defined(ETHR_X86_RUNTIME_CONF__)
void
ethr_x86_cpuid__(int *eax, int *ebx, int *ecx, int *edx)
{
#if ETHR_SIZEOF_PTR == 4
int have_cpuid;
/*
* If it is possible to toggle eflags bit 21,
* we have the cpuid instruction.
*/
__asm__ ("pushf\n\t"
"popl %%eax\n\t"
"movl %%eax, %%ecx\n\t"
"xorl $0x200000, %%eax\n\t"
"pushl %%eax\n\t"
"popf\n\t"
"pushf\n\t"
"popl %%eax\n\t"
"movl $0x0, %0\n\t"
"xorl %%ecx, %%eax\n\t"
"jz no_cpuid\n\t"
"movl $0x1, %0\n\t"
"no_cpuid:\n\t"
: "=r"(have_cpuid)
:
: "%eax", "%ecx", "cc");
if (!have_cpuid) {
*eax = *ebx = *ecx = *edx = 0;
return;
}
#endif
#if ETHR_SIZEOF_PTR == 4 && defined(__PIC__) && __PIC__
/*
* When position independet code is used in 32-bit mode, the B register
* is used for storage of global offset table address, and we may not
* use it as input or output in an asm. We need to save and restore the
* B register explicitly (for some reason gcc doesn't provide this
* service to us).
*/
__asm__ ("pushl %%ebx\n\t"
"cpuid\n\t"
"movl %%ebx, %1\n\t"
"popl %%ebx\n\t"
: "=a"(*eax), "=r"(*ebx), "=c"(*ecx), "=d"(*edx)
: "0"(*eax)
: "cc");
#else
__asm__ ("cpuid\n\t"
: "=a"(*eax), "=b"(*ebx), "=c"(*ecx), "=d"(*edx)
: "0"(*eax)
: "cc");
#endif
}
#endif /* ETHR_X86_RUNTIME_CONF__ */
/*
* --------------------------------------------------------------------------
* Exported functions
* --------------------------------------------------------------------------
*/
int
ethr_init(ethr_init_data *id)
{
int res;
if (!ethr_not_inited__)
return EINVAL;
#if defined(ETHR_PPC_RUNTIME_CONF__)
res = ppc_init__();
if (res != 0)
goto error;
#endif
res = ethr_init_common__(id);
if (res != 0)
goto error;
main_thr_tid.id = current_process();
main_thr_tid.tsd_key_index = 0;
set_envp(current_process(),own_tid_key,(OSADDRESS)&main_thr_tid);
signal_fsem(current_process());
ETHR_ASSERT(&main_thr_tid == ETHR_GET_OWN_TID__);
ethr_not_inited__ = 0;
ethr_tsd_key_create(ðr_ts_event_key__,"ethread_tse");
return 0;
error:
ethr_not_inited__ = 1;
return res;
}
int
ethr_late_init(ethr_late_init_data *id)
{
int res = ethr_late_init_common__(id);
if (res != 0)
return res;
ethr_not_completely_inited__ = 0;
return res;
}
int
ethr_thr_create(ethr_tid *tid, void * (*func)(void *), void *arg,
ethr_thr_opts *opts)
{
int res;
int use_stack_size = (opts && opts->suggested_stack_size >= 0
? opts->suggested_stack_size
: 0x200 /* Use system default */);
OSPRIORITY use_prio;
char *use_name;
char default_thr_name[20];
static int no_of_thr = 0;
cpuid_t use_core;
union SIGNAL *init_msg;
SIGSELECT sigsel[] = {1,ETHREADWRAPDATASIG};
void *prep_func_res;
if (opts != NULL) {
LOG(("ERTS - ethr_thr_create(): opts supplied: name: %s, coreNo: %u.\n",
opts->name, opts->coreNo));
use_name = opts->name;
use_core = opts->coreNo;
if (0 != ethr_get_prio(use_name, &use_prio)) {
if (0 != ethr_get_prio("DEFAULT", &use_prio)) {
use_prio = get_pri(current_process());
LOG(("ERTS - ethr_thr_create(): Using current process' prio: %d.\n", use_prio));
} else {
LOG(("ERTS - ethr_thr_create(): Using default prio: %d.\n", use_prio));
}
} else {
LOG(("ERTS - ethr_thr_create(): Using configured prio: %d.\n", use_prio));
}
} else {
LOG(("ERTS - ethr_thr_create(): opts not supplied. Using defaults.\n"));
no_of_thr++;
sprintf(default_thr_name, "ethread_%d", no_of_thr);
use_name = default_thr_name;
use_core = ose_cpu_id();
if (0 != ethr_get_prio("DEFAULT", &use_prio)) {
use_prio = get_pri(current_process());
LOG(("ERTS - ethr_thr_create(): Using current process' prio: %d.\n", use_prio));
}
}
#ifdef ETHR_MODIFIED_DEFAULT_STACK_SIZE
if (use_stack_size < 0)
use_stack_size = ETHR_MODIFIED_DEFAULT_STACK_SIZE;
#endif
#if ETHR_XCHK
if (ethr_not_completely_inited__) {
ETHR_ASSERT(0);
return EACCES;
}
if (!tid || !func) {
ETHR_ASSERT(0);
return EINVAL;
}
#endif
if (use_stack_size >= 0) {
size_t suggested_stack_size = (size_t) use_stack_size;
size_t stack_size;
#ifdef ETHR_DEBUG
suggested_stack_size /= 2; /* Make sure we got margin */
#endif
#ifdef ETHR_STACK_GUARD_SIZE
/* The guard is at least on some platforms included in the stack size
passed when creating threads */
suggested_stack_size += ETHR_B2KW(ETHR_STACK_GUARD_SIZE);
#endif
if (suggested_stack_size < ethr_min_stack_size__)
stack_size = ETHR_KW2B(ethr_min_stack_size__);
else if (suggested_stack_size > ethr_max_stack_size__)
stack_size = ETHR_KW2B(ethr_max_stack_size__);
else
stack_size = ETHR_PAGE_ALIGN(ETHR_KW2B(suggested_stack_size));
use_stack_size = stack_size;
}
init_msg = alloc(sizeof(ethr_thr_wrap_data__), ETHREADWRAPDATASIG);
/* Call prepare func if it exist */
if (ethr_thr_prepare_func__)
init_msg->data.prep_func_res = ethr_thr_prepare_func__();
else
init_msg->data.prep_func_res = NULL;
LOG(("ERTS - ethr_thr_create(): Process [0x%x] is creating '%s', coreNo = %u, prio:%u\n",
current_process(), use_name, use_core, use_prio));
tid->id = create_process(OS_PRI_PROC, use_name, thr_wrapper,
use_stack_size, use_prio, 0,
get_bid(current_process()), NULL, 0, 0);
if (ose_bind_process(tid->id, use_core)) {
LOG(("ERTS - ethr_thr_create(): Bound pid 0x%x (%s) to core no %u.\n",
tid->id, use_name, use_core));
} else {
LOG(("ERTS - ethr_thr_create(): Failed binding pid 0x%x (%s) to core no %u.\n",
tid->id, use_name, use_core));
}
/*FIXME!!! Normally this shouldn't be used in shared mode. Still there is
* a problem with stdin fd in fd_ processes which should be further
* investigated */
efs_clone(tid->id);
tid->tsd_key_index = 0;
tid->res = NULL;
init_msg->data.tse = ethr_get_ts_event();
init_msg->data.thr_func = func;
init_msg->data.arg = arg;
init_msg->data.tid = tid;
init_msg->data.name = opts->name;
send(&init_msg, tid->id);
start(tid->id);
init_msg = receive(sigsel);
res = init_msg->data.result;
prep_func_res = init_msg->data.prep_func_res;
free_buf(&init_msg);
/* Cleanup... */
if (ethr_thr_parent_func__)
ethr_thr_parent_func__(prep_func_res);
LOG(("ERTS - ethr_thr_create(): Exiting.\n"));
return res;
}
int
ethr_thr_join(ethr_tid tid, void **res)
{
SIGSELECT sigsel[] = {1,OS_ATTACH_SIG};
#if ETHR_XCHK
if (ethr_not_inited__) {
ETHR_ASSERT(0);
return EACCES;
}
#endif
if (tid.id == ETHR_INVALID_TID_ID)
return EINVAL;
attach(NULL,tid.id);
receive(sigsel);
if (res)
*res = tid.res;
return 0;
}
int
ethr_thr_detach(ethr_tid tid)
{
#if ETHR_XCHK
if (ethr_not_inited__) {
ETHR_ASSERT(0);
return EACCES;
}
#endif
return 0;
}
void
ethr_thr_exit(void *res)
{
ethr_tid *tid;
#if ETHR_XCHK
if (ethr_not_inited__) {
ETHR_ASSERT(0);
return;
}
#endif
tid = ETHR_GET_OWN_TID__;
if (!tid) {
ETHR_ASSERT(0);
kill_proc(current_process());
}
thr_exit_cleanup(tid, res);
/* Harakiri possible? */
kill_proc(current_process());
}
ethr_tid
ethr_self(void)
{
ethr_tid *tid;
#if ETHR_XCHK
if (ethr_not_inited__) {
ethr_tid dummy_tid = {ETHR_INVALID_TID_ID, 0, NULL};
ETHR_ASSERT(0);
return dummy_tid;
}
#endif
tid = ETHR_GET_OWN_TID__;
if (!tid) {
ethr_tid dummy_tid = {ETHR_INVALID_TID_ID, 0, NULL};
return dummy_tid;
}
return *tid;
}
int
ethr_equal_tids(ethr_tid tid1, ethr_tid tid2)
{
return tid1.id == tid2.id && tid1.id != ETHR_INVALID_TID_ID;
}
/*
* Thread specific events
*/
ethr_ts_event *
ethr_get_ts_event(void)
{
return ethr_get_ts_event__();
}
void
ethr_leave_ts_event(ethr_ts_event *tsep)
{
ethr_leave_ts_event__(tsep);
}
/*
* Thread specific data
*/
int
ethr_tsd_key_create(ethr_tsd_key *keyp, char *keyname)
{
#if ETHR_XCHK
if (ethr_not_inited__) {
ETHR_ASSERT(0);
return EACCES;
}
if (!keyp) {
ETHR_ASSERT(0);
return EINVAL;
}
#endif
ose_create_ppdata(keyname,keyp);
return 0;
}
int
ethr_tsd_key_delete(ethr_tsd_key key)
{
#if ETHR_XCHK
if (ethr_not_inited__) {
ETHR_ASSERT(0);
return EACCES;
}
#endif
/* Not possible to delete ppdata */
return 0;
}
int
ethr_tsd_set(ethr_tsd_key key, void *value)
{
void **ppdp;
#if ETHR_XCHK
if (ethr_not_inited__) {
ETHR_ASSERT(0);
return EACCES;
}
#endif
ppdp = (void **)ose_get_ppdata(key);
*ppdp = value;
return 0;
}
void *
ethr_tsd_get(ethr_tsd_key key)
{
#if ETHR_XCHK
if (ethr_not_inited__) {
ETHR_ASSERT(0);
return NULL;
}
#endif
return *(void**)ose_get_ppdata(key);
}
/*
* Signal functions
*/
#if ETHR_HAVE_ETHR_SIG_FUNCS
int ethr_sigmask(int how, const sigset_t *set, sigset_t *oset)
{
#if ETHR_XCHK
if (ethr_not_inited__) {
ETHR_ASSERT(0);
return EACCES;
}
if (!set && !oset) {
ETHR_ASSERT(0);
return EINVAL;
}
#endif
return pthread_sigmask(how, set, oset);
}
int ethr_sigwait(const sigset_t *set, int *sig)
{
#if ETHR_XCHK
if (ethr_not_inited__) {
ETHR_ASSERT(0);
return EACCES;
}
if (!set || !sig) {
ETHR_ASSERT(0);
return EINVAL;
}
#endif
if (sigwait(set, sig) < 0)
return errno;
return 0;
}
#endif /* #if ETHR_HAVE_ETHR_SIG_FUNCS */
ETHR_IMPL_NORETURN__
ethr_abort__(void)
{
abort();
}