190 lines
6.6 KiB
Text
190 lines
6.6 KiB
Text
-----------------------
|
|
Ethernet Driver Guide
|
|
-----------------------
|
|
|
|
The networking stack in Das U-Boot is designed for multiple network devices
|
|
to be easily added and controlled at runtime. This guide is meant for people
|
|
who wish to review the net driver stack with an eye towards implementing your
|
|
own ethernet device driver. Here we will describe a new pseudo 'APE' driver.
|
|
|
|
------------------
|
|
Driver Functions
|
|
------------------
|
|
|
|
All functions you will be implementing in this document have the return value
|
|
meaning of 0 for success and non-zero for failure.
|
|
|
|
----------
|
|
Register
|
|
----------
|
|
|
|
When U-Boot initializes, it will call the common function eth_initialize().
|
|
This will in turn call the board-specific board_eth_init() (or if that fails,
|
|
the cpu-specific cpu_eth_init()). These board-specific functions can do random
|
|
system handling, but ultimately they will call the driver-specific register
|
|
function which in turn takes care of initializing that particular instance.
|
|
|
|
Keep in mind that you should code the driver to avoid storing state in global
|
|
data as someone might want to hook up two of the same devices to one board.
|
|
Any such information that is specific to an interface should be stored in a
|
|
private, driver-defined data structure and pointed to by eth->priv (see below).
|
|
|
|
So the call graph at this stage would look something like:
|
|
board_init()
|
|
eth_initialize()
|
|
board_eth_init() / cpu_eth_init()
|
|
driver_register()
|
|
initialize eth_device
|
|
eth_register()
|
|
|
|
At this point in time, the only thing you need to worry about is the driver's
|
|
register function. The pseudo code would look something like:
|
|
int ape_register(bd_t *bis, int iobase)
|
|
{
|
|
struct ape_priv *priv;
|
|
struct eth_device *dev;
|
|
|
|
priv = malloc(sizeof(*priv));
|
|
if (priv == NULL)
|
|
return 1;
|
|
|
|
dev = malloc(sizeof(*dev));
|
|
if (dev == NULL) {
|
|
free(priv);
|
|
return 1;
|
|
}
|
|
|
|
/* setup whatever private state you need */
|
|
|
|
memset(dev, 0, sizeof(*dev));
|
|
sprintf(dev->name, "APE");
|
|
|
|
/* if your device has dedicated hardware storage for the
|
|
* MAC, read it and initialize dev->enetaddr with it
|
|
*/
|
|
ape_mac_read(dev->enetaddr);
|
|
|
|
dev->iobase = iobase;
|
|
dev->priv = priv;
|
|
dev->init = ape_init;
|
|
dev->halt = ape_halt;
|
|
dev->send = ape_send;
|
|
dev->recv = ape_recv;
|
|
dev->write_hwaddr = ape_write_hwaddr;
|
|
|
|
eth_register(dev);
|
|
|
|
#ifdef CONFIG_CMD_MII)
|
|
miiphy_register(dev->name, ape_mii_read, ape_mii_write);
|
|
#endif
|
|
|
|
return 1;
|
|
}
|
|
|
|
The exact arguments needed to initialize your device are up to you. If you
|
|
need to pass more/less arguments, that's fine. You should also add the
|
|
prototype for your new register function to include/netdev.h.
|
|
|
|
The return value for this function should be as follows:
|
|
< 0 - failure (hardware failure, not probe failure)
|
|
>=0 - number of interfaces detected
|
|
|
|
You might notice that many drivers seem to use xxx_initialize() rather than
|
|
xxx_register(). This is the old naming convention and should be avoided as it
|
|
causes confusion with the driver-specific init function.
|
|
|
|
Other than locating the MAC address in dedicated hardware storage, you should
|
|
not touch the hardware in anyway. That step is handled in the driver-specific
|
|
init function. Remember that we are only registering the device here, we are
|
|
not checking its state or doing random probing.
|
|
|
|
-----------
|
|
Callbacks
|
|
-----------
|
|
|
|
Now that we've registered with the ethernet layer, we can start getting some
|
|
real work done. You will need five functions:
|
|
int ape_init(struct eth_device *dev, bd_t *bis);
|
|
int ape_send(struct eth_device *dev, volatile void *packet, int length);
|
|
int ape_recv(struct eth_device *dev);
|
|
int ape_halt(struct eth_device *dev);
|
|
int ape_write_hwaddr(struct eth_device *dev);
|
|
|
|
The init function checks the hardware (probing/identifying) and gets it ready
|
|
for send/recv operations. You often do things here such as resetting the MAC
|
|
and/or PHY, and waiting for the link to autonegotiate. You should also take
|
|
the opportunity to program the device's MAC address with the dev->enetaddr
|
|
member. This allows the rest of U-Boot to dynamically change the MAC address
|
|
and have the new settings be respected.
|
|
|
|
The send function does what you think -- transmit the specified packet whose
|
|
size is specified by length (in bytes). You should not return until the
|
|
transmission is complete, and you should leave the state such that the send
|
|
function can be called multiple times in a row.
|
|
|
|
The recv function should process packets as long as the hardware has them
|
|
readily available before returning. i.e. you should drain the hardware fifo.
|
|
For each packet you receive, you should call the NetReceive() function on it
|
|
along with the packet length. The common code sets up packet buffers for you
|
|
already in the .bss (NetRxPackets), so there should be no need to allocate your
|
|
own. This doesn't mean you must use the NetRxPackets array however; you're
|
|
free to call the NetReceive() function with any buffer you wish. So the pseudo
|
|
code here would look something like:
|
|
int ape_recv(struct eth_device *dev)
|
|
{
|
|
int length, i = 0;
|
|
...
|
|
while (packets_are_available()) {
|
|
...
|
|
length = ape_get_packet(&NetRxPackets[i]);
|
|
...
|
|
NetReceive(&NetRxPackets[i], length);
|
|
...
|
|
if (++i >= PKTBUFSRX)
|
|
i = 0;
|
|
...
|
|
}
|
|
...
|
|
return 0;
|
|
}
|
|
|
|
The halt function should turn off / disable the hardware and place it back in
|
|
its reset state. It can be called at any time (before any call to the related
|
|
init function), so make sure it can handle this sort of thing.
|
|
|
|
The write_hwaddr function should program the MAC address stored in dev->enetaddr
|
|
into the Ethernet controller.
|
|
|
|
So the call graph at this stage would look something like:
|
|
some net operation (ping / tftp / whatever...)
|
|
eth_init()
|
|
dev->init()
|
|
eth_send()
|
|
dev->send()
|
|
eth_rx()
|
|
dev->recv()
|
|
eth_halt()
|
|
dev->halt()
|
|
|
|
-----------------------------
|
|
CONFIG_MII / CONFIG_CMD_MII
|
|
-----------------------------
|
|
|
|
If your device supports banging arbitrary values on the MII bus (pretty much
|
|
every device does), you should add support for the mii command. Doing so is
|
|
fairly trivial and makes debugging mii issues a lot easier at runtime.
|
|
|
|
After you have called eth_register() in your driver's register function, add
|
|
a call to miiphy_register() like so:
|
|
#if defined(CONFIG_MII) || defined(CONFIG_CMD_MII)
|
|
miiphy_register(dev->name, mii_read, mii_write);
|
|
#endif
|
|
|
|
And then define the mii_read and mii_write functions if you haven't already.
|
|
Their syntax is straightforward:
|
|
int mii_read(char *devname, uchar addr, uchar reg, ushort *val);
|
|
int mii_write(char *devname, uchar addr, uchar reg, ushort val);
|
|
|
|
The read function should read the register 'reg' from the phy at address 'addr'
|
|
and store the result in the pointer 'val'. The implementation for the write
|
|
function should logically follow.
|