Introduction
The BlockDevices module provides a single, central registry for block-addressable storage devices - controllers, drives, discs, partitions and similar objects - so that client software does not need to know how any particular piece of storage hardware is attached to the machine or driven.
Two quite different audiences use the module:
- Client software, such as filing systems, disc utilities and partitioning tools, which wants to enumerate the storage devices present on a machine and read or write blocks of data on them, without needing to know anything about the underlying interface (IDE, SCSI, NVMe, USB and so on).
- Device drivers, which know how to talk to a particular piece of storage hardware, and which register the devices they control with BlockDevices so that client software can find and use them.
BlockDevices does not talk to any hardware itself. It is purely a registry and dispatcher: it allocates device identifiers, keeps track of the relationships between devices (for example, that a partition belongs to a particular disc), validates block ranges, and forwards read, write and other requests to whichever driver registered the device concerned. This decouples the client interface, described below, from the driver registration interface used to add and remove devices.
Overview
Every device known to BlockDevices is identified by a small positive integer, the device id, allocated by the module at the time a driver registers the device. The value zero is never a valid device id; it is used to mean "no device" or "no parent" where such a meaning is required.
Devices form a simple tree. A device may have a parent device, recorded as another device's id, so that (for example) a partition can record which disc it was found on, and a disc can record which controller or drive it was found through. Client software can either walk every registered device with SWI BlockDevice_Enumerate, or restrict itself to the direct children of a particular device with SWI BlockDevice_EnumerateChildren.
Each device carries a type classification along four independent axes: what kind of device it is (controller, drive, disc, partition or track), what interface it is attached through (IDE, SCSI, NVMe and so on), what physical connection carries that interface (motherboard, podule, USB and so on), and what kind of media it represents (floppy, hard disc, SSD and so on). These are described in full in Device, Interface, Connection and Media Types.
Device names are expected to follow a hierarchical convention, similar to a filing system pathname, so that a device's name reflects its position in the tree. For example, a driver might choose to register devices using names such as:
- scsi/1/2/3 - the SCSI device on bus 1, device 2, LUN 3.
- nvme/0/1 - the second disc attached to the first NVMe device.
- nvme/0/1/gpt/0 - the first partition on the above device, described using its GUID Partition Table entry.
This hierarchy is a convention for driver authors to follow when choosing the name they pass to SWI BlockDevice_Register; the module itself does not construct or verify path-like names, it only guarantees that the name presented to client software is not ambiguous. See Device Naming for exactly what the module does with the name it is given.
A device only becomes visible to client software once it has been registered. The module announces newly registered and removed devices to the rest of the system using its own service calls, described in Service Calls; these are particularly important to filing systems and other software that wants to react promptly to storage being attached or removed, rather than polling SWI BlockDevice_Enumerate repeatedly.
Technical Details
Device Identifiers
Device ids are allocated sequentially by BlockDevices as devices are registered, starting from 1. The value 0 (DeviceId_Invalid in BlockDevices.h) is never allocated to a real device, and is used both to mean "no parent device" in a device's information block, and to mean "start enumeration from the beginning" in the enumeration SWIs.
A device id remains valid, and continues to refer to the same device, for as long as that device stays registered. Deregistering a device (SWI BlockDevice_Deregister) invalidates its id permanently; the id is not reused. Rescanning a device (SWI BlockDevice_Rescan) does not change its id.
Device Information Block
Every device is described by a device information block, a structure of type device_info_t defined in BlockDevices.h. A pointer to this structure is what is passed to a driver at registration, and what is returned by SWI BlockDevice_Info, SWI BlockDevice_Enumerate and SWI BlockDevice_EnumerateChildren.
| Field | Type | Description |
|---|---|---|
| name | string pointer | The device's registered name (see Device Naming). |
| flags | unsigned | Device flags; see Device Flags. |
| parent_device | unsigned | The device id of this device's parent, or 0 if it has none. |
| device_index | unsigned | An explicit index to use when disambiguating the device's name, or 0 to have one allocated automatically; see Device Naming. |
| block_count | 64-bit unsigned | The total number of addressable blocks on the device. |
| block_size | unsigned | The size, in bytes, of one block on the device. |
| device_type | enumeration | What kind of device this is; see Device, Interface, Connection and Media Types. |
| interface_type | enumeration | The interface the device is attached through. |
| connection_type | enumeration | The physical connection carrying that interface. |
| media_type | enumeration | The kind of media the device represents. |
| description | string pointer | A human-readable description of the device, suitable for display to a user. |
BlockDevices.h is built for both 32-bit and 64-bit RISC OS. The name and description fields are ordinary pointers, and block_count is always a genuine 64-bit value; the byte offset of every field after the first pointer therefore differs between a 32-bit build, where pointers are 4 bytes, and a 64-bit build, where they are 8 bytes. Code that needs the structure's exact in-memory layout should always do so via the supplied header rather than by assuming fixed byte offsets.
Device, Interface, Connection and Media Types
A device's classification is recorded along four independent axes. Each is a small enumeration:
| Value | Device type |
|---|---|
| 0 | Unknown |
| 1 | Controller |
| 2 | Drive |
| 3 | Disc |
| 4 | Partition |
| 5 | Track |
| Value | Interface type |
|---|---|
| 0 | Unknown |
| 1 | FDC |
| 2 | ST506 |
| 3 | IDE |
| 4 | SATA |
| 5 | SCSI |
| 6 | NVMe |
| 7 | RAM |
| Value | Connection type |
|---|---|
| 0 | Unknown |
| 1 | Motherboard |
| 2 | Podule |
| 3 | PCI |
| 4 | Parallel |
| 5 | Serial |
| 6 | USB |
| Value | Media type |
|---|---|
| 0 | Unknown |
| 1 | Floppy |
| 2 | HardDisc |
| 3 | SSD |
| 4 | RAM |
| 5 | CD |
| 6 | DVD |
| 7 | BluRay |
Each of the four enumerations also defines a value of -1, named Parent in each case (for example BlockDevice_DeviceType_Parent). This value is only meaningful when registering a new device: it tells BlockDevices to copy that particular classification from the device's parent, rather than have the registering driver state it explicitly. If Parent is used but the device has no valid parent - either because parent_device is 0, or because it does not refer to a currently registered device - registration fails with Error_RegisterFailed.
Device Flags
The flags field of the device information block is a bit mask:
| Bit(s) | Name | Meaning | |
|---|---|---|---|
| 0 | ReadOnly | Set if the device cannot be written to. | |
| 1 | CanEject | Set if the device supports an eject operation. | |
| 2-30 | Reserved, must be zero | ||
| 31 | Debug | Set to indicate that debug output is enabled for the device. | |
Transfer Block
Read, write and verify requests describe the data to be transferred using a transfer block, of type transfer_block_t:
| Field | Type | Description |
|---|---|---|
| lba | 64-bit unsigned | The logical block address at which the transfer starts. |
| address | pointer | The memory address to transfer to or from. Ignored for a verify operation. |
| count | unsigned | The number of blocks to transfer. |
The transfer block is passed by reference and is updated in place by the device driver, even when the transfer fails partway through, so that the caller can see exactly how much of the request was actually completed.
FIXME: The bounds check applied to a transfer block before it is passed to a device driver currently differs between 32-bit and 64-bit builds of this module: one treats "count" consistently as a number of blocks, as documented above and in the header, while the other divides it by the device's block size as though it were a byte count. Until this is resolved, do not rely on a request being accepted or rejected identically across architectures right at the edge of a device's capacity.
Device Naming
When a device is registered, BlockDevices takes the name supplied in the device information block and appends a sequence number to it, separated by a hyphen, to guarantee that the name presented to client software is unique.
If device_index is non-zero, that value is used directly as the sequence number. If it is zero, BlockDevices allocates the next unused sequence number itself, counting separately for each combination of supplied name and parent device; the first device registered with a given name under a given parent is given sequence number 1, the second is given sequence number 2, and so on, regardless of the order in which they are later deregistered.
A registering driver that wants its device to appear with a name reflecting its position in the device hierarchy (see Overview) is responsible for constructing that name itself before calling SWI BlockDevice_Register, typically by reading its parent's registered name with SWI BlockDevice_Info and appending an identifying component to it. BlockDevices does not build this hierarchy on the driver's behalf, and does not require the name to start with a / character or to contain any particular separator.
A device information block with no name at all (a null pointer) is rejected by SWI BlockDevice_Register, which fails with Error_RegisterFailed; an empty string is accepted, and produces a name consisting only of a hyphen and the sequence number.
Service Calls
BlockDevices defines four service calls of its own, which it issues to announce its own lifecycle and the arrival and departure of individual devices. It does not expect any of these to be claimed. It also handles the standard Service_ShutDownComplete service, using it as a final opportunity to announce every device it still has registered as removed before the system finishes shutting down.
| R1 | = | &81200 (service number) |
| R0 - R9 | preserved | |
This service is issued once, shortly after the module has finished starting up, and before any devices have had the opportunity to register. Software that wants to know when the block device registry is available for use, for example to trigger an initial scan for storage hardware, should watch for this service rather than assuming the module is present and ready as soon as it has been loaded.
| R1 | = | &81201 (service number) |
| R0 - R9 | preserved | |
This service is issued as the very last step before the module finishes finalising, once it has already announced every device still registered as removed (see Service_BlockDevices_DeviceRemoved). Software that has been keeping its own list of devices, built up from Service_BlockDevices_DeviceAdded and Service_BlockDevices_DeviceRemoved, can use this service purely as confirmation that the registry itself has gone; by the time it arrives, that list should already be empty.
| R0 | = | The device id of the newly registered device |
| R1 | = | &81202 (service number) |
| R2 | = | Pointer to the device's device information block |
| R0 - R9 | preserved | |
This service is issued once for every device that is registered with SWI BlockDevice_Register. It is delivered asynchronously, from a callback queued after the registering SWI has already returned; a driver's call to SWI BlockDevice_Register therefore completes, and returns the new device id, before this service has necessarily been broadcast. If several devices are registered in quick succession, the corresponding services are queued and delivered one at a time, in the order the devices were registered.
The pointer given in R2 is only valid for the duration of this service call; it must not be retained beyond it.
| R0 | = | The device id of the device being removed |
| R1 | = | &81203 (service number) |
| R2 | = | Pointer to the device's device information block, as it was immediately before removal |
| R0 - R9 | preserved | |
Unlike Service_BlockDevices_DeviceAdded, this service is issued synchronously, at the point the removal happens, in each of three circumstances:
- A driver calls SWI BlockDevice_Deregister to remove one of its devices. The service is issued before the SWI returns.
- A driver, or other software, calls SWI BlockDevice_Rescan on a device. In this case the service is followed immediately by a fresh Service_BlockDevices_DeviceAdded for the same device id, and the device itself is not actually removed from the registry; this pairing exists so that software watching for these services can be told to re-read a device's information, for example after a media change, without having to treat the device as if it had been unplugged and replaced.
- The module itself is finalised, in which case it is issued once for every device still registered, before Service_BlockDevices_Dying is issued.
As with Service_BlockDevices_DeviceAdded, the pointer given in R2 is only valid for the duration of the service call. Once it returns, the device id may no longer be used with any of the client or driver SWIs, except in the rescan case above, where the same id continues to refer to the same device.
SWI Calls
BlockDevices provides SWIs to two different audiences. The first group, described first, is intended for client software that wants to find and use registered devices. The second group is intended for device drivers, and is used to register and deregister the devices those drivers control.
| R0 | = | The device id of the last device returned, or 0 to start enumeration from the first device |
| R0 | = | 0 if there are no more devices; otherwise the device id of the next registered device |
| R1 | = | Pointer to that device's device information block. Only meaningful if R0 is non-zero. |
Repeated calls, each passing back the device id returned in R0 by the previous call, visit every device currently registered, regardless of its position in the device hierarchy, until R0 comes back as 0. To enumerate only the direct children of a particular device, use SWI BlockDevice_EnumerateChildren instead.
The set of devices visited is not fixed for the duration of an enumeration: devices registered or removed between calls may or may not be seen, depending on where they fall relative to the point the enumeration has reached.
| R0 | = | The device id of the last child returned, or 0 to start enumeration from the first matching device |
| R1 | = | The device id of the parent device whose children are being enumerated, or 0 to enumerate devices which have no parent |
| R0 | = | 0 if there are no more matching devices; otherwise the device id of the next child device |
| R1 | = | Pointer to that device's device information block. Only meaningful if R0 is non-zero. |
This behaves as SWI BlockDevice_Enumerate does, except that only devices whose parent_device matches the value supplied in R1 are considered; it does not recurse into grandchildren.
Because R1 is used both to supply the parent device id on entry and to return a device information pointer on exit, the parent device id is not preserved across calls. The same parent device id must be passed in R1 again on every call used to continue a single enumeration.
| R0 | = | Device id |
| R1 | = | Pointer to the device's device information block |
This provides a direct way to look up a single device's information block when its device id is already known, without walking the full list of registered devices with SWI BlockDevice_Enumerate.
| R0 | = | Device id to read from |
| R1 | = | Flags for the read operation |
| R2 | = | Pointer to a transfer block describing the read |
| R2 | = | Pointer to the same transfer block, updated to reflect the amount of data actually transferred |
BlockDevices checks that the requested block range lies within the device's reported capacity before calling the device driver; if it does not, the call fails with Error_TransferOutsideDevice and the driver is not called at all.
No flag bits in R1 are currently defined by BlockDevices itself; the value is passed through unchanged to the device driver, which is free to interpret it according to its own requirements. Callers should pass 0 unless a specific driver documents otherwise.
| R0 | = | Device id to write to |
| R1 | = | Flags for the write operation |
| R2 | = | Pointer to a transfer block describing the write |
| R2 | = | Pointer to the same transfer block, updated to reflect the amount of data actually transferred |
This behaves as SWI BlockDevice_Read does, except that data is transferred from memory to the device. If the device was registered with the ReadOnly flag set, a well-behaved device driver returns Error_ReadOnly rather than performing the write.
| R0 | = | Device id to verify |
| R1 | = | Flags for the verify operation |
| R2 | = | Pointer to a transfer block describing the verify. The address field is ignored. |
| R2 | = | Pointer to the same transfer block, updated to reflect the amount of data actually verified |
This behaves as SWI BlockDevice_Read does, except that no data is transferred to memory; the device driver is simply asked to confirm that the given blocks can be read successfully.
| R0 | = | Device id |
| R1 | = |
Operation code: |
| R2 - R7 | = | Dependent on operation code |
This SWI is forwarded directly to the device's driver, which is responsible for implementing whichever of the operation codes below it supports, and for returning Error_BadMiscOp for any it does not.
| R0 | = | Device id |
| R1 | = | 1 (operation code) |
| R0 | = | Pointer to a zero-terminated string giving the device's serial number |
Returns the serial number reported by the underlying hardware, where the driver is able to obtain one.
| R0 | = | Device id |
| R1 | = | 2 (operation code) |
| R0 | = | Pointer to a zero-terminated string giving the device's model name |
Returns the model name reported by the underlying hardware.
| R0 | = | Device id |
| R1 | = | 3 (operation code) |
| R0 | = | Pointer to a zero-terminated string giving the device's firmware version |
Returns the firmware version reported by the underlying hardware.
| R0 | = | Device id |
| R1 | = | 4 (operation code) |
| R2 | = | Pointer to a buffer to receive SMART attribute entries, or 0 to only discover how many are available |
| R3 | = | The size of the buffer, in entries; or, together with R4 set to 0, requests that the number of entries needed is returned instead of reading any |
| R3 | = | The number of entries returned, or the number required if the supplied buffer was too small |
Each entry returned is a smart_attribute_t, giving the attribute's status, type, identifying number and value, in a form that depends on the interface type of the underlying device (general, IDE, SCSI or NVMe). If the supplied buffer is not large enough for the number of attributes available, R3 is set to the number that would have been needed, and, unless R2 was 0, an error is returned rather than a partial result.
| R0 | = | Device id | ||||||||||||
| R1 | = | 5 (operation code) | ||||||||||||
| R2 | = |
Test type:
|
Requests that the device begin the given self-test, or abort a test already running. Progress and results are read back with SWI BlockDevice_DeviceOp 6.
| R0 | = | Device id |
| R1 | = | 6 (operation code) |
| R2 | = | Pointer to a buffer to receive log entries |
| R3 | = | The number of entries to retrieve |
Each entry returned is a self_test_result_t, giving the status, failure reason, percentage complete, test type and interface type of one previously requested self-test, together with the time it was run and, if applicable, the LBA at which it failed.
| R0 | = | Device id |
| R1 | = | 7 (operation code) |
| R0 | = |
The partition scheme used:
|
This is only meaningful for a device of type Partition, and describes the scheme-specific entry the partition was created from, rather than anything about the underlying disc as a whole.
| R0 | = | Device id | ||||||
| R1 | = |
Operation code:
| ||||||
| R2 - R7 | = | Dependent on operation code |
As with SWI BlockDevice_DeviceOp, this SWI is forwarded directly to the device's driver.
| R0 | = | Device id |
| R1 | = | 2 (operation code) |
| R2 | = | Pointer to a buffer to receive block size entries |
| R3 | = | Index of the first entry to return, or 0 to start from the first |
| R4 | = | The number of entries to retrieve |
| R3 | = | Index to pass on the next call, to continue after the entries just returned |
| R4 | = | The number of entries actually returned |
Each entry returned is a block_size_t, giving a supported block size in bytes together with a relative priority, and an optional textual description of that priority, so that client software can choose sensibly between several supported sizes.
| R0 | = | Device id |
| R1 | = | 3 (operation code) |
| R2 | = | The new block size, in bytes |
Requests that the device driver reformat its addressing to use the given block size, which should be one of those previously reported by SWI BlockDevice_MediaOp 2. This is expected to change the device's reported block_size and block_count.
| R0 | = | Device id |
| R1 | = | Operation code |
| R2 - R7 | = | Dependent on operation code |
This SWI is reserved for security-related operations, such as those governing a device's own password locking or encryption facilities, and is forwarded directly to the device's driver.
FIXME: No operation codes are yet defined for this SWI.
| R0 | = | Device id | ||||
| R1 | = |
Operation code:
| ||||
| R2 - R7 | = | Dependent on operation code |
As with SWI BlockDevice_DeviceOp, this SWI is forwarded directly to the device's driver.
| R0 | = | Device id |
| R1 | = | 1 (operation code) |
| R2 | = | Pointer to a trim_request_t, giving the starting LBA and the number of blocks concerned |
This allows client software to tell a device, typically an SSD, that a range of blocks no longer holds meaningful data, so that the device can reclaim the underlying storage. It has no effect on what is subsequently read back from the given range, beyond that the content becomes undefined.
| R0 | = | Device id |
| R1 | = | Operation code |
| R2 - R7 | = | Dependent on operation code |
Unlike SWI BlockDevice_DeviceOp, SWI BlockDevice_MediaOp, SWI BlockDevice_SecurityOp and SWI BlockDevice_SectorOp, no operation codes for this SWI are defined or reserved by BlockDevices itself. It exists purely as a low-level extension point through which a device driver may expose operations specific to itself, with the meaning of every register beyond R0 left entirely to that driver to define and document.
The following SWIs are used by device drivers to register and deregister the devices they control, and to ask BlockDevices to re-announce a device whose information has changed. They are not normally of interest to other client software.
| R0 | = | Flags. Must be 0; non-zero values are reserved for future use. |
| R1 | = | Pointer to a device information block describing the device |
| R2 | = | The address of the driver's entry point (see Entry Points) |
| R3 | = | The value to give the driver in R12 (its private word) every time its entry point is called for this device |
| R4 | = | An opaque reference the driver will receive in R9 every time its entry point is called for this device |
| R0 | = | The device id allocated to the newly registered device |
BlockDevices copies the fields of the device information block it needs, so the block supplied in R1 need not remain valid once this SWI returns. The name given is adjusted as described in Device Naming, and any classification field given as Parent is resolved against the given parent device, as described in Device, Interface, Connection and Media Types.
The module must have finished starting before a device can be registered; calling this SWI too early fails with Error_Starting. Registration is announced to the rest of the system with Service_BlockDevices_DeviceAdded, delivered asynchronously after this SWI has already returned.
| R0 | = | Device id to deregister |
This permanently removes the device from the registry; its device id is not reused. Removal is announced synchronously, with Service_BlockDevices_DeviceRemoved issued before this SWI returns. A driver should only deregister devices it registered itself, once they are no longer available, for example because the underlying hardware has been removed.
| R0 | = | Device id to rescan |
This tells BlockDevices to issue Service_BlockDevices_DeviceRemoved immediately followed by Service_BlockDevices_DeviceAdded for the given device, both before this SWI returns, without actually removing the device from the registry or changing its device id. It is intended for a driver to call after something about a device has changed in a way its current device information block does not reflect, for example a media change on removable storage, so that software watching the service calls re-reads the device rather than treating it as newly attached hardware.
FIXME: Passing 0 is documented in the module's own sources as a request to rescan every registered device, but the current implementation does not do this: it silently succeeds without rescanning anything. Until this is corrected, rescan every device of interest individually by device id.
Entry Points
A device driver registers a single entry point address with SWI BlockDevice_Register, together with a private word and an opaque reference. BlockDevices calls this entry point directly, in SVC mode, with R12 set to the private word supplied at registration, for every read, write, verify or miscellaneous operation requested for that device; the driver never needs to be a full module, and does not see the client's original SWI number.
The entry point is always called with the opaque reference supplied at registration in R9, and a reason code, identifying which operation is being requested, in R8. This allows one entry point, and one private word, to serve several devices: a driver can use the reference in R9 to tell its devices apart, without needing a separate entry point per device.
| R8 | = |
Reason code: |
| R9 | = | The opaque reference supplied when the device was registered |
On failure, the entry point returns a pointer to a standard RISC OS error block, exactly as a SWI handler would. On success, it returns registers updated as described for each reason code below.
| R0 | = | Device id |
| R1 | = | Flags, as supplied to SWI BlockDevice_Read |
| R2 | = | Pointer to the transfer block supplied to SWI BlockDevice_Read |
| R8 | = | 1 (reason code) |
| R9 | = | Opaque reference |
| R2 | = | The same transfer block, updated to reflect the data actually read |
BlockDevices has already checked that the requested range lies within the device's reported capacity before calling the driver; see Transfer Block. The driver is not given any registers to update directly: it signals how much was transferred purely by updating the fields of the transfer block itself.
| R0 | = | Device id |
| R1 | = | Flags, as supplied to SWI BlockDevice_Write |
| R2 | = | Pointer to the transfer block supplied to SWI BlockDevice_Write |
| R8 | = | 2 (reason code) |
| R9 | = | Opaque reference |
| R2 | = | The same transfer block, updated to reflect the data actually written |
A driver for a device registered with the ReadOnly flag set should return Error_ReadOnly here rather than performing the write.
| R0 | = | Device id |
| R1 | = | Flags, as supplied to SWI BlockDevice_Verify |
| R2 | = | Pointer to the transfer block supplied to SWI BlockDevice_Verify. The address field is not meaningful. |
| R8 | = | 3 (reason code) |
| R9 | = | Opaque reference |
| R2 | = | The same transfer block, updated to reflect the data actually verified |
The driver should confirm that the given blocks can be read successfully, without transferring their contents anywhere.
| R0 - R7 | = | As supplied to SWI BlockDevice_DeviceOp, with R0 the device id and R1 the operation code |
| R8 | = | 4 (reason code) |
| R9 | = | Opaque reference |
| R0 - R7 | = | Returned to the caller of SWI BlockDevice_DeviceOp exactly as left by the driver; any register the driver does not explicitly set retains the value it had on entry |
The driver should implement whichever of the operation codes described under SWI BlockDevice_DeviceOp it supports, and return Error_BadMiscOp for any it does not.
| R0 - R7 | = | As supplied to SWI BlockDevice_MediaOp, with R0 the device id and R1 the operation code |
| R8 | = | 5 (reason code) |
| R9 | = | Opaque reference |
| R0 - R7 | = | Returned to the caller of SWI BlockDevice_MediaOp exactly as left by the driver; any register the driver does not explicitly set retains the value it had on entry |
The driver should implement whichever of the operation codes described under SWI BlockDevice_MediaOp it supports.
| R0 - R7 | = | As supplied to SWI BlockDevice_SecurityOp, with R0 the device id and R1 the operation code |
| R8 | = | 6 (reason code) |
| R9 | = | Opaque reference |
| R0 - R7 | = | Returned to the caller of SWI BlockDevice_SecurityOp exactly as left by the driver; any register the driver does not explicitly set retains the value it had on entry |
No operation codes are yet defined for this reason; a driver called with it today should return Error_BadMiscOp.
| R0 - R7 | = | As supplied to SWI BlockDevice_SectorOp, with R0 the device id and R1 the operation code |
| R8 | = | 7 (reason code) |
| R9 | = | Opaque reference |
| R0 - R7 | = | Returned to the caller of SWI BlockDevice_SectorOp exactly as left by the driver; any register the driver does not explicitly set retains the value it had on entry |
The driver should implement whichever of the operation codes described under SWI BlockDevice_SectorOp it supports.
| R0 - R7 | = | As supplied to SWI BlockDevice_DriverOp, with R0 the device id and R1 an operation code defined entirely by the driver |
| R8 | = | 8 (reason code) |
| R9 | = | Opaque reference |
| R0 - R7 | = | Returned to the caller of SWI BlockDevice_DriverOp exactly as left by the driver; any register the driver does not explicitly set retains the value it had on entry |
This reason exists purely so that individual drivers can offer their own extensions; BlockDevices imposes no meaning at all on the registers beyond R0.
Error Messages
BlockDevices reports the following errors, all allocated from a single registered error base. Several of these - notably Error_ReadOnly and Error_BadMiscOp - are intended to be returned by an individual device driver rather than by BlockDevices itself.
Returned by the driver registration SWIs, and by the module's star commands, if they are called before the module has finished starting up.
Reported if the module's own workspace could not be set up when it was started; the module does not start successfully.
Returned by any SWI given a device id that does not currently refer to a registered device.
Reserved for use where an operation is understood but cannot be carried out in the caller's current circumstances.
Returned by SWI BlockDevice_Register if the supplied device information could not be used to create a device, for example because a required parent device could not be found.
Reserved for SWI BlockDevice_Register, for use when a flags value that is not recognised is supplied in R0.
Expected to be returned by a device driver when a write is attempted against a device that was registered with the ReadOnly flag set.
Returned by SWI BlockDevice_Read, SWI BlockDevice_Write and SWI BlockDevice_Verify when the requested block range extends beyond the device's reported capacity. The device driver is not called.
Expected to be returned by a device driver when it is asked to perform an operation code, under SWI BlockDevice_DeviceOp, SWI BlockDevice_MediaOp, SWI BlockDevice_SecurityOp, SWI BlockDevice_SectorOp or SWI BlockDevice_DriverOp, that it does not implement.
Reserved for use where a parameter supplied to one of the miscellaneous operation SWIs is invalid for the operation requested.
Reserved for use where an operation requires at least one registered device to be present.
*Commands
BlockDevices provides two star commands, both purely informational, for inspecting the devices currently registered from the command line.
Displays a table of every registered device, with top-level devices listed first and their children indented beneath them, giving each device's id, name, description, type, interface, connection, media type and capacity.
<device> | - | The device id of the device to describe, as shown by *BlockDevices. |
Displays the description, capacity, device type, interface, connection type, media type, parent device id and flags of the given device.
Examples
Enumerating registered devices
The following walks every device currently registered, printing its id and description:
#include "kernel.h"
#include "swis.h"
#include "BlockDevices.h"
void list_devices(void)
{
int device_id = 0;
device_info_t *info;
for (;;)
{
_kernel_oserror *err = _swix(BlockDevice_Enumerate, _INR(0,0) | _OUTR(0,1),
device_id, &device_id, &info);
if (err || device_id == 0)
break;
printf("%d: %s\n", device_id, info->description);
}
}
Reading blocks from a device
Given a device id, a buffer to read into and a starting block address, a single read is issued like this:
#include "kernel.h"
#include "swis.h"
#include "BlockDevices.h"
_kernel_oserror *read_blocks(int device_id, blockaddr_t lba,
void *buffer, unsigned count)
{
transfer_block_t transfer;
transfer.lba = lba;
transfer.address = buffer;
transfer.count = count;
return _swix(BlockDevice_Read, _INR(0,2), device_id, 0, &transfer);
}
Writing and registering a block device driver
A driver registers each device it controls once it has identified it, supplying a description of the device and an entry point through which BlockDevices will call it back:
#include "kernel.h"
#include "swis.h"
#include "BlockDevices.h"
static device_info_t my_device =
{
"mydriver", /* name */
0, /* flags */
0, /* parent */
0, /* auto index */
2097152, /* blocks */
512, /* block size */
BlockDevice_DeviceType_Disc,
BlockDevice_InterfaceType_SCSI,
BlockDevice_ConnectionType_USB,
BlockDevice_MediaType_HardDisc,
"My Example Disc"
};
extern void mydriver_entry(void); /* implemented in assembler, or as a
generic veneer, calling
mydriver_handler below */
int mydriver_register(void *private_word, void *unit_reference)
{
int device_id;
_kernel_oserror *err;
err = _swix(BlockDevice_Register, _INR(0,4) | _OUTR(0,0),
0, &my_device, mydriver_entry, private_word, unit_reference,
&device_id);
if (err)
return -1;
return device_id;
}
The registered entry point dispatches on the reason code it is given in R8, handling at least reads and writes:
_kernel_oserror *mydriver_handler(_kernel_swi_regs *r, void *pw)
{
switch (r->r[8])
{
case BlockDevices_Reason_Read:
return mydriver_read(pw, r->r[9],
(transfer_block_t *)r->r[2]);
case BlockDevices_Reason_Write:
return mydriver_write(pw, r->r[9],
(transfer_block_t *)r->r[2]);
default:
return err_BadMiscOp;
}
}
When the underlying hardware is later removed, the driver deregisters the device it created for it:
void mydriver_remove(int device_id)
{
_swix(BlockDevice_Deregister, _INR(0,0), device_id);
}