Serial driver - what is a good approach?

hermarcel wrote on Monday, February 14, 2005:

I am about to start writing a general-use serial port driver for the port (wizC) I’m working on. I would like to ask you all what approach would be the most appropiate.

Receiving is simple: to avoid race-conditions, only a single task should be receiving at any time.

Xmitting is more interesting. Several tasks should be able to transmit data through a single uart. The output of these tasks might (will!) be mixed up. Of course, this is not desirable. These are the scenarios I came up with:

=================================
ResourceReservation
Before a task can start sending data to the xmit-queue, a semaphore has to be taken. After a not-to-be-interrupted chunk has been queued, the semaphore is released. At this point another task can take it’s turn by taking the semaphore.

Pro’s:
- small datasize on queue
- small ram-overhead on sending task
- simple

Con’s:
- possibly many messages to queue
- It may take long for a task to generate it’s chunk

Messagepreparing
The xmitqueue contains a pointer to the data a task wants to xmit. An entry in the queue will be processed in it’s entirety before the next queue-entry is retrieved. A method must be found so a task knows it’s message has been processed and the buffer can be re-used.

Pro’s
- small datasize on queue
- few messages on the queue
- tasks may write to the queue any time

Con’s
- (much) more ram needed
- buffer-free mechanism

How would you do this?

Marcel

rtel wrote on Monday, February 14, 2005:

> Receiving is simple: to avoid race-conditions, only a single task should be
> receiving at any time.
>
> Xmitting is more interesting. Several tasks should be able to transmit data
> through a single uart. The output of these tasks might (will!) be mixed up.
> Of course, this is not desirable. These are the scenarios I came up with:

Really this is an application design issue - and also very dependent on your available resources.  On very resource restrained systems is may be best to design so only one task transmits and one receives (can be the same task of coarse).

If this is not possible then an alternative might be to have a ‘Tx task’.  The Tx task has sole control over the COM port and no other task can access it directly.  With this scheme tasks that want to send data send the data to the Tx task (this can just be a pointer to a buffer of coarse) via a queue.   The Tx task removed data from the queue one message at a time and sends it to the COM port.  The queue mechanism ensures mutual exclusion as data is posted to the queue.  Having one task move data between the queue and the COM port ensure mutual exclusion of the hardware and ensure one message does not start to be transmitted before the last has finished.

> =================================
> ResourceReservation
> Before a task can start sending data to the xmit-queue, a semaphore has to be
> taken. After a not-to-be-interrupted chunk has been queued, the semaphore is
> released. At this point another task can take it’s turn by taking the semaphore.
>
> Pro’s:
> - small datasize on queue
> - small ram-overhead on sending task
> - simple
>
> Con’s:
> - possibly many messages to queue
> - It may take long for a task to generate it’s chunk
> =================================

This is a good solution for the reasons you say.  It can result in difficult application design - priority inversion and the like.

> Messagepreparing
> The xmitqueue contains a pointer to the data a task wants to xmit. An entry
> in the queue will be processed in it’s entirety before the next queue-entry
> is retrieved. A method must be found so a task knows it’s message has been processed
> and the buffer can be re-used.
>
> Pro’s
> - small datasize on queue
> - few messages on the queue
> - tasks may write to the queue any time
>
> Con’s
> - (much) more ram needed
> - buffer-free mechanism
> =================================

I think this is similar to the mechanism I suggest with the Tx task?  This is good in that you don’t have to copy too much data around as well.  I think this is how I did the I2C driver for the WizNET demo (I would have to refresh my memory).

>
> How would you do this?

Not the answer you wanted…because its not really an answer…but it really does depend on the particular application needs.  My approach with the demo applications is to provide a sample driver, and then note that it is not necessarily the best solution because one solution will never fit all.  I would suggest you do the same.  Have a driver using either of the mechanisms as a sample, but then you can say in the comments that there are obviously other solutions.

Regards.

hermarcel wrote on Monday, February 14, 2005:

Thanks. Not the answer I wanted, but I must admit it is the answer I expected.

Another question:
Suppose I would like to write a task that should "listen" on multiple queues. As far as I can see, I would have to check the number of messages in each queue and yield(), delay() or … and then test again.

Is there a smarter way? Perhaps a new "wait on multiple queues" api-call that returns the handle of a queue that has at least 1 msg or NULL if the wait-timeout expires?

rtel wrote on Tuesday, February 15, 2005:

As you point out, there is no way to wait on multiple queues at present.  This could be introduced as a new API call, … but …, the reason it was left out was to do with code size.  The kernel is written with size in mind and allowing a task to wait on multiple queues would require some major changes with a large efficiency impact.

An alternative:  Have the task wait on a single queue with an identifier in the queued message to say what the message is or where it came from.  For example,  if you want to receive messages from queueA and queueB you could replace the following code:

void vATask( void *pvParameters )
{
    for(;:wink:
    {
        if( something on queueA )
        {
            receive item from queueA;
        }
        if( something on queueB )
        {
            receive item from queueB;
        }
    }
}

with

#define QueueA 0
#define QueueB 1

typedef struct Q_ITEM
{
    int Q;
    void *PointerToData;
} xQData;

void vATask( void *pvParameters )
{
xQData Data;

    for(;:wink:
    {
        if( cQueueReceive( Queue, &Data, 0xffff ) )
        {
            switch( Data.Q )
            {
                 case QueueA : Process data;
                                       break;
                 case QueueB : Process data;
                                       break;
            }
        }
    }
}