How to implement synchronised communication with driver callback functions

If you have a simple question and just want an answer.
Post Reply
User avatar
Thomas
Experienced Member
Posts: 66
Joined: Fri Feb 05, 2010 12:34 pm

How to implement synchronised communication with driver callback functions

Post by Thomas »

Drivers or other higher level protocols are sometimes integrated with the underlying HW by implementing callback functions.
I.e. The river is implemented in a self contained or even closed SW library.
Callback functions are implemented on the application level to exchange data between the HW interface and the driver.
This is illustrated here: https://en.wikipedia.org/wiki/Callback_ ... ogramming)

HW interfaces are implemented in SW on XMOS Processors and the communication facilitated over channels or interfaces.
This brings the advantage that the communication is implicitly synchronous and race conditions between multiple tasks are prevented.

This example shows how synchronised communication between a HW interface (e.g. USB, I2C, etc) and a driver callback function can be implemented.

The challenge is that the channel that facilitates the synchronised communication cannot be passed to the driver callback function because this C function can not be modified.
The solution is storing the chanend passed to the driver_task at the application level into a global variable.
This global variable is then passed to a function get_data_from_HW by the driver_get_data_callback function.

get_data_from_HW implements the client side of the channel protocol.
It receives the num_bytes to read and the pointer to the Driver Buffer from the callback function.
Then it transmits that data over the channel prot_read_sync to the hw_interface_task.

hw_interface_task implements the server side of the channel protocol and copies the data from the HW buffer into the Driver Buffer.
Finally hw_interface_task synchronises with get_data_from_HW (which runs on the driver_task) by sending the number of bytes that were actually read back over the channel prot_read_sync.

After that synchronised data exchange both the HW interface (hw_interface_task) and Driver (driver_task) carry on with other work.

This concept can be extended to more elaborate buffering schemes.
E.g. by using a double buffer to minimise the time taken for the synchronised communication.

Here is the code:
1. driver_task.c

Code: Select all

#include <xccompat.h>
#include <stdint.h>
#include <stdio.h>

unsigned global_chanend;

extern unsigned get_data_from_HW(chanend prot_read_sync, unsigned char* data, unsigned num_bytes);

// Implementation of driver callback function.
uint16_t driver_get_data_callback(void* data, uint16_t num_bytes) {
  return get_data_from_HW(global_chanend, (unsigned char*) data, (unsigned) num_bytes);
}

void driver_task(chanend prot_read_sync) {
	// store the chanend into a global variable that can be passed to get_data_from_HW. See above.
    global_chanend = prot_read_sync;

    char driver_buffer[20];

    // Driver calls the callback function.
    driver_get_data_callback(driver_buffer, 20);

    printf("Protocol Read driver_buffer:\n");
    for(unsigned i=0; i<20; ++i) printf("%d\n",driver_buffer[i]);

}

2. application.xc

Code: Select all

#include <xs1.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

// Implements the client side of the protocol over the channel prot_read_sync:
unsigned get_data_from_HW(chanend prot_read_sync, unsigned char* data, unsigned num_bytes) {
  prot_read_sync <: num_bytes; // initiate read by sending num_bytes
  prot_read_sync <: (unsigned) data;   // send data pointer as uint to satisfy compiler

  prot_read_sync :> num_bytes; // sync and get actual number of bytes
  return num_bytes;
}

unsafe {
void hw_interface_task(chanend prot_read_sync) {
    char hw_buffer[20] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19};
    while(1) {
        unsigned num_bytes;
        select {
            // Implements the server side of the protocol over the channel prot_read_sync:
            case prot_read_sync :> num_bytes:
                printf("Read of %d bytes initiated\n", num_bytes);
                unsigned driver_buffer_addr;
                prot_read_sync :> driver_buffer_addr;
                // copy HW buffer into Driver Buffer
                memcpy((unsigned char*) driver_buffer_addr, hw_buffer, num_bytes);
                prot_read_sync <: num_bytes; // sync end of transfer
                break;
        }
    }
}
}

extern void driver_task(chanend prot_read_sync);

int main() {
    chan prot_read_sync; // channel to sync reads from protocol and communicate num_bytes and pointer to read data buffer
    par {
        hw_interface_task(prot_read_sync);
        driver_task(prot_read_sync);
    }
    return 0;
}



Post Reply