2. Data Link Control Layer


Communication between the Host and Careplane | Initialisation | Testing Careplane | Writing Requests | Reading Responses | Semaphores | Timed Buffer Reading | Termination of the Communication Link


Communication between the Host and Careplane

An area of the host PC's main memory is allocated as "Shared Memory", enabling the user and Careplane to communicate (figure 1.2). A separate module coded in IBM-PC assembler contains 4 functions to handle communication between the host and Careplane:

Initialisation

A segment register on the board is written to by the user to position the shared memory area in the lower 1,024 Kb of the host memory. The unit of data transfer is a 16-bit word and DMA allows a high rate of transmission. The shared memory is divided into:

Shared Memory Control Block (SMCB)

The Control Block contains information about the position, size and status of receive and transmit buffers. It is implemented in assembler as a data structure of 32 words, a list of equates representing the offsets from the start of the block, in bytes.

Receive and Transmit buffers

The Data Link Control Layer (DLCL) is responsible for writing commands to the transmit buffer and reading the command responses from the receive buffer (figure 2.1)

Figure 2.1 Transmit and Receive buffers

Allocation of Memory from DOS

The total size of the shared memory area in words is calculated as:
Total Size = SMCB (32 words) + Receive Buffer + Transmit Buffer
The size of the transmit and receive buffers are passed as parameters to the initialisation routine. The value is divided by 8 to get the size in paragraphs, and DOS interrupt 21H, function 48H (allocate memory) is called. Success returns the starting address in the AX.

Initialisation of the Control Block

Following memory allocation, the address of the shared memory segment (in the AX) is copied to the Extra Segment (ES) register. The control block offsets are used as indices into the data structure (figure 2.2). For example, to write the total size of the shared memory into word 0 of the control block, the following syntax is used:
MOV ES:[SHM_SIZE],DX

Figure 2.2 Initialisation of the Shared Memory Control Block
(Full Initialisation Values are given in Appendix A)

DMA Controller

Careplane is able to access the host's memory using DMA. One of the host's DMA controllers must be configured to allow temporary suspension of the host CPU when data transfer is in progress. This application configures DMA controller 2 to use channel 5 (16 bit). To disable the host CPU during data transfer, a mode byte (C1C1h) is written to the controller's mode register at 0D6h. The controller is then set to use channel 5 by writing 0101h to the mask register at 0D4h (figure 2.3).

Figure 2.3 Initialisation of the Host DMA controller

Shared Memory Segment Register

Careplane has a single 16-bit I/O register to take the address at which the shared memory segment begins. To produce a 1 Mb address space (20 bits), the value in the register is multiplied by 16. Careplane does this internally, in the same way as the Intel 80286 processor. After power-on, the register contains a random value, and must be cleared by writing in a 0. The correct address is then written to the register. This is achieved by:

MOV AX, _shm_seg
MOV DX,CPL_PORT
OUT DX,AX
where CPL_PORT is the I/O address of the register.

Testing Careplane

The first message sent to the board requests the UNIT STATUS (see Appendix A for format of the messages). This is a check to ensure that Careplane is online. If an error is received, a RESET and TEST message is sent to invoke the board's self-test procedures. Assuming there is no hardware fault, the results are returned and may be checked. In the event of hardware failure, the board has a set of LED's that give additional status information. This completes the user initialisation of Careplane. The board is now ready to transmit and receive data on the SDN.

Writing requests to Careplane

The various Careplane messages are of variable length, but the UNIT STATUS and RESET and TEST messages are both 2 words long, with the first byte specifying the message type, the remaining 3 bytes are zeroes. These requests are coded as arrays of type byte, a user-defined alias for unsigned char. For example, the Unit Status message is coded as:
byte UnitStatus[] = {0xAF,0,0,0}; where 0xAF is the message type. This request is written to the shared memory by function void CplWrite(byte *address, unsigned int n), defined in the assembler module. It takes 2 arguments:

The request is copied into the transmit buffer a word at a time using LODSW and STOSW instructions (figure 2.4). The SI and DI registers are used to hold the segment offset addresses, with the shared memory as the destination (ES:DI) and the address of the Unit message and number of words to write as the source (DS:SI)

Figure 2.4 Writing Requests to Careplane
The address of the request and the number of words to write are passed as parameters on the stack, and accessed using the DS:SI and ES:DI registers.

A Semaphore is used to protect the shared memory from simultaneous writing and reading. The detailed implementation is discussed below in the section "Sempahores". The following code copies a word from the message to shared memory:

do_write:	LODSW			;get word from address in SI
		XCHG AH,AL		;reverse the bytes
                STOSW			;write to transmit buffer
                CMP DI,xmit_lastb    	;last byte of sh_mem reached?
                JBE w_next		; no, write next word
                MOV DI,ES:[XMIT_START]	; yes, wrap to start of buffer
                SHL DI,1		;offset in bytes
w_next:		LOOP do_write

Finally, ES:[XMIT_NEXT] is updated to point to the next free word, and ES:[XMIT_COUNT] is incremented by 2 to notify Careplane that there are 2 words to read in the transmit buffer.

Reading Responses from Careplane

Responses are read by function:
unsigned int CplRead(byte *address, unsigned int n)
with 2 arguments:

It returns the number of words read.

The method for reading from the receive buffer is similar to that for writing to it, with one important difference. LODSW and STOSW are used to transfer words from the receive buffer to the calling function, but the source and destination are now reversed (figure 2.5)

Figure 2.5 Reading messages from the receive buffer
As an example, the response to a Unit Status message is transferred from the receive buffer to another location in memory, using the SI and DI registers as offset addresses to the DS and ES segment registers, respectively. The value in the DX is used as a counter.
To swap the ES and DS registers, the stack is used:

PUSH DS
POP ES
MOV DS,shm_seg

When a message has been sent to Careplane, the response is not immediate, so CplRead() has a short timeout built in to allow for this. If no data is forthcoming after 100 decrement operations, the value 0 is returned. Attempts to read a greater number of words than are available in the buffer are caught by the following code:

r_compare:	CMP DX,DS:[RECV_COUNT]	;Compare no.of words to read with
					; no.in buffer
		JG r_end		;Not enough words in buffer->exit

A count of the number of words read is kept in the CX register, and at the end of the procedure, DS:[RECV_COUNT] is updated to reflect this. A read operation ends by setting DS:[RECV_NEXT] back to the point where the message began. As a consequence, each time Careplane sends a message, the data overwrites the previous message, limiting the amount of time for reading.

Semaphores

Together, the host's 80486 processor and Careplane form a tightly coupled dual-processor system, but simultaneous shared-memory access must be prevented, otherwise the values in the SMCB may be corrupted. The transmit and receive buffers are therefore protected by semaphores, and each process must perform its read or write operation within a critical section. It is important that the test and setting of the semaphore occurs within a single unbroken processor cycle, otherwise it would be possible for both processors to read the semaphore at the same time, and to enter the critical sections simultaneously. Fortunately, the 8086 instruction set has a Read-Modify-Write opcode: XCHG that exchanges the contents of 2 operands in a non-dividable cycle.
The procedure for writing is as follows:

Similarly, for reading:

In the case of the receive buffer, a short timeout is coded to wait for the semaphore to unlock. The actual implementation, in assembler, is as follows:

Transmit semaphore


w_wait:		MOV AX,0FFFFH		;set all 16 bits of XMIT_SEM
					; (can use 8000H, as only bit 15 is required
		XCHG AX,ES:[XMIT_SEM]	;
		CMP AX,0		; wait for XMIT_SEM to unlock
		JNE w_wait		; XCHG locks it with 0xFFFF

Receive Semaphore


		MOV BX,100		;TimeOut value
r_wait:		DEC BX
		JE r_no_data		;TimeOut -> no data in buffer
		MOV AX,0FFFFH
		XCHG AX,DS:[RECV_SEM]	;check semaphore
		CMP AX,0		; locked?
		JNE r_wait		;  yes,decrement timer and loop

Timed Buffer Read from Calling Function

On occasion, there may be a considerably longer delay before a request is acknowledged, or data is forthcoming from Careplane. A longer timeout is built into the calling function to allow for this. Each time a buffer read is requested, CplRead() is called from within:
int TimedCplRead(byte Buffer[],int NumWords)
which returns 0 if no response is received after 12 seconds. This indicates that there is an internal problem with Careplane, and a re-start is required. The timeout value should not be less than six seconds, as this is the time required for Careplane to run through its self-test procedures after a RESET and TEST request.

Termination of the Communication Link

The fourth function in the assembler module void CplExit (void) is called at the end of the program to: