Pyromaniac

Pyromaniac PRM: BlockDevices

Pyromaniac PRM: BlockDevices

RISC OS PyromaniacBlockDevices

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.

FieldTypeDescription
namestring pointerThe device's registered name (see Device Naming).
flagsunsignedDevice flags; see Device Flags.
parent_deviceunsignedThe device id of this device's parent, or 0 if it has none.
device_indexunsignedAn explicit index to use when disambiguating the device's name, or 0 to have one allocated automatically; see Device Naming.
block_count64-bit unsignedThe total number of addressable blocks on the device.
block_sizeunsignedThe size, in bytes, of one block on the device.
device_typeenumerationWhat kind of device this is; see Device, Interface, Connection and Media Types.
interface_typeenumerationThe interface the device is attached through.
connection_typeenumerationThe physical connection carrying that interface.
media_typeenumerationThe kind of media the device represents.
descriptionstring pointerA 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:

ValueDevice type
0Unknown
1Controller
2Drive
3Disc
4Partition
5Track
ValueInterface type
0Unknown
1FDC
2ST506
3IDE
4SATA
5SCSI
6NVMe
7RAM
ValueConnection type
0Unknown
1Motherboard
2Podule
3PCI
4Parallel
5Serial
6USB
ValueMedia type
0Unknown
1Floppy
2HardDisc
3SSD
4RAM
5CD
6DVD
7BluRay

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)NameMeaning
0ReadOnlySet if the device cannot be written to.
1CanEjectSet if the device supports an eject operation.
2-30Reserved, must be zero
31DebugSet 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:

FieldTypeDescription
lba64-bit unsignedThe logical block address at which the transfer starts.
addresspointerThe memory address to transfer to or from. Ignored for a verify operation.
countunsignedThe 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.

Service_BlockDevices_StartedService Call &81200
The BlockDevices module has completed initialisation
R1=&81200 (service number)
R0 - R9preserved

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.

Service_BlockDevices_DyingService Call &81201
The BlockDevices module is about to be finalised
R1=&81201 (service number)
R0 - R9preserved

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.

Service_BlockDevices_DeviceAddedService Call &81202
A new block device has been registered
R0=The device id of the newly registered device
R1=&81202 (service number)
R2=Pointer to the device's device information block
R0 - R9preserved

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.

Service_BlockDevices_DeviceRemovedService Call &81203
A block device has been deregistered
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 - R9preserved

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.

BlockDevice_EnumerateSWI &5A6D3
Enumerates the block devices currently registered
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.
Interrupts are undefined
Fast interrupts are undefined
Processor is in SVC mode
Not defined

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.

BlockDevice_EnumerateChildrenSWI &5A6D4
Enumerates the direct children of a given device
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.
Interrupts are undefined
Fast interrupts are undefined
Processor is in SVC mode
Not defined

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.

BlockDevice_InfoSWI &5A6C0
Reads the device information block for a single device
R0=Device id
R1=Pointer to the device's device information block
Interrupts are undefined
Fast interrupts are undefined
Processor is in SVC mode
Not defined

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.

BlockDevice_ReadSWI &5A6C1
Reads one or more blocks from a device
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
Interrupts are undefined
Fast interrupts are undefined
Processor is in SVC mode
Not defined

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.

BlockDevice_WriteSWI &5A6C2
Writes one or more blocks to a device
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
Interrupts are undefined
Fast interrupts are undefined
Processor is in SVC mode
Not defined

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.

BlockDevice_VerifySWI &5A6C3
Verifies one or more blocks on a device
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
Interrupts are undefined
Fast interrupts are undefined
Processor is in SVC mode
Not defined

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.

BlockDevice_DeviceOpSWI &5A6C4
Performs a miscellaneous whole-device operation
R0=Device id
R1=

Operation code:

ValueOperation
1Reads the device's serial number
2Reads the device's model name
3Reads the device's firmware version
4Reads SMART health attributes for the device
5Starts, or aborts, a self-test on the device
6Reads the device's self-test log
7Reads the partition table entry a partition device was created from

R2 - R7=Dependent on operation code
None
Interrupts are undefined
Fast interrupts are undefined
Processor is in SVC mode
Not defined

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.

BlockDevice_DeviceOp 1SerialNumberSWI &5A6C4
Reads the device's serial number
R0=Device id
R1=1 (operation code)
R0=Pointer to a zero-terminated string giving the device's serial number
Interrupts are undefined
Fast interrupts are undefined
Processor is in undefined mode
Not defined

Returns the serial number reported by the underlying hardware, where the driver is able to obtain one.

BlockDevice_DeviceOp 2ModelNameSWI &5A6C4
Reads the device's model name
R0=Device id
R1=2 (operation code)
R0=Pointer to a zero-terminated string giving the device's model name
Interrupts are undefined
Fast interrupts are undefined
Processor is in undefined mode
Not defined

Returns the model name reported by the underlying hardware.

BlockDevice_DeviceOp 3FirmwareSWI &5A6C4
Reads the device's firmware version
R0=Device id
R1=3 (operation code)
R0=Pointer to a zero-terminated string giving the device's firmware version
Interrupts are undefined
Fast interrupts are undefined
Processor is in undefined mode
Not defined

Returns the firmware version reported by the underlying hardware.

BlockDevice_DeviceOp 4SMARTSWI &5A6C4
Reads SMART health attributes for the device
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
Interrupts are undefined
Fast interrupts are undefined
Processor is in undefined mode
Not defined

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.

BlockDevice_DeviceOp 5SelfTestSWI &5A6C4
Starts, or aborts, a self-test on the device
R0=Device id
R1=5 (operation code)
R2=

Test type:

ValueTest
1Short self-test
2Long self-test
3Conveyance self-test
4Extended self-test
-1Abort any self-test in progress

None
Interrupts are undefined
Fast interrupts are undefined
Processor is in undefined mode
Not defined

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.

BlockDevice_DeviceOp 6SelfTestLogSWI &5A6C4
Reads the device's self-test log
R0=Device id
R1=6 (operation code)
R2=Pointer to a buffer to receive log entries
R3=The number of entries to retrieve
None
Interrupts are undefined
Fast interrupts are undefined
Processor is in undefined mode
Not defined

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.

BlockDevice_DeviceOp 7PartitionInfoSWI &5A6C4
Reads the partition table entry a partition device was created from
R0=Device id
R1=7 (operation code)
R0=

The partition scheme used:

ValueScheme
1MBR. R1 gives the partition type byte.
2GPT. R1 points at the partition type GUID, and R2 at the partition's own GUID.

Interrupts are undefined
Fast interrupts are undefined
Processor is in undefined mode
Not defined

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.

BlockDevice_MediaOpSWI &5A6C5
Performs a miscellaneous media-related operation
R0=Device id
R1=

Operation code:

ValueOperation
2Lists the block sizes the media supports
3Changes the block size used to address the media

R2 - R7=Dependent on operation code
None
Interrupts are undefined
Fast interrupts are undefined
Processor is in SVC mode
Not defined

As with SWI BlockDevice_DeviceOp, this SWI is forwarded directly to the device's driver.

BlockDevice_MediaOp 2BlockSizesSWI &5A6C5
Lists the block sizes the media supports
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
Interrupts are undefined
Fast interrupts are undefined
Processor is in undefined mode
Not defined

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.

BlockDevice_MediaOp 3SetBlockSizeSWI &5A6C5
Changes the block size used to address the media
R0=Device id
R1=3 (operation code)
R2=The new block size, in bytes
None
Interrupts are undefined
Fast interrupts are undefined
Processor is in undefined mode
Not defined

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.

BlockDevice_SecurityOpSWI &5A6C6
Performs a miscellaneous security-related operation
R0=Device id
R1=Operation code
R2 - R7=Dependent on operation code
None
Interrupts are undefined
Fast interrupts are undefined
Processor is in SVC mode
Not defined

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.

BlockDevice_SectorOpSWI &5A6C7
Performs a miscellaneous operation on a range of sectors
R0=Device id
R1=

Operation code:

ValueOperation
1Advises the device that a range of blocks is no longer in use

R2 - R7=Dependent on operation code
None
Interrupts are undefined
Fast interrupts are undefined
Processor is in SVC mode
Not defined

As with SWI BlockDevice_DeviceOp, this SWI is forwarded directly to the device's driver.

BlockDevice_SectorOp 1TRIMSWI &5A6C7
Advises the device that a range of blocks is no longer in use
R0=Device id
R1=1 (operation code)
R2=Pointer to a trim_request_t, giving the starting LBA and the number of blocks concerned
None
Interrupts are undefined
Fast interrupts are undefined
Processor is in undefined mode
Not defined

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.

BlockDevice_DriverOpSWI &5A6C8
Performs an operation defined entirely by the device driver
R0=Device id
R1=Operation code
R2 - R7=Dependent on operation code
None
Interrupts are undefined
Fast interrupts are undefined
Processor is in SVC mode
Not defined

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.

BlockDevice_RegisterSWI &5A6D0
Registers a new block device
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
Interrupts are undefined
Fast interrupts are undefined
Processor is in SVC mode
Not defined

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.

BlockDevice_DeregisterSWI &5A6D1
Deregisters a block device
R0=Device id to deregister
None
Interrupts are undefined
Fast interrupts are undefined
Processor is in SVC mode
Not defined

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.

BlockDevice_RescanSWI &5A6D2
Announces that a device's information should be re-read
R0=Device id to rescan
None
Interrupts are undefined
Fast interrupts are undefined
Processor is in SVC mode
Not defined

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.

BlockDeviceEntry
Called by BlockDevices to perform an operation on a registered device
R8=

Reason code:

ValueOperation
1Reads blocks from the device
2Writes blocks to the device
3Verifies blocks on the device
4Performs a whole-device operation requested through BlockDevice_DeviceOp
5Performs a media-related operation requested through BlockDevice_MediaOp
6Performs a security-related operation requested through BlockDevice_SecurityOp
7Performs a sector-range operation requested through BlockDevice_SectorOp
8Performs an operation requested through BlockDevice_DriverOp

R9=The opaque reference supplied when the device was registered
None
Interrupts are undefined
Fast interrupts are undefined
Processor is in SVC mode
Not defined

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.

BlockDeviceEntry 1Read
Reads blocks from the device
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
Interrupts are undefined
Fast interrupts are undefined
Processor is in undefined mode
Not defined

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.

BlockDeviceEntry 2Write
Writes blocks to the device
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
Interrupts are undefined
Fast interrupts are undefined
Processor is in undefined mode
Not defined

A driver for a device registered with the ReadOnly flag set should return Error_ReadOnly here rather than performing the write.

BlockDeviceEntry 3Verify
Verifies blocks on the device
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
Interrupts are undefined
Fast interrupts are undefined
Processor is in undefined mode
Not defined

The driver should confirm that the given blocks can be read successfully, without transferring their contents anywhere.

BlockDeviceEntry 4DeviceOp
Performs a whole-device operation requested through BlockDevice_DeviceOp
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
Interrupts are undefined
Fast interrupts are undefined
Processor is in undefined mode
Not defined

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.

BlockDeviceEntry 5MediaOp
Performs a media-related operation requested through BlockDevice_MediaOp
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
Interrupts are undefined
Fast interrupts are undefined
Processor is in undefined mode
Not defined

The driver should implement whichever of the operation codes described under SWI BlockDevice_MediaOp it supports.

BlockDeviceEntry 6SecurityOp
Performs a security-related operation requested through BlockDevice_SecurityOp
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
Interrupts are undefined
Fast interrupts are undefined
Processor is in undefined mode
Not defined

No operation codes are yet defined for this reason; a driver called with it today should return Error_BadMiscOp.

BlockDeviceEntry 7SectorOp
Performs a sector-range operation requested through BlockDevice_SectorOp
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
Interrupts are undefined
Fast interrupts are undefined
Processor is in undefined mode
Not defined

The driver should implement whichever of the operation codes described under SWI BlockDevice_SectorOp it supports.

BlockDeviceEntry 8DriverOp
Performs an operation requested through BlockDevice_DriverOp
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
Interrupts are undefined
Fast interrupts are undefined
Processor is in undefined mode
Not defined

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.

Error_BlockDevice_StartingError &822300
Module is starting

Returned by the driver registration SWIs, and by the module's star commands, if they are called before the module has finished starting up.

Error_BlockDevice_InitFailedError &822301
Initialisation failed

Reported if the module's own workspace could not be set up when it was started; the module does not start successfully.

Error_BlockDevice_BadDeviceError &822302
Bad device identifier to BlockDevices

Returned by any SWI given a device id that does not currently refer to a registered device.

Error_BlockDevice_UnsupportedOperationError &822303
Unsupported operation

Reserved for use where an operation is understood but cannot be carried out in the caller's current circumstances.

Error_BlockDevice_RegisterFailedError &822304
Device registration failed

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.

Error_BlockDevice_RegisterFailedFlagsError &822305
Device registration failed (flags not supported)

Reserved for SWI BlockDevice_Register, for use when a flags value that is not recognised is supplied in R0.

Error_BlockDevice_ReadOnlyError &822306
Device cannot be written to (device is read only)

Expected to be returned by a device driver when a write is attempted against a device that was registered with the ReadOnly flag set.

Error_BlockDevice_TransferOutsideDeviceError &822307
Block transfer is beyond device end

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.

Error_BlockDevice_BadMiscOpError &822308
BlockDevice does not support MiscOp operation

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.

Error_BlockDevice_BadParameterError &822309
Bad parameter

Reserved for use where a parameter supplied to one of the miscellaneous operation SWIs is invalid for the operation requested.

Error_BlockDevice_NoDevicesError &82230A
No block devices found

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.

*BlockDevices
Lists the block devices currently registered
*BlockDevices
None

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.

*BlockDevices
*BlockDeviceInfo
Displays information about a single block device
*BlockDeviceInfo device
<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.

*BlockDeviceInfo 3

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);
}