Project Home
Project Home
Documents
Documents
Wiki
Wiki
Discussion Forums
Discussions
Project Information
Project Info
wiki1041: GeneralDeveloperInformation (Version 2)

QNX has been developing software for a number of years, and coding techniques and styles have evolved over that period of time as technology has evolved ... and so we evolve with it.

The QNX Coding Standard and the QNX Best Practices documents capture some of the lessons learned during the development of our software for multiple target architectures and multiple host environments. The documents are also continuously evolving as we gain new experience and introduce additional technologies.


QNX Best Practices#


1. Introduction

The purpose of this document is to establish the best practices for QNX software development. Reference: QNX Coding Standard - QMS0021

2. QNX Best Practices

2.1. GPL/Third Party Code

Under no circumstance should GPL (Linux) code be used. When in doubt talk to the QNX Legal Department.

2.2. Portablity

When implementing source, you should always try to write the code in a manner that is OS and CPU independent. If it's not possible to do that, the next best thing is to implement a function level API that hides the difference. The main source code calls this function that will be re-implemented individually for each OS or CPU. The source code for the function will live in separate source files, lower down in the source hierarchy. If a functional interface is not appropriate for efficiency or other reasons, the next best thing is to use a function-like macro that's defined in a header file to hide the difference. ""Only as a very last resort should a conditional compilation directive be placed in a source file"".

When you add a #ifdef chain to something, always terminate it with a:

	...
	#else
		#error THING not supported
	#endif
True, it means that the next guy might get errors when he first compiles something, but better to make him look at the code and decide what to do than let it silently go through and spend a day or so trying to figure out what's wrong. Taking this further, when you're implementing something, if you know that it won't work for some given condition and you can test for that condition, put in an #error report.

Drivers for PCI devices must support big and little endian architectures.

  • Refer to /usr/include/gulliver.h for endian conversion macros.
  • Refer to /usr/include/hw/inout.h for endian aware I/O routines
	ie inle32().
2.3. Function Prototypes

Use function prototypes. There is no excuse for not using them. You should define a function prototype only once in the entire source, in one header file. Defining multiple prototypes defeats the purpose of having a function prototype in the first place.

2.4. Coding for Reliability

Even if your algorithms and code implement all of a specification or requirements document, your code can and will be subject to incorrect, out-of-specification, or malicious data. In order for your code to survive, you must check for out-of-specification or unexpected data values and act accordingly and reliably when such input values are passed to your code. Validate data at "external" input points and assume proper input for "internal" points. Always check return values.

The assert() statement is enabled only during debug builds of the software. This statement can be left in the release source code if it is not distracting such that if need be experimental debug versions can be distributed that highlight error conditions quickly.

2.5. Case statements

Include default cases on all switch statements, and do not allow an unhandled value to fall through into the code that follows the switch statement. Also, make note of when you intend a case of a switch statement to fall through into the next case selector. For example:

	switch (number) {
	case 0:
		printf( "You entered zero. I assume you meant 1.\n" );
		/* Fall through */
	case 1:
		dostuff( );
		break;
	case 2:
		...
		break;
	default:
		printf( "Unknown case\n" );
		break;
	}
2.6. IF...Else statements

Always bracket if/else statements. Your code is not likely to remain unchanged forever and adding brackets ensures that the logic of your code is evident. Your code is like For example:

	if (something) {
		do stuff...
	}
Avoid single line statements such as:
	if (something) { do stuff }
These type of statements make it impossible to plant breakpoints on the inner code in the debugger and may result in misleading results for profilers and memory analysis tools.

2.7. Goto Statement

Avoid using the goto statement. However in some case it can be used to preform error cleanup for functions that allocate a lot of resources.

2.8. Typecasting

Do not use gratuitous typecasting. Functions that return void * (known as an opaque type) need no typecasting of the returned pointer. Doing so defeats the whole purpose of declaring the function with an opaque type.

2.9. Obscure C Features

Avoid using obscure C language features, such as the "," operator. If you are in doubt about a feature's "obscureness" ask your fellow developers about the feature. If they are not intimately familiar with the language feature then it may be questionable to use it.

The maintenance improvement from better source code readability far outweighs any optimizations that might have been achieved with older compilers.

2.10. Ensuring Correct Results

Do not depend on nonobvious associativity and side effects for correct results. It is unwise to rely on the order of evaluation of side effects performed on variables passed to functions, and it is extremely unwise to use side effects in the invocation of a macro.

2.11. Static Class

Use the static storage class to reduce the scope of variables and functions. Do not make any variable or function an extern unless it is used outside the file in which it is defined.

2.12. Const Qualifier

Use const type qualifiers to allow the compiler to enforce read-only declarations of read-only global storage and variables that should not change at run time.

In addition to declaring initialized storage to be const, you should also declare and define the read-only parameters of functions to be const.

2.13. Conversion from Signed to Unsigned Types

Pay attention to the conversion between signed and unsigned types, in both directions. This is one area where the transition from K&R C to ANSI C occasionally surprises people.

2.14. Enumerated Types and #defines

When possible, use ordinals declared using enum, especially for arguments to switch statements. GCC will warn you about unhandled values, a warning you will not receive when using arguments of type int or unsigned integers.

If you are creating a list of #define macros for enumerated constants, consider whether you can use a real enumerated type rather than the #define idiom. You can assign explicit values to enumerated type names if required, as shown in this example:

	enum {
		first_value = 1,
		next_value  = 2,
		last_value  = 3
	};
If you must use the #define idiom instead of enumerated types, write the #defines so they are self-indexing if that is the intended use. For example:
	#define EXAMPLE_ITEM_ONE        (1)
	#define EXAMPLE_ITEM_TWO        (EXAMPLE_ITEM_ONE+1)
	#define EXAMPLE_ITEM_THREE      (EXAMPLE_ITEM_TWO+1)
If the #define idiom is being used for bitfields or field masks, align the entries so that errors can be quickly identified. For example:
	#define FLAG_A  0x00000001
	#define FLAG_B  0x00000002
	#define FLAG_C  0x00000004
is prefferable over:
	#define FLAG_A  1
	#define FLAG_B  2
	#define FLAG_C  4
One reason to favour the #define idiom over an enumerated type would be that the constant definitions can be processed by both the C preprocessor and other, non-C, macro languages.

2.15 Floating-Point Operations

Many of our target platforms have no floating-point hardware and the operations will have to use the emulator. Do not use floating-point operations in: drivers, driver utilities, libc, kernel. Consider using fixed point math.

2.16 Coding for Performance

Use "natural" sizes. If a target architecture accesses a 16-bit memory location faster than it accesses an 8-bit location, consider declaring your storage to be a 16-bit large area. You must then weigh this increase in size against other factors, such as cache hit ratios and cache line packing.

2.17 Naming

When giving names to functions/variables/macros that are CPU/device dependent, always prefix the name with something that identifies the CPU/device that the symbol is for. For example, anything that is specific to an Intel x86 processor is always prefixed with "x86" or "X86" as appropriate. This serves two purposes. First, it makes sure that we don't have any naming conflicts between CPU/device's.

Second, it makes it obvious when the source code is CPU/device dependent. This prefix naming continues to lower levels as well. For example, if you're defining the bits on the ABC register of the XYZ processor, all the definitions should be prefixed with XYZ_ABC. Examples:

        MIPS_SREG_BEV       - MIPS status register BEV bit
        PPC_FPSCR_VE        - PPC floating point status/control reg VE bit
        PPC403_BESR_DSES    - PPC 403 bus error syndrome register DSES bit
        S2681_CSR_S1BAUD75  - Signetics 2681 serial chip clock select reg
Also, separate the definitions into appropriately named files. For example, all definitions about generic PPC CPU registers appear in <ppc/cpu.h>. Definitions specific to the PPC 403 chip appear in <ppc/403cpu.h>. Note that this is not a hard and fast rule. If there are only one or two additional registers/bits for a particular processor, it might be more efficient to just include the definitions in the main file (but still using the more explicit prefix!). Use your own judgment on this.

All function/variable names should be lowercase with the following properties:

Legibility
Names should be meaningful and have a maximum length of 15 characters.
Short Names
Single letter names should not be used except for counters (i, j, k).
Separating Names
Use underscores to separate names.

2.17.1 Namespaces

Reserved Namespace

The 'C' standard requires that all system-specific macros be part of the reserved namespace. All names which begin with two under-scores, or an underscore and a capital letter, are reserved for compiler and C library to use as they wish.

Libraries should endeavour to reserve their own chunks of namespace to ensure that accidental name collisions are minimized.

User Namespace

The user namespace must meet the following requirements:

  • It does not begin with an underscore.
  • It is not a keyword in the language
  • It is not reserved for the ANSI library.

3. Driver issues:

3.1 Interrupts

Handlers should be as short/fast as possible. Remember that QNX is an RTOS and customers expect a small interrupt latency. Customers measure the latency, so your evils will be found.

Drivers must support sharing interrupts with other devices.

Common problems:

  • Attaching to an interrupt before the driver is able to properly service it.
  • Not disabling the device interrupt when the driver terminates.

3.2 Process Time Handlers

Most QNX drivers handle interrupts at process time by using InterruptAttachEvent(). The handler should clear the device interrupt as fast as possible and call InterruptUnmask.

3.3 Interrupt Time Handlers

The handler should clear or mask the device interrupt as fast as possible.

3.4 Bitfields

Don't use bitfields when attempting to define a structure that something external will see (device register layout, etc). Not all compilers will lay out bitfields in the same way. Use of explicit constants with and/or'ing is much more portable and will likely give you better code generation anyway.

3.5 Register Descriptions

All register offsets and contents must have Macro definitions. Magic numbers are not allowed.

Busy waits must be bounded. Never rely on the hardware performing as documented. Use a counter to break out of the loop and follow up with error recovery. For example:

	int reset_device(int iobase) {
		out8( iobase + DEVICE_CTRL, DEVICE_CTRL_RESET );
		for (retry = MAX_RETRIES; retry; retry--) {
			if ( in8( iobase + DEVICE_CTRL ) & DEVICE_CTRL_RESET_CMPLT ) {
				return EOK;
			}
		}
		// reset failed
        	return ENODEV;
    	}
3.6 Comments

Always document hardware anomalies.

3.7 Address Translation

Drivers should never assume a 1 to 1 virtual/physical address mapping.

PCI drivers should never use addresses read from PCI configuration space. The pci server is aware of the translation being used and returns it via the pci_dev_info structure (see CpuBaseAddressx members).

The mmap_device_io and mmap_device_memory functions should be used to map registers into the drivers address space. The mapped memory should be declared volatile.

3.8 Busmaster

PCI drivers should never assume a 1 to 1 address mapping for bus-mastering. The pci server is aware of the translation being used and returns it via the pci_dev_info struct (see CpuBmstrTranslation member). The translation must be applied to the physical address before it is given to the busmaster engine.

3.9 DMA/Busmaster Safe Memory

Never use memory returned by malloc for DMA tranfers (see mmap).

3.10 Performance

Drivers should avoid calling mem_offset/mem_offset64 repeatedly since it is a message pass to Procnto. It is better to cache the physical address returned. This said, don't go wild with caching since it mandates that the memory be locked in place.

3.11 Threads

Most QNX drivers are multi-threaded therefore critical data structures and hardware registers must be protected with mutexes or semaphores. When drivers have a real interrupt handler spin locks can be used. Do not disable interrupts for protection.

3.12 Shutdown/Power Management

Drivers should perform the following:

  • Disable device interrupt (as applicable)-
  • Put chip in a powered-down state (as applicable)
  • Free any system resources

4. Libc issues:

5. Tool issues:

6. Kernel issues:


QNX Coding Standard#


1. Introduction

CONSISTENCY is at the heart of readable source code. Mixed coding style is harder to maintain than any particular "bad" coding style. When working on existing source code (GNU, Dinkum, QNX4, Photon etc), the particular format and style used by that source and its associated modules (headers, other source files) should be maintained regardless of this document. The intention is not to retro fit a new style onto all of QNX's existing source code base.

For new projects this document provides a common set of C coding standards and recommendations that should be followed. Standardizing will provide consistancy, improve portability, reduce errors and help new employees/third parties and customers reading our source learn the "QNX way".

2. What To Do When It Isn't Specified

This document provides a number of guidelines based on existing QNX source code and industry best practices; it does not cover every single case imaginable. When something is not specified (ie continuation behaviour of long function names or long argument lists) and there are no examples in the existing source to follow (Rule #1 Be Consistant) make a choice based on providing a readable result that you will be able to use consistantly.

3. File Organization

There is no maximum length limit for files, but files with more than about 1000 lines are cumbersome to deal with. Lines longer than 79 columns are not handled well by all terminals and should be avoided if possible. The maximum line length is 132 columns.

3.1. Source File Naming Conventions

Source file names are made up of a base name, a period and a suffix. File names must be lower-case, do not have spaces, contain only a single 'dot' and have meaningful names. Avoid using file or directory names that can cause problems for certain filesystems: CON, PRN, AUX, CLOCK$, NUL, COM1-COM9, LPT1-LPT9 should be avoided.

When working with QNX DDK's the file names/directory organization should be adhered to.

Some compilers and tools require certain suffix conventions for names of files. The following suffixes are required:

  • C header file names must end in .h
  • C source file names must end in .c
  • C++ source file names must end in .cpp
  • C++ header file names must end in .hpp
  • Java source file names must end with .java
  • Assembler source file names must end in .S or .s
  • Yacc source file names end in .y
  • Lex source file names end in .l
  • ... add other types here...
3.2. Source Files

The order of sections for a program file are as follows:

QNX License/Copyright Header (refer to section 7) Module Description (refer to section 4.8)

#include Directives typedefs externs

globals Source code ....

3.2.1. Order of Functions Within a File

Deliberately arrange the order in which the functions are defined in a file so as to make forward declarations unnecessary and your source easier to maintain and follow. Typically, you do this by placing the function that calls most other static functions at the bottom of the source file and placing the functions at the bottom of the caller/called hierarchy towards the top of the file.

3.3. Header Files

Header files should keep the number of other headers they include to the bare minimum, subject to other rules - see below.

3.3.1. System Headers (i.e. headers files in /usr/include):

The order of sections for are as follows:

	QNX License/Copyright Header (refer to section 7)

	Module Description (refer to section 4.8)

	#ifndef _HEADERFILE_H_INCLUDED
	#define _HEADERFILE_H_INCLUDED

	... stuff in file ...
  
	#endif
For header files in sub directories use the following syntax:
	#ifndef _DIR_HEADERFILE_H_INCLUDED
	#include <DIR/HEADERFILE.h>
	#endif
where:
	DIR = directory relative to /usr/include where the file will exist
	HEADERFILE = name of the file
The special case of files in /usr/include/sys will have DIR = "" resulting in names such as HEADERFILE.

For the special case of header files in /usr/include/sys

	#ifndef __HEADERFILE_H_INCLUDED
	#include <sys/HEADERFILE.h>
	#endif
For all type references in public header files other than those that are fundamental types, the file must include the appropriate definitions for those types. (If these definitions are in some other header files, thosefiles must be included enclosed by the appropriate #ifndef ... #endif blocks.)

Whenever possible, public header files must avoid polluting the user name space with extra symbol names. This means that when using types, if there's a version of the typename in the implementer's reserved name space (leading underscore's), you should use that version rather than the public typename. All of the reserved name versions are defined by including <sys/platform.h>, so by including that file, you hopefully will be able to avoid including other headers and introducing more symbol names than necessary. E.g. do:

	#include <sys/platform.h>
    
	typedef _Uint32t  my_type;
 
rather than:
	#include <inttypes.h>

	typedef uint32_t my_type;
In headers, function prototypes parameter names should all be declared so that they are prefixed by . This is just a safeguard against unexpected macro expansions for common names in parameters.

When defining constants to be used in flags, make sure the constants clearly indicate how large the target flag variable is. i.e. if the target flag storage is 32 bits, add appropriately enough leading zeroes to the constant.

Surround any typedef's or function prototype definitions in a public header file using:

	__BEGIN_DECLS

	/* typedefs and function prototypes go here */

	__END_DECLS
The BEGIN_DECLS & END_DECLS macros are obtained by including <sys/platform.h>.

New header files should as often as possible avoid installing to /usr/include to avoid pollution of the /usr/include directory with a lot of random stuff. Preferably, if the header files belong to a specific module, then the header could install to /usr/include/modulename (as a directory, with the actual headers under it).

Existing module subdirectories:

	ppc, mips, x86, arm, sh
		CPU specific headers
	hw 
		headers describing hardware stuff
	photon 
		Photon headers
3.3.2. Application Header Files: The order of sections are as follows:
	QNX License/Copyright Header (refer to section 7)

	Module Description (refer to section 4.8)

	#ifndef HEADERFILE_H_INCLUDED
	#define HEADERFILE_H_INCLUDED

	... stuff in file ...

	#endif
Application header files should never use types defined in platform.h. They should instead use inttypes.h. E.g. do:
	#include <inttypes.h>
    
	typedef uint32_t my_type;
rather than:
	#include <sys/platform.h>
    
	typedef _Uint32t  my_type;
3.4. Executable/Library File Naming Conventions

Refer to requirements/design documents.

3.5. Installation

Refer to Filesystem Hierarchy Standard (version 2.2 final)

3.6. Source Code Build & Layout

Refer to Source Code Build & Layout document.

4. Specific Code Formatting Issues

The following points address specific code formatting issues. CONSISTANCY with existing source formatting and style takes precedance over these guidelines.

Running a formatting utility like indent over source code should be considered only as a last resort to restore sanity to source code that has been irreparably infected with multiple formats and styles. In this case it is suggested to use these guidelines for the re-formatting.

4.1 Indention

The standard indention is four (4) spaces.

4.2. If...Else Statements

The else clause of an if {...} else {...} should be "cuddled" as shown in this example:

	if (something) {
		/* Do some stuff here. */
	} else {
		/* Do something else. */
	}
4.3. Case Statements

Case statements should be "cuddled" as shown in this example:

	switch (number) {
	case 0:
		...
		break;
	case 1:
		...
		/* Fall through */
	default:
		break;
	}
Continuations from one case to another should be clearly identified with a comment as above.

4.4. For Statements

For statements should be "cuddled" as shown in this example:

	for (cnt = 0; cnt < some_value; cnt++) {
		... do stuff ...	
	}
4.5. While Statements

While statements should be "cuddled" as shown in this example:

	while (some_condition) {
		... do stuff ...
	}

	do {
		... do stuff ...
	} while (some_condition);
4.6. Format of Data Structures

Do not make assumptions about the layout or padding of a structure or the allocated size of a data structure. These depend on the compiler implementation and can vary significantly with the type of target CPU.

When defining a structure that will be used in message passing or will be written to external device (e.g a hard disk, serial line), the types of all the fields must be explicitly sized. eg: struct foo { int bar; }; is a no-no, while for a PUBLIC header (ie /usr/include)

	struct foo {
		_Int32t bar;
	};
or NON-PUBLIC header
	struct foo {
		int32_t bar;
	}; 
is correct. Note that even when a _Int32t style declaration is used the documentation should use the int32_t as the reference type.

On the opposite side, do NOT use an explicitly sized type unless you really need it - internal structure definitions can get along quite happily without them and you don't limit yourself in the future when we're running on a 64-bit CPU (and that's going to be sooner than you think). It is also a good idea to plan for expansion when defining public data structures. ie

	struct foo {
		int     bar;
		int     rsvd[4];
	};
4.7. Data Alignment

Align your data, especially structure elements, on their native boundaries. This means that shorts should start on any even address, longwords on addresses evenly divisible by four, and 64-bit values (quadwords) on addresses evenly divisible by eight.

	struct foo {
		int16_t	type;
		int16_t	rsvd;	 /* Explicit padding/reserved */
		int32_t	data;
	};
4.8. Module Description

Each source/header file should contain a block comment immediately after the license header that gives a short description of what the module does and (if not clear) how to use it. Discussion of non-trivial design decisions and side-effects is also appropriate. For example:

	/*
	 * options.c
	 *
	 * This file contains code to parse command line options.
	 */
4.9. Function Description

Each function should be preceded by a block comment. The comment should provides usefull information about:

  • A synopsis of what the function does
  • Appropriate or in appropriate uses for the function
  • Side effects (state changes, acquired locks) of calling the function
  • Any non-trivial design decisions or historical rationale for behaviour

The comment block should be formatted in a manner such that it is easy to read and automated documentation generation tools such as doxygen can extract meaningful developer information. Avoid dup-licating information clear from the code. One doxygen example format:

	/** 
	 * function_name A short synopsis of the function
	 * - Description: This is a longer description of what
	 * the function does.
	 * - Arguments:
	 *   - param1: The first parameter description
	 *   - param2: The second parameter description
	 * - Returns: 
	 *   - Something that comes back
	 */         
Real world example:
	/**
	 * smc9000_parse_options: command line option processing
	 * - Description: This function handles the special exception
	 * cases in the general option processing for the SMC driver.	
	 * - Arguments:
	 *   - opt: a pointer to the command line options
	 * - Returns:
	 *   - Option parsing status: EOK on success, EINVAL on failure	
	 */         
	 int smc9000_parse_options( char *opt )
4.10. Function Naming

Public functions should be named using all lower case letters and underscores.

5. Comments

C style (/* */) and C++ style (//) comments are allowed. The only excecption is that C++ style comments are not permited in public header files.

Short/Single Line Comments

Very short comments may appear on the same line as the code they describe, and should be tabbed over to separate them from the statements. If more than one short comment appears in a block of code they should all be tabbed to the same tab setting.

	if (a == EXCEPTION) {
		b = TRUE; /* special case */
	} else {
		b = isprime(a); /* works only for odd a */
	}
Formatting Block Comments

Format block comments as follows:

	/*
	 * This is a block comment. Don't embellish these with lots of "stars
	 * and bars."
	 */
Some Comments about Comments

Comments must be in english and should tell the reader something non-obvious. A comment that repeats what is blindingly obvious is annoying at best. The following is an example:

	counter += 10;    /* Add ten to counter */
It is often better to aggregate comments about high-level and architectural issues in one place, to allow the reader and maintainers of your code to learn much more in a shorter time than if they had to piece together the issues and ideas about your features from comments strewn throughout several files.

6. Best Practices

The QNX coding best practices can be found in QMS0026.

7. License/Copyright Headers:

All source (*.c/cpp, *.h/cpp, *.s, *.S, etc) and common.mk's should have a license/copyright header at the top.

You don't have to have license/copyright headers for usage files or "Makefile"'s - if the Makefiles are using the common makefile scheme (that is, they just include "recurse.mk" or "common.mk").

All "old" copyright headers (e.g. OCL, QCL, CSL etc) are deprecated. The only copyright header that should be put on is the standard QSS_Copyright_Notice. The mechanism for annotating source code with this license is to make use of the QNX custom RCS tags that will be automatically updated on source code checkout/checkin:

$QNXLicenseC: $ : For use with C/C++ source files $QNXLicenseA: $ : For use with Assembly source files $QNXLicenseM: $ : For use with Makefile source files

For example:

	/*
 	 * $QNXLicenseC: $
 	 */
Assembly file:
	#
	# $QNXLicenseA: $
	#
If additional (file-specific) text is required, these can be inserted in the same comment block but after the second "$" character. For example:
	/*
	 * $QNXLicenseC: $
	 *
	 * This is some additional information that is being added
	 * as an extra part of this comment.
	 */