# Device-Driver-Component

## Device-Driver-Component

Now, after understanding what the event loop is and how to implement it in C++, I'd like to describe **Device-Driver-Component** stack concept before proceeding to practical examples.

![Image: Device-Driver-Component Stack](https://1571411321-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M3rfv4Fnund0TW39t4r%2F-M3rfyEI71kb-Tcsp3n9%2F-M3rg3iXDMOJQy0In1mz%2Fdevice_driver_component.png?generation=1585777250917963\&alt=media)

The **Device** is a platform specific peripheral(s) control layer. Sometimes it is called HAL - **H**ardware **A**bstraction **L**ayer. It has an access to platform specific peripheral control registers. Its job is to implement predefined interface required by upper **Driver** layer, handle the relevant interrupts and report them to the **Driver** via callbacks.

The **Driver** is a generic platform independent layer. Its job is to receive requests for asynchronous operation from the **Component** layer and forward the request to the **Device**. It is also responsible for receiving notifications about the interrupts from the **Device** via callbacks, perform minimal processing of the hardware event if necessary and schedule the execution of proper event handling callback from the **Component** in non interrupt context using [**Event Loop**](event_loop).

The **Component** is a generic or product specific layer that works fully in event loop (non-interrupt) context. It initiates asynchronous operations using **Driver** while providing a callback object to be called in event loop context when the asynchronous operation is complete.

There are several main operations required for any asynchronous event handling: 1. Start the operation. 1. Complete the operation. 1. Cancel the operation. 1. Suspend the operation. 1. Resume suspended operation.

All the peripherals described in [Peripherals](../peripherals) chapter will follow the same scheme for these operations with minor changes, such as having extra parameters or intermediate stages.

### Starting Asynchronous Operation

![Image: Starting Asynchronous Operation](https://1571411321-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M3rfv4Fnund0TW39t4r%2F-M3rfyEI71kb-Tcsp3n9%2F-M3rg3iZ23k0JuueOyj1%2Fasync_op_start.png?generation=1585777249768121\&alt=media)

Any non-interrupt context operation is initiated from some event handler executed by the [Event Loop](event_loop) or from the `main()` function before the event loop started its execution. The handler being executed invokes some function in some **Component**, which requests the **Driver** to perform some asynchronous operation while providing a callback object to be executed when such operation is complete. The **Driver** stores the provided callback object and other parameters in its internal data structures, then forwards the request to the **Device**, which configures the hardware accordingly and enables all the required interrupts.

### Completing Asynchronous Operation

The first entity, that is aware of asynchronous operation completion, is **Device** when appropriate interrupt occurs. It must report the completion to the **Driver** somehow. As was described earlier, the **Device** is a platform specific layer that resides at the bottom of the **Device-Driver-Component** stack and is not aware of the generic **Driver** layer that uses it. The **Device** must provide a way to set an operation completion report object. The **Driver** will usually assign such object during construction/initialisation stage:

![Image: Assigning callback](https://1571411321-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M3rfv4Fnund0TW39t4r%2F-M3rfyEI71kb-Tcsp3n9%2F-M3rg3iaf3IeqCeQMjxG%2Fasync_op_callback.png?generation=1585777250337446\&alt=media)

When the expected interrupt occurs, the **Device** reports operation completion to the **Driver**, which in turn schedules execution of the callback object from the **Component** in non-interrupt context using [**Event Loop**](event_loop)

![Image: Completing Asynchronous Operation](https://1571411321-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M3rfv4Fnund0TW39t4r%2F-M3rfyEI71kb-Tcsp3n9%2F-M3rg3icrEupPpSWBbwX%2Fasync_op_complete.png?generation=1585777251100179\&alt=media)

Note that the operation may fail, due to some hardware faults, This is the reason to have `status` parameter reporting success and/or error condition in both callback invocations.

### Canceling Asynchronous Operation

There must be an ability to cancel asynchronous operations in progress. For example some **Component** activates asynchronous operation request on some hardware peripheral together with asynchronous wait request to the timer to measure the operation timeout. If timeout callback is invoked first, then there is a need to cancel the outstanding asynchronous operation. Or the opposite, once the read is successful, the timeout measure should be canceled. However, the cancellation may be a bit tricky. One of the main requirements for asynchronous events handling is that the **Component**'s callback **MUST** be called and called only **ONCE**. It creates a situation when cancellation may become unsuccessful. For instance, the callback of the asynchronous operation was posted for execution in Event Loop, but hasn't been executed by the latter yet. It brings us to the necessity to provide an indication whether the cancellation request was successful. Simple boolean return value is enough.

![Image: Canceling Asynchronous Operation](https://1571411321-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M3rfv4Fnund0TW39t4r%2F-M3rfyEI71kb-Tcsp3n9%2F-M3rg3ieLf2pjxAodzgI%2Fasync_op_cancel1.png?generation=1585777249928874\&alt=media)

When the cancellation is successful the **Component**'s callback object is invoked with `status` specifying that operation was `Aborted`.

One possible case of unsuccessful cancellation is when callback was posted for execution in event loop, but hasn't been executed yet when cancellation is attempted. In this case **Driver** is aware that there is no pending asynchronous operation and can return `false` immediately.

![Image: Canceling Asynchronous Operation Unsuccessful1](https://1571411321-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M3rfv4Fnund0TW39t4r%2F-M3rfyEI71kb-Tcsp3n9%2F-M3rg3igtqawsCC3gBxR%2Fasync_op_cancel2.png?generation=1585777250731747\&alt=media)

Another possible case of unsuccessful cancellation is when completion interrupt occurs in the middle of cancellation request:

![Image: Canceling Asynchronous Operation Unsuccessful2](https://1571411321-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M3rfv4Fnund0TW39t4r%2F-M3rfyEI71kb-Tcsp3n9%2F-M3rg3ii8ePr4McIQKdm%2Fasync_op_cancel3.png?generation=1585777250118232\&alt=media)

In this case the **Device** must be able to handle such race condition appropriately, by temporarily disabling interrupts before checking whether the completion callback was executed. The **Driver** must also be able to handle interrupt context execution in the middle on non-interrupt one.

### Suspend / Resume Asynchronous Operation

There may be a **Driver**, that is required to support multiple asynchronous operations at the same time, while managing internal queue of such requests and issuing them one by one to the **Device**. In this case there is a need to prevent "operation complete" callback being invoked in interrupt mode context, while trying to access the internal data structures in the event loop (non-interrupt) context. The **Device** must provide both `suspendOp()` and `resumeOp()` to suppress invocation of the callback and allow it back again respectively. Usually suspension means disabling the interrupts without stopping current operation, while resume means re-enabling them again.

![Image: Suspending Asynchronous Operation](https://1571411321-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M3rfv4Fnund0TW39t4r%2F-M3rfyEI71kb-Tcsp3n9%2F-M3rg3ikCh7PAgXRPs9q%2Fasync_op_suspend.png?generation=1585777250505848\&alt=media)

Note that the `suspendOp()` request must also indicate whether the suspension was successful or the completion callback has been already invoked in interrupt mode, just like with the cancellation. After the operation being successfully suspended, it must be either **resumed** or **canceled**.

### **Device** Function Invocation Context

Let's think about the case when **Driver** supports multiple asynchronous operations at the same time and queuing them internally while issueing start requests to the **Device** one by one.

![Image: Starting Multiple Asynchronous Operations](https://1571411321-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M3rfv4Fnund0TW39t4r%2F-M3rfyEI71kb-Tcsp3n9%2F-M3rg3imKEJ0RZhY-9uo%2Fasync_op_context.png?generation=1585777251301774\&alt=media)

The reader may notice that the `startOp()` member function of the **Device** was invoked in event loop (non-interrupt) context while the second time it was in interrupt context right after the completion of the first operation was reported. There may be a need for the **Device**'s implementation to differentiate between these calls.

One of the ways to do so is to have different names and make the **Driver** use them depending on the current execution context:

```cpp
class MyDevice
{
public:
    void startOp();
    void startOpInterruptCtx();
}
```

Another way is to use a [tag dispatching idiom](http://www.generic-programming.org/languages/cpp/techniques.php#tag_dispatching), which I decided to use in [embxx](https://github.com/arobenko/embxx) library.

It defines two extra tag structs in [embxx/device/context.h](https://github.com/arobenko/embxx/blob/master/embxx/device/context.h):

```cpp
namespace embxx
{

namespace device
{

namespace context
{

// Event loop context tag class.
struct EventLoop {};

// Interrupt context tag class.
struct Interrupt {};

} // namespace context

} // namespace device

} // namespace embxx
```

Then, almost every member function defined by **Device** class has to specify extra tag parameter indicating context:

```cpp
class MyDevice
{
public:
    typedef embxx::device::context::EventLoop EventLoopCtx;
    typedef embxx::device::context::Interrupt InterruptCtx;

    void startOp(EventLoopCtx context)
    {
        static_cast<void>(context); // unused parameter
        ... // Perform operation when called in event loop context
    }

    void startOp(InterruptCtx context)
    {
        static_cast<void>(context); // unused parameter
        ... // Perform operation when called in interrupt context
    }
};
```

The **Driver** class will invoke the **Device** functions using relevant temporary context object passed as the last parameter:

```cpp
class MyDriver
{
public:
    typedef embxx::device::context::EventLoop EventLoopCtx;
    typedef embxx::device::context::Interrupt InterruptCtx;

    // Invoked by some Component object in Event Loop Context
    void asyncOp(...)
    {
        ...
        device_.startOp(EventLoopCtx());
        ...
    }

private:

   // Some registered event callback handler, 
   // invoked in interrupt context
   void interruptCallbackHandler()
   {
       ...
       device_.startOp(InterruptCtx());
   }
};
```

If some function needs to be called only in, say `EventLoop` context, and not supported in `Interrupt` context, then it is enough to implement only supported variant. If **Driver** layer tries to invoke the function with unsupported context tag parameter, the compilation will fail:

```cpp
class MyDevice
{
public:
    typedef embxx::device::context::EventLoop EventLoopCtx;

    void cancelOp(EventLoopCtx context)
    {
        static_cast<void>(context); // unused parameter
        ... // Cancel recent operation
    }
};
```

If there is no need to differentiate between the contexts the function is invoked in, then it is quite easy to unify them:

```cpp
class SomeDevice
{
public:

    template <typename TContext>
    void startOp(TContext context)
    {
        static_cast<void>(context); // unused parameter
        startOpInternal();
    }

private:
    void startOpInternal()
    {
        ...
    }
};
```

## Reporting Errors

When issuing asynchronous operation request to the **Driver** and/or **Component**, there must be a way to report success / failure status of the operation, and if it failed provide some extra information about the reason of the failure. Providing such information as first parameter to the callback functor object is a widely used convention among the developers.

In most cases, the numeric value of error code is good enough.

The [embxx](https://github.com/arobenko/embxx) library provides a short list of such values in enumeration class defined in [embxx/error/ErrorCode.h](https://github.com/arobenko/embxx/blob/master/embxx/error/ErrorCode.h):

```cpp
namespace embxx
{

namespace error
{

enum class ErrorCode
{
    Success, ///< Successful completion of operation.
    Aborted, ///< The operation was cancelled/aborted.
    BufferOverflow, /// The buffer is full with read termination condition being false
    HwProtocolError, ///< Hardware peripheral reported protocol error.
    Timeout, ///< The operation takes too much time.
    NumOfStatuses ///< Number of available statuses. Must be last
};

}  // namespace error

}  // namespace embxx
```

There is also a wrapper class around the `embxx::error::ErrorCode`, called `embxx::error::ErrorStatus` (defined in [embxx/error/ErrorStatus.h](https://github.com/arobenko/embxx/blob/master/embxx/error/ErrorStatus.h)):

```cpp
namespace embxx
{

namespace error
{

template <typename TErrorCode = ErrorCode>
class ErrorStatusT
{
public:
    /// @brief Error code enum type
    typedef TErrorCode ErrorCodeType;

    /// @brief Default constructor.
    /// @details The code value is 0, which is "success".
    ErrorStatusT();

    /// @brief Constructor
    /// @details This constructor may be used for implicit 
    ///          construction of error status object out 
    ///          of error code value.
    /// @param code Numeric error code value.
    ErrorStatusT(ErrorCodeType code);

    /// @brief Copy constructor is default
    ErrorStatusT(const ErrorStatusT&) = default;

    /// @brief Destructor is default
    ~ErrorStatusT() = default;

    /// @brief Copy assignment is default
    ErrorStatusT& operator=(const ErrorStatusT&) = default;

    /// @brief Retrieve error code value.
    const ErrorCodeType code() const;

    /// @brief boolean conversion operator.
    /// @details Returns true if error code is not equal 0, 
    ///          i.e. any error will return true, success 
    ///          value will return false.
    operator bool() const;

    /// @brief Same as !(static_cast<bool>(*this)).
    bool operator!() const;

private:
    ErrorCodeType code_;
};

typedef ErrorStatusT<ErrorCode> ErrorStatus;

}  // namespace error

}  // namespace embxx
```

It allows implicit conversion from `embxx::error::ErrorCode` to `embxx::error::ErrorStatus` and convenient evaluation whether error has occurred in `if` sentences:

```cpp
embxx::error::ErrorStatus es;
GASSERT(!es); // No error
...
if (/* some condition */) {
    es = embxx::error::ErrorCode::BufferOverflow;
}
...
if (es) {
    ... // Error occurred, access the arror code by calling es.code()
}
```

By convention every callback function provided with any asynchronous request to any **Driver** and/or **Component** implemented in [embxx](https://github.com/arobenko/embxx) library will receive `const embxx::error::ErrorStatus&` as its first argument:

```cpp
void callback(const embxx::error::ErrorStatus& es, ... /* some other parameters */)
{
    if (es == embxx::error::ErrorCode::Aborted) {
        return; // Nothing to do
    }

    if (es) {
        ... // Error occurred
        return;
    }
    ... // Success
}
```

### Cooperation

As it is seen in the charts above, the **Driver** must have an access to the **Device** as well as **Event Loop** objects. However, the former is not aware of the exact type of the latter. In order to write fully generic code, the **Device** and **Event Loop** types must be provided as template arguments:

```cpp
template <typename TDevice, typename TEventLoop>
class MyDriver
{
public:
    // During the construction store references to Device
    // and Event Loop objects.
    MyDriver(TDevice& device, TEventLoop& el)
      : device_(device),
        el_(el)
    {
    }

    ...

private:

    TDevice& device_;
    TEventLoop& el_;
};
```

The **Component** needs an access only to the **Device** and maybe **Event Loop**. The reference to the latter may be retrieved from the **Device** object itself:

```cpp
template <typename TDevice, typename TEventLoop>
class MyDriver
{
public:
    TEventLoop& getEventLoop()
    {
        return el_;
    }

private:
    TEventLoop& el_;
};

template <typename TDriver>
class MyComponent
{
public:
    MyComponent(TDriver& driver)
      : driver_(driver)
    {
    }

    void someFunc()
    {
        auto& el = driver_.getEventLoop();
        el.post(...);
    }

private:
    TDriver& driver_;
};
```

### Storing Callback Object

The **Driver** needs to provide a callback object to the **Device** to be called when appropriate interrupt occurs. The **Component** also provides a callback object to be invoked in non-interrupt context when the asynchronous operation is complete, aborted or terminated due to some error condition. These callback objects need to be stored somewhere. The best way to do so in conventional C++ is using [std::function](http://en.cppreference.com/w/cpp/utility/functional/function).

```cpp
template <typename TDevice, typename TEventLoop>
class MyDriver
{
public:
    template <typename TFunc>
    void asyncOp(TFunc&& callbackObj)
    {
        callback_ = std::forward<TFunc>(callbackObj);
        ... // Start the operation
    }

private:
    typedef std::function<void embxx::error::ErrorStatus&> CallbackType;

    void opCompleteInterruptCallback(void embxx::error::ErrorStatus& es)
    {
        ... // Complete the operation
        el_.postInterruptCtx(std::bind(std::move(callback_), es));
    }

    EventLoop& el_;
    CallbackType callback_;
};
```

There are two problems with using [std::function](http://en.cppreference.com/w/cpp/utility/functional/function): exceptions and dynamic memory allocation. It is possible to suppress the usage of exceptions by making sure that function object is never invoked without proper object being assigned to it, and by overriding appropriate `__throw_*` function(s) to remove exception handling code from binary image (described in [Exceptions](../compiler_output/exceptions) chapter). However, it is impossible to get rid of dynamic memory allocation in this case, which reduces number of bare metal products the **Driver** code can be reused in, i.e. it makes the **Driver** class not fully generic.

The problem is resolved by defining the callback storage type as a template parameter to the **Driver**:

```cpp
template <typename TDevice, 
          typename TEventLoop, 
          typename TCallbackType>
class MyDriver
{
private:
    ...
    TCallbackType callback_;
};
```

For projects that allow dynamic memory allocation `std::function<...>` can be passed, for others `embxx::util::StaticFunction<...>` or similar must be used.
