diff --git a/doc/source/admin/index.rst b/doc/source/admin/index.rst
index 3e11d35818..a6ac6182dd 100644
--- a/doc/source/admin/index.rst
+++ b/doc/source/admin/index.rst
@@ -32,6 +32,7 @@ the services.
    Booting a Ramdisk or an ISO <ramdisk-boot>
    Deploying with anaconda deploy interface <anaconda-deploy-interface>
    Hardware Burn-in <hardware-burn-in>
+   Vendor Passthru <vendor-passthru>
 
 Drivers, Hardware Types and Hardware Interfaces
 -----------------------------------------------
diff --git a/doc/source/admin/vendor-passthru.rst b/doc/source/admin/vendor-passthru.rst
new file mode 100644
index 0000000000..7aa85da116
--- /dev/null
+++ b/doc/source/admin/vendor-passthru.rst
@@ -0,0 +1,113 @@
+Vendor Passthru
+===============
+
+The bare metal service allows drivers to expose vendor-specific API known as
+*vendor passthru*.
+
+Node Vendor Passthru
+--------------------
+
+Drivers may implement a passthrough API, which is accessible via
+the ``/v1/nodes/<Node UUID or Name>/vendor_passthru?method={METHOD}``
+endpoint. Beyond basic checking, Ironic does not introspect the message
+body and simply "passes it through" to the relevant driver.
+
+A method:
+
+* can support one or more HTTP methods (for example, GET, POST)
+
+* is asynchronous or synchronous
+
+  + For asynchronous methods, a 202 (Accepted) HTTP status code is returned
+    to indicate that the request was received, accepted and is being acted
+    upon. No body is returned in the response.
+
+  + For synchronous methods, a 200 (OK) HTTP status code is returned to
+    indicate that the request was fulfilled. The response may include a body.
+
+* can require an exclusive lock on the node. This only occurs if the method
+  doesn't specify require_exclusive_lock=False in the decorator. If an
+  exclusive lock is held on the node, other requests for the node will be
+  delayed and may fail with an HTTP 409 (Conflict) error code.
+
+This endpoint exposes a node's driver directly, and as such, it is
+expressly not part of Ironic's standard REST API. There is only a
+single HTTP endpoint exposed, and the semantics of the message body
+are determined solely by the driver. Ironic makes no guarantees about
+backwards compatibility; this is solely up to the discretion of each
+driver's author.
+
+To get information about all the methods available via the vendor_passthru
+endpoint for a particular node,  use CLI:
+
+.. code-block:: console
+
+    $ baremetal node passthru list <redfish-node>
+    +-----------------------+------------------------+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------+
+    | Name                  | Supported HTTP methods | Async | Description                                                                                                                                                                                            | Response is attachment |
+    +-----------------------+------------------------+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------+
+    | create_subscription   | POST                   | False | Creates a subscription on a node. Required argument: a dictionary of {'destination': 'destination_url'}                                                                                                | False                  |
+    | delete_subscription   | DELETE                 | False | Delete a subscription on a node. Required argument: a dictionary of {'id': 'subscription_bmc_id'}                                                                                                      | False                  |
+    | eject_vmedia          | POST                   | True  | Eject a virtual media device. If no device is provided then all attached devices will be ejected. Optional arguments: 'boot_device' - the boot device to eject, either 'cd', 'dvd', 'usb', or 'floppy' | False                  |
+    | get_all_subscriptions | GET                    | False | Returns all subscriptions on the node.                                                                                                                                                                 | False                  |
+    | get_subscription      | GET                    | False | Get a subscription on the node. Required argument: a dictionary of {'id': 'subscription_bmc_id'}                                                                                                       | False                  |
+    +-----------------------+------------------------+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------+
+
+The response will contain information for each method,
+such as the method's name, a description, the HTTP methods supported,
+and whether it's asynchronous or synchronous.
+
+You can call a method with CLI, for example:
+
+.. code-block:: console
+
+    $ baremetal node passthru call <redfish-node> eject_vmedia
+
+Driver Vendor Passthru
+----------------------
+
+Drivers may implement an API for requests not related to any node,
+at ``/v1/drivers/<driver name>/vendor_passthru?method={METHOD}``.
+
+A method:
+
+* can support one or more HTTP methods (for example, GET, POST)
+
+* is asynchronous or synchronous
+
+  + For asynchronous methods, a 202 (Accepted) HTTP status code is
+    returned to indicate that the request was received, accepted and is
+    being acted upon. No body is returned in the response.
+
+  + For synchronous methods, a 200 (OK) HTTP status code is returned
+    to indicate that the request was fulfilled. The response may include
+    a body.
+
+.. note::
+  Unlike methods in `Node Vendor Passthru`_, a request does not lock any
+  resource, so it will not delay other requests and will not fail with an
+  HTTP 409 (Conflict) error code.
+
+Ironic makes no guarantees about the semantics of the message BODY sent
+to this endpoint. That is left up to each driver's author.
+
+To get information about all the methods available via the driver
+vendor_passthru endpoint, use CLI:
+
+.. code-block:: console
+
+   $ baremetal driver passthru list redfish
+
+The response will contain information for each method,
+such as the method's name, a description, the HTTP methods supported,
+and whether it's asynchronous or synchronous.
+
+.. warning::
+   Currently only the methods available in the default interfaces of the
+   hardware type are available.
+
+You can call a method with CLI, for example:
+
+.. code-block:: console
+
+    $ baremetal driver passthru call <driver> <method>
diff --git a/doc/source/contributor/deploy-steps.rst b/doc/source/contributor/deploy-steps.rst
index ae79e56f18..be5b960f07 100644
--- a/doc/source/contributor/deploy-steps.rst
+++ b/doc/source/contributor/deploy-steps.rst
@@ -1,5 +1,8 @@
-Developing a new Deploy Step
-============================
+Developing deploy and clean steps
+=================================
+
+Deploy steps basics
+-------------------
 
 To support customized deployment step, implement a new method in an interface
 class and use the decorator ``deploy_step`` defined in
@@ -78,13 +81,139 @@ The above command outputs the ``driver_internal_info`` as following::
     }
   }
 
-.. note::
-
-    Similarly, clean steps can be implemented using the ``clean_step``
-    decorator.
-
 In-band deploy steps (deploy steps that are run inside the ramdisk) have to be
 implemented in a custom :ironic-python-agent-doc:`IPA hardware manager
 <contributor/hardware_managers.html#custom-hardwaremanagers-and-deploying>`.
 All in-band deploy steps must have priorities between 41 and 99, see
 :ref:`node-deployment-core-steps` for details.
+
+Clean steps basics
+------------------
+
+Clean steps are written similarly to deploy steps, but are executed during
+:doc:`cleaning </admin/cleaning>`. Steps with priority > 0 are executed during
+automated cleaning, all steps can be executed explicitly during manual
+cleaning. Unlike deploy steps, clean steps are commonly found in these
+interfaces:
+
+``bios``
+    Steps that apply BIOS settings, see `Implementing BIOS settings`_.
+``deploy``
+    Steps that undo the effect of deployment (e.g. erase disks).
+``management``
+    Additional steps that use the node's BMC, such as out-of-band firmware
+    update or BMC reset.
+``raid``
+    Steps that build or tear down RAID, see `Implementing RAID`_.
+
+.. note::
+   When designing a new step for your driver, try to make it consistent with
+   existing steps on other drivers.
+
+Just as deploy steps, in-band clean steps have to be
+implemented in a custom :ironic-python-agent-doc:`IPA hardware manager
+<contributor/hardware_managers.html#custom-hardwaremanagers-and-cleaning>`.
+
+Implementing RAID
+-----------------
+
+RAID is implemented via deploy and clean steps in the ``raid`` interfaces.
+By convention they have the following signatures:
+
+.. code-block:: python
+
+    from ironic.drivers import base
+
+    class MyRAID(base.RAIDInterface):
+
+        @base.clean_step(priority=0, abortable=False, argsinfo={
+            'create_root_volume': {
+                'description': (
+                    'This specifies whether to create the root volume. '
+                    'Defaults to `True`.'
+                ),
+                'required': False
+            },
+            'create_nonroot_volumes': {
+                'description': (
+                    'This specifies whether to create the non-root volumes. '
+                    'Defaults to `True`.'
+                ),
+                'required': False
+            },
+            'delete_existing': {
+                'description': (
+                    'Setting this to `True` indicates to delete existing RAID '
+                    'configuration prior to creating the new configuration. '
+                    'Default value is `False`.'
+                ),
+                'required': False,
+            }
+        })
+        def create_configuration(self, task, create_root_volume=True,
+                                 create_nonroot_volumes=True,
+                                 delete_existing=False):
+            pass
+
+        @base.clean_step(priority=0)
+        @base.deploy_step(priority=0)
+        def delete_configuration(self, task):
+            pass
+
+        @base.deploy_step(priority=0,
+                          argsinfo=base.RAID_APPLY_CONFIGURATION_ARGSINFO)
+        def apply_configuration(self, task, raid_config,
+                                create_root_volume=True,
+                                create_nonroot_volumes=False,
+                                delete_existing=False):
+            pass
+
+Notes:
+
+* ``create_configuration`` only works as a clean step, during deployment
+  ``apply_configuration`` is used instead.
+* ``apply_configuration`` accepts the target RAID configuration explicitly,
+  while ``create_configuration`` uses the node's ``target_raid_config`` field.
+* Priorities default to 0 since RAID should not be built by default.
+
+Implementing BIOS settings
+--------------------------
+
+BIOS is implemented via deploy and clean steps in the ``raid`` interfaces.
+By convention they have the following signatures:
+
+.. code-block:: python
+
+    from ironic.drivers import base
+
+    _APPLY_CONFIGURATION_ARGSINFO = {
+        'settings': {
+            'description': (
+                'A list of BIOS settings to be applied'
+            ),
+            'required': True
+        }
+    }
+
+    class MyBIOS(base.BIOSInterface):
+
+        @base.clean_step(priority=0)
+        @base.deploy_step(priority=0)
+        @base.cache_bios_settings
+        def factory_reset(self, task):
+            pass
+
+        @base.clean_step(priority=0, argsinfo=_APPLY_CONFIGURATION_ARGSINFO)
+        @base.deploy_step(priority=0, argsinfo=_APPLY_CONFIGURATION_ARGSINFO)
+        @base.cache_bios_settings
+        def apply_configuration(self, task, settings):
+            pass
+
+Notes:
+
+* Both ``factory_reset`` and ``apply_configuration`` can be used as deploy
+  and clean steps.
+* The ``cache_bios_settings`` decorator is used to ensure that the settings
+  cached in the ironic database is updated.
+* Priorities default to 0 since BIOS settings should not be modified
+  by default.
diff --git a/doc/source/contributor/drivers.rst b/doc/source/contributor/drivers.rst
index bd1b4f7c35..95b0b21867 100644
--- a/doc/source/contributor/drivers.rst
+++ b/doc/source/contributor/drivers.rst
@@ -121,88 +121,16 @@ create entry points for them in the ``setup.cfg`` file::
     ironic.hardware.interfaces.management =
         my-management = ironic.drivers.modules.my_hardware:MyManagement
 
+Deploy and clean steps
+----------------------
+
+Significant parts of the bare metal functionality is implemented via
+:doc:`deploy steps </admin/node-deployment>` or :doc:`clean steps
+</admin/cleaning>`. See :doc:`deploy-steps` for information on how to write
+them.
+
 Supported Drivers
 -----------------
 
 For a list of supported drivers (those that are continuously tested on every
 upstream commit) please consult the :doc:`drivers page </admin/drivers>`.
-
-Node Vendor Passthru
---------------------
-
-Drivers may implement a passthrough API, which is accessible via
-the ``/v1/nodes/<Node UUID or Name>/vendor_passthru?method={METHOD}``
-endpoint. Beyond basic checking, Ironic does not introspect the message
-body and simply "passes it through" to the relevant driver.
-
-A method:
-
-* can support one or more HTTP methods (for example, GET, POST)
-
-* is asynchronous or synchronous
-
-  + For asynchronous methods, a 202 (Accepted) HTTP status code is returned
-    to indicate that the request was received, accepted and is being acted
-    upon. No body is returned in the response.
-
-  + For synchronous methods, a 200 (OK) HTTP status code is returned to
-    indicate that the request was fulfilled. The response may include a body.
-
-* can require an exclusive lock on the node. This only occurs if the method
-  doesn't specify require_exclusive_lock=False in the decorator. If an
-  exclusive lock is held on the node, other requests for the node will be
-  delayed and may fail with an HTTP 409 (Conflict) error code.
-
-This endpoint exposes a node's driver directly, and as such, it is
-expressly not part of Ironic's standard REST API. There is only a
-single HTTP endpoint exposed, and the semantics of the message body
-are determined solely by the driver. Ironic makes no guarantees about
-backwards compatibility; this is solely up to the discretion of each
-driver's author.
-
-To get information about all the methods available via the vendor_passthru
-endpoint for a particular node, you can issue an HTTP GET request::
-
-  GET /v1/nodes/<Node UUID or name>/vendor_passthru/methods
-
-The response's JSON body will contain information for each method,
-such as the method's name, a description, the HTTP methods supported,
-and whether it's asynchronous or synchronous.
-
-
-Driver Vendor Passthru
-----------------------
-
-Drivers may implement an API for requests not related to any node,
-at ``/v1/drivers/<driver name>/vendor_passthru?method={METHOD}``.
-
-A method:
-
-* can support one or more HTTP methods (for example, GET, POST)
-
-* is asynchronous or synchronous
-
-  + For asynchronous methods, a 202 (Accepted) HTTP status code is
-    returned to indicate that the request was received, accepted and is
-    being acted upon. No body is returned in the response.
-
-  + For synchronous methods, a 200 (OK) HTTP status code is returned
-    to indicate that the request was fulfilled. The response may include
-    a body.
-
-.. note::
-  Unlike methods in `Node Vendor Passthru`_, a request does not lock any
-  resource, so it will not delay other requests and will not fail with an
-  HTTP 409 (Conflict) error code.
-
-Ironic makes no guarantees about the semantics of the message BODY sent
-to this endpoint. That is left up to each driver's author.
-
-To get information about all the methods available via the driver
-vendor_passthru endpoint, you can issue an HTTP GET request::
-
-  GET /v1/drivers/<driver name>/vendor_passthru/methods
-
-The response's JSON body will contain information for each method,
-such as the method's name, a description, the HTTP methods supported,
-and whether it's asynchronous or synchronous.