[Previous] [Contents] [Index] [Next]

Customizing the Flash Filesystem

Introduction

QNX ships with a small number of prebuilt flash filesystem drivers for particular embedded systems. For the currently available drivers, look in the ${QNX_TARGET}/${PROCESSOR}/bin directory. The flash filesystem drivers are named devf-system, where system is derived from the name of the embedded system. You'll find a general description of the flash filesystem in the System Architecture book and descriptions of all the flash filesystem drivers in the Utilities Reference.

If a driver isn't provided for your particular target embedded system, you may be able to use our "generic" driver (devf-generic). This driver works with standard flash hardware -- it assumes a supported memory technology driver (MTD) and linear memory addressing.

If none of our drivers works for your hardware, you'll need to build your own driver. We provide all the source code needed for you to customize a flash filesystem driver for your target. After installation, look in the bsp_working_dir/src/hardware/flash/boards directory -- you'll find a a subdirectory for each board we support.

Besides the boards directory, you should also refer to the following sources to find out what boards/drivers we currently support:

Note that we currently support customizing a driver only for embedded systems with onboard flash memory (also called a resident flash array or RFA). If you need support for removable media like PCMCIA or compact or miniature memory cards, then please contact us.

Driver structure

Every flash filesystem driver consists of the following components:

When customizing the flash filesystem driver for your system, you'll be modifying the main() routine for the flash filesystem and providing an implementation of the socket services component. The other components are supplied as libraries to link into the driver.


Structure of a flash filesystem driver


Structure of the flash filesystem driver.


resmgr and iofunc layers

Like all QNX device managers, the flash filesystem uses the standard resmgr/iofunc interface and accepts the standard set of resource manager messages. The flash filesystem turns these messages into read, write, and erase operations on the underlying flash devices.

For example, an open message would result in code being executed that would read the necessary filesystem data structures on the flash device and locate the requested file. A subsequent write message will modify the contents of the file on flash. Special functions, such as erasing the flash device, are implemented using devctl messages.

Flash filesystem component

The flash filesystem itself is the "personality" component of the flash filesystem driver. The filesystem contains all the code to process filesystem requests and to manage the filesystem on the flash devices. The socket and flash services components are used by the flash filesystem to access the flash devices.

The code for the flash filesystem component is platform-independent and is provided in the libio-flash.a library.

Socket services component

The socket services component is responsible for any system-specific initialization required by the flash devices at startup and for providing addressability to the flash devices (this applies mainly to windowed flash interfaces).

Before reading/writing the flash device, other components will use socket services to make sure the required address range can be accessed. On systems where the flash device is linearly mapped into the processor address space, addressability is trivial. On systems where the flash is either bank-switched or hidden behind some other interface (such as PCMCIA), addressability is more complicated.

The socket services component is the one that will require the most customization for your system.

Flash services component

The flash services component contains the device-specific code required to write and erase particular flash devices. This component is also called the memory technology driver (MTD).

The directory ${QNX_TARGET}/${PROCESSOR}/lib contains the MTD library libmtd-flash.a to handle the flash devices we support.

Probe routine component

The probe routine uses a special algorithm to estimate the size of the flash array. Since the source code for the probe routine is available, you should be able to readily identify any failures in the sizing algorithm.

Building your flash filesystem driver

Before you start customizing your own flash filesystem driver, you should examine the source of all the sample drivers supplied. Most likely, one of the existing drivers can be easily customized to support your system. If not, the devf-ram source provides a good template to start with.

The source tree

The QNX source files are organized as follows:


Figure showing the flash directory structure


Flash directory structure.


The following pathnames apply to the flash filesystems:

Pathname Description
${QNX_TARGET}/usr/include/sys Header files f3s_api.h, f3s_socket.h, f3s_flash.h, and f3s_mtd.h.
${QNX_TARGET}/${PROCESSOR}/lib Libraries for flash filesystem and flash services.
bsp_working_dir/src/hardware/flash/boards Source code for socket services.
bsp_working_dir/src/hardware/flash/mtd-flash Source code for flash services as well as for probe routine and helper functions.

Before you modify any source, you should:

  1. Create a new directory for your driver in the bsp_working_dir/src/hardware/flash/boards directory.
  2. Copy the files from the sample directory you want into your new directory.

For example, to create a driver called myboard based on the 800FADS board example, you would:

cd bsp_working_dir/src/hardware/flash/boards
mkdir myboard
cp -cRv 800fads myboard
cd myboard
make clean

The copy command (cp) specified a recursive copy (the -R option). This will copy all files from the specified source directory including the subdirectory indicating which CPU this driver should be built for. In our example above, the 800fads directory has a ppc subdirectory -- this will cause the new driver (myboard in our example) to be built for the PowerPC.

The Makefile

When you go to build your new flash filesystem driver, you don't need to change the Makefile. Our recursive makefile structure ensures you're linking to the appropriate libraries.

The main() function

The main() function for the driver, which you'll find in the main.c file in the sample directories, is the first thing that needs to be modified for your system. Let's look at the main.c file for the 800FADS board example:

#include <sys/f3s_mtd.h>

/*
** Function: main
*/

int main(int argc, char **argv)
{
  int error;

  static f3s_service_t service[]=
  {
    {
      sizeof(f3s_service_t),
      f3s_800fads_open,
      f3s_800fads_page,
      f3s_800fads_status,
      f3s_800fads_close
    },
    {
      0, 0, 0, 0, 0  /* mandatory last entry */
    }
  };

  static f3s_flash_t flash[]=
  {
    {
      sizeof(f3s_flash_t),
      f3s_a29f040_ident,
      f3s_a29f040_reset,
      NULL,
      f3s_a29f040_write,
      f3s_a29f040_erase,
      f3s_a29f040_suspend,
      f3s_a29f040_resume,
      f3s_a29f040_sync
    },
    {
      0, 0, 0, 0, 0, 0, 0, 0, 0  /* mandatory last entry */
    }
  };

  /* init f3s */

  f3s_init(argc, argv, flash);

  /* start f3s */

  error=f3s_start(service, flash);

  return error;
}

The service array contains one or more f3s_service_t structures, depending on how many different sockets your driver has to support. The f3s_service_t structure, defined in <sys/f3s_socket.h>, contains function pointers to the socket services routines.

The flash array contains one or more f3s_flash_t structures, depending on how many different types of flash device your driver has to support. The f3s_flash_t structure, defined in <sys/f3s_flash.h>, contains function pointers to the flash services routines.

The f3s_init() and f3s_start() functions are defined in the <sys/f3s_api.h> header file:

f3s_init()

f3s_init (int argc,
          char **argv,
          f3s_flash_t *flash_vect)

This function passes the command-line arguments to the flash filesystem component, which then initializes itself.

f3s_start()

f3s_start (f3s_service_t *service,
           f3s_flash_t *flash)

This function passes the service and flash arrays to the filesystem component so it can make calls to the socket and flash services, and then starts the driver. This function returns only when the driver is about to exit.

When writing your main.c, you'll need to enter:

If you have a system with only one socket consisting of the same flash devices, then there will be only a single entry in each array.

Socket services interface

The socket services interface, defined in the sys/f3s_socket.h header file, consists of the following functions:

f3s_open()

int32_t f3s_open (f3s_socket_t *socket, 
                  uint32_t flags)

This function is called to initialize a socket or a particular window in a socket. The function should process any socket options, initialize and map in the flash devices, and initialize the socket structure.

f3s_page()

uint8_t f3s_page (f3s_socket_t *socket,
                  uint32_t flags,
                  uint32_t offset,
                  int32_t *size)

This function is called to access a window_size sized window at address offset from the start of the device; it must be provided for both bank-switched and linearly mapped flash devices. If the size parameter is non-null, you should set it to the size of the window. The function must return a pointer suitable for accessing the device at address offset.

f3s_status()

int32_t f3s_status (f3s_socket_t *socket,
                    uint32_t flags)

This function is called to get the socket status. It's used currently only for interfaces that support dynamic insertion and removal. For onboard flash, you should simply return EOK.

f3s_close()

void f3s_close (f3s_socket_t *socket,
                uint32_t flags)

This function is called to close the socket. If you need to, you can disable the flash device and remove any programming voltage, etc.

The following flags are defined for the flags parameter in the socket functions:

F3S_POWER_VCC
Apply read power.
F3S_POWER_VPP
Apply program power.
F3S_OPER_SOCKET
Operation applies to socket given in socket_index.
F3S_OPER_WINDOW
Operation applies to window given in window_index.

The socket parameter is used for passing arguments and returning results from the socket services and for storing information about each socket. To handle complex interfaces such as PCMCIA, the structure has been defined so that there can be more than one socket; each socket can have more than one window. A simple linear flash array would have a single socket and no windows.

The socket structure is defined as:



typedef struct f3s_socket_s
{
  /*
   * these fields are initialized by the flash file system and
   * later validated and set by the socket services
   */

  uint16_t struct_size;   /* size of this structure */
  uint16_t status;        /* status of this structure */
  uint8_t *option;        /* option string from flashio */
  uint16_t socket_index;  /* index of socket */
  uint16_t window_index;  /* index of window */

  /*
   * these fields are initialized by the socket services and
   * later referenced by the flash file system
   */

  uint8_t *name;          /* name of driver */
  paddr_t address;        /* physical address 0 for allocated */
  uint32_t window_size;   /* size of window */
  uint32_t array_size;    /* size of array 0 for unknown */
  uint32_t unit_size;     /* size of unit 0 for probed */
  uint32_t flags;         /* flags for capabilities */
  uint16_t bus_width;     /* width of bus */
  uint16_t window_num;    /* number of windows 0 for not windowed */

  /*
   * these fields are initialized by the socket services and later
   * referenced by the socket services
   */

  uint8_t* memory;     /* access pointer for window memory */
  void *socket_handle; /* socket handle pointer for external lib */
  void *window_handle; /* window handle pointer for external lib */

  /*
   * this field is modified by the socket services as different
   * window pages are selected
   */

  uint32_t window_offset;  /* offset of window */
}
f3s_socket_t;

Here's a description of the fields:

option
Option string from command line; parse using the f3s_socket_option() function.
socket_index
Current socket.
window_index
Current window.
name
String containing name of driver.
address
Base address of flash array.
window_size
Size of window in bytes.
array_size
Size of array in bytes; 0 indicates unknown.
unit_size
Size of unit in bytes; 0 indicates probed.
flags
The flags field is currently unused.
bus_width
Width of the flash devices in bytes.
window_num
Number of windows in socket; 0 indicates non-windowed.
memory
Free for use by socket services; usually stores current window address.
socket_handle
Free for use by socket services; usually stores pointer to any extra data for socket.
window_handle
Free for use by socket services; usually stores pointer to any extra data for window.
window_offset
Offset of window from base of device in bytes.

Options parsing

The socket services should parse any applicable options before initializing the flash devices in the f3s_open() function. Two support functions are provided for this:

f3s_socket_option()

int f3s_socket_option (f3s_socket_t *socket)

Parse the driver command-line options that apply to the socket services.

Currently only a single option is defined:

-s address,size

where address is the base address of the socket/window and size is the size of the socket/window.

f3s_socket_syspage()

int f3s_socket_syspage (f3s_socket_t *socket)

Parse the syspage options that apply to the socket services.

The syspage options allow the socket services to get any information about the flash devices in the system that is collected by the startup program and stored in the syspage. See the chapter on Customizing Image Startup Programs for more information.

Flash services interface

The flash services interface, defined in the <sys/f3s_flash.h> header file, consists of the following functions:

f3s_ident()

int32_t f3s_ident (f3s_dbase_t *dbase,
                   f3s_access_t *access,
                   uint32_t text_offset,
                   uint32_t flags)

Identifies the flash device at address text_offset and fills in the dbase structure with information about the device type and geometry.

f3s_reset()

void f3s_reset (f3s_dbase_t *dbase,
                f3s_access_t *access,
                uint32_t text_offset)

Resets the flash devices into the default read-mode after calling the fs3_ident() function or after a device error.

f3s_read()

int32_t f3s_read (f3s_dbase_t *dbase,
                  f3s_access_t *access,
                  uint32_t text_offset,
                  int32_t buffer_size,
                  uint8_t *buffer)

This optional function is called to read buffer_size bytes from address text_offset into buffer. Normally the flash devices will be read from directly.

f3s_write()

int32_t f3s_write (f3s_dbase_t *dbase,
                   f3s_access_t *access,
                   uint32_t text_offset,
                   uint32_t flags,
                   int32_t buffer_size,
                   uint8_t *buffer)

Writes buffer_size bytes from buffer to address text_offset.

f3s_erase()

void f3s_erase (f3s_dbase_t *dbase,
                f3s_access_t *access,
                uint32_t text_offset,
                int32_t unit_size)

Erases unit_size bytes starting with the block at address text_offset.

f3s_suspend()

int32_t f3s_suspend (f3s_dbase_t *dbase,
                     f3s_access_t *access,
                     uint32_t text_offset)

Suspends an erase operation (if supported by device).

f3s_resume()

void f3s_resume (f3s_dbase_t *dbase,
                 f3s_access_t *access,
                 uint32_t text_offset)

Resumes an erase operation (if supported by device).

f3s_sync()

int32_t f3s_sync (f3s_dbase_t *dbase,
                  f3s_access_t *access,
                  uint32_t text_offset,
                  int32_t unit_size)

Checks whether an erase operation has completed.

The dbase structure is used to store information about the flash device, such as JEDEC number, write and erase times, and geometry. The access structure contains the socket structure and pointer to the socket services functions.


Note: Please note that since we currently don't support user-customized flash services, we don't supply detailed descriptions of the flash services implementation.

Choosing the right routines

We provide several device-specific variants of the core set of flash services:

For example, if you have a 16-bit Intel device and you want to use f3s_erase(), you'd use the f3s_i28f008x16_erase() routine.

To choose the right routine for your particular device, please refer to the header file <sys/f3s_mtd.h>, which lists all the device-specific routines for the various AMD and Intel devices supported.

Example: The devf-ram driver

This driver uses main memory rather than flash for storing the flash filesystem. Therefore, the filesystem is not persistent -- all data is lost as soon as the driver terminates. This driver is used mainly for test purposes.

main()

In the main() function, we declare a single services array entry for the socket services functions and a null entry for the flash services functions.

/*
** File: f3s_ram_main.c
**
** Description:
**
** This file contains the main function for the f3s 
** flash filesystem
**
*/

/*
** Includes
*/

#include "f3s_ram.h"

/*
** Function: main
*/

int main(int argc,
         char **argv)
{
  int error;
  static f3s_service_t service[]=
  {
    {
      sizeof(f3s_service_t),
      f3s_ram_open,
      f3s_ram_page,
      f3s_ram_status,
      f3s_ram_close
    },
    {
      0, 0, 0, 0, 0  /* mandatory last entry */
    }
  };
  static f3s_flash_t flash[]=
  {
    {
      sizeof(f3s_flash_t),
      f3s_sram_ident,
      f3s_sram_reset,
      NULL,
      f3s_sram_write,
      f3s_sram_erase,
      NULL,
      NULL,
      f3s_sram_sync
    },
    {
      0, 0, 0, 0, 0, 0, 0, 0, 0  /*mandatory last entry */

    }
  };

  /* init f3s */

  f3s_init(argc, argv);

  /* start f3s */

  error=f3s_start(service, flash);

  return error;
}

/*
** End
*/

f3s_ram_open()

In the socket services open() function, we assign a name for the driver and then process any options. If no options are specified, a default size is assigned and the memory for the (virtual) flash is allocated.

/*
** File: f3s_ram_open.c
**
** Description:
**
** This file contains the open function for the ram library
**
*/

/*
** Includes
*/

#include <sys/mman.h>
#include <unistd.h>
#include "f3s_ram.h"

/*
** Function: f3s_ram_open
*/

int32_t f3s_ram_open(f3s_socket_t *socket,
                     uint32_t flags)
{
  int fd;
  char name[8];
  static void *memory;

  /* check if not initialized */

  if (!memory)
  {

    /* set up devf-ram socket name */

    socket->name="RAM (flash simulation)";

    /* check if there are socket options */

    if (f3s_socket_option(socket))
    {

      /* setup window size for ram */

      socket->window_size=1024*1024;
    }

    /* check if array size was not chosen */

    if (!socket->array_size)
    {

      /* default array size to window size */

      socket->array_size=socket->window_size;
    }

    /* set shared memory name */

    sprintf(name, "/fs%X", socket->socket_index);

    /* open shared memory */

    fd=shm_open(name, O_CREAT|O_RDWR, 0777);

    /* set size of shared memory */

    ftruncate(fd, socket->window_size);

    /* map physical address into memory */

    memory=mmap(NULL, socket->window_size, PROT_READ|PROT_WRITE,
     MAP_SHARED, fd, socket->address);
  }

  /* set socket memory pointer to previously initialized value */

  socket->memory=memory;

  /* everything is fine */

  return EOK;
}

/*
** End
*/

f3s_ram_page()

In the socket services page() function, we first check that the given offset doesn't exceed the bounds of the allocated memory, and then assign the window size if required. The function returns the offset address modulo the window size.

/*
** File: f3s_ram_page.c
**
** Description:
**
** This file contains the page function for the ram library
**
*/

/*
** Includes
*/

#include "f3s_ram.h"

/*
** Function: f3s_ram_page
*/

uint8_t *f3s_ram_page(f3s_socket_t *socket,
                      uint32_t flags,
                      uint32_t offset,
                      int32_t *size)
{

  /* check if offset does not fit in array */

  if (offset>=(socket->array_size-1)) return NULL;

  /* select proper page */

  socket->window_offset=offset&~(socket->window_size-1);

  /* set size properly */

  *size=min((offset&~(socket->window_size-1))+
    socket->window_size-offset, *size);

  /* return memory pointer */

  return socket->memory+(offset&(socket->window_size-1));
}

/*
** End
*/

The socket services status() and close() don't do anything interesting in this driver.


[Previous] [Contents] [Index] [Next]