 Serial
channel
1.Introduction
This is project from our real time system course at UVic.
. The project was divided into two major components, a terminal
driver and user functions.
1.1 Terminal driver
The terminal driver receives characters through a serial
channel from a keyboard. The terminal driver resides in an
MBX860 board running MQX. The terminal driver sends all received
characters back down the serial channel where they are echoed
to the terminal. Figures 1 and 2 depict the previously described
process.

Figure 1: Example of the terminal driver receiving the character
'c'. [1]

Figure 2: Example of the terminal driver echoing to the monitor
the character 'c'. [1]
There are two interrupt service routines (ISRs) that correspond
to the terminal. One of these is called when the terminal
driver receives a character. The other is called when the
terminal driver transmits a character. The ISRs communicate
with a handler using message passing. The handler is the central
control for the serial driver operations.
The second part of the project requires the
implementation of access functions. These include OpenR,
_getline, OpenW, _putline, and
Close. User tasks can call these functions to communicate
with the handler and gain access to the serial channel. The
access functions communicate with the handler using message
passing. Figure 3 depicts the communication between the system
elements.
Figure 3: Overall system configuration. [1]
The handler both sends and receives messages to ISRs and
user tasks. The commands and data that the handler outputs
depends on the information it receives on its input queues.
These queues are designed to collect messages; a task can
remove these messages and view their information. The user
tasks also utilize message queues to send and receive messages.
The user tasks don’t communicate directly with the handler,
but instead pass information through the access functions.
A layered depiction of the system structure is depicted in
Figure 4.

Figure 4: Overall system layers. [1]
2. Design
2.1 Access Functions
The access functions are used by user tasks to pass or receive
information from the terminal to keyboard respectively. These
functions include OpenR, OpenW, _putline,
_getline, and close.
Name:
OpenW
Input: none
Return: queue id – The queue id attached
to the device handler.
Purpose:
Allows the user task to access the serial channel for writing
that is, for sending characters to the terminal. Only ONE
task can have writing privileges
The pseudo-code
below shows the design of this function.
Start
Get task id
Send write access message
DO
Peek the reply message queue
Check task id
Until reply yours
Get reply
If okay to write return queue id
Else return zero
End
The function simply sends a message to the handler which
maintains a data structure of the writer task. Since the handler
broadcasts a reply to all the tasks requesting write access,
OpenW has to make sure the reply message on the reply queue
is indeed addressed to the task that made the request. It
should not receive someone’s reply. Therefore, OpenW
“peeks” at the front of the reply and checks the
task id on the messages before receiving that message. This
way every task get it’s reply. The same idea was extended
to OpenR.
Name:
OpenR
Input: uint_16 stream_no – A queue
owned by the user task at which characters received by the
handler will be sent
Return: Boolean – True if read privileges
have been granted to the user task, else false if the false
if a task already has ready privileges or was not able to
be granted access rights.
Purpose:
Allows the user task to access the serial channel for reading.
It should be noted that many tasks can have reading privileges.
Start
Get task id
Get task queue id
Send read access message
Wait for reply
Get reply
If okay return true
Else return false
End
It should be observed message passing was used, that is,
the OpenR function sends a message the handler which maintains
a list of tasks that that read access.
Name:
_putline
Input: qid – Queue id of the queue to send characters
to.
Input: string – A string of characters to send.
Return: Boolean – True if the task was able to send
characters else return false.
Purpose:
A task that has write privileges can call on this function
send characters to the serial channel.
The pseudo-code shows how this function works.
Start
Check for write access
If granted
Send string to qid
Return true
Else
return false
End
We first ensure that the requesting task has
write privileges, and also the specified queue id is valid.
If everything is okay, we send each character of the string
to the specified queue id and return true. It should be noted
that the handler is responsible for further processing of
the string mainly to add the new line character, “\n”.
If an error occurred while trying to send the string or the
task does not have write access we simply return false.
Name:
_getline
Input: string – A location where the strings of characters
from the handler are to be received by the
task.
Return: Boolean – True if the task was able to receive
the string of characters else return false.
Purpose:
A task that has read privileges can call on this function
receive characters from the serial channel whenever the handler
receives a new line character.
The pseudo-code shows how this function works.
Start
Check for read access
If granted
Request for a string //blocking
Get string
Return true
Else
return false
End
2.2 Handler
The handler exchanges control and data messages with the
access functions. It also exchanges data messages with the
interrupt service routines. The handler functions include
Handler, send_char, check_getline, send_message, delete_Word_Line,
check_readers_writers, delete_writer, check_readers, getSize,
addReader, findReader, delete_reader, check_putline,
and send_to_readers.
Name:
Handler
Input: none
Return: none
Purpose:
This is the main function of the handler, it is invoked when
the handler task begins. The handler function covers initialization
of the handler in which message queues are created for communication
with the access functions and the ISRs. After initialization,
the handler enters an infinite loop in which it continuously
monitors the incoming message queues.
Below is the pseudo-code for our Handler function.
Start
Initialize system message pools
Initialize message queues
Infinite loop
Check access function request queue
If an access function request
Handle request
Check ISR data sent queue
If ISR data sent and a reader task is open
Determine if data is special character
Send character(s) to the terminal
End
Loop
End
The handler must first initialize system message
pools so tasks can allocate memory for messages. The handler
must also initialize its incoming message queues for receiving
commands and data, as well as initialize outgoing message
queues for sending commands and data. Once the initialization
is complete, the handler enters an infinite loop. In the loop,
the handler continuously checks access function requests.
If there is a request, the handler calls a function to manage
the request. If the ISR has sent a data message to the handler
and a reader task is open, the handler checks whether the
data is a special character or not. Once the character has
been determined, the handler calls a task to send the appropriate
character(s) to the terminal.
Name:
send_char
Input: none
Return: none
Purpose:
This function is called by the handler to send a character
to the terminal. If the serial channel is available for writing
to, the function sends a message to the terminal via the serial
channel.
Below is the pseudo-code for the send_char function.
Start
Check if serial channel is ready to use
If the serial channel is ready
Poll data message queue until message received
Send char to terminal through serial channel
Enable interrupts
End
End
send_char utilizes a global boolean
variable that maintains the ready status of the serial channel.
If the serial channel is ready, send_char waits on a message
queue for data to arrive. The message queue is a system queue
that receives messages from the handler task. When the message
is received, send_char sends the character to the terminal
through the serial channel. The function then enables interrupts
so the transmission ISR can fire when the character is successfully
transmitted.
Name:
check_getline
Input: char pointer – Pointer to a string of characters
to be copied to the user function string.
Return: none
Purpose:
Checks through the readers data structure to see if any readers
invoked _getline requests. If so, the function copies the
most recent line of the buffer (terminated by a \n) to the
string corresponding to each applicable user function.
The pseudo-code shows how this function works.
Start
While node of reader queue is not NULL
If the reader is waiting on a _getline
Copy the last buffer line to the user task string
Send completion message to the user task
End
Check the next reader node
Loop
End
First the check_getline function looks
at the data structure representing the readers. If there is
a reader present, then it checks if the reader has requested
a _getline. If the reader did request a _getline,
then the function copies the most recent buffer line to the
string sent down by the reader user task. If the reader did
not request a _getline, then the function does not
copy the string and proceeds to the next node in the reader
queue. It iterates through the reader queue until a NULL node
is reached, in which case the function returns.
Name:
send_message
Input: character – A character to send to the output
message queue.
Return: none
Purpose:
This function sends a message to the output queue to be received
by send_char. The message contains a character sent down by
the handler.
The pseudo-code shows how this function works.
Start
Create a message with a character as data
Send the message to the output queue
End
The message created must contain the character
sent down by the handler as data. The message is then sent
to the output queue. This output queue is depicted as Output
queue in Figure 3.
Name:
delete_Word_Line
Input: int – The number of spaces that the terminal
should back off.
Return: none
Purpose:
This function sends a series of messages to the output queue.
The series is a control macro that the terminal will recognize
as move cursor back n times and erase to end of line, where
n is the int sent as a parameter.
The pseudo-code shows how this function works.
Start
Send message combination to move cursor back
Send message combination to erase to end of line
End
The function sends a series of characters via
send_message that the terminal will interpret as two control
commands. The cursor of the terminal will be backed off the
number of spaces sent down as a parameter. The terminal will
then erase to the end of the line. This is a useful function
for delete line and delete word.
Name:
check_readers_writers
Input: _task_id pointer – Pointer to the task id of
a writer user task.
Input: _queue_id receive_qid – The queue that the handler
receives _putline data on.
Input: _queue_id writer_qid – The queue that the handler
uses to send control replies to the access functions.
Input: _queue_id task_qid – The queue that the handler
uses to receive control messages from the access functions.
Returns: none
Purpose:
This function checks the parameters of control messages sent
down by the access functions. Based on the parameters, the
function accommodates the access function requests appropriately.
The pseudo-code shows how this function works.
Start
If there is a message on the access function request queue
If the request is for a Close
If the user task has access privileges
Revokes access privileges
Else if the request is for a reader
Call check_readers function
Else if the request is OpenW
If there are no writers
Grant write access to the user task
End
End
End
End
The function receives a message from the access
function request queue (if a message exists). Then the function
decodes the message and handles it according to the type of
request.
Name:
delete_writer
Input: _task_id pointer – The task id of the writer
task requesting to have its access privileges revoked.
Input: _queue_id writer_qid – The queue id of the message
queue to which the handler sends control replies.
Input: message pointer – A pointer to the control message
sent by an access function.
Purpose:
This function revokes the write access privilege of a user
function.
The pseudo-code shows how this function works.
Start
If the user task has write privileges
Revoke the write privileges
Send a response message to the access function
End
This function must update the data structure
corresponding to user task write privileges. Then it must
send a control reply to the access function that requested
to have write privileges revoked.
Name:
check_readers
Input: message pointer – A pointer to the message sent
by the requesting access function.
Input: _queue_id – The queue to which the handler sends
control replies to the access functions.
Return: none
Purpose:
This function checks the queue that contains a list of the
reader tasks.
The pseudo-code shows how this function works.
Start
If the request has read privileges
If the request is _getline
Mark the task as waiting on _getline
Else
If the request is OpenR
Grant the task read privileges
End
End
End
This function must check for the task in the
reader queue using findReader. If the task has read
privileges and the request is for _getline, the function
updates the reader node in the reader queue to indicate that
the task is waiting on a _getline. If the task has
no read privileges and the request is an OpenR, the
function must add the task to the reader queue and reply to
the task that had its read access granted.
Name:
getSize
Input: none
Return: int – The size of the reader queue.
Purpose:
This function iterates the reader queue and determines the
length of the queue.
The pseudo-code shows how this function works.
Start
While the reader queue node is not null
Increment the counter
Iterate to the next node
Return the count
End
This function must access the reader queue data
structure.
Name:
addReader
Input: message pointer
Return: none
Purpose:
This function checks the reader queue. If the reader queue
is empty, then the function adds the task as the head. Else
the function adds the task to the end of the reader queue.
The pseudo-code shows how this function works.
Start
If the reader queue head is null
Add the reader task to the head
Else
Add the reader task to the tail
End
This function must access the reader queue data
structure.
Name:
findReader
Input: _task_id – The task id of the reader to be found.
Returns: reader pointer – A pointer to the node of the
reader in the reader queue.
Purpose:
This function searches the reader queue for the task. If the
function finds the task, then it returns the corresponding
node of the reader queue. Else the function returns a NULL
pointer.
The pseudo-code shows how this function works.
Start
While the reader queue node is not null
If the node contains the task
Return the node
Check the next node
End
The function must access the reader queue data
structure.
Name:
delete_reader
Input: message pointer – Pointer to the request message
sent by the access function.
Return: boolean – Status of a successful deletion.
Purpose:
This function checks for a task in the reader queue. If it
finds the task, then the function deletes it and returns true.
Else, the function returns false.
The pseudo-code shows how this function works.
Start
If the head of the reader queue is NULL
Return FALSE
ELSE
While the node is not NULL
If the task is in the node
Delete the node
Return TRUE
End
Loop
Check the next node
End
End
This function must check the reader queue data
structure. Upon deletion of a node, the function must set
the previous node to the next node and the next node to the
previous node.
Name:
check_putline
Input: _queue_id output_qid – The queue to send the
character data message to.
Input: _queue_id receive_qid – The queue to receive
characters from.
Return: none
Purpose:
If a _putline request has sent its message to the
handler, this function passes the line onto the output queue.
The pseudo-code shows how this function works.
Start
If the _putline request sent characters
While there are characters to send
Send the character to the output queue
Go to the next character
Loop
Send a carriage return macro to output queue
End
End
The function sends all characters received from
the receive queue to the output queue. These queues are depicted
as _putline and Output queue respectively in Figure
3. The function then sends a control macro to the output queue
that the terminal interprets as a carriage return.
Name:
send_to_readers
Input: char – A character to be sent to all readers.
Return: none
Purpose:
This function iterates through the reader queue and sends
a character to every reader. Used for a _getline request.
The pseudo-code below shows the design of this function.
Start
While reader node is not NULL
Create a message
Set the message data to the char parameter
Send the message to the reader task
Check the next reader node
Loop
End
The function must access the reader queue data
structure.
2.3 Interrupt Service Routines
There are two interrupt service routines (ISRs) that are
associated with the serial channel. When a character is received
at the terminal driver, the Rx ISR is called. When a character
is successfully transmitted to the terminal, the Tx ISR is
called.
Name:
Rx
Purpose:
This ISR is called when the terminal driver receives a character
from the keyboard.
The pseudo-code shows how this ISR works.
Start
Create a message
Set the message data to the received character
Send the message to the handler
End
Name:
Tx
Purpose:
This ISR is called when the terminal has successfully received
a sent character.
The pseudo-code shows how this ISR works.
Start
Set the global boolean ready to TRUE
If the output queue is not empty
Call send_char
End
End
The function send_char can fully execute
only when the boolean ready is TRUE. If send_char gets past
this test, then ready is set to FALSE. The output queue and
the Tx ISR are depicted in Figure 3 as Output queue and Interrupt
Service Routine output respectively. If there is a backlog
of messages in the output queue, then the ISR calls send_char
to send the next character to the terminal.
3. Implementation
3.1 Access functions
3.1.1 OpenW
The pseudo code shown on page 3 was used to implement OpenW.
The code implementation of this function is listed on the
appendix under the file function.c. We first obtain the task
id of the task invoking the function using MQX’s task_get_id()
function, and we then ensure that the task does exist before
continuing to use the id.
.
user_id = _task_get_id();
if (!(user_id))
printf ("\n Unable obtain task id!!!\n");
.
We then continue to create a message to send
a request to the handler. The message is of type TASK_MSG
defined in the include file function.h listed on the appendix.
We allocate memory for the message and ensure that indeed
memory was allocated. We then call on the helper function
send_request_writer to send the request to handler
and wait for the reply.
.
// allocate memory for the message and ensure
// it was allocates
req_ptr = _msg_alloc_system(sizeof(TASK_MSG));
if(!req_ptr) {
printf("Unable to allocate memory line 169\n");
_mqx_exit(1);
}
// Send request for openW
resp_ptr = send_request_writer(OPEN, req_ptr, user_id);
.
The send_request_writer function
addresses the request to the handler’s input message
queue for tasks and also initializes the attributes of our
messages accordingly.
.
req_ptr->HEADER.TARGET_QID = task_qid;
req_ptr->task_id = user_id;
req_ptr->OK = TRUE;
req_ptr->die = FALSE;
req_ptr->RW = WRITE;
.
Send_request_writer then sends
the message and waits for a reply from the handler. The handler
uses a system message queue to reply all the tasks requesting
write access. Since, there could potentially be other tasks
invoking OpenW, there could be several replies from
the handler. Therefore, this function should ensure that the
reply received belongs to the calling task. This is achieved
by using the MQX function _msgq_peek to check the
message at the front of the queue without actually removing
it from the queue. We then check the task id attribute of
the reply. If it does not correspond that of the calling task
we keep looping, else we receive the message and return it
to OpenW.
.
// Obtain the reply from handler
// Check for a reply from handler
while (resp_ptr == NULL) {
resp_ptr = _msgq_peek(writer_qid);
if (resp_ptr != NULL) {
if (resp_ptr->task_id == user_id)
resp_ptr = _msgq_poll(writer_qid);
else
resp_ptr = NULL;
}
}
return resp_ptr;
.
At OpenW we inspect the OK attribute
of our message. The handler should set OK to true if the task
has been granted write or false if not granted privileges.
If OK is true we obtain the queue id from the putline_qid
field of the message (which should be initialized by the handler)
and then return it, else we return zero and then free memory
allocated for the message.
...
//Make sure a null message was not received
if (resp_ptr != NULL){
// If OK to write, get output qid
if (resp_ptr->task_id == user_id && resp_ptr->OK) {
putline_qid = resp_ptr->qid;
} else if (resp_ptr->task_id == user_id && !(resp_ptr->OK)) {
putline_qid = 0;
}
}
// clean up
_msg_free(resp_ptr);
return putline_qid;
...
3.1.2 OpenR
The implementation of this function is also listed on the
appendix in the file named functions.c. In fact, all the user
access functions are defined in this file. We first obtain
the queue id for the task’s queue where the characters
are to be sent to using the MQX function _msgq_get_id.
We ensure that a valid id was returned before proceeding.
We also obtain the task id of the task invoking OpenR.
We then proceed to allocate memory for our request message
ensuring that memory was allocated to the message. The helper
function send_request_reader is then called upon
to send the message to the handler. This function is similar
to send_request_writer described under the function
OpenW.
.
// Get the queue id for the stream_no, the queue where _getline is // sent to
read_rec_qid = _msgq_get_id(0, stream_no);
if (!(read_rec_qid))
printf ("\n queue id for the stream_no!!!\n"); // Get the id of the calling task.
user_id = _task_get_id();
if (!(user_id))
printf ("\n Unable obtain task id!!!\n");
req_ptr = _msg_alloc_system(sizeof(TASK_MSG));
if(!req_ptr) {
printf("Unable to allocate memory line 169\n");
_mqx_exit(1);
}
// Send request for OpenR
resp_ptr = send_request_reader(OPEN, req_ptr, user_id,
read_rec_qid, NULL);
.
Send_request_reader obtains
the input queue that the handler uses to communicate with
tasks and initializes the attributes of the message for sending
an open request message
.
req_ptr->HEADER.TARGET_QID = task_qid;
req_ptr->task_id = user_id;
req_ptr->RW = READ;
req_ptr->die = FALSE;
.
//Sending a request for an open reader
req_ptr->qid = read_rec_qid;
req_ptr->getline = FALSE;
req_ptr->OK = TRUE;
.
The function then sends the message.
Just like with OpenW we have the problem that the handler
replies the readers through a system queue, therefore, we
cannot just receive a message. We have to make sure that the
reply is indeed meant for the task that called this OpenR.
We achieve by continuously peeking at the front of the reply
queue and checking the task id attribute to see if it corresponds
to the task id of the caller user task. If it is a match,
we can confidently receive them message and check the OK attribute
to decode the response from the handler. If OK is true, then
the caller task has obtained write access. We, therefore,
return true, else we return false.
3.1.3 _putline
We first have to ensure that the calling task has write privileges.
As with OpenW, we obtain the id of the task calling this function,
and then allocate message for request message. We then call
on the send_request_writer to send the message.
.
resp_ptr = send_request_writer(PUTLINE, req_ptr, user_id);
.
Since this is a putline request, the send_request_writer
initializes the attributes of our message accordingly and
sends the message.
.
req_ptr->OK = FALSE;
req_ptr->die = FALSE;
req_ptr->RW = WRITE;
.
if (!_msgq_send(req_ptr)) {
printf ("Unable to send request for writer function!!!\n");
_mqx_exit(1);
}
...
We then wait for our reply without receiving
a message that is not ours from the system queue. If it is
okay to write, we copy the string to write to a buffer and
call the helper function put_message to send each character
of the buffer to the handler.
.
// copy line to a buffer
sprintf(buffer,line);
size = strlen(line);
//Send every character to terminal
for (i = 0; i < size;i++)
put_message (buffer[i], qid);
sent = TRUE;
.
If the string is successfully sent we return
true else we return false. We remember to clean up by freeing
memory that was allocated for the initial request message.
3.1.4 _getline
We first have to make sure that the task calling this function
has read access. Just as with OpenR, we obtain id of the caller
task, allocate memory for our request message. We then call
send_request_reader to send a getline request.
.
// Send request for getline
resp_ptr = send_request_reader(GETLINE, req_ptr, user_id, 0, line);
.
Send_request_reader initializes the
appropriate attributes of a message accordingly to signal
the handler that this a putline request.
.
req_ptr->HEADER.TARGET_QID = task_qid;
req_ptr->task_id = user_id;
req_ptr->RW = READ;
.
req_ptr->die = FALSE;
switch (mode){
case GETLINE: //Sending a request for a getline
req_ptr->line = line;
req_ptr->getline = TRUE;
req_ptr->OK = FALSE;
.
This function then waits for a reply
from the handler which it returns to _getline. If
the task has write access we return true else we return false.
3.1.5 Close
This function simply obtains the task id of the task calling
it, allocates memory for the request message. It then calls
send_request_writer to send a close a request.
.
// Send request for close, use send_request_writer for any combo
// reader/writer
resp_ptr = send_request_writer(CLOSE, req_ptr, user_id);
.
Send_request_writer sets the die attribute
of the message to signal that this is close request. It then
sends the message and waits for the reply. It finally returns
the reply to the Close function. We then check if the rights
for the task were revoked. If true we return true else we
return false.
.
// If OK to write, get output qid
else if (resp_ptr->task_id == user_id && resp_ptr->die)
dead = TRUE;
else
dead = FALSE;
//clean up
_msg_free(resp_ptr);
return dead;
.
3.2 Handler Task
All functions of the handler task closely follow the design
specifications stated in the Design section. The source code
for all of the functions is listed in Appendix A. All of the
functions are included in the file handler.c. The noteworthy
implementation details for each handler function are included
below.
3.2.1 Handler
When the handler task begins, the first thing it does is
run the Handler function. This function initializes the handler
before entering into an infinite loop.
An important variable that the function creates is the char_buffer.
This is a character array of size BUFFER_SIZE, a
constant defined in handler.h. The buffer stores a copy of
the present line of the terminal. An integer named current
acts as an index to the buffer.
During initialization, the handler function creates two system
message pools the size of EXAMPLE_MSG and TASK_MSG.
These structures can be found in the file handler.h. By using
system message pools, all running tasks are able to allocate
messages from them.
Also during initialization, the handler function creates
six message queues, three are private queues, the other three
are system queues. Two of the private queues correspond to
Handler input queue and _putline as depicted in Figure
3. The purpose of the third private queue is to receive control
messages from the access functions. Two of the system queues
correspond to Output queue and _getline as depicted
in Figure 3. The purpose of the third system queue is to send
control response messages to the access functions.
Once the initialization is complete, the Handler function
lowers the priority of the handler task.
.
old_priority = priority;
_task_get_priority(_task_get_id(), &priority);
if (priority > 0 )
priority = priority+3;
task_set_priority(_task_get_id(), priority, &old_priority);
.
The priority of the handler task is set
to 5 upon creation. After initialization, the priority of
the handler task is set to 5 + 3 = 8. This is the same priority
that all of the other tasks are created with. Since the scheduling
scheme is round robin, each task has a fair chance to utilize
the processor.
Now begins the infinite loop. At the top of the loop, the
function checks the access functions control queue for messages.
.
if (_msgq_get_count(task_qid))
.
If there are messages on the queue, the function
calls check_readers_writers to process the received command.
The function then checks for messages sent from the ISR to
the receive queue. If a message is present and if there are
reader tasks, then Handler proceeds to process the message.
If no messages are present or if no readers are open, then
the function simply frees the message to clean-up memory.
Handler starts the processing of a message by echoing the
received character to all reader tasks. The function then
checks if the received character is a carriage return. If
so, it sends a series of characters to the terminal. The terminal
interprets these characters as a new line. The function sends
the buffer to all _getline requests using check_getline and
sets the buffer index to zero.
If the received character is not a carriage return, Handler
checks for the special characters backspace, delete word and
delete line. If it encounters any of these, the function sends
an appropriate set of macro commands to the terminal in order
to move the cursor to the appropriate location and to clear
any unwanted characters. In each of these cases, Handler updates
the buffer setting any erased characters as ‘\0’.
If the received character is not a special character, the
function simply echoes the character to the terminal.
3.2.1.1 send_char
This function utilizes a global boolean variable named ready
to determine if the serial channel is available for writing
to. If the boolean is false, then send_char simply exits.
If the boolean is true, then send_char continues to execute.
The function checks the output queue for a character
sent by the handler. If the queue is not empty, then the function
sets the local variable named transmit_char to the
data of the message and frees the message from memory. send_char
then sends transmit_char to the terminal through
the serial channel. After send_char sends the character,
it enables interrupts so the Tx ISR will run when a message
transmission completes.
3.2.1.2 check_getline
This function is called by the Handler function
when a carriage return is detected from the serial channel.
check_getline creates temp, a temporary READER_PTR
used to iterate through the reader queue data structure. This
data structure is represented by the global variable named
head, this is a READER_PTR itself. The reader queue
pointer variable is global so that all functions of the handler
task can access it. The READER_PTR structure is located
in the file handler.h.
The temporary node named temp is set to head for the start
of the reader queue iteration. The function then enters the
following loop:
.
while (temp != NULL)
if (temp->getline)
sprintf(temp->line,charBuffer);
temp->getline = FALSE;
.
temp = temp->next; The getline attribute of temp is set to
true if a reader task is waiting on a _getline command
and the line attribute is a pointer to a string. If the user
task is waiting on a _getline, then the function
copies the buffer to the string associated with the user task.
The getline attribute of temp is then set to false as the
user function is no longer waiting on a _getline.
A message must be sent to the user task to wake it up, as
it will block itself in the _getline function. check_getline
then iterates to the next node in the reader queue.
3.2.1.3 send_message
This function takes a character as a parameter. It allocates
a message from a system pool and sets the message data to
the character. The target of the message is set to the output
queue, a system queue that send_char polls to send
characters to the terminal. send_message then sends the message
to the output queue. The main utility of this function is
to simplify code as many character messages are created and
sent to the output queue.
3.2.1.4 delete_Word_Line
This function uses multiple send_message calls to
send two macros to the terminal. These macros are move a number
of spaces left and erase to the end of a line. This function
is used for the delete word and delete line commands. delete_Word_Line
contains one piece of important logic.
.
if (backoff > 9) {
send_message (backoff/10 +48);
send_message (backoff%10 +48);
else
send_message (backoff+48);
}
backoff is a parameter of the function;
it represents the number of characters that the terminal is
to move left. As the terminal only recognizes the numbers
0 to 9 as characters, the function must split up the backoff
if it is greater than 9. If backoff is less than 10, the function
can simply send that number. A note, 48 must be added to the
number before sending it in order to convert it to an ASCII
number.
3.2.1.5 check_readers_writers
This function processes control messages sent from the access
functions.
.
if (req_ptr->die)
.
If the message attribute die is set, then
the request is for a close.
reader = findReader(req_ptr->task_id); The function calls findReader to check if the
requesting task is in the reader queue.
.
if (reader != NULL)
.
if ((*writer) == req_ptr->task_id)
.
If the task is a reader then the function
erases it from the reader queue. If the task is a writer,
then the function sets the writer data structure to zero.
This data structure is simply an integer that keeps track
of the sole task (if there is one) that has write access.
If the task is neither a reader nor a writer, then the function
sends a message back to the access functions with die = FALSE
so the access function knows it was an unsuccessful close.
On successful close, die is set to TRUE before the function
sends the message
.
if (!req_ptr->die)
.
If die is FALSE, then the request is not for
close.
.
if (req_ptr->RW)
.
If the attribute RW is TRUE, then the request
is either OpenR or _putline. The function proceeds
to call check_readers if RW is TRUE, this handles the above
two mentioned requests.
.
else if (!req_ptr->RW)
if ((*writer) != 0)
if (req_ptr->task_id == (*writer) )
.
else if (req_ptr->task_id != (*writer))
.
If RW is FALSE, then it is a request for
OpenW or _putline. If a task already has write access,
then the function checks if the requesting task owns the write
access. If this is TRUE, then the function sends a message
back to the user task with the OK attribute set to TRUE as
it is okay to write. The qid attribute of the message is set
to the queue id that the user task can send _putline
requests to. If the requesting task does not own the write
access, then the function sends a message back to the user
task telling it that it is not okay to write.
.
else if (*writer == 0)
if (!req_ptr->OK)
.
else
.
If no user tasks currently own the write
access, the function checks if the task has been given the
okay to write. If the OK attribute is FALSE, then this is
a _putline request sent before an OpenR.
In this case, the function sends back a message that it is
not okay to write. If the OK attribute is TRUE, then the function
sends back a message that it is okay to write. Included in
the message is the queue id of the _putline queue
3.2.1.6 delete_writer
This function revokes write access from a user task.
.
if (*writer == req_ptr->task_id)
*writer = 0;
.
If the user task owns the write access, then
the function sets the writer data structure to zero. delete_writer
then sends a confirmation message back to the user task. If
the user task does not own the write access, then the function
sends a message back to the user task with the die attribute
set to false as the function cannot close write access.
3.2.1.7 check_readers
This function processes OpenR and _getline requests.
.
reader = findReader(req_ptr->task_id);
if (reader != NULL)
if (req_ptr->getline)
.
else
.
else
.
if (req_ptr->getline)
.
The function calls findReader
to check if the requesting task is in the reader queue. If
the reader is found, then the function checks if the request
is for a _getline determined by the getline attribute
of the request message. If the request is a _getline,
then the reader attribute getline is set to TRUE and the request
message is freed. The handler does not send a reply message
to the task requesting the _getline until a carriage
return is detected on the serial channel. This in turn blocks
the user task on a _getline. If the user task has
read access and the request is not a getline, then the request
is an OpenR. The handler should not give the user
task further read access, and simply reply with a message
with an OK attribute set to false.
If the user task is not a reader and it requests a _getline,
then the handler should send a reply message to the user function
with the OK attribute set to FALSE. If the task is not a reader
and the request is not a getline, then it is an OpenR. In
this case, check_readers calls the function addReader
to add the user task to the reader queue and then sends a
reply message to the user task with the OK attribute set to
TRUE.
3.2.1.8 getSize
This function returns the size of the reader queue.
.
if (head == NULL)
return 0;
else if (head->next == NULL) {
return 1;
else {
scan = head;
i = 1;
while (scan->next != NULL)
i++;
scan = scan->next;
.
If the head of the reader queue is NULL,
then there are no current readers and the function returns
zero. If the head is not NULL, then the function increments
the counter i. getSize scans through the entire reader queue
and increments the counter until a NULL node is found in which
case it returns the final count
3.2.1.9 addReader
This function adds a user task to the reader queue giving
the task read access.
.
head = _mem_alloc(sizeof(READER));
.
temp = _mem_alloc(sizeof(READER));
.
while (scan->next != NULL)
scan = scan->next;
scan->next = temp;
temp->previous = scan;
.
If the head of the reader queue is NULL,
then the function sets the user task as the sole reader. It
then initializes the attributes for the reader node. If the
head node is not NULL, then the function allocates memory
for a new reader node. addReader then iterates to the tail
of the queue and sets the newly created node as the new tail
3.2.1.10 findReader
This function iterates through the reader queue and returns
a pointer to a reader node if it finds the target user task
in the queue.
.
while (scan != NULL)
if (task_id == scan->task_id){
temp = scan;
return temp;
} else
scan = scan->next;
. A similar iteration through the queue is
encountered in previous task implementations. As soon as the
task_id attribute of a node is found to be the same
as the target task id, then the function returns a pointer
to the applicable node. If the iteration runs into a NULL
node, then the user task is not a reader and the function
returns NULL.
3.2.1.11 delete_reader
This function iterates through the reader queue and deletes
a node if it corresponds to the target user task id. The iteration
is similar to previous function implementations
.
if (req_ptr->task_id == scan->task_id)
...
scan->previous->next = scan->next;
scan->next->previous = scan->previous;
_mem_free(scan);
.
Of particular interest is the case
where the applicable node is found. The function must set
the previous node’s next attribute to the next node,
and it must also set the next node’s previous node to
the previous node. Then the function frees the memory of the
deleted reader node.
3.2.1.12 check_putline
This function checks the queue from which the handler receives
characters from a _putline request. This queue is
depicted as _putline in .
...
while (putline_chars)
put_line_ptr = _msgq_receive(receive_qid, 0);
.
The function first counts the number of characters
on the message queue. The while loop continues until the function
has processed all characters by sending them to the output
queue. check_putline then appends a carriage return to the
end of the string by sending the applicable character macro.
3.2.1.13 send_to_readers
This function takes a character as a parameter and sends
it to all reader tasks. It does this by iterating through
the reader queue and sending a message to the queue id of
each node. This function is called every time the handler
receives a character from the keyboard, assuming at least
one reader is open.
3.2.2 Interrupt Service Routines
Both of the interrupt service routines (ISRs) closely follow
the design specifications stated in the Design section. The
source code for both of the ISRs is listed in Appendix A.
Both of the ISRs are included in the file handler.c.
The noteworthy implementation details for each ISR are included
below.
3.2.2.1 Rx
This ISR is called whenever the terminal driver receives
a character on the serial channel sent from the keyboard.
Rx creates a message from a system message pool. The ISR sets
the target queue attribute of the message to the receive queue
of the handler. It sets the data attribute of the message
to the character received on the serial channel. Rx then sends
the message to the handler.
3.2.2.2 Tx
This ISR is called whenever the terminal driver successfully
transmits a character to the terminal. An important operation
of the ISR is to set the status of the global variable named
ready to TRUE. This allows send_char to access the
serial channel in order to transmit further characters to
the terminal. Tx then checks the output queue for backlogged
characters that have yet to be sent to the terminal. If the
queue is not empty, then Tx calls send_char to send
the next character to the terminal.
4. Testing
4.1 Code inspection and walk through
We took it upon ourselves to perform the code inspections
and walk though especially for modules and functions that
were developed independently. When one of the members finishes
his function or module he then passes it over to another member
who then inspects it looking for logical and semantic errors.
Once inspected the module is then integrated into the system.
4.2 System Testing
The test our system one super task MTask was created
to create other user tasks. MTask has write access.
The other tasks would them send messages (error or confirmation
messages), to be displayed on the screen, to this task which
in turn used the putline function to output the messages on
the screen. The test bench is listed in the appendix on the
appendix under the file named main.c.
4.3 Line editing tests
| Precondition: |
At least one task with read access started |
| |
|
| Name: |
test 1 |
| Function: |
erase character (^H) |
| Description: |
Entered a one character, then entered ^H to erase the
characters
just before the cursor. Then entered the entire line,
then entered ^H |
| Expected results: |
The character just before the cursor should be erased. |
| Actual Results: |
The character just before the cursor was erased. |
| Comments: |
Test passed. |
| |
|
| |
|
| Name: |
test 2 |
| Function: |
erase character (^H) |
| Description: |
Entered the entire line, kept the back space key pressed |
| Expected results: |
The character just before the cursor should be erased
one after the
other until we release to the key. |
| Actual Results: |
Some of the characters were not erased. |
| Comments: |
Test failed. |
| Problem: |
The bug was traced to the fact that it was not ensured
that the
system is ready to transmit a character before actually
sending one!
|
| Solution: |
A global boolean initially set to true was declared.
The function
send_char check this flag before sending a character.
If true the
function send a character and sets it false. When the
Tx_Ready
interrupt occurs (we are now capable of sending another
character),
we set the flag back to false. |
| Name: |
test 3 |
| Function: |
erase character (^H) |
| Description: |
Entered the entire line, kept the back space key pressed |
| Expected results: |
The character just before the cursor should be erased
one after the
other until we release to the key. |
| Actual Results: |
The characters were erased one after the other. |
| Comments: |
Test passed. |
| Name: |
test 4 |
| Function: |
erase previous word (^W) |
| Description: |
Entered a one word, then entered ^W to erase entered
word. Then Entered word on the entire line, then entered
^W |
| Expected results: |
The word just before the cursor should be erased not
including the
white space. |
| Actual Results: |
The word just before the cursor was erased. |
| Comments: |
Test passed. |
| Name: |
test 5 |
| Function: |
erase line (^U) |
| Description: |
Entered entire line, then entered ^U |
| Expected results: |
The whole line should be erased |
| Actual Results: |
The whole was erased. |
| Comments: |
Test passed. |
4.4 User access Functions tests
| Name: |
test 1 |
| Function: |
OpenW |
| Description: |
Task A invokes OpenW |
| Expected results: |
OpenW should return zero because we already have a writer,
i.e. the super task should therefore send an error “Task
A: Unable to OpenW” to be displayed |
| Actual Results: |
Error: Task A: Unable to OpenW |
| Comments: |
Test passed. |
| Name: |
test 2 |
| Function: |
_putline |
| Description: |
Task A invokes _putline |
| Expected results: |
_putline should return false and Task should
send an error “Task A: Unable to putline”
|
| Actual Results: |
Error: Task A: Unable to putline |
| Comments: |
Test passed. |
| Name: |
Test 3 |
| Function: |
_getline |
| Description: |
Task C invokes _getline before invoking OpenR |
| Expected results: |
_getline should return false since the task
has not obtain read
access. We should get the error message that “Task
C: Unable to getline” |
| Actual Results: |
Error: Task C: Unable to getline |
| Comments: |
Test passed. |
| Name: |
test 4 |
| Function: |
OpenR |
| Description: |
Task A and Task C invoke OpenR |
| Expected results: |
These 2 tasks should be granted read access, therefore,
we see two
Confirmation messages: “Task A: Granted read access”
and “Task C: Granted read access” |
| Actual Results: |
Confirmation messages: “Task A: Granted read access”
and “Task C: Granted read access”
displayed.
|
| Comments: |
Test passed. |
| Name: |
test 5 |
| Function: |
_getline |
| Description: |
Having invoked OpenR Task A and C invokes _getline |
| Expected results: |
When a new line character is received the handler, both
task should
get a copy of the line entered on the terminal.
|
| Actual Results: |
Both tasks were able to receive a copy of the line entered |
| Comments: |
Test passed. |
| Name: |
test 6 |
| Function: |
_getline |
| Description: |
Task C invokes _getline |
| Expected results: |
When a new line character is received the handler,
task C should
get a copy of the line entered on the terminal |
| Actual Results: |
Task C was able to receive a copy of the line entered |
| Comments: |
Test passed. |
| Name: |
test 7 |
| Function: |
Close |
| Description: |
Task B invokes Close without having obtained read or
write access |
| Expected results: |
The function should return false, therefore, we should
get the error “Task B: Unable to close”
|
| Actual Results: |
Error: “Task B: Unable to close” |
| Comments: |
Test passed. |
| Name: |
test 8 |
| Function: |
Close |
| Description: |
Task A invokes Close to revoke it’s read access |
| Expected results: |
Close should return true, therefore, we should the confirmation
that “Task A: Closed” |
| Actual Results: |
confirmation: “Task A: Closed” |
| Comments: |
Test passed. |
| Name: |
test 9 |
| Function: |
Close and _getline |
| Description: |
Task A does not invoke Close to revoke it’s read
access |
| Expected results: |
When a new line character is received the handler, Task
A should
not get a copy of the line entered on the terminal |
| Actual Results: |
Task does not get anything. We simply move the cursor
to the next
line. |
| Comments: |
Test passed. |
| Name: |
test 10 |
| Function: |
Close |
| Description: |
Having closed all the reader tasks we enter characters |
| Expected results: |
Nothing should appear on the screen, i.e. the handler
discards all
characters. |
| Actual Results: |
Nothing gets printed out. |
| Comments: |
Test passed. |
The figure below shows the test reults.

Figure 5: Test results
5. Summary
In this project, Pantanowitz and Dan designed and implemented
both a terminal driver and access functions for the terminal
driver. The terminal driver receives characters from a keyboard
on a serial channel. It then sends the characters back out
on the serial channel to a terminal where they are echoed.
User tasks use the access functions to read and write to the
terminal driver. There can be many reader tasks, but only
one writer task.
For the design process, much pseudo-code was written. This
code was very useful for generating a hierarchical layout
of the system. The design was separated into three different
components. These are the access functions, the handler and
the interrupt service routines (ISRs). These components are
depicted as layers in Figure 4. The access functions are the
highest level layer while the ISRs are the lowest level layer.
The implementation was done on an MBX860 board running the
MQX operating system. Message passing was used to pass messages
between the access functions, the handler and the ISRs. This
approach allows the access functions to act as a buffer between
the user tasks and the handler. The user tasks have no direct
communications with the handler. All of the main components
of the system were split into many functions; this introduced
modularity and reduced code size.
The test procedure was designed to cover as many cases as
possible. During testing, many errors were encountered. On
occasion, this would demand an overhaul of both the design
and/or of the implementation.
The project was a success in meeting the goals outlined by
the specification.
6. References
- [1] N.J. Dimopoulos, J. Dorocicz and E. Laxdal, “CENG
455 Real Time Systems Collection of Lab Projects.”
Victoria, 2003, Available at: http://www.ece.uvic.ca/lab/ceng455/
Appendix A: Source Code
build.bat
functions.h
functions.c
handler.h
handler.c
main.c
|