Copyright (c) Hyperion Entertainment and contributors.
NSD Command Specifications
Contents
Revision
Version 1.7 (2013-04-11)
NSD Command Space
The AmigaOS Development Team reserves two ranges in the set of command values for future enhancements. Only commands as specified by the AmigaOS Development Team may be used here. It is illegal for a device developer to randomly "add" custom commands or command features in the reserved are!
There are 65536 command values possible with io_Command:
$0000 - $3FFF old style and 3rd party commands $4000 - $7FFF RESERVED AREA! $8000 - $BFFF old style and 3rd party commands $C000 - $FFFF RESERVED AREA!
To say it again: Commands in the reserved areas may only be assigned and specified by the AmigaOS Development Team. Any "custom" implementation of these commands or other commands in these reserved areas is illegal and violates programming standards!
Design Basis
A device driver is REQUIRED to return IOERR_NOCMD on any command it does not understand. This requirement is very old. It has been documented publicly in 1991 already. If the device understands a command but the underlying hardware can not handle it, IOERR_NOCMD should not be returned. IOERR_NOCMD should be an indicator for non supported, i.e., non-implemented, commands of the device, not for those that just cannot be executed due to environment constraints.
What will new style commands do for you?
With old style devices it is impossible to find out about the type of device you are accessing. You don't know if the command set is serial.device like or trackdisk like. You don't know either what kind of request structure is appropriate. New style commands will let you query the device for its capabilities. As capabilities are added to a device, device users can use more features transparently without compatibility hacks.
There are two types of new style commands. "General" commands are the same for all new style devices. Depending on the device type there are device specific commands, too.
General commands will be defined in the range $4000-$7FFF, device specific commands are using the range $C000-$FFFF. It is illegal to use a device specific command before using the general commands to confirm the device's capabilities.
With a new style device, you can be sure that no 3rd party command will interact with the standard meaning of all the device's commands as documented for V40 of the OS.
Basic requirements
Let's make a quick list about the most basic requirements for any new style device. Keep it in mind when reading on.
- IOERR_NOCMD support
- No redefinition of standard V40 or reserved commands
- No 3rd party commands in reserved areas
- NSCMD_DEVICEQUERY is supported
- lib_IdString must contain the name, version, and creation date of the device and any other readable information to make unique identification of a device for 3rd party command use possible.
- Check mn_Length of the request on OpenDevice() for the minimum required length
Some devices may have additional requirements. These will be listed in NSD Device Specifics.
Device Types
There are various types of devices in AmigaOS. audio.device is substantially different from serial.device, just like input.device is different from a SANA-II device like a2065.device. NSD does not want to describe the semantics behind each type of device. It puts the different V40 command sets needed for different types of devices into useful groups and tags these groups with an NSD device type identifier. This is to make sure that the existing standard V40 command set in each group is not redefined arbitrarily. The NSD device type identifier does not refer to specific functionality. User's of an NSD device must be able to fall back on these standard commands when they are implemented and get results that match usage of the respective V40 devices listed below. When a device is "like" a certain type mentioned, the emphasis is put on the the command set being alike to allow for grouping. Here is an annotated list of NSD device types, naming the respective V40 devices and headers which define the command numbers that may not be redefined if they are implemented.
- NSDEVTYPE_UNKNOWN (0) No suitable category, anything
- Any device that does not fit in a group mentioned below. Think hard if you really need this category. Most likely you want an exact identification then as described below to make sure what you got. This category is dangerous as you can't say anything about most of the old style command set without an exact NSD identification.
- V40 Command Numbers: 1-8
- NSDEVTYPE_GAMEPORT (1) like gameport.device
- gameport.device <devices/gameport.h>
- V40 Command Numbers: 1-13
- NSDEVTYPE_TIMER (2) like timer.device
- timer.device <devices/timer.h>
- V40 Command Numbers: 1-11
- NSDEVTYPE_KEYBOARD (3) like keyboard.device
- keyboard.device <devices/keyboard.h>
- V40 Command Numbers: 1-13
- NSDEVTYPE_INPUT (4) like input.device
- input.device <devices/input.h>
- V40 Command Numbers: 1-16
- NSDEVTYPE_TRACKDISK (5) like trackdisk.device
- trackdisk.device <devices/trackdisk.h>
- scsi.device <devices/scsidisk.h>
- cd.device <devices/cd.h>
- mfm.device similar to <devices/trackdisk.h>
- V40 Command Numbers: 1-23,28-29,32-46,$8002-$8005,$8009-$800B,$8010-$8011
- The command sets of these three devices are sufficiently alike to put them under a single label. It is expected that the device interface for devices like these converge over time.
- NSDEVTYPE_CONSOLE (6) like console.device
- console.device <devices/console.h>
- V40 Command Numbers: 1-12
- NSDEVTYPE_SANA2 (7) A >=SANA2R2 networking device
- any SANA-II device <devices/sana2.h>
- V40 Command Numbers: 1-26
- NSDEVTYPE_AUDIO (8) like audio.device
- audio.device <devices/audio.h>
- V40 Command Numbers: 1-14,32
- NSDEVTYPE_CLIPBOARD (9) like clipboard.device
- clipboard.device <devices/clipboard.h>
- V40 Command Numbers: 1-12
- NSDEVTYPE_PRINTER (10) like printer.device
- printer.device <devices/printer.h>
- V40 Command Numbers: 1-12
- NSDEVTYPE_SERIAL (11) like serial.device
- serial.device <devices/serial.h>
- V40 Command Numbers: 1-11
- NSDEVTYPE_PARALLEL (12) like parallel.device
- parallel.device <devices/parallel.h>
- V40 Command Numbers: 1-10
The meaning of "No Command Redefinition"
An NSD device of a certain type is not allowed to redefine any V40 command or any command from the NSD reserved ranges. Each NSD device type as described above groups the corresponding V40 devices with similar command sets. Each of these devices has an established, documented, and implemented set of commands with documented meaning. An NSD device must always handle all these commands, if implemented, like the respective V40 device or like the set of V40 devices for that NSD device type. It is not acceptable if a CMD_READ suddenly turns into functionality of CMD_WRITE or if HD_SCSICMD will suddenly be like a TD_EJECT. If you find, e.g., HD_SCSICMD or CD_INFO in the supported command list of a NSDEVTYPE_TRACKDISK device, you shall be sure that these commands behave like the V40 commands of the respective devices would behave. Note that if you find one command supported for an NSD device type, this does not mean that other commands for that NSD device type are necessarily supported. Check for the command set that you need.
For the common eight usable Exec standard commands, intentionally no distinction is made between different V40 devices which may fall under the same NSD device type. The device I/O operations should be consistent and uniform according to the RKM and NSD does not try to differentiate between possible execution differences for the Exec standard commands. Because of this requirement for consistency and uniformity, the standard Exec commands obviously may not be redefined arbitrarily either. If an Exec standard command is implemented in a device, the implementation must not deviate from the specification for that device type or common usage for that command.
All command slots that are not part of the V40 command set for the respective devices under the NSD device types or of the reserved NSD areas are free to be used for 3rd party extensions. NSD neither defines nor restricts their usage. From the NSD perspective, the meaning of any implemented 3rd party command is undefined. The user software is responsible for an exact device identification then to use 3rd party commands safely via the device's lib_IDString as described below.
General commands
At the moment there is just a single new style general command:
#define NSCMD_DEVICEQUERY 0x4000
It is required for all new style devices.
You set up io_Data and io_Length pointing to a struct NSDeviceQueryResult below. You must clear SizeAvailable and set up DevQueryFormat before sending the command to the device. A new style device will set up the data in the buffer appropriately. If the command executed successfully the returned io_Actual value must be greater than zero and equal to the SizeAvailable value that has been set by the device. As with device usage in general, an io_Actual value that is alrger than the passed in io_Length value should be considered an error. The device may only be considered a new style device if these conditions are met. And only in this case the results of the query may be considered valid.
struct NSDeviceQueryResult { /* ** Standard information */ ULONG DevQueryFormat; /* this is type 0 */ ULONG SizeAvailable; /* bytes available */ /* ** Common information (READ ONLY!) */ UWORD DeviceType; /* what the device does */ UWORD DeviceSubType; /* depends on the main type */ UWORD *SupportedCommands; /* 0 terminated list of cmd's */ /* May be extended in the future! Check SizeAvailable! */ };
The device will fill in the struct NSDeviceQueryResult with read only data for the caller. All data must remain constant and valid as long as the device is open. After CloseDevice(), all bets are off.
- SizeAvailable
- Tells you how much of the structure contains valid data. It is measured in bytes. Do not try to use fields in the structure that are not fully included in the SizeAvailable range. SizeAvailable must include SupportedCommands for any successful query. So the minimum valid value is 16.
- DeviceType
- This tells you about the NSD type of device you are accessing. The type names often match existing devices in AmigaOS. If a device returns such a type, the device must be able to at least handle all the documented features for V40 of the operating system that the respective original device has. If the NSD device type refers to a group of V40 devices, at least the basic command subset as for the device named by the NSD device type shall be handled.
- Note well that while all the documented features for an implemented command shall be handled, it is permissible to return error codes or appropriate dummy returns for non existing features in the NSD device as long as the handling conforms to the original specification.
- If an old style or new style command cannot be supported in a compatible manner, the device is required to return IOERR_NOCMD for safety reasons. Modification of the specification is not allowed. Add a 3rd party command if needed.
- It is illegal to "reuse" command numbers of documented standard commands that cannot be supported for other purposes. E.g. a device driver of type NSDEVTYPE_TRACKDISK that does not support TD_GETGEOMETRY and uses this command for different functionality can never be a new style device and may not support new style commands.
- It is also illegal to use the results of an NSCMD_DEVICEQUERY without testing DeviceType first.
- The NSD device types are listed and explained above in the section "Device Types".
- More types will be defined by the AmigaOS Development Team as necessary and requested. Each NSD device type represents a certain command set that the device must handle. If the type does not match a device that does exist in OS V40, the exact requirements are defined below or in NSD Device Specifics. Note that some DeviceType entries may cover multiple types of devices, so you have to check the documentation carefully, when considering the standard command set for a specific type.
- DeviceSubType
- There might be special incarnations of a device with special capabilities beyond the standard set. At the moment none are defined and a device is required to return 0 here. As extensions that are marked by this field have to be upwards compatible, this field need not be tested by users who don't need any special capabilities beyond the standard set. Otherwise it must be tested first.
- SupportedCommands
- This points to a 0 terminated list of io_Command values that are supported by the device. This list must contain all legal command values, old style, new style, and special 3rd party commands that are understood by the device and do not cause an IOERR_NOCMD right away.
You might notice the similarity to the SANA2-R2 (S2R2) S2_DEVICEQUERY command. It is intentional.
If you are developing software that needs to use 3rd party commands, you must try an exact 3rd party device identification via the lib_IdString in the device base and reject all devices that you don't know for a new style device.
New Style Check in C
A code fragment that could help you set up a new device query follows.
struct IOStdReq *io; struct NSDeviceQueryResult nsdqr; LONG error; BOOL newstyle = FALSE; BOOL does64bit = FALSE; BOOL doesETD64bit = FALSE; UWORD *cmdcheck; struct MsgPort *replyport; ... /* See important *CAVEAT* below! */ io = IExec->AllocSysObjectTags(ASOT_IOREQUEST, ASOIOR_ReplyPort, replyport, ASOIOR_Size, sizeof(struct IOStdReq) + 128, TAG_END); /* Add error checking in production code! */ ... nsdqr.SizeAvailable = 0; nsdqr.DevQueryFormat = 0; io->io_Command = NSCMD_DEVICEQUERY; io->io_Length = sizeof(nsdqr); io->io_Data = (APTR)&nsdqr; error = IExec->DoIO((struct IORequest *)io); if((!error) && (io->io_Actual >= 16) && (io->io_Actual <= sizeof(nsdqr)) && (nsdqr.SizeAvailable == io->io_Actual)) { /* Ok, this must be a new style device */ newstyle = TRUE; /* Device specific code follows here */ if(nsdqr.DeviceType == NSDEVTYPE_TRACKDISK) { /* Is it safe to use 64 bits with this driver? We can reject * bad mounts pretty easily via this check! */ for(cmdcheck = nsdqr.SupportedCommands; *cmdcheck; cmdcheck++) { if(*cmdcheck == NSCMD_TD_READ64) { /* This trackdisk style device supports the basic * 64 bit command set without returning IOERR_NOCMD! */ does64bit = TRUE; } /* if */ if(*cmdcheck == NSCMD_ETD_READ64) { /* This trackdisk style device supports the extended * 64 bit command set without returning IOERR_NOCMD! * According to specs, this implies does64bit == TRUE! */ doesETD64bit = TRUE; } /* if */ } /* for */ } /* if */ } /* if */
CAVEAT
There are some noteworthy points about this code fragment:
The check of io_Actual filters out anything that is obviously wrong. An NSD device may not return less than the minimum query structure and of course it may not try to return more than the buffer space available.
Error checking for request allocation has been left out for brevity. Obviously, production code should contain decent error checking code.
This code fragment uses a pointer to a struct IOStdReq to do the checks. Unfortunately, using a request of this size may result in severe problems with certain devices like SANA-II devices, which need a larger request structure. A current device should at minimum check in its OpenDevice() code if the passed in request is of at least the minimum required size to work with via its mn_Length field and fail to open if it isn't. This is a simple but effective paranoia check with the effect that surely broken request structures will be rejected right away. Unfortunately most devices don't do that check. Two basic assumptions can be relevant here:
- 1. The trusting assumption
- You simply use the type and size of request structure appropriate for the type of device you intend to use and assume that the user did not give you the wrong type of device. This may fail and result in a system crash.
- 2. The safer assumption
- You use the type of request for the device type your are expecting (or an IOStdReq for a general check) with an extra 128 zeroed bytes when opening and checking a device. This will be very safe as current devices are required to check the minimum request length before opening successfully. So any wrong type of device will not cause problems here unless it both:
- a) uses a really strange long request that is referenced on OpenDevice() time
- and
- b) does not implement the required mn_Length check
- You only need that one larger request for the check on opening the device. Afterwards you know what you are working with and can use reasonably sized requests.
Obviously we cannot approve of the trusting assumption, because device access should be as safe as possible. So we require you to implement the safer assumption. Again, the basic idea behind this is that you should always strive to make device access as safe as possible. Some little things like validating the request structure on OpenDevice() will increase stability of the device system a lot at hardly any extra cost. Play it as safe as you can. The user might have some messed up code accessing the device!
It is wise to avoid writing devices that need arguments set up in the request structure for an OpenDevice() call. If you have to write a device like this, validate the request structure well and fail gracefully if you don't find the arguments needed.
It is valid and strongly suggested behaviour for a device to reject all new style commands except NSCMD_DEVICEQUERY with IOERR_NOCMD unless the caller executed a valid and successful NSCMD_DEVICEQUERY at least once before. This is intentionally stretching the meaning of IOERR_NOCMD to protect old software.
Recommended use
Anyone who wants to take advantage of the new style device interface should do this:
- OpenDevice() as described above - Try a NSCMD_DEVICEQUERY as described above - If successful according to the rules mentioned above, use the commands as listed in "SupportedCommands". - Otherwise it must be an old style device. Hope and pray that you got what you wanted.
Note: The above description and the code fragment shown assumes that you use OpenDevice() with a freshly created and appropriately initialised request structure. If you intend to reuse a previously used request structure for another OpenDevice() to make a general query, you should set at least the following fields to zero before calling OpenDevice(): io_Actual, io_Length, io_Data, io_Offset. It is strongly recommended to always do a query with a freshly set up request structure.
Old Style and/or 3rd party vs. NSD
NSD conforming devices may only be identified via NSCMD_DEVICEQUERY. NSD does not make any judgements about old style devices or their usage. NSD does not make any judgements about 3rd party features within the NSD framework. If NSD is not available in a device, you are free to use other functionality as you see fit for the underlying old style device. If an NSD feature is not available, you are free to use other functionality not conflicting with the NSD specification.