doc: documenting Platforms and Modules
Adding documentation for Platforms and Modules API.
diff --git a/doc/modules.rst b/doc/modules.rst
index 8dc27bd..8835663 100644
--- a/doc/modules.rst
+++ b/doc/modules.rst
@@ -1,3 +1,5 @@
+.. _modules:
+
Modules
=======
@@ -64,7 +66,7 @@
:param cpu: The cpu; could be a numeric or the corresponding string (e.g.
``1`` or ``"cpu1"``).
-.. method:: target.cpufreq.set_governor(cpu, governor, **kwargs)
+.. method:: target.cpufreq.set_governor(cpu, governor, \*\*kwargs)
Sets the governor for the specified cpu.
@@ -83,7 +85,7 @@
:param cpu: The cpu; could be a numeric or the corresponding string (e.g.
``1`` or ``"cpu1"``).
-.. method:: target.cpufreq.set_governor_tunables(cpu, **kwargs)
+.. method:: target.cpufreq.set_governor_tunables(cpu, \*\*kwargs)
Set the tunables for the current governor on the specified CPU.
@@ -169,4 +171,192 @@
API
---
-TODO
+Generic Module API Description
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Modules implement discrete, optional pieces of functionality ("optional" in the
+sense that the functionality may or may not be present on the target device, or
+that it may or may not be necessary for a particular application).
+
+Every module (ultimately) derives from :class:`Module` class. A module must
+define the following class attributes:
+
+:name: A unique name for the module. This cannot clash with any of the existing
+ names and must be a valid Python identifier, but is otherwise free-from.
+:kind: This identifies the type of functionality a module implements, which in
+ turn determines the interface implemented by the module (all modules of
+ the same kind must expose a consistent interface). This must be a valid
+ Python identifier, but is otherwise free-form, though, where possible,
+ one should try to stick to an already-defined kind/interface, lest we end
+ up with a bunch of modules implementing similar functionality but
+ exposing slightly different interfaces.
+
+ .. note:: It is possible to omit ``kind`` when defining a module, in
+ which case the module's ``name`` will be treated as its
+ ``kind`` as well.
+
+:stage: This defines when the module will be installed into a :class:`Target`.
+ Currently, the following values are allowed:
+
+ :connected: The module is installed after a connection to the target has
+ been established. This is the default.
+ :early: The module will be installed when a :class:`Target` is first
+ created. This should be used for modules that do not rely on a
+ live connection to the target.
+
+Additionally, a module must implement a static (or class) method :func:`probe`:
+
+.. method:: Module.probe(target)
+
+ This method takes a :class:`Target` instance and returns ``True`` if this
+ module is supported by that target, or ``False`` otherwise.
+
+ .. note:: If the moudule ``stage`` is ``"early"``, this method cannot assume
+ that a connection has been established (i.e. it can only access
+ attrubutes of the Target that do not rely on a connection).
+
+Installation and invocation
+***************************
+
+The default installation method will create an instance of a module (the
+:class:`Target` instance being the sole argument) and assign it to the target
+instance attribute named after the module's ``kind`` (or ``name`` if ``kind`` is
+``None``).
+
+It is possible to change the installation procedure for a module by overriding
+the default :func:`install` method. The method must have the following
+signature:
+
+.. method:: Module.install(cls, target, **kwargs)
+
+ Install the module into the target instance.
+
+
+Implementation and Usage Patterns
+*********************************
+
+There are two common ways to implement the above API, corresponding to the two
+common uses for modules:
+
+- If a module provides an interface to a particular set of functionality (e.g.
+ an OS subsystem), that module would typically derive directly form
+ :class:`Module` and would leave ``kind`` unassigned, so that it is accessed
+ by it name. Its instance's methods and attributes provide the interface for
+ interacting with its functionality. For examples of this type of module, see
+ the subsystem modules listed above (e.g. ``cpufreq``).
+- If a module provides a platform- or infrastructure-specific implementation of
+ a common function, the module would derive from one of :class:`Module`
+ subclasses that define the interface for that function. In that case the
+ module would be accessible via the common ``kind`` defined its super. The
+ module would typically implement :func:`__call__` and be invoked directly. For
+ examples of this type of module, see common function interface definitions
+ below.
+
+
+Common Function Interfaces
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This section documents :class:`Module` classes defining interface for common
+functions. Classes derived from them provide concrete implementations for
+specific platforms.
+
+
+HardResetModule
+***************
+
+.. attribute:: HardResetModule.kind
+
+ "hard_reset"
+
+.. method:: HardResetModule.__call__()
+
+ Must be implemented by derived classes.
+
+ Implements hard reset for a target devices. The equivalent of physically
+ power cycling the device. This may be used by client code in situatians
+ where the target becomes unresponsive and/or a regular reboot is not
+ possible.
+
+
+BootModule
+**********
+
+.. attribute:: BootModule.kind
+
+ "hard_reset"
+
+.. method:: BootModule.__call__()
+
+ Must be implemented by derived classes.
+
+ Implements a boot proceedure. This takes the device from (hard or soft)
+ reset to a booted state where the device is ready to accept connections. For
+ a lot of commercial devices the process is entirely automatic, however some
+ devices (e.g. development boards), my require additional steps, such as
+ interactions with the bootloader, in order to boot into the OS.
+
+.. method:: Bootmodule.update(\*\*kwargs)
+
+ Update the boot settings. Some boot sequencies allow specifying settings
+ that will be utilized during boot (e.g. linux kernel boot command line). The
+ default implmentation will set each setting in ``kwargs`` as an attribute of
+ the boot module (or update the existing attribute).
+
+
+FlashModule
+***********
+
+.. attribute:: FlashModule.kind
+
+ "flash"
+
+.. method:: __call__(image_bundle=None, images=None, boot_config=None)
+
+ Must be implemented by derived classes.
+
+ Flash the target platform with the specified images.
+
+ :param image_bundle: A compressed bundle of image files with any associated
+ metadata. The format of the bundle is specific to a
+ particular implmentation.
+ :param images: A dict mapping image names/identifiers to the path on the
+ host file system of the corresponding image file. If both
+ this and ``image_bundle`` are specified, individual images
+ will override those in the bundle.
+ :param boot_config: Some platforms require specifying boot arguments at the
+ time of flashing the images, rather than during each
+ reboot. For other platforms, this will be ignored.
+
+
+Module Registration
+~~~~~~~~~~~~~~~~~~~
+
+Modules are specified on :class:`Target` or :class:`Platform` creation by name.
+In order to find the class associated with the name, the module needs to be
+registered with ``devlib``. This is accomplished by passing the module class
+into :func:`register_module` method once it is defined.
+
+.. note:: If you're wiring a module to be included as part of ``devlib`` code
+ base, you can place the file with the module class under
+ ``devlib/modules/`` in the source and it will be automatically
+ enumarated. There is no need to explicitly register it in that case.
+
+The code snippet below illustrates an implementation of a hard reset function
+for an "Acme" device.
+
+.. code:: python
+
+ import os
+ from devlib import HardResetModule, register_module
+
+
+ class AcmeHardReset(HardResetModule):
+
+ name = 'acme_hard_reset'
+
+ def __call__(self):
+ # Assuming Acme board comes with a "reset-acme-board" utility
+ os.system('reset-acme-board {}'.format(self.target.name))
+
+ register_module(AcmeHardReset)
+