mirror of https://github.com/ARMmbed/mbed-os.git
				
				
				
			Add EMAC driver README.md with porting guide
							parent
							
								
									fbd920777b
								
							
						
					
					
						commit
						953f1b615a
					
				| 
						 | 
				
			
			@ -0,0 +1,185 @@
 | 
			
		|||
# mbed OS Ethernet MAC (EMAC) drivers
 | 
			
		||||
 | 
			
		||||
This document describes how to port and test an Ethernet MAC (EMAC) driver to
 | 
			
		||||
mbed OS. It is based on work on the feature-emac branch as of mbed OS 5.8,
 | 
			
		||||
which is intended to be merged into mbed OS 5.9
 | 
			
		||||
 | 
			
		||||
The scope of this document is limited to Ethernet (IEEE 802.3) or Ethernet-like
 | 
			
		||||
devices such as Wi-Fi (IEEE 802.11), where the device presents a MAC interface
 | 
			
		||||
to send and receive frames, and this will be used by one of the onboard
 | 
			
		||||
network stacks that runs on mbed OS on the host processor.
 | 
			
		||||
 | 
			
		||||
(If the device has an off-board network stack, a driver would need to implement
 | 
			
		||||
`NetworkStack` directly instead to pass network calls to that offboard
 | 
			
		||||
stack).
 | 
			
		||||
 | 
			
		||||
## Abstractions
 | 
			
		||||
 | 
			
		||||
The EMAC interface is designed to abstract network stacks and drivers, and to
 | 
			
		||||
easily permit multiple instances. The key API classes are:
 | 
			
		||||
 | 
			
		||||
* `NetworkInterface` - an mbed OS network interface of any type
 | 
			
		||||
* `NetworkStack` - an mbed OS network stack of any type (may be off-board)
 | 
			
		||||
* `OnboardNetworkStack` - an on-board network stack 
 | 
			
		||||
* `EMAC` - an Ethernet MAC device driver
 | 
			
		||||
* `EMACMemoryManager` - a memory manager used to pass data between driver and stack
 | 
			
		||||
* `EMACInterface`- a `NetworkInterface` that uses an `EMAC` driver and an `OnboardNetworkStack`
 | 
			
		||||
 | 
			
		||||
## The EMAC driver core
 | 
			
		||||
 | 
			
		||||
The first step in the port is to create a driver class that can be instantiated
 | 
			
		||||
to control your device. This must be derived from class `EMAC`. 
 | 
			
		||||
This API is used by a network stack (or test framework) to control your driver.
 | 
			
		||||
 | 
			
		||||
The EMAC-derived driver would normally be installed in
 | 
			
		||||
features/netsocket/emac-drivers, often in a `TARGET_XXX` directory.
 | 
			
		||||
 | 
			
		||||
Class EMAC is entirely abstract - you need to implement about a dozen calls
 | 
			
		||||
to activate the driver, send and receive packets, and perform other control
 | 
			
		||||
and information functions.
 | 
			
		||||
 | 
			
		||||
There are also callback registration functions for upcalls from the driver - the
 | 
			
		||||
stack can register callback functions for packet reception and link status
 | 
			
		||||
changes.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## The EMAC memory manager
 | 
			
		||||
 | 
			
		||||
For the send and receive paths, data is transferred in memory buffers controlled
 | 
			
		||||
via an `EMACMemoryManager` object. The network stack using an EMAC driver
 | 
			
		||||
provides it with a reference to the memory manager in use before powering up -
 | 
			
		||||
this will be constant as long as the EMAC is powered up.
 | 
			
		||||
 | 
			
		||||
On the output call, the EMAC driver is given ownership of a buffer chain - it
 | 
			
		||||
must free the chain when it has finished with the data. The data may or may
 | 
			
		||||
not be contiguous. A driver can express alignment preferences for outgoing data,
 | 
			
		||||
but the network stack is not required to meet these prefernces, so a driver
 | 
			
		||||
relying on alignment may need a slow path that copies data into an aligned
 | 
			
		||||
(or contiguous) buffer.
 | 
			
		||||
 | 
			
		||||
For reception, the EMAC driver must allocate memory from the `EMACMemoryManager`
 | 
			
		||||
to store the received packets - this is then passed to the link input callback,
 | 
			
		||||
which will free it. By preference this memory should be allocated using the pool,
 | 
			
		||||
but if contiguous memory is required it can be allocated from the heap. 
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## EthernetInterface
 | 
			
		||||
 | 
			
		||||
If your driver is a pure Ethernet driver, there is no further implementation
 | 
			
		||||
required. The class `EthernetInterface` can use any `EMAC` driver to provide
 | 
			
		||||
an mbed OS `NetworkInterface`:
 | 
			
		||||
 | 
			
		||||
    MyEMAC my_emac(params);
 | 
			
		||||
    EthernetInterface net(&my_emac);
 | 
			
		||||
 | 
			
		||||
    net.connect();
 | 
			
		||||
 | 
			
		||||
This will attach the default network stack (normally lwIP - the other
 | 
			
		||||
current alternative is Nanostack) to the specified EMAC driver, and provide all
 | 
			
		||||
the generic `NetworkInterface` and `NetworkStack` APIs.
 | 
			
		||||
 | 
			
		||||
## Being the default EMAC / EthernetInterface
 | 
			
		||||
 | 
			
		||||
To make your EMAC the default for applications you should define the static function
 | 
			
		||||
`EMAC::get_default_instance()` to return an instance of your emac, eg:
 | 
			
		||||
 | 
			
		||||
    MBED_WEAK EMAC &EMAC::get_default_instance()
 | 
			
		||||
    {
 | 
			
		||||
        static MyEMAC my_emac(params);
 | 
			
		||||
        return &my_emac;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
This permits this example application code to work:
 | 
			
		||||
 | 
			
		||||
    EthernetInterface net;  // uses EMAC::get_default_instance()
 | 
			
		||||
    net.connect();
 | 
			
		||||
 | 
			
		||||
This definition would normally be gated by a target label of some sort. As
 | 
			
		||||
target code, your definition of EMAC::get_default_instance() must be weak -
 | 
			
		||||
this permits it to be overridden by application code.
 | 
			
		||||
 | 
			
		||||
## Wi-Fi interfaces
 | 
			
		||||
 | 
			
		||||
As a Wi-Fi interface, a little more work is required - at a minimum you need
 | 
			
		||||
to implement the extra configuration calls in `WiFiInterface`. This
 | 
			
		||||
is because the network stacks and EMAC APIs are only related to the
 | 
			
		||||
Ethernet-like data path - they have no knowledge of any other configuration
 | 
			
		||||
mechanisms and assume they are already set up. 
 | 
			
		||||
 | 
			
		||||
To do this, you should create a C++ class that inherits from both
 | 
			
		||||
`WiFiInterface` and `EMACInterface`. The `EMACInterface` is a helper class
 | 
			
		||||
that implements all the core `NetworkInterface` functionality for you. You
 | 
			
		||||
then just need to implement the extra `WiFiInterface` configuration methods.
 | 
			
		||||
 | 
			
		||||
For reference, note that `EthernetInterface` also derives from
 | 
			
		||||
`EMACInterface`, but has no extra code as there is no extra configuration
 | 
			
		||||
required.
 | 
			
		||||
 | 
			
		||||
As a Wi-fi driver, you will not normally be directly exposing your `EMAC` class -
 | 
			
		||||
it would not normally be declared as `EMAC::get_default_instance`, but you
 | 
			
		||||
would pass it to the constructor of your base `EMACInterface`. This then
 | 
			
		||||
will make it visible via the `get_emac` method. This is for test purposes,
 | 
			
		||||
meaning the test framework can do:
 | 
			
		||||
 | 
			
		||||
    MyWiFiInterface net;
 | 
			
		||||
    net.set_credentials();
 | 
			
		||||
    EMAC &emac = net.get_emac();
 | 
			
		||||
    do_emac_test(emac);
 | 
			
		||||
    
 | 
			
		||||
This must work in your driver - it must be possible to power up and use the
 | 
			
		||||
built-in EMAC directly without the `NetworkInterface::connect()` method being
 | 
			
		||||
invoked, as long as the credentials have been set. This structure will come naturally if
 | 
			
		||||
you just use the default `EMACInterface::connect()` implementation.
 | 
			
		||||
 | 
			
		||||
Note also that your constructor must allow the network stack to be specified using
 | 
			
		||||
the same form as `EthernetInterface`:
 | 
			
		||||
 | 
			
		||||
    MyWiFiInterface(OnboardNetworkStack &stack = OnboardNetworkStack::get_default_instance());
 | 
			
		||||
 | 
			
		||||
## OnboardNetworkStack
 | 
			
		||||
 | 
			
		||||
The precise details of the `OnboardNetworkStack` API should not concern
 | 
			
		||||
an EMAC driver writer - it provides the mechanism to bind a driver to a stack, and
 | 
			
		||||
the APIs needed to implement a `NetworkInterface`, but this is handled by
 | 
			
		||||
`EMACInterface`, either as a base class of your own `XXXInterface` or as
 | 
			
		||||
the base of `EthernetInterface`.
 | 
			
		||||
 | 
			
		||||
## DEVICE_EMAC
 | 
			
		||||
 | 
			
		||||
At present, as an interim measure, targets providing `EMAC::get_default_instance()`
 | 
			
		||||
should add "EMAC" in `device_has` in their `targets.json`. This activates
 | 
			
		||||
network tests in CI builds.
 | 
			
		||||
 | 
			
		||||
This is subject to change, but is necessary in lieu of the previous typical
 | 
			
		||||
behaviour of gating tests on `FEATURE_LWIP`.
 | 
			
		||||
 | 
			
		||||
## Tuning memory allocations
 | 
			
		||||
 | 
			
		||||
Depending on its use of pool and heap memory, and other factors, a driver might
 | 
			
		||||
want to tune the configuration of particular network stacks. This can be done via
 | 
			
		||||
the `mbed_lib.json` of each network stack, using their `target_overrides`
 | 
			
		||||
section.
 | 
			
		||||
 | 
			
		||||
## Testing
 | 
			
		||||
 | 
			
		||||
The mbed OS tree contains Greentea-based tests that exercise the EMAC API
 | 
			
		||||
directly, and more general socket tests.
 | 
			
		||||
 | 
			
		||||
See here for general Greentea information: <https://github.com/ARMmbed/greentea>
 | 
			
		||||
 | 
			
		||||
See here for the emac tests:
 | 
			
		||||
<https://github.com/ARMmbed/mbed-os/tree/feature-emac/TESTS/network/emac>
 | 
			
		||||
 | 
			
		||||
Greentea socket tests are at:
 | 
			
		||||
<https://github.com/ARMmbed/mbed-os/tree/feature-emac/TESTS/netsocket>
 | 
			
		||||
 | 
			
		||||
The driver should also be exercised with real-world examples like
 | 
			
		||||
<https://github.com/ARMmbed/mbed-os-example-client>
 | 
			
		||||
 | 
			
		||||
The driver should also be tested with both network stacks available in mbed OS,
 | 
			
		||||
as they will use the driver somewhat differently - try with the JSON option
 | 
			
		||||
`nsapi.default-stack` set to each of `LWIP` and `NANOSTACK`.
 | 
			
		||||
 | 
			
		||||
Nanostack is IPv6 only. IPv6 operation should also be tested with lwIP, as this
 | 
			
		||||
is likely to reveal problems with multicast filtering that may not be spotted
 | 
			
		||||
by IPv4 or Nanostack.
 | 
			
		||||
		Loading…
	
		Reference in New Issue