diff options
-rw-r--r-- | doc/README.drivers.eth | 177 |
1 files changed, 177 insertions, 0 deletions
diff --git a/doc/README.drivers.eth b/doc/README.drivers.eth new file mode 100644 index 0000000..7f21909 --- /dev/null +++ b/doc/README.drivers.eth @@ -0,0 +1,177 @@ +----------------------- + 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. If +the state is maintained as global data, it makes using both of those devices +impossible. + +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 0; +} + +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. 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. +The common code sets up packet buffers for you already (NetRxPackets), so there +is no need to allocate your own. For each packet you receive, you should call +the NetReceive() function on it with the packet length. 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. + +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. |