DevelopmentC/C++
CV | My profile | Photo gallery | Hobbies
Development
C/C++
   Serial channel
   DDPS
   Unix sockets
Java
PHP/SQL/HTML
Visual Basic
Perl
 
Table of contents
Introduction
  Driver
Design
  Access Functions
  OpenW
  OpenR
  _putline
  _getline
  Handler
  send_char
  check_getline
  send_message
  delete_Word_Line
  check_readers_writers
  delete_writer
  check_readers
  getSize
  addReader
  findReader
  delete_reader
  check_putline
  send_to_readers
  Interrupt Service   Routines
  Rx
  Tx
Implementation
  Access functions
  OpenW
  OpenR
  _putline
  _getline
  Close
  Handler Task
  Handler
  send_char
  check_getline
  send_message
  delete_Word_Line
  check_readers_writers  
  delete_writer
  check_readers
  getSize
  addReader
  findReader
  delete_reader
  check_putline
  send_to_readers
  Interrupt Service   Routines
  Rx
  Tx
Testing
  Code inspectio
  System Testing
  Line editing tests
  User access Functions   tests
Summary
References
Appendix A: Source Code

 

DevelopmentC/C++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 Figure 3.

					...
		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