This is a quick overview of the interface that we used to communicate with
ethernet devices from our realtime task.  It is important to note that this
gives us an interface to RAW ethernet frames, not the IP/UDP/TCP networking
layers.  This is not that much of an issue considering that remote Linux
machines can open sockets of type SOCK_PACKET which will receive raw frames
(actually ALL frames).  In addition this means that all error-checking
(except for the hardware CRC) has to be done by the application.  Our
application communicates with a remote device at 800hz over a dedicated
ethernet using this method.  We are using kernel 2.2.10, the beta11 release
of RTL, and the tulip driver as our baseline.

Ethernet Drivers
-----------------------
The communication from the kernel to an ethernet driver is through function
pointers in a 'device' structure, so that there can be multiple devices
loaded at once.  As devices are modprob'd in, a new 'device' structure is
built, and registered via the core networking code in linux/net/core/dev.c.
An 'skb' is a 'struct sk_buff *' which is the encapsulation that the network
drivers use send/receive frames.

The functions of interest are:
dev->open( dev ): open the device, prepare for use
dev->stop( dev ): stop the device, shut it down
dev->hard_start_xmit( skb, dev ): transmit the frame in the skb
dev->do_ioctl( dev, struct ifreq *, ioctl number): described later

The variables of interest are:
dev->tbusy: the device busy status flag
dev->flags: some drivers check dev->flags to make sure IF_IFUP is set in
their dev->open routine.  The tulip driver does not do this.  Thanks to
D.Becker for pointing this out.
dev->dev_addr[6]: the hardware address of the card.

Ethernet devices communicate to the kernel via the following functions:
netif_rx( skb ):  informs the core networking code that a frame has been
received
mark_bh( NET_BH ): informs the kernel that the "bottom half" of the driver
routine for the ethernet subsystem should be run at it's earliest convience.

There are several additional functions that are required from the core
networking code:
dev_get( "ethN" ): get the device structure for the ethernet device numbered
'N'.
dev_alloc_skb( length ): allocate an skb of the given size
dev_kfree_skb( skb ): free the skb
skb_put( skb, length ): reserve 'length' bytes at the end of the packet, and
return a pointer to it.
skb_push( skb, length ): reserve 'length' bytes at the start of the packet,
and return a pointer to it.

Device driver modifications
--------------------------------------
Given the above information, there are two places in the ethernet driver
that need to be modified: the calls to netif_rx and mark_bh.  These routines
need to be hooked such that they call our code, as opposed to the kernel/net
code.

We chose to modify the 'private' data structure that the driver attaches to
dev->priv (when dev->open() is called), and add a function pointer to be
called whenever netif_rx or mark_bh was to be used.  In addition we modified
the do_ioctl of the driver and added a new SIOCDEVPRIVATE to set the
function pointer.  In this way it was supposed that the driver could be used
by either the RT side, or the Linux side by checking the function pointer to
see if it was NULL at the appropriate times.  Our implementation uses one
function call that passes an skb if netif_rx was to be called, and NULL if
mark_bh was to be called.  Not all drivers have the do_ioctl compiled in.
In our case, we modified the 'tulip' driver, which did not compile it in by
default.

It should go without saying that the device MUST be ifconfig'd down (or
never ifconfig'd at all) to use the above method.  RT and non-RT would not
play well together.

We modified the dev->stop() routine to set the dev->priv->functionPointer to
NULL before the rest of the shutdown of the device occurs.  When this was
not done, we experienced spurious crashes on the shutdown of the device in
the tulip driver.

Device Init
---------------
To initialize the device and prepare it for use:

dev = dev_get( "eth1" );
dev->open( dev );
ifr.data = rxPacket; // our receive routine
dev->do_ioctl( dev, &ifr, SIOCDEVPRIVATE+3 ); // our special ioctl
memcpy( localHWAddress, dev->dev_addr, 6 );

Transmit
------------
To transmit a frame:

if( dev->tbusy == 0 ) {
    skb = dev_alloc_skb( length );
    ptr = skb_put( skb, length );
    memcpy( &ptr[0], localHWAddress, 6 );
    memcpy( &ptr[6], remoteHWAddress, 6 );
    ptr[6+6] = ETH_TYPE_HI;
    ptr[6+6+1] = ETH_TYPE_LO;
    memcpy( &ptr[6+6+1+1], outData, length );
    dev->hard_start_xmit( skb, dev );
}

Notes:
1) The proper header for an 'ethernet' (as opposed to 802.3) encoded packet
is:
[ 6 bytes of source hardware address ][ 6 bytes of dest hardware
ddress ][ 2 bytes of 'type' ][...data...]
2) There is a minimum length of 48 bytes in the data portion of an ethernet
encoded packet.
3) The maximum length of the data portion is 1500 - (6+6+2)
4) Use care when selecting a 'type'.  We use 0x0900, which is the code from
an old ethernet debugging device, I believe.  It also needs to be in
'network' byte order, which is big endian.  802.3 uses this portion as the
'length' field.
5) The device itself free's the skb when it is done with it.

Receive
------------
When the driver receives a frame (or wants the "bottom-half" run), it will
call our rxPacket routine via the function pointer registered earler.  In
our case, if the argument was NULL, then rxPacket was being called in place
of the mark_bh routine.  We do nothing in this case (in fact, it is rarely
if ever called in the tulip driver).  In the event the argument was
non-NULL, then rxPacket was being called in place of the netif_rx with an
skb of data:

skb_push( skb, 6+6+2 ); // optional
memcpy( inData, skb->data, skb->len );
dev_kfree_skb( skb );
packetAvailable = 1;

Notes:
1) The driver attempts to align the skb->data portion on a 4 byte boundary
(I believe), and therefore removes the 14 byte header from the skb->data
area.  The skb_push() will move the data pointer back by 14 bytes to
recapture the header.  This may not be required in all circumstances, and
there may be another way to get the header - I have not looked into it.
2) The data does not have to be copied out of the skb immediately, but
eventually the skb needs to be free'd by your code.

Shutdown
--------------
To shutdown the driver call dev->close( dev ).



We created a separate kernel-loadable module which encapsulates the above
method that we load as part of our startup sequence.  A fair amount of this
was typed from memory, so there may be a typo or two.  It is not clear to us
how much time may be taken up in the dev_alloc_skb call (which eventually
calls a kernel routine for skb_alloc which, in turn, calls kmalloc().  We
have not tested this code on an SMP configuration, since 2.2.10+beta11 would
crash when compiled with SMP on.

We welcome comments on the use of the above method, and would be happy to
share the module and modified tulip driver as reference implementations.

---
Michael M. Morrison
VP, Chief Technical Officer
Hyperion Technologies, Inc.
(970) 493-1900

--- [rtl] ---
To unsubscribe:
echo "unsubscribe rtl" | mail [EMAIL PROTECTED] OR
echo "unsubscribe rtl <Your_email>" | mail [EMAIL PROTECTED]
----
For more information on Real-Time Linux see:
http://www.rtlinux.org/~rtlinux/

Reply via email to