System Design Considerations
Since Neutrino is a protected-mode 32-bit operating system, many limiting design considerations won't apply (particularly on the x86 platform, which is steeped in DOS and 8088 legacy concerns). By noting the various “do's” and “don'ts” given in this appendix, you'll be able to design and build an embedded system tailored for Neutrino.
You may also be able to realize certain savings, in terms of design time, hardware costs, and software customization effort.
Before you begin designing your system, here are some typical questions you might consider:
- What speed of processor do you need?
- How much memory is required?
- What peripherals are required?
- How will you debug the platform?
- How will you perform field upgrades?
Naturally, your particular system will dictate whether all of these (or others) are relevant. But for the purposes of this discussion, we'll assume all these considerations apply.
Although Neutrino is a realtime operating system, this fact alone doesn't necessarily mean that any given application will run quickly. Graphical user interface applications can consume a reasonable amount of CPU and are particularly sensitive to the end-user's perception of speed.
If at all possible, try to prototype the system on either a standard PC (in the case of x86-based designs) or a supported evaluation board (in the case of x86, PPC, ARM, SH, and MIPS designs). This will very quickly give you a “feel” for the speed of a particular processor.
During initial prototyping, you should plan on more memory on the target than during the final stages. This is because you'll often be running debugging versions of software, which may be larger. Also, you'll want to include diagnostics and utility programs, which again will consume more memory than expected. Once your prototype system is up and running, you can then start thinking about how much memory you “really” need.
Given a choice, you should use peripherals that are listed as supported by Neutrino. This includes such items as disk controllers, network cards, PC-Card controllers, flash memory chips, and graphics controllers. For lists of supported hardware, see the Community area of our website, http:www.qnx.com; for information about third-party products, see the Download area.
Graphics controllers are one of the particularly delicate areas in the design of an embedded system, often because a chip may be very new when it's selected and we may not yet have a driver for it. Also, if you're using a graphics controller in conjunction with an LCD panel, beware that this is perhaps the most complicated setup because of the many registers that must be programmed to make it work.
Note that QNX Software Systems can do custom development work for you; for more information, contact your sales representative. Other consulting houses offer similar services to the QNX community.
In many cases, especially in cost-sensitive designs, you won't want to provide any additional functionality beyond that absolutely required for the project at hand. But since the project is usually a brand new design, you'll need to ensure that the hardware actually works per se and then actually works with the software.
We recommend that you install some form of easy-to-get-at hardware debugging port, so that the software can output diagnostics as it's booting. Generally, something as simple as a latched output that can drive a single LED is sufficient, but an 8- or 16-bit port that drives a number of 7-segment LEDs would be even better. Best of all is a simple serial port, because more meaningful diagnostics can be written by the software and easily captured.
This debug port can be left off for final assembly or a slightly modified “final” version of the board can be created. The cost savings in terms of software development time generally pay for the hardware modifications many times over.
You can handle the issue of field upgrades in various ways, depending on the nature of your particular target system:
- a JTAG port
- socketed Flash/EPROM devices
- a communications port.
You may need such a vehicle for your update software even during your initial software development effort. At this early phase, you'll effectively be performing “field upgrades” as your software is being developed.
There are other design considerations that relate to both the hardware and software development process. In this section, we'll discuss some of the more common ones.
Solid-state mass storage can be located anywhere in the address space — it should be linearly mapped. In legacy designs (particularly x86), the mass storage device was often forced into a window of some size (typically from 8 KB to 64 KB), with additional hardware being required to map that window into the processor's address space. Additionally, this window was traditionally located in the first 1 MB of memory.
With a modern, 32-bit processor, the physical address space of the processor is usually sufficient to address the entire mass storage device. In fact, this makes the software easier by not having to worry about how to address the window-mapping hardware.
The two driving factors to be considered in the hardware design of solid-state media are cost and compatibility. If the medium is to be soldered onto the board, then there's little chance that it may need to be compatible with other operating systems. Therefore, simply map the entire medium into the address space of the processor and don't add additional hardware to perform windowing or bank switching.
Adhering to standards (e.g. PCMCIA, FFS2, etc.) for solid-state memory is also unnecessary — our Flash filesystem drivers know how to address and use just a raw Flash device.
When the time comes to decide on the logical layout of the flash memory chips, the tradeoff will be between the size of the erase block and the speed of access. By taking four flash devices and organizing them into a 32-bit wide bus, you gain speed. However, you also increase the erase block size by a factor of four (e.g. 256 KB erase blocks).
Note that we don't recommend trying to XIP out of flash memory that's being used for a flash filesystem. This is because the flash filesystem may need to erase a particular block of memory. While this erase operation is in progress, depending on the particular type of flash memory device you have, the entire device may be unusable. If this is also the device containing the code that the processor is actively executing from, you'll run into problems. Therefore, we recommend that you use at least two independent sets of flash devices: one set for the filesystem and one set for the code.
Under Neutrino, the only location requirement is that the ROM boot device that performs the IPL be addressable at the processor's reset vector. No special hardware is required to be able to “move” the location of the boot ROM.
All the drivers under Neutrino can be programmed to deal with graphics hardware at any address — there's no requirement to map the VGA video aperture below 1 MB.
On the x86 platform, another vestige of the legacy 1 MB address limitation is usually found in something called an A20 gate. This is a piece of hardware that would force the A20 address line to zero, regardless of the actual setting of the A20 address line on the processor.
The justification for this was for legacy software that would depend on the ability to wrap past location 0xFFFFF back to 0x00000. Neutrino doesn't have such a requirement. As a result, the OS doesn't need any A20 gate hardware to be installed. Note that some embedded x86 processors have the A20 gate hardware built right into the processor chip itself — the IPL will disable the A20 gate as soon as possible after startup.
|If your system requires a standard BIOS, there's a small chance that the BIOS will make use of the A20 gate. To find out for certain, consult your BIOS supplier.|
Neutrino doesn't require the external ISA bus to be mapped into the usual x86 0x00000-to-0xFFFFF address range. This simplifies the hardware design, eliminating issues such as shadow RAM and the requirement to move a portion of the RAM (usually 0xA0000 through 0xFFFFF) to some other location.
But if your hardware needs to run with a standard BIOS and to support BIOS extensions, then this optimization can't be implemented, because the BIOS expects extensions at 0xA0000 through 0xEFFFF (typically).
In Neutrino, all PCI drivers interface to a PCI resource manager (e.g. pci-bios, pci-p5064, pci-raven), which handles the hardware on behalf of the drivers.
For details, see the pci-* entries in the Utilities Reference.
Neutrino can be driven with an external clock. In some systems there's a “standard” clock source supplied as part of the system or of the highly integrated CPU chip itself. For convenience, the OS can operate with an external clock source that's not generated by this component. However, keep two things in mind:
- The timing resolution for software timers will be no better than the timing resolution of the external clock.
- The hardware clock will be driving a software interrupt handler.
Therefore, keep the rates down to a reasonable number. Almost all modern processors can handle clock interrupts at 1 kHz or lower — processors with higher CPU clock rates (e.g. Pentium-class, 300 MHz RISC processors, etc.) can handle faster clock interrupts.
Note that there's no requirement to keep the clock frequency to some “round number.” If it's convenient to derive the clock interrupt from a baud rate generator or other crystal, the OS will be able to accurately scale the incoming clock rate for use in its internal timers and time-of-day clocks.
On an x86 design, the default startup supports two Programmable Interrupt Controllers (PICs). These must be 8259-compatible, with the standard configuration of a secondary 8259 connected to the IRQ2 line of the primary interrupt controller.
|Beware of hanging devices off IRQ7 and IRQ15 on an 8259 chip — these are generally known as the “glitch interrupts” and can be unreliable.|
If your x86 hardware design differs, there's no constraint about the PICs, but you must write the code to handle them.
On non-x86 designs, be aware that there may be only one interrupt line going to the processor and that a number of hardware devices may be sharing that one line. This is generally accomplished in one of two ways:
- PIC chip
In either case, the relevant design issue is to determine the ordering and priority of interrupts from hardware sources. You'll want to arrange the hardware and software to give highest priority (and first order) to the interrupt source that has the most stringent latency requirements. (For more details, see the chapter on Writing an Interrupt Handler in the Programmer's Guide, along with the InterruptAttach() and InterruptAttachEvent() function calls in the Library Reference.)
Serial and parallel ports are certainly desirable — and highly recommended — but not required. The 16550 component with 16-byte FIFOs is suitable for Neutrino. Our drivers can work with these devices on a byte-aligned or doubleword-aligned manner.
If you're going to support multiple serial ports on your device, you can have the multiple devices share the same interrupt. It's up to the software to decide which device generated the interrupt and then to handle that interrupt. The standard Neutrino serial port handlers are able to do this.
Although the serial driver can be told to use a “nonstandard” clock rate when calculating its divisor values, this can cause the baud rate to deviate from the standard.
Try to run DTR, DSR, RTS, CTS if possible, because hardware flow control will help on slower CPUs.
Generally, the parallel port does not require an interrupt line — this isn't used by our standard parallel port drivers.
Avoid the Non-Maskable Interrupt (NMI) in x86 designs. PPC, MIPS, ARM, and SH-4 don't even support it. An NMI is an interrupt which can't be disabled by clearing the CPU's interrupt enable flag, unlike most normal interrupts. Non-Maskable interrupts are typically used to signal events that require immediate action, such as a parity error, a hardware failure, or imminent loss of power.
The problem with NMIs is that they can occur even when interrupts have been disabled. This is important because sometimes it's assumed that interrupts can be masked to avoid being interrupted. NMIs undermine this assumption and this can lead to unexpected behaviour if an NMI fires during a period in which that software expects to be operating without interruption.
For this reason, NMIs are normally only used when the subsequent condition of the machine is not a relevant consideration; for instance, when the machine is about to shut down, or when an unrecoverable hardware error has occurred.
Anytime an NMI is used, any software may experience unexpected behavior and there's no good way to predict what the behavior may be.
Before you commit to a design, take a look at the following tips — you may save yourself some grief. Although some of these points assume you're relying on our Custom Engineering services, the principles behind all of them are sound.
- Do design in more speed/memory than you think you need.
- Do try a proof of concept using off-the-shelf hardware, if possible.
- Do have a serial port/debug output device on the board; have it reasonably close to the CPU in hardware terms (i.e. don't put it on the other side of a PCI bridge).
- Do allow the ROM/flash devices holding the IPL code to be socketed.
- If you're using a MIPS processor, make sure any devices needed by the IPL/startup sequence are in the physical address range of 0x00000000 to 0x20000000 — that makes it accessible from the kseg1 virtual address block.
- Do consider staggering a device's ports by any power of 2, but don't mix up the address lines so that the I/O registers appear in a strange order.
- Do try to use a timer chip that allows free-running operation, rather than one that requires poking after every interrupt.
- Do put the timer on its own interrupt line so that the kernel doesn't have to check that the interrupt actually came from the timer.
- Do follow the CPU's interface for reporting a bus error — don't report it as a hardware interrupt.
- If you have optional pieces, make sure you have some positive method of determining what pieces are present (something other than poking at it and seeing if it responds).
- Do run your design by us, ideally before you build it.
- Do make a point of stating requirements you think are obvious.
- Do remember to point out any pitfalls you know about.
- Do send us as much documentation as you have available on chipsets, panels, etc.
- Don't use write-only registers.
- Don't nest interrupt controller chips too deeply — one big wide interrupt controller is better.
- Don't use hardware that requires short delays between register accesses (e.g. Zilog SCC).
- Don't put information from many different places into the same I/O register location if the OS/drivers also have to do RMW cycles to it.
- Don't decide that no-BIOS is the way to go just because it sounds cool.
- Don't use a $2.00 chip instead of a $3.00 chip and expect the performance of a $10.00 chip.
- Don't build your first run of boards without leaving a way to debug the system.
- Don't build your first run of boards with only 1 MB of RAM on board.
- Don't send us anything without correct schematics that match what you send.
- Don't program the flash and then solder it on, leaving us with no option to reprogram it.
- Don't build just one prototype that must be shipped back and forth several times.