Neutrino 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}/sbin 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 should first try our “generic” driver (devf-generic). This driver often — but not always — works with standard flash hardware. The driver 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 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.
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.
Like all Neutrino 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.
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 libfs-flash3.a library.
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.
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.
bsp_working_dir/src/hardware/flash/mtd-flash contains source for the libmtd-flash.a library. |
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.
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 files are organized as follows:
The following pathnames apply to the flash filesystems:
Pathname | Description |
---|---|
${QNX_TARGET}/usr/include/sys | Header file f3s_mtd.h. |
${QNX_TARGET}/usr/include/fs | Header files f3s_api.h, f3s_socket.h, and f3s_flash.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:
For example, to create a driver called myboard based on the 800FADS board example, you would:
cd bsp_working_dir/hardware/flash/boards mkdir myboard cp -cRv 800fads myboard cd myboard make clean
The copy command (cp) specifies 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.
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.
You should use the following command to make the driver:
make F3S_VER=3 MTD_VER=2
For more information, see the technical note Migrating to the New Flash Filesystem.
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:
/* ** File: main.c for 800FADS board */ #include <sys/f3s_mtd.h> #include "f3s_800fads.h" 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 }, { /* mandatory last entry */ 0, 0, 0, 0, 0 } }; static f3s_flash_v2_t flash[] = { { sizeof(f3s_flash_v2_t), f3s_a29f040_ident, /* Common Ident */ f3s_a29f040_reset, /* Common Reset */ /* v1 Read/Write/Erase/Suspend/Resume/Sync (Unused) */ NULL, NULL, NULL, NULL, NULL, NULL, NULL, /* v2 Read (Use default) */ f3s_a29f040_v2write, /* v2 Write */ f3s_a29f040_v2erase, /* v2 Erase */ f3s_a29f040_v2suspend, /* v2 Suspend */ f3s_a29f040_v2resume, /* v2 Resume */ f3s_a29f040_v2sync, /* v2 Sync */ /* v2 Islock/Lock/Unlock/Unlockall (not supported) */ NULL, NULL, NULL, NULL }, { /* mandatory last entry */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }; /* 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 <fs/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 <fs/f3s_flash.h>, contains function pointers to the flash services routines.
The f3s_init() and f3s_start() functions are defined in the <fs/f3s_api.h> header file.
Don't use the <fs/f3s_socket.h>, <fs/f3s_flash.h>, and <fs/f3s_api.h> header files directly. Instead, you should include <sys/f3s_mtd.h> for backward and forward compatibility. |
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_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.
The socket services interface, defined in the <fs/f3s_socket.h> header file, consists of the following functions:
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.
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. On error, it should return NULL and set errno to ERANGE.
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.
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:
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 */ _Uint16t struct_size; /* size of this structure */ _Uint16t status; /* status of this structure */ _Uint8t *option; /* option string from flashio */ _Uint16t socket_index; /* index of socket */ _Uint16t window_index; /* index of window */ /* * these fields are initialized by the socket services and later * referenced by the flash file system */ _Uint8t *name; /* name of driver */ _Paddr64t address; /* physical address 0 for allocated */ _Uint32t window_size; /* size of window power of two mandatory */ _Uint32t array_offset; /* offset of array 0 for based */ _Uint32t array_size; /* size of array 0 for window_size */ _Uint32t unit_size; /* size of unit 0 for probed */ _Uint32t flags; /* flags for capabilities */ _Uint16t bus_width; /* width of bus */ _Uint16t window_num; /* number of windows 0 for not windowed */ /* * these fields are initialized by the socket services and later * referenced by the socket services */ _Uint8t* memory; /* access pointer for window memory */ void *socket_handle; /* socket handle pointer for external library */ void *window_handle; /* window handle pointer for external library */ /* * this field is modified by the socket services as different window * pages are selected */ _Uint32t window_offset; /* offset of window */ } f3s_socket_t;
Here's a description of the fields:
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:
int f3s_socket_option (f3s_socket_t *socket)
Parse the driver command-line options that apply to the socket services.
Currently the following options are defined:
-s baseaddress,windowsize, arrayoffset, arraysize, unitsize, buswidth, interleave
where:
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.
The flash services interface, defined in the <fs/f3s_flash.h> header file, consists of the following functions:
The values for the flags parameter are defined in <fs/s3s_flash.h>. The most important one is F3S_VERIFY_WRITE. If this is set, the routine must perform a read-back verification after the write as a double check that the write succeeded. Occasionally, however, the hardware reports success even when the write didn't work as expected. |
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.
void f3s_reset (f3s_dbase_t *dbase, f3s_access_t *access, uint32_t text_offset)
Resets the flash device at address text_offset into the default read-mode after calling the fs3_ident() function or after a device error.
int32_t f3s_v2read (f3s_dbase_t *dbase, f3s_access_t *access, _Uint32t flags, _Uint32t text_offset, _Int32t buffer_size, _Uint8t *buffer);
This optional function is called to read buffer_size bytes from address text_offset into buffer. Normally the flash devices will be read directly via memcpy().
On success, it should return the number of bytes read. If an error occurs, it should return -1 with errno set to one of the following:
int32_t f3s_v2write (f3s_dbase_t *dbase, f3s_access_t *access, _Uint32t flags, _Uint32t text_offset, _Int32t buffer_size, _Uint8t *buffer);
This function writes buffer_size bytes from buffer to address text_offset.
On success, it should return the number of bytes written. If an error occurs, it should return -1 with errno set to one of the following:
int f3s_v2erase (f3s_dbase_t *dbase, f3s_access_t *access, _Uint32t flags, _Uint32t text_offset);
This function begins erasing the flash block containing the text_offset. It can optionally determine if an error has already occurred, or it can just return EOK and let f3s_v2sync() detect any error.
On success, it should return EOK. If an error occurs, it should return one of the following:
int f3s_v2suspend (f3s_dbase_t *dbase, f3s_access_t *access, _Uint32t flags, _Uint32t text_offset);
This function suspends an erase operation, when supported, for a read or for a write.
On success, it should return EOK. If an error occurs, it should return one of the following:
int f3s_v2resume (f3s_dbase_t *dbase, f3s_access_t *access, _Uint32t flags, _Uint32t text_offset);
This function resumes an erase operation after a suspend command has been issued.
On success, it should return EOK. If an error occurs, it should return one of the following:
int f3s_v2sync (f3s_dbase_t *dbase, f3s_access_t *access, _Uint32t flags, _Uint32t text_offset);
This function determines whether an erase operation has completed and returns any detected error.
On success, it should return EOK. If an error occurs, it should return one of the following:
int f3s_v2islock (f3s_dbase_t *dbase, f3s_access_t *access, _Uint32t flags, _Uint32t text_offset);
This function determines whether the block containing the address text_offset can be written to (we term it as success) or not.
On success, it should return EOK. If the block cannot be written to, it should return EROFS. Otherwise, an error has occurred and it should return one of the following:
int f3s_v2lock (f3s_dbase_t *dbase, f3s_access_t *access, _Uint32t flags, _Uint32t text_offset);
This function write-protects the block containing the address text_offset (if supported). If the block is already locked, it does nothing.
On success, it should return EOK. If an error occurs, it should return one of the following:
int f3s_v2unlock (f3s_dbase_t *dbase, f3s_access_t *access, _Uint32t flags, _Uint32t text_offset);
This function clears write-protection of the block containing the address text_offset (if supported). If the block is already unlocked, it does nothing. Note that some devices do not support unlocking of arbitrary blocks. Instead all blocks must be unlocked at the same time. In this case, use f3s_v2unlockall() instead.
On success, it should return EOK. If an error occurs, it should return one of the following:
int f3s_v2unlockall (f3s_dbase_t *dbase, f3s_access_t *access, _Uint32t flags, _Uint32t text_offset);
This function clears all write-protected blocks on the device containing the address text_offset. Some boards use multiple chips to form one single logical device. In this situation, each chip will have f3s_v2unlockall() invoked on it separately.
On success, it should return EOK. If an error occurs, it should return one of the following:
We currently don't support user-customized flash services, nor do we supply detailed descriptions of the flash services implementation. |
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_v2erase(), you'd use the f3s_iCFI_v2erase() routine.
For more information, see the technical note Choosing the correct MTD Routine for the Flash Filesystem.
The file <sys/f3s_mtd.h> can be found in:
bsp_working_dir/src/hardware/flash/mtd-flash/public/sys/f3s_mtd.h. |
This driver uses main memory rather than flash for storing the flash filesystem. Therefore, the filesystem is not persistent — all data is lost when the system reboots or /dev/shmem/fs0 is removed. This driver is used mainly for test purposes.
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 ** */ #include "f3s_ram.h" 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 }, { /* mandatory last entry */ 0, 0, 0, 0, 0 } }; static f3s_flash_v2_t flash[] = { { sizeof(f3s_flash_v2_t), f3s_sram_ident, /* Common Ident */ f3s_sram_reset, /* Common Reset */ NULL, /* v1 Read (Deprecated) */ NULL, /* v1 Write (Deprecated) */ NULL, /* v1 Erase (Deprecated) */ NULL, /* v1 Suspend (Deprecated) */ NULL, /* v1 Resume (Deprecated) */ NULL, /* v1 Sync (Deprecated) */ NULL, /* v2 Read (Use default) */ f3s_sram_v2write, /* v2 Write */ f3s_sram_v2erase, /* v2 Erase */ NULL, /* v2 Suspend (Unused) */ NULL, /* v2 Resume (Unused) */ f3s_sram_v2sync, /* v2 Sync */ f3s_sram_v2islock, /* v2 Islock */ f3s_sram_v2lock, /* v2 Lock */ f3s_sram_v2unlock, /* v2 Unlock */ f3s_sram_v2unlockall /* v2 Unlockall */ }, { /* mandatory last entry */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }; /* init f3s */ f3s_init(argc, argv, (f3s_flash_t *)flash); /* start f3s */ error = f3s_start(service, (f3s_flash_t *)flash); return (error); }
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 ** */ #include "f3s_ram.h" int32_t f3s_ram_open(f3s_socket_t *socket, uint32_t flags) { static void * memory; char name[8]; int fd; int flag; /* check if not initialized */ if (!memory) { /* get io privileges */ ThreadCtl(_NTO_TCTL_IO, NULL); /* setup socket name */ socket->name = "RAM (flash simulation)"; /* check if there are socket options */ if (f3s_socket_option(socket)) socket->window_size = 1024 * 1024; /* check if array size was not chosen */ if (!socket->array_size) socket->array_size = socket->window_size; /* check if array size was not specified */ if (!socket->array_size) return (ENXIO); /* set shared memory name */ sprintf(name, "/fs%X", socket->socket_index); /* open shared memory */ fd = shm_open(name, O_CREAT | O_RDWR, 0777); if (fd < 0) return (errno); /* set size of shared memory */ flag = ftruncate(fd, socket->array_size); if (flag) { close(fd); return (errno); } /* map physical address into memory */ memory = mmap(NULL, socket->array_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, socket->address); if (!memory) { close(fd); return (errno); } /* copy socket handle */ socket->socket_handle = (void *)fd; } /* set socket memory pointer to previously initialized value */ socket->memory = memory; return (EOK); }
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 ** */ #include "f3s_ram.h" 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->window_size) { errno = ERANGE; 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); }
The socket services status() and close() don't do anything interesting in this driver.