Friday, January 31, 2014

Climbing up Electronics

 
Pull up and Pull down:

Assume the following function.

(Example 1)

int io_var = 0;
void sw_routine()
{
    io_var = io_read(0xDEADBEEF);
}

(Example 2)

int io_var = 1;
void sw_routine()
{
    io_var = io_read(0xDEADBEEF);
}
 

The exact value of io_var is read from the address 0xDEADBEEF. But, before assigning value to it, it can be initialized to 0 or 1 rather than leaving it with garbase values.

The same thing is done by Pull-up or Pull-down circuit in electronics to the pin which is left open. Initializing the pin with logic HIGH (+5v, etc.) is called pull up. Initializing with logic LOW (0v) is called pull-down. This is just initialization when the pin is not connected to any active module. When the active module is driving the pin, the driven value is reflected regardless of the initial value.

This will apply for input as well as output pin. If you want the pin to reflect what exactly you write or to read exactly the pin status, then Pull-up/Pull-down can be safely selected.

Open-collector/Open-drain/Wired-OR/Wired-AND

These all are about safely sharing a single line between multiple devices with simple logic.

Wired-OR: (A+B+C+D)'

When all devices A,B,C,D are in logic LOW, the output will be HIGH. When any one of device becomes logic HIGH, then the output will become LOW. Don't you think that this is enough to have a single interrupt line to just signal CPU of interrupt from devices. Only thing is the interrupt signal will be active LOW. This is the reason why INT' and RST' signals are active LOW.

Wired-AND is just complement of Wired-OR: (A.B.C.D)'

If any one of device input is logic LOW, the output will be logic HIGH.

Open-collector/Open-drain:

In case of NPN transister, it is called Open-collector. In case of MOSFET, it is called Open-drain. Open-collector/Open-drain means "Emitter is connected to the ground. Base is driven as Input. Collector is open as output". It acts just as a Switch to pull the output line to LOW, when the base is driven by input. If you connect a pull-up at output and tie output of all devices, when all devices are idle, the output is at logic HIGH. When any device drive the switch, it can safely pull only the shared output line to LOW. The meaning of sharing is when one connected device driver LOW and other drives HIGH, there should not be any short circuite. This is guaranteed with open-collector/open-drain.

One more advantage is that the line voltage is decided by the external pull up circuite. So, it can be used to interface two components of different voltage levels.

I2C bus has two lins SDA and SCL, both are open-drain with exactly two pull-up resistors. I2C protocol is built-over the open-drain. Since I2C master initially starts the transaction by pulling the clock and data lines, all other devices just listen/senses the lines and decodes what the I2C master tries to say. After sensing the address given by the master, the corresponding slave transfers the data. But, master as well as devices just creates the data and clock pulses by pulling down the output line to LOW. For detailed, I2C bus details, please refer the following: The output line can be read just by reading it as input pin.

So, if you want to connect the pin to a shared bus or to interface with different voltage level, configure it as open-drain/open-collector.
 
Interfacing different voltate levels:

 
Passing water from 3.3 water level canel to 5 water level will not damage. But, it is not enough. What effects will it create? A few exceptions are on some CMOS devices and when interfacing 3.3V to a 5V device with a Schmitt-Trigger input. Why?

But, passing water from 5 water level to 3 water level will damage the field.
 

pthread - Local data

pthread_once():

This is like constructor function.

int pthread_once( pthread_once_t * once_control, void (*init_routine)(void));

  • pthread_once_t:control variable. Needs to be initialized with PTHREAD_ONCE_INIT.
  • init_routine:Initialization function which will be called only once. This function even called from multiple threads, init_routine will be executed only once. And, the functions whoever calling this function will block untill the execution is completed.

    And only the global data can be accessed in the init_routine function.

    a. Stack
    b. Thread ID
    c. Signal mask
    d. Alternative signal stack
    e. errno variable
    f. scheduling policy and priority.

    Let's see functions which will make sure completely private area for the thread.
    • int pthread_key_create( pthread_key_t * key, void ( *destr_function )( void * ));
    Initialized the thread specific data key.
    key - thread specific data key. By this key, the thread specific data is accessed. It is necessary to execute pthread_key_create only once. Usually, pthread_once is used.
    destr_function - Destructor. if not used, assign NULL.
    • int pthread_key_delete( pthread_key_t key );
                Deletes thread specific key.
    • int pthread_setspecific( pthread_key_t key, const void * pointer );
                pointer value is associated with thread specific data key. Thread sepcific data is assigned to pointer.
    • void * pthread_getspecific( pthread_key_t key );
                Acquires the value managed by thread specific data key. If it is NULL, it needs to be set by pthread_setspcific();

    Though the variable is declared as global, it can be assigned data from multiple data and the data will be distinct for each thread. There are two ways:

    1)  declare using __thread
    __thread tsd_t * value = 0;
    2) The data needs to be declared as pthread_key_t and use pthread_key_create(& tsd_key, 0)
        pthread_key_t   tsd_key;
    Set and access using pthread_setspecific and pthread_getspecific.

    Destructor:

    Destructor will be executed when thread ends and it will have the data key as parameter. Allocated memory can be released. But, key itself can be deleted when the final destructor of the thread is called.

    After desctructor, pthread_join is called.

    TLS is simple and easy, just __thread prefix in the declaration.

    These can be used when porting multiprocessed application to multithreaded to declare static and extern.
     
  • pthread - conditional variable

    Conditional variables are like flags.
     
    When you want to wake up some threads when some condition is met, conditional variables are used. It is used with mutex always, since the conditional variable itself is protected by the mutex.
     
    • int pthread_cond_init( pthread_cond_t * cond, pthread_condattr_t * cond_attr );
            cond is initialized with cond_attr. When NULL, default is set. It can be done as follows too.
                pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
    • int pthread_cond_destroy ( pthread_cond_t * cond );
                It will destroy the variable.
    • int pthread_cond_wait( pthread_cond_t * cond, pthread_mutex_t * mutex);
                It suspends the thread. It unlocks the mutex. It has to be waken-up pthread_cond_signal or pthread_cond_broadcast is necessary. When it wake-up, again it locks the mutex.
    • int pthread_cond_timedwait( pthread_cond_t * cond, pthread_mutex_t *mutex, const struct timespec * abstime );
                It is same as pthread_cond_wait in addition to timeout functionality.

    • int pthread_cond_signal( pthread_cond_t * cond );
            It wakes-up one of the threads waiting. If sheduling policy is not set, it is not decided which will be woke-up.
    • int pthread_cond_broadcast( pthread_cond_t * cond );
            All waiting tasks will be woken-up.


    One Object or Class which needs to be protected by single lock. But, both producer and consumer threads will try to access the object. If both do not have any condition for their execution, then there are no issues. If they are depend on each other, like the consumer needs to object to have the count not to be zero, the consumer has some condition on its execution. Similarly, the producer will have the condition the queue needs to have some slots. In such a case, if any one of them holds the lock and waits for others to satisfy the condition, then the others cannot do simply because the lock is hold by the waiting one. So, release the key just before waiting.

    Assume accessing a toilet, but there is no toilet paper. Then, leave the key and wait on the bench. Let the attender use the key to fill the paper. If use spinlock, you cannot wait, take the key, check the paper, leave the key continuously till it is getting filled. Attender also will take the key, fill the paper, leave the key till it is getting emptied. If there is paper, then no issues, just keep on hold the key, finish your job and leave the key, come out.

    Same mutex can be used on multiple condition variables, but not the vice-versa. For a single variable "size of queue" protected by a single mutex, there can be two conditions that "wait for empty space in the queue" and "wait for any element in the queue". So, two different conditional variables associated with same mutex. But, there is no sense that two mutexes are used for one condition variable.

    If more than one predicate condtions are associated with single condition variable, then "broadcast" will wake-up all threads. The threads for which the condition became true will continue executions. The wrong ones will go to sleep again. However, it represents a weaker condition than the conditions being checked by individual threads.
      
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    pthread_cond_t  cond  = PTHREAD_COND_INITIALIZER;

    int main( int argc, char ** argv ) {
            :
        pthread_cond_broadcast( &cond );
        return 0;
    }


    void * temp_func( void * arg ) {
        pthread_mutex_lock( &mutex );
        while (!p) { // While the condition/predicate/assertion that we are waiting for is not true...
             pthread_cond_wait( &cond, &mutex ); // Wait on this monitor's lock and condition variable.
        }

         // Do critical processing
        pthread_mutex_unlock( &mutex ) ;
        return 0;
    }

     

    pthread - Synchronization

    Three types of mutual exclustion ways in pthreads.

    1) mutex

    Accepted one. Used widely. Efficiency is Not bad. Suspend-Waiting mechanism.

    2) spin

    Same as mutex. But, wait by busy loop. Efficiency will be bad by number of threads.

    3) read/write locks

    Mostly used when accessing cache information. High class synchronization process. New standard, worst efficient.

    Mutex:

    Easy to use.
    • int pthread_mutex_init( pthread_mutex_t * mutex, const pthread_mutex_attr_t * mutexattr );
    • int pthread_mutex_destroy( pthread_mutex_t * mutex );
    • int pthread_mutex_lock( pthread_mutex_t * mutex );
    • int pthread_mutex_unlock( pthread_mutex_t * mutex );
    • int pthread_mutex_trylock( pthread_mutex_t * mutex );
    Initialize the variable mutex and use pthread_mutex_lock() before critical section and pthread_mutex_unlock() after critical section. If you use pthread_mutex_trylock(), it wil try to lock. If locked by another thread, it will return EBUSY without suspending the task. If the mutex usage is over, it can be destroyed using pthread_mutex_destroy().

    pthread_mutexattr_t is used to set the type of mutex and the priority inversion protocol.

    The following two methods can be used for initialization and have same meaning.
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    pthread_mutex_init( &mutex, 0 );
    
    SPIN:

    CPU usage will be 100%.
    • int pthread_spin_init( pthread_spinlock_t * __lock, int __pshared )
    • int pthread_spin_destroy( pthread_spinlock_t * __lock )
    • int pthread_spin_lock( pthread_spinlock_t * __lock )
    • int pthread_spin_unlock( pthread_spinlock_t * __lock )
    • int pthread_spin_trylock( pthread_spinlock_t * __lock )
    __pshared is used to specify whether this spinlock can be used by other processes or only the threads of this process only.
    PTHREAD_PROCESS_PRIVATE - Only for this process. Operation is not guaranteed when used by other processes.
    PTHREAD_PROCESS_SHARED - Can be used by other processes

    When using spin lock, the switching can be fast to some extent. But, when the number of threads are more than the number of CPUs, the efficiency will degrade sharply.

    Spinlock can be implemented using Mutex as in wrap_spin.c

    http://en.wikipedia.org/wiki/Inter-process_communication


    read-write lock:

    When a thread is going to change a content, then it should lock. But, when few threads just want to read the content, there is not need for lock.
    mutex/spin do not have such kind of flexibility, but suspends all threads and causes bottleneck.

    For example, when access to the cached information etc will only read the contents, simultaneous access also okay. read/write lock is to implement this.

    • int pthread_rwlock_init( pthread_rwlock_t * __rwlock, __const pthread_rwlockattr_t * __attr )
    • int pthread_rwlock_destroy( pthread_rwlock_t * __rwlock )
    • int pthread_rwlock_rdlock( pthread_rwlock_t * __rwlock )
    • int pthread_rwlock_tryrdlock( pthread_rwlock_t * __rwlock )
    • int pthread_rwlock_timedrdlock( pthread_rwlock_t * __rwlock, __const struct timespec * __abstime )
    • int pthread_rwlock_wrlock( pthread_rwlock_t * __rwlock )
    • int pthread_rwlock_trywrlock( pthread_rwlock_t * __rwlock )
    • int pthread_rwlock_timedwrlock( pthread_rwlock_t * __rwlock, __const struct timespec * __abstime )
    • int pthread_rwlock_unlock( pthread_rwlock_t * __rwlock )

    pthread_rwlockattr_t __attr is similar to spin and decides whether can be used across processes.

    read/write lock can be implemetned using mutex is shown in wrap_rwlock.c.


  • read_lock
  •     if in the mid of write or there is waiting task for write, then suspends.
  • read_unlock     If not in the middle of read and there is waiting task, the signals(wakes-up) write waiting task.
  • write_lock     if in the mid of read or in the mid of write, then suspends the task.
  • write_unlock     if there is write waiting task then to the write-waiting task, if there is no write-waiting but read-waiting, the to the read waiting task, signal (wake-up) is issued.

  •  But, read-write locks are costly since the condition needs to be checked everytime. So, there is extra processing compared to just locking mutex. So, examine from performance point of view before using it.

     This post has reference from http://codezine.jp/article/detail/1893 (pthreadについて(同期)).

    pthreads - Outline

    When multiple processes, the child process should notify the end to the parent process.
    The parent process receives SIGCHLD as follows. Parent process can ignore it as follows:
     
        sa.sa_handler = SIG_IGN;
        sigemptyset( &sa.sa_mask );
        sigaction( SIGCHLD, &sa, 0 );
    When process is forked all the copy of the program including the stack and file descriptors are made.
    So, the child process can use the (stack information)local variables and file descriptors as it is. But,
    in case of threads, though the memory region is shared, the stack is separated the child process cannot
    refer the stack information of parent process. That is why the local information is passed by structures.
     
    The pthread_create takes the 4th argument and launches the function in 3rd argument. When the function
    calls pthread_detach() it declares that it will not notify the parent of its end. So, parent process will not maintain
    any of the child process's end records.
     
    Difference between multiprocess and multithreading:
     
    1. memory space is independent
     
    Since the memory is independent, there is no need concern about mutual access of static and global variables.
    But, in other side, if there is communication needed between two processes the proper IPC is needed between these.
     
    2. Number of processes created affects
     
    Zombie -> Child exited. But, parent did not read the status using wait(). So, the entry remains in the process table. Do not hold any memory resource. But, only the PID entry. So, system will run out of PID if zombie remains. Can be checked using 'ps' command and process status will be 'z'. Usually, when child exits, SIGCHLD signal is sent to the parent. Parent will call 'wait' in the signal handler. So, manually, SIGCHLD can be sent to the parent using kill. Or, parent can be killed, so that init process will become new parent  and it will read the status and clean up.
     
    Orphan -> Child still running, but parent died. init process becomes the new parent process.
     
    There are maximum number of processes in the system.
     
    Threads
     
    1. Thread specific information
     
    Only the following information is independed for each thread.
    a. Stack
    b. Thread ID
    c. Signal mask
    d. Substitue signal mask
    e. errno variable
    f. scheduling policy and priority.
     
        All others are shared. This advantage of not much IPC mechanisms are needed. But, same time, if synchronization is not performed, it will cause deadlock, etc.
     
    2. No zombies
    Thread is ultimately the resource maintained by process. So, it will be released when process dies.
     
    3. Thread safe
     It is important to make sure that the functions are thread safe.
     
    a) Function does not process any global or static variables
    b) Does not call other non thread-safe functions
    c) if uses such variables, uses mutex and does mutual exclusion
     
    functions having _r suffix are thread-safe version of functions.
     
    Threads very light weight compared to processes in resource consumption. Multi threads can be easily ported to multi-process. But, the reverse is difficult.
     
    ----
    • int pthread_create( pthread_t * id, pthread_attr_t * attr, void * (*routine)(void *), void * arg);
    • int pthread_join( pthread_t id, void ** thread_return );
    • int pthread_detach( pthread_t id );
    • void pthread_exit(void *retval );
    • pthread_t pthread_self( );
    pthread_create -> ID is assgined. pthread_self() can be used to refer it's own ID.
                    'attr' will specify the joinable, scheduling off, stack size (default is 10M) are specified. processed with pthread_attr_ function.
     
    Third argument is start function and the fourth is the argument passed into it. Return value of start function will be the second argument of pthread_join.
     
    pthread_detach will detach the process from parent. Parent does not need to wait for the child. pthread_join will return error.
     
    pthread_exit(): will wait for end. returns the return value.
     
    guest $ cat /proc/sys/kernel/threads-max

    Linux IPC Mechanisms

    1) RPC
     
    Mutex:
     
    pthread_mutex_t init_config_mutex = PTHREAD_MUTEX_INITIALIZER ;
     
    pthread_mutex_lock(&init_config_mutex);
    pthread_mutex_unlock(&init_config_mutex);
     
    Semaphore:

    1) int sem_init(sem_t *sem, int pshared, unsigned int value);
    Initialise an unnamed semaphore.value is initial value of semaphore.
     
    sem_t *p_sem;
     int Success = FAIL;
     Success = sem_init((sem_t *)p_sem, 0, semaphore_info->isemcnt);
    2)       sem_t *sem_open(const char *name, int oflag,
                           mode_t mode, unsigned int value);
    initialize and open a named semaphore
    #define SHM_SEM_NAME "/shm_sem"
    temp_sem = sem_open(SHM_SEM_NAME, 0);
    3)       int sem_post(sem_t *sem);
           int sem_wait(sem_t *sem);
           int sem_trywait(sem_t *sem);
           int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

     sem_t* temp_sem = NULL;
      temp_sem = sem_open(SHM_SEM_NAME, 0);
      if (temp_sem == SEM_FAILED)
     if(sem_wait(temp_sem) < 0)
     {
      if(sem_post(temp_sem) < 0)
      {

    4)       int sem_unlink(const char *name);
           sem_unlink - remove a named semaphore
     if(sem_unlink(SHM_SEM_NAME) < 0)
    5)  int sem_destroy(sem_t *sem);
    sem_destroy - destroy an unnamed semaphore
     ret_val = sem_destroy(p_sem);
    
    6)  int sem_close(sem_t *sem);
     return sem_close((sem_t *)p_sem);
    
    
    Message Queue:
    Supported by Operating System.
     
    1)       mqd_t mq_open(const char *name, int oflag, mode_t mode,
                         struct mq_attr *attr);

    For first time, open with attribyte set.
     
            #define MSGQOBJ_NAME_RX    "/rpc_proc_RXQUEUE"
            struct mq_attr msgq_attr, msgq_attr1;
            msgq_attr.mq_maxmsg = numberof_msgs;
            msgq_attr.mq_msgsize= msg_size;
                       :
            msgq_id = mq_open(MSGQOBJ_NAME_RX, O_RDWR | O_CREAT , S_IRWXU | S_IRWXG, &msgq_attr);
    After that just open.
     
            msgq_id = mq_open(MSGQOBJ_NAME_RX, O_RDWR , S_IRWXU | S_IRWXG, NULL);
    2)       int mq_send(mqd_t mqdes, const char *msg_ptr,
                         size_t msg_len, unsigned int msg_prio);
            msgprio = 0;
            ret_value = mq_send(msgq_id, ptr, length_of_msg, msgprio);
     
    3)       int mq_close(mqd_t mqdes);

            ret_value = mq_close(msgq_id);
    4)       ssize_t mq_receive(mqd_t mqdes, char *msg_ptr,
                              size_t msg_len, unsigned int *msg_prio);

            unsigned int sender = 0;
            msgsz = mq_receive(msgq_id, ptr , length_of_msg , &sender);
    5)       int mq_getattr(mqd_t mqdes, struct mq_attr *attr);
            mq_getattr(msgq_id, &msgq_attr1);
    6)       int mq_unlink(const char *name);

            mq_unlink(MSGQOBJ_NAME_TX);
    7)        ssize_t mq_timedreceive(mqd_t mqdes, char *msg_ptr,
                              size_t msg_len, unsigned int *msg_prio,
                              const struct timespec *abs_timeout);

    #define TIMEOUT_VALUE 180
     struct timespec tm;

     ret_value = clock_gettime(CLOCK_REALTIME, &tm);
     if( ret_value != SUCCESS)
     {
      return FAIL;
     }
     tm.tv_sec += TIMEOUT_VALUE; //absolute timeout 
     msgsz = mq_timedreceive (msgq_id, ptr , length_of_msg , &sender,&tm);
     if (msgsz == -1) {
     
    SHARED_MEMORY:

           int shm_open(const char *name, int oflag, mode_t mode);

           int shm_unlink(const char *name);

    FILE:
    SIGNAL:
    SOCKET:
     
    Pthreads:
     
    pthread_detach()
     
    Which is best way to create mutually exclusive access? In terms of efficeincey, multi-processor environments..
     
    mutex in case of pthreads.
    semaphore in case of kernel in multiprocessor environments or spinlock in case of very short processing.

    Linux Target

    Wednesday, January 15, 2014

    Rendezvous: Reply, Refer, Delete

    ① ITRON implementation of rpl_rdv

    ER rpl_rdv(RDVNO rdvno, VP msg, UINT rmsgsz)
    {
            Sanity check on arguments
            Enter critical section (Interrupt Disable)
            Check whether a task exists specified by the given Rendezous number.
            Check whether the calling task is in termination-waiting state.
            Confirm whether that task currently waits on this rendezous only.
            Check the return message size is not bigger than the maximu message size of cal_por.
            Copy the message from the argument to the call_por's pointer in the tcb
            Set the reply message size in the error code field of the task's tcb.
            if (tw-task is waiting in Timeout)
                    Remove from timer queue
            Set tw-task status as Ready
            if (the tw-task is NOT suspended) {
                    Add the tw-task to the Ready Queue of priority
                    If (the tw-task priority is higher than the current){
                            Call scheduler
                            Return error code from scheduler
                    } else {
                            Exit critical section and return error code from it (E_OK)
                            (return E_OK)
                    }
            }
            Exit critical section and return the error code from it (E_OK)
            (return E_OK)
    }

    ② ITRON implementation of ref_por

    ER ref_por(ID porid, T_RPOR *pk_rpor)
    {
            Sanity check on arguments
            Enter critical section (Interrupt Disable)
            Assign ctskid with TSK_NONE or first valid task id at the head of call wait queue
            Assign atskid  with TSK_NONE or first valid task id at the head of accept wait queue
            Exit critical section and return the error code from it (E_OK)
            (return E_OK)
    }

    ③ ITRON implementation of ref_rdv

    ER ref_rdv(RDVNO rdvno, T_RRDV *pk_rrdv)
    {
            Sanity check on arguments
            Enter critical section (Interrupt Disable)
            Check the taskid specified by the rdvno waits for the same rdvno;
            If so assign wtskid with the task id, otherwise TSK_NONE.
            Exit critical section and return the error code from it (E_OK)
            (return E_OK)
    }

    ④ ITRON implementation of del_por

    ER del_por(ID porid)
    {
            Sanity check on arguments
            If called from ISR
                    return E_CTX;
            Enter critical section (Interrupt Disable)
            /* Release all tasks waiting with E_DLT
            for (each task in the accept & calling waiting queue)
            {
                    Get the task control block from the task ID
                    Assign E_DLT in the return error code of the task's context;
                    if task is waiting in Timeout
                            remove from timer queue
                    Set task status as Ready
                    if the task is NOT suspended {
                            Change the task to the Ready Queue of priority
                            If (the task priority is higher than the current)
                                    set the flag to call scheduler
                    } else {
                            Just delete the task from the send wait queue
                    }
            }
            Free all the memory allocated for the data queue;
            if (the flag to call scheduler is set) {
                    call scheduler;
            } else {
                    exit critical section and return E_OK;
            }
    }
     

    Rendezvous: Forward

    ① Forwarding rendezous to a port

    You know in one port the accept and calling tasks meet and rendezous is created. Immedialtely, the accept tasks dispatches for execution and the calling task waits in the termination waiting state. Now, think that there can me multiple accept and calling waiting tasks in a single port. And, also think that the termination-waiting task can issue one more call to another accept-waiting task instead of simply sleeping till the previous accept waiting task ends the rendezous. So, a single task can be sleeping totally for all rendezous are ended. The rendezous is remembered at the accept-waiting task side and the data at the calling side will be overwritten.

    ② ITRON implementation of fwd_por

    ER fwd_por(ID porid, RDVPTN calptn, RDVNO rdvno, VP msg, UINT cmsgsz)
    {
            Sanity check on arguments
            Enter critical section (Interrupt Disable)
            Check whether a task exists specified by the given Rendezous number.
            Check whether the calling task is in termination-waiting state.
            Confirm whether that task currently waits on this rendezous only.
            Check whether argument message size is not bigger than the port and rendezous.
            (This argument is going to reuse the previous call's message buffer)
            Check whether the port's maximum receive size is not bigger than the rendezous
            Take the accept waiting task in the port;
            /* if there is a accept-waiting task in the port */
            for (each accept-waiting task) {
                    Take the tcb;
                    Get the accept pattern;
                    if (accept pattern has match with calling pattern) {
                            Take the argument buffer pointer of the accept-waiting task from the TCB
                            Copy the message from the API to the aceept-waiting call's buffer
                            Set the message size to the return value field of the accept-wait task's TCB
                            Form the new rendezvous number and assign to both call and accept task tcbs;
                            Confirm the terminate-waiting task's status set as rendezous terminate-waiting
                            Overwrite the terminate-waiting task's pattern and message size with new as in arguments
                            /* Wake up processing for the accept waiting task */
                            if (aw-task is waiting in Timeout)
                                    Remove from timer queue
                            Set aw-task status as Ready
                            if (the aw-task is NOT suspended) {
                                    Change the aw-task to the Ready Queue of priority
                                    If (the aw-task priority is higher than the current){
                                            Call scheduler
                                            Return error code from scheduler
                                    } else {
                                            Exit critical section and return error code from it (E_OK)
                                            (return E_OK)
                                    }
                            } else {
                                    Just delete the aw-task from the receive wait queue
                                    Exit critical section and return the error code from it (E_OK)
                                    (return E_OK)
                            }
                    }
            }
            /* No accept waiting tasks. So, change from termination waiting to call-waiting */
            Overwrite the termination-waiting state to calling-waiting state.
            Overwrite the previous call's message buffer with the current argument message content
            (Input message buffer content is changed)
            Overwrite the call-waiting task's message size
            Save the calling pattern in the TCB.
            Select Queue according to queueing order
            If (Tasks should be granted in FIFO order)
                    Select send wait queue->Queue[0] as Queue
            Else (Should be granted in Priority order)
                    Select send wait queue->Queue[Current Tasks Priority]
            Add the task to the Rendezous queue's call waiting Queue (It would be in no queue)
            Exit critical section and return the error code from it (E_OK)
            (return E_OK)
    }
    "Compelling a call request in the hands of a task which is waiting for a rendezvous to end."

    Friday, January 10, 2014

    uITRON4.0 Specification Memory Protection Extension (Page13)


    Moreover, TDOM_NONE (=-2) will specify kernel objects that does not belong to any protection domain. TDOM_SELF (=0) will specify the protection domain that belongs to the task itself.
     
    2.2.2 Creation of Protection Domain

    By this specification, protection domain will be created statically and dynamic creation and deletion of protection domains are not supported.

    To create the protection domain statically, the following descriptions should be made into the configuration file.

                <Protection Domain type><Protection Domain ID>{
                            Registration of kernel objects belonging to the protection domain
    };
     
    Here, <Protection Domain type>is specified with system_domain or user_domain. <Protection Domain ID>is specified with positive integer or single identifier. When single identifier is specified, configurator allocates unique protection domain ID and creates preprocessor directive (#define) defining the ID number as macro definition insided kernel_id. Insided the protection domain block (parenthesis), the static API which registers (creation, definition, etc.) kernel objects belonging to that protection domain.

    When there are no kernel objects belonging to the protection domain or when only the protection domain identifier needs to be defined as forward reference, the following method is used.

                <Protection Domain Type><Protection Domain ID> ;

    Kernel Domain exists always and no need to create it. When registering kernel objects belonging to the kernel domain, the following method is used.

    kernel_domain {
              registration of kernel objects belonging to the kernel domain
    }

     

    uITRON4.0 Specification Memory Protection Extension (Page12)


    (4)   Memory object
     
    The sequence of memory area which becomes target of kernel memory protection is called memory object. Memory object is one type of kernel object and identified using the starting address. Moreover, memory object is sometimes specified using the arbitrary address inside the memory area.
     
    The start address and size of the memory object are restricted to have the boundary and unit that can be used by memory protection through hardware. Moreover, the memory objects do not overlap each other.

    In this specification, except the operation of memory block acquisition/release, there is no dynamic unit/split of memory object.

    (5)   Kernel Domain, System Domain, User Domain

    The protection domain which has the access rights equal to kernel, in other words, which can perform all kind of operations/access over all kernel objects is called kernel domain. The processing units belonging to kernel domain executes in privileged mode of processor. In a system, only one kernel domain exists.
     
    The protection domain, which has operation/access restrictions over kernel objects, but the processing units belonging to that executes in privileged mode, is called system domain. Using the processing units executed in privileged mode, the access rights of memory area can be changed without any restrictions. Therefore, malicious programs cannot use system domain for protection. Inside a system, there can be multiple system domains.

    The protection domain in which processing units execute in unprivileged mode and there are restrictions in operating/accessing kernel objects is called user domain. Inside a system, there can be multiple user domains.

    (6)   Independent Objects

    Kernel objects excluding the processing units, can be made not to belong to any protection domain. These kernel objects are called independent objects and can be operated/accessed from all protection domains by default. Similar to kernel objects belonging to protection domain, it is possible to setup a set of protection domains which can operate/access each kernel object.

    2.2 Protection Domain Management and setting of belonging Protection Domain

    2.2.1 Protection Domain ID number

    Protection Domain is a kernel object identified by ID number. The ID number of protection domain will be called as Protection Domain ID.

    System Domain and user domain will have positive ID number. (the rule that distinguishes the system objects by defining the negative ID number in uITRON4.0 specification is not adopted here) Kernel Domain will have special ID number (-1).
     

    uITRON4.0 Specification Memory Protection Extension (Page11)

    Inside the extended service routine called from task, the calling task is TSK_SELF. With this, as for the extended service routine called from task, [protection domain belonging to the executing processing unit] is different from [protection domain belonging to TSK_SELF]. This applies to when CPU exception handler is executed from Task context.
     
    (4)   Protection Domain
     
    Protection domain is closure of kernel objects for providing the protection functionality.

    Processing unit belongs to any one of protection domain. Kernel objects except the processing unit belongs to any one of protection domains or does not belong to any protection domain.

    By this specification, the access rights are managed by protection domain, not by each processing unit. Namely, processing units belonging to same protection domain will have same access rights. Because of that, if we strictly describe the above description about the protection functionality [the processing unit belonging to which protection domain does what kind of operation/access permission to which kernel object]. From now on, the operation/access performed by processing units belonging to protection domain will be described as protection domain performs the operation/access.
     
    Kernel object belonging to a protection domain can be operated/accessed only from the same protection domain by default. By each kernel object, the default setting can be changed, and the set of protection domains from where the operation/access is possible can be set. Therefore, even if the kernel objects belong to same protection domain, it is not necessarily to have been protected in the same way.

    Moreover, though the protection domain is independent of link units by kernel specification, when building the configuration environment, it is necessary to associate with link units. Please refer to paragraph 5.1 about the relation between link units and protection domain.

    (5)   Access permission pattern and access permission vector
     
    Access permission pattern specifies the set of protection domains allowed to perform a particular operation/access over a particular kernel object.
     
    The operation/access over the kernel objects has been classified into 4 (Normal operation 1, Normal operation 2, Management Operation, Reference operation). The Access Permission Vector is bundle of the 4 of the Access Permission Patterns.

    However, the service calls corresponding to uITRON4.0 standard profile are classified into Normal operation 1, Normal operation 2. Therefore, when implementing only the functionality corresponding to the standard profile, Access Permission vector will have only 2 access permission vectors.

    Furthermore, it is also permissible to put together all the operation/access rights of a single kernel object into single access permission pattern without classifying the operation/access rights. In that case, access permission vector will have only one access permission pattern.

    uITRON4.0 Specification Memory Protection Extension (Page10)


    Chapter 2. Concept of Specification and Common Rules

     

    2.1 Main Concept


    (1) Kernel Object
     
    The resources processed by kernel are called Kernel Object (refer to uITRON4.0 specification 2.1.3 paragraph)
     
    Kernel objects for this specification meant, the kernel objects excluding variable length memory pool and adding protection domain, memory object, protected memory pool, protected mailbox, system time, system state.
     
    (2) Processing Unit
     
    The program unit executed by kernel, is called as processing unit (refer to uITRON4.0 specification 3.5.1 paragraph). Kernel objects include Processing units.
     
    Processing units for this specification will be, the processing units defined by uITRON4.0 specification. Namely, Interrupt handler, Interrupt Service Routine, Timer Event Handler (Periodic Handler, Alarm Handler, Overrun Handler), CPU exception Handler, Extended service call routine, Task, Task Exception processing routines.
     
    (3) Access Subject and Access Object

    The Access protection functionality provided by kernel will manage that which access object does what kind of manipulation or access permission to which access object, and will forbid the not permitted operation or access.
     
    In this specification the access subject will be the processing unit and the access object will be the kernel objects. Namely, kernel will manage the information that which processing unit does what kind of operation/access permission to which kernel object.
     
    [Supplement Explanation] It is not necessary that all kernel objects will be target to the protection. About the kernel objects which will be the targeted protection objects, please refer to 2.3 paragraph.
     
    Extended service call routine is a processing unit which is independent from the processing unit which calls it. So, when kernel object is manipulated from extended service routine called from task, the extended service routine is considered as access subject, but not the task. According to this, TSK_SELF is cannot be applied to processing unit.
     

    uITRON4.0 Specification Memory Protection Extension (Page9)


    1.2.Specification Policy Development and Target
     
    For adding access protection functionality to memory and kernel objects of uITRON4.0 Specification, following policies and targets are setup.

    (1)    All the 3 purposes described in the previous sections will be covered by one specification.

    More specifically, complete protection will be provided so that even malicious programs cannot breach the protection, and it will be used as base for other purposes by allowing to abbreviating one part. For that, the specification should be easy to remove the protection functionalities which cause large overhead.
     
    At one side, it is important to correct the existing application architecture model to use the protection functionality, at another side, it is also strongly considered to introduce the protection without correcting the application as can as possible. The specification will target to make both cases possible.
     
    (2)    The Specification will aim towards implementation with less overhead
     
    For this, as said above, in addition to making possible to remove the functionality with large overhead, the following 2 policies are set.
     
    (A)  There is no need for address conversion
     
    (B)   By using Static settings, lot of space will be provided for optimization
     
    However, the extent to which the overhead can be reduced using these policies will depend on the Hardware (MMU etc.) used for memory protection. Moreover, to provide the full fledged protection, some level of overhead is unavoidable.
     
    (3)    Make the specification as simple as can
     
    It is important to make the specification simple for the user to easily understand the behavior of the system. Understanding the behavior of the system is important not to implement security hole by mistake.
     
    Though the simple specification will contribute to minimize the overhead, since it is allowed to omit a part of protection functionality in implementation, even if the specification is complex, it would not be a big problem from the view point of overhead.
     
    (4)    Multiple Address space and multiple ID spaces are not supported
     
    To reduce the overhead, the multiple address space which requires address translation will not be supported. Though it is basic to have same physical address and logical address, it is fine to have some kind of address translation between logical address and physical address using implementation definition. Moreover, multiple ID space also is not supported. However, in future versions, the extension for multiple address space and multiple ID space may be investigated.