Chapter 43. Usage

MMFS is accessed through the FILEIO package which presents a standard POSIX compatible IO interface through which applications use standard open(), read(), write() and close() calls. Streaming support is provided through a small library, mmfslib, that presents a more application-friendly interface.

43.1. FILEIO Interface

MMFS supplies most of the standard file IO functionality. However, since it is optimized for supporting streamed data, it has a number of restrictions that mean that it does not always behave like a general-purpose filesystem.

  • Files may not be resized after creation and are essentially write-once/read-many. Between the initial open() and close() that creates a file it will be extended as requires. On subsequent opens, even those that specify O_WRITE, data may only be written to the existing file extent.
  • If an attempt is made to create a file that already exists, the open() will fail. Instead the file must be deleted first and may then be created anew.
  • Creating a file with O_EXCL will always fail.
  • If an attempt is made to rename a file to a filename that already exists, the rename() will fail, rather than overwriting the destination file. This includes attempting to rename the file to its own name. Instead the destination file must be deleted first.

43.2. MMFSLib

MMFS provides a simple library for handling streamed data. This comprises a small set of API function. The following sections describe the API, followed by a simple example.

43.2.1. MMFSLib API

The following functions are supported.

int mmfs_stream_open( const char *path, int flags);

Open an MMFS file for streaming. The flags argument is either MMFS_FLAGS_READ to open an existing file for reading or MMFS_FLAGS_WRITE to create a new file for writing. If the file doesn't exist (when opening for reading) or it does exist (when opening for writing) -1 will be returned and errno will be set to a suitable error code. On success a file descriptor is returned which may be used in other mmfslib calls, or in normal FILEIO calls.

The stream starts in RANDOM mode and must be set to streaming mode with a call to mmfs_stream_set_mode().

int mmfs_stream_info( int fd, mmfs_file_info *info );

This function returns information about the file. The mmfs_file_info structure contains the following fields:

buffer_size
The size of data buffers that will be exchanged using mmfs_stream_next_buffer().
multi_buffers
The number of buffers that the application may have in hand between calls to mmfs_stream_next_buffer().
file_size
The current size of the file.
max_size
The maximum size the file may grow to.
int mmfs_stream_set_mode( int fd, int mode, int stride );

Set the file mode. The mode may be one of the following:

MMFS_MODE_RANDOM
This is the default mode. A file in this mode should be accessed using the standard filesystem API.
MMFS_MODE_READ_FORWARD
In this mode the file is being read forward. The stride argument defines the number of buffers skipped between each call to mmfs_stream_next_buffer(). A stride of 1 will read the whole file sequentially. A stride of 2 will read every other buffer; a stride of 3 will read every third buffer, and so on.
MMFS_MODE_READ_BACKWARD
This is similar to MMFS_MODE_READ_FORWARD except that the file is read backwards. The stride applies in exactly the same way except, obviously, the buffers supplied move progressively backwards through the file.
MMFS_MODE_WRITE
This sets the file up for streamed writing. The stride argument is not used and is forced to 1.

Mode changes take effect immediately and apply from the file's current location. A file only becomes capable of streaming after mmfs_stream_set_mode() has been called for the first time. Changing file mode during streaming may incur a performance penalty as new data blocks are fetched. A file may not be switched from a read mode to a write mode or vice versa.

int mmfs_stream_get_mode( int fd, int *mode, int *speed );
This function returns the mode and speed previously set by a call to mmfs_stream_set_mode().
int mmfs_stream_next_buffer( int fd, void **buffer );

This function fetches the next stream buffer. The exact semantics of this function depend on the mode and the level of multi-buffering.

If the file has been set to one of the read modes, then each call returns the next buffer full of data from the file according to the direction and stride. The level of multi-buffering determines how many buffers the application may have in hand at any one time. For example, with a multi-buffering level of 2, the first two calls to this routine will return the first two buffers from the stream. The third call will return the third buffer, but will also cause the first buffer to become invalid and be returned to the filesystem for reuse. The fourth call will return the fourth buffer but will also invalidate the second buffer, and so on through the stream.

If the file has been set to the write mode, then each call returns an empty buffer for the application to fill with data. The multi-buffering level determines when the buffers will be written to the file. For example, with a multi-buffering level of 2, the first two calls will return an empty buffer each. The third call will cause the first buffer to be written to the file and will return a new empty buffer to replace it. The fourth call will cause the second buffer to be written to the file and a new buffer to be returned, and so on.

int mmfs_stream_set_data( int fd, void *buffer );
This function sets the per-directory entry data on the file. The buffer argument must point to MMFS_DATASIZE bytes of data that will be written into the directory entry.
int mmfs_stream_get_data( int fd, void *buffer );
This function reads the per-directory entry data on the file. The buffer argument must point to MMFS_DATASIZE bytes of memory that will be set to the data read from the directory entry.
int mmfs_stream_close( int fd );
This function closes the file. Any buffers still in possession of the application will be invalidated. If the file was open for writing the contents of these buffers will be written to the file.

43.2.2. Example

The following code provides a very simple example of how MMFSLib should be used. The code presented here is somewhat simplified and for clarity does not contain any error checking and recovery. It is assumed that the IO devices are accessed via a simple DMA interface; clearly real devices might be more complex than this.

First, a simple routine to stream data from a device to a file for a given duration:

static void write_stream( char *name, int duration )
{
    int i;
    int fd;
    int result;
    void *buffer;
    int bufno = 0;
    int buffer_size;
    mmfs_file_info info;
    cyg_tick_count end;

    // Open the stream for writing.
    fd = mmfs_stream_open( name, MMFS_OPEN_WRITE );

    // Get stream information, we are only interested in the buffer
    // size.
    result = mmfs_stream_info( fd, &info );

    buffer_size = info.buffer_size;

    // Set the stream into streamed write mode. The filesystem in now
    // ready to stream data to this file.
    result = mmfs_stream_set_mode( fd, MMFS_MODE_WRITE, 1 );

    // Convert duration from seconds to an absolute end time in system
    // ticks.
    end = cyg_current_time() + duration*ticks_per_second;

    // Prime the device with the first set of buffers, this will start
    // the DMA transfers going.
    for( i = 0; i < CYGNUM_FS_MMFS_MULTI_BUFFER; i++ )
    {
        result = mmfs_stream_next_buffer( fd, &buffer );

        dma_start( &input, bufno, DMA_READ, buffer, buffer_size );

        bufno++;
        if( bufno >= CYGNUM_FS_MMFS_MULTI_BUFFER ) bufno = 0;
    }

    // Wait for the first buffer to fill.
    dma_wait( &input, bufno );

    // Now stream to the file for the given duration.
    while( cyg_current_time() < end )
    {
        // Fetch a new buffer from MMFS. As a side effect this also
        // invalidates the oldest buffer, which will be the one that
        // has just finished its DMA transfer.
        result = mmfs_stream_next_buffer( fd, &buffer );

        // Set up a DMA transfer from the device
        dma_start( &input, bufno, DMA_READ, buffer, buffer_size );

        bufno++;
        if( bufno >= CYGNUM_FS_MMFS_MULTI_BUFFER ) bufno = 0;

        // Wait for the next device buffer to complete.
        dma_wait( &input, bufno );
    }

    // Wait for remaining buffers to finish
    for( i = 0; i < CYGNUM_FS_MMFS_MULTI_BUFFER; i++ )
    {
        bufno++;
        if( bufno >= CYGNUM_FS_MMFS_MULTI_BUFFER ) bufno = 0;

        dma_wait( &input, bufno );
    }

    // Finally close the stream.
    result = mmfs_stream_close( fd );
}

The code to read a stream is very similar, although this time it is parameterized by the required stride rather than the duration:

static void read_stream( char *name, int stride )
{
    int i;
    int fd;
    int result;
    void *buffer;
    int bufno = 0;
    int buffer_size;
    mmfs_file_info info;

    // Open the stream for reading.
    fd = mmfs_stream_open( name, MMFS_OPEN_READ );

    // Get stream information, we are only interested in the buffer
    // size.
    result = mmfs_stream_info( fd, &info );

    buffer_size = info.buffer_size;

    // Set the stream into streamed read mode using the given
    // stride. The filesystem will start preparation for streaming by
    // prefetching the first data blocks up to the multi-buffer limit.
    result = mmfs_stream_set_mode( fd, MMFS_MODE_READ_FORWARD, stride );

    // Prime the device with the first set of buffers, this will start
    // the DMA transfers going.
    for( i = 0; i < CYGNUM_FS_MMFS_MULTI_BUFFER; i++ )
    {
        result = mmfs_stream_next_buffer( fd, &buffer );

        dma_start( &output, bufno, DMA_WRITE, buffer, buffer_size );

        bufno++;
        if( bufno >= CYGNUM_FS_MMFS_MULTI_BUFFER ) bufno = 0;
    }

    // Wait for the first buffer to empty.
    dma_wait( &input, bufno );

    // Now stream from the file to the device until we reach the end
    // of the file.
    for(;;)
    {
        // Fetch a new buffer full of data from MMFS. As a side effect
        // this also invalidates the oldest buffer, which will be
        // the one that has just finished its DMA transfer.
        result = mmfs_stream_next_buffer( fd, &buffer );
        if( result < 0 && errno == EEOF )
            break;

        // Set up a DMA transfer to the device
        dma_start( &output, bufno, DMA_WRITE, buffer, buffer_size );

        bufno++;
        if( bufno >= CYGNUM_FS_MMFS_MULTI_BUFFER ) bufno = 0;

        // Wait for the next device buffer to complete.
        dma_wait( &output, bufno );
    }

    // Wait for remaining buffers to finish
    for( i = 0; i < CYGNUM_FS_MMFS_MULTI_BUFFER; i++ )
    {
        bufno++;
        if( bufno >= CYGNUM_FS_MMFS_MULTI_BUFFER ) bufno = 0;

        dma_wait( &output, bufno );
    }

    // Finally close the stream.
    result = mmfs_stream_close( fd );
}

The example.c test program contains versions of both of these routines.