|
| 1 | +# mbed OS Ethernet MAC (EMAC) drivers |
| 2 | + |
| 3 | +This document describes how to port and test an Ethernet MAC (EMAC) driver to |
| 4 | +mbed OS. It is based on work on the feature-emac branch as of mbed OS 5.8, |
| 5 | +which is intended to be merged into mbed OS 5.9 |
| 6 | + |
| 7 | +The scope of this document is limited to Ethernet (IEEE 802.3) or Ethernet-like |
| 8 | +devices such as Wi-Fi (IEEE 802.11), where the device presents a MAC interface |
| 9 | +to send and receive frames, and this will be used by one of the onboard |
| 10 | +network stacks that runs on mbed OS on the host processor. |
| 11 | + |
| 12 | +(If the device has an off-board network stack, a driver would need to implement |
| 13 | +`NetworkStack` directly instead to pass network calls to that offboard |
| 14 | +stack). |
| 15 | + |
| 16 | +## Abstractions |
| 17 | + |
| 18 | +The EMAC interface is designed to abstract network stacks and drivers, and to |
| 19 | +easily permit multiple instances. The key API classes are: |
| 20 | + |
| 21 | +* `NetworkInterface` - an mbed OS network interface of any type |
| 22 | +* `NetworkStack` - an mbed OS network stack of any type (may be off-board) |
| 23 | +* `OnboardNetworkStack` - an on-board network stack |
| 24 | +* `EMAC` - an Ethernet MAC device driver |
| 25 | +* `EMACMemoryManager` - a memory manager used to pass data between driver and stack |
| 26 | +* `EMACInterface`- a `NetworkInterface` that uses an `EMAC` driver and an `OnboardNetworkStack` |
| 27 | + |
| 28 | +## The EMAC driver core |
| 29 | + |
| 30 | +The first step in the port is to create a driver class that can be instantiated |
| 31 | +to control your device. This must be derived from class `EMAC`. |
| 32 | +This API is used by a network stack (or test framework) to control your driver. |
| 33 | + |
| 34 | +The EMAC-derived driver would normally be installed in |
| 35 | +features/netsocket/emac-drivers, often in a `TARGET_XXX` directory. |
| 36 | + |
| 37 | +Class EMAC is entirely abstract - you need to implement about a dozen calls |
| 38 | +to activate the driver, send and receive packets, and perform other control |
| 39 | +and information functions. |
| 40 | + |
| 41 | +There are also callback registration functions for upcalls from the driver - the |
| 42 | +stack can register callback functions for packet reception and link status |
| 43 | +changes. |
| 44 | + |
| 45 | + |
| 46 | +## The EMAC memory manager |
| 47 | + |
| 48 | +For the send and receive paths, data is transferred in memory buffers controlled |
| 49 | +via an `EMACMemoryManager` object. The network stack using an EMAC driver |
| 50 | +provides it with a reference to the memory manager in use before powering up - |
| 51 | +this will be constant as long as the EMAC is powered up. |
| 52 | + |
| 53 | +On the output call, the EMAC driver is given ownership of a buffer chain - it |
| 54 | +must free the chain when it has finished with the data. The data may or may |
| 55 | +not be contiguous. A driver can express alignment preferences for outgoing data, |
| 56 | +but the network stack is not required to meet these prefernces, so a driver |
| 57 | +relying on alignment may need a slow path that copies data into an aligned |
| 58 | +(or contiguous) buffer. |
| 59 | + |
| 60 | +For reception, the EMAC driver must allocate memory from the `EMACMemoryManager` |
| 61 | +to store the received packets - this is then passed to the link input callback, |
| 62 | +which will free it. By preference this memory should be allocated using the pool, |
| 63 | +but if contiguous memory is required it can be allocated from the heap. |
| 64 | + |
| 65 | + |
| 66 | +## EthernetInterface |
| 67 | + |
| 68 | +If your driver is a pure Ethernet driver, there is no further implementation |
| 69 | +required. The class `EthernetInterface` can use any `EMAC` driver to provide |
| 70 | +an mbed OS `NetworkInterface`: |
| 71 | + |
| 72 | + MyEMAC my_emac(params); |
| 73 | + EthernetInterface net(&my_emac); |
| 74 | + |
| 75 | + net.connect(); |
| 76 | + |
| 77 | +This will attach the default network stack (normally lwIP - the other |
| 78 | +current alternative is Nanostack) to the specified EMAC driver, and provide all |
| 79 | +the generic `NetworkInterface` and `NetworkStack` APIs. |
| 80 | + |
| 81 | +## Being the default EMAC / EthernetInterface |
| 82 | + |
| 83 | +To make your EMAC the default for applications you should define the static function |
| 84 | +`EMAC::get_default_instance()` to return an instance of your emac, eg: |
| 85 | + |
| 86 | + MBED_WEAK EMAC &EMAC::get_default_instance() |
| 87 | + { |
| 88 | + static MyEMAC my_emac(params); |
| 89 | + return &my_emac; |
| 90 | + } |
| 91 | + |
| 92 | +This permits this example application code to work: |
| 93 | + |
| 94 | + EthernetInterface net; // uses EMAC::get_default_instance() |
| 95 | + net.connect(); |
| 96 | + |
| 97 | +This definition would normally be gated by a target label of some sort. As |
| 98 | +target code, your definition of EMAC::get_default_instance() must be weak - |
| 99 | +this permits it to be overridden by application code. |
| 100 | + |
| 101 | +## Wi-Fi interfaces |
| 102 | + |
| 103 | +As a Wi-Fi interface, a little more work is required - at a minimum you need |
| 104 | +to implement the extra configuration calls in `WiFiInterface`. This |
| 105 | +is because the network stacks and EMAC APIs are only related to the |
| 106 | +Ethernet-like data path - they have no knowledge of any other configuration |
| 107 | +mechanisms and assume they are already set up. |
| 108 | + |
| 109 | +To do this, you should create a C++ class that inherits from both |
| 110 | +`WiFiInterface` and `EMACInterface`. The `EMACInterface` is a helper class |
| 111 | +that implements all the core `NetworkInterface` functionality for you. You |
| 112 | +then just need to implement the extra `WiFiInterface` configuration methods. |
| 113 | + |
| 114 | +For reference, note that `EthernetInterface` also derives from |
| 115 | +`EMACInterface`, but has no extra code as there is no extra configuration |
| 116 | +required. |
| 117 | + |
| 118 | +As a Wi-fi driver, you will not normally be directly exposing your `EMAC` class - |
| 119 | +it would not normally be declared as `EMAC::get_default_instance`, but you |
| 120 | +would pass it to the constructor of your base `EMACInterface`. This then |
| 121 | +will make it visible via the `get_emac` method. This is for test purposes, |
| 122 | +meaning the test framework can do: |
| 123 | + |
| 124 | + MyWiFiInterface net; |
| 125 | + net.set_credentials(); |
| 126 | + EMAC &emac = net.get_emac(); |
| 127 | + do_emac_test(emac); |
| 128 | + |
| 129 | +This must work in your driver - it must be possible to power up and use the |
| 130 | +built-in EMAC directly without the `NetworkInterface::connect()` method being |
| 131 | +invoked, as long as the credentials have been set. This structure will come naturally if |
| 132 | +you just use the default `EMACInterface::connect()` implementation. |
| 133 | + |
| 134 | +Note also that your constructor must allow the network stack to be specified using |
| 135 | +the same form as `EthernetInterface`: |
| 136 | + |
| 137 | + MyWiFiInterface(OnboardNetworkStack &stack = OnboardNetworkStack::get_default_instance()); |
| 138 | + |
| 139 | +## OnboardNetworkStack |
| 140 | + |
| 141 | +The precise details of the `OnboardNetworkStack` API should not concern |
| 142 | +an EMAC driver writer - it provides the mechanism to bind a driver to a stack, and |
| 143 | +the APIs needed to implement a `NetworkInterface`, but this is handled by |
| 144 | +`EMACInterface`, either as a base class of your own `XXXInterface` or as |
| 145 | +the base of `EthernetInterface`. |
| 146 | + |
| 147 | +## DEVICE_EMAC |
| 148 | + |
| 149 | +At present, as an interim measure, targets providing `EMAC::get_default_instance()` |
| 150 | +should add "EMAC" in `device_has` in their `targets.json`. This activates |
| 151 | +network tests in CI builds. |
| 152 | + |
| 153 | +This is subject to change, but is necessary in lieu of the previous typical |
| 154 | +behaviour of gating tests on `FEATURE_LWIP`. |
| 155 | + |
| 156 | +## Tuning memory allocations |
| 157 | + |
| 158 | +Depending on its use of pool and heap memory, and other factors, a driver might |
| 159 | +want to tune the configuration of particular network stacks. This can be done via |
| 160 | +the `mbed_lib.json` of each network stack, using their `target_overrides` |
| 161 | +section. |
| 162 | + |
| 163 | +## Testing |
| 164 | + |
| 165 | +The mbed OS tree contains Greentea-based tests that exercise the EMAC API |
| 166 | +directly, and more general socket tests. |
| 167 | + |
| 168 | +See here for general Greentea information: <https://github.com/ARMmbed/greentea> |
| 169 | + |
| 170 | +See here for the emac tests: |
| 171 | +<https://github.com/ARMmbed/mbed-os/tree/feature-emac/TESTS/network/emac> |
| 172 | + |
| 173 | +Greentea socket tests are at: |
| 174 | +<https://github.com/ARMmbed/mbed-os/tree/feature-emac/TESTS/netsocket> |
| 175 | + |
| 176 | +The driver should also be exercised with real-world examples like |
| 177 | +<https://github.com/ARMmbed/mbed-os-example-client> |
| 178 | + |
| 179 | +The driver should also be tested with both network stacks available in mbed OS, |
| 180 | +as they will use the driver somewhat differently - try with the JSON option |
| 181 | +`nsapi.default-stack` set to each of `LWIP` and `NANOSTACK`. |
| 182 | + |
| 183 | +Nanostack is IPv6 only. IPv6 operation should also be tested with lwIP, as this |
| 184 | +is likely to reveal problems with multicast filtering that may not be spotted |
| 185 | +by IPv4 or Nanostack. |
0 commit comments