mirror of
				https://github.com/smaeul/u-boot.git
				synced 2025-11-03 21:48:15 +00:00 
			
		
		
		
	The dev->halt() func can be called at any time, and the dev->recv() func does not need to use NetRxPackets[] when calling NetReceive(). Signed-off-by: Mike Frysinger <vapier@gentoo.org> Signed-off-by: Ben Warren <biggerbadderben@gmail.com>
		
			
				
	
	
		
			186 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			186 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
-----------------------
 | 
						|
 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;
 | 
						|
 | 
						|
	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 four 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);
 | 
						|
 | 
						|
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.
 | 
						|
 | 
						|
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.
 |