| !!! WARNING !!! | |
| This guide describes to the old way of doing things. No new Ethernet drivers | |
| should be implemented this way. All new drivers should be written against the | |
| U-Boot core driver model. See doc/driver-model/README.txt | |
| ----------------------- | |
| 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; | |
| struct mii_dev *bus; | |
| priv = malloc(sizeof(*priv)); | |
| if (priv == NULL) | |
| return -ENOMEM; | |
| dev = malloc(sizeof(*dev)); | |
| if (dev == NULL) { | |
| free(priv); | |
| return -ENOMEM; | |
| } | |
| /* 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_PHYLIB | |
| bus = mdio_alloc(); | |
| if (!bus) { | |
| free(priv); | |
| free(dev); | |
| return -ENOMEM; | |
| } | |
| bus->read = ape_mii_read; | |
| bus->write = ape_mii_write; | |
| mdio_register(bus); | |
| #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 net_process_received_packet() function on it | |
| along with the packet length. The common code sets up packet buffers for you | |
| already in the .bss (net_rx_packets), so there should be no need to allocate your | |
| own. This doesn't mean you must use the net_rx_packets array however; you're | |
| free to call the net_process_received_packet() 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(&net_rx_packets[i]); | |
| ... | |
| net_process_received_packet(&net_rx_packets[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_PHYLIB / 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 mdio_alloc() and mdio_register() like so: | |
| bus = mdio_alloc(); | |
| if (!bus) { | |
| free(priv); | |
| free(dev); | |
| return -ENOMEM; | |
| } | |
| bus->read = ape_mii_read; | |
| bus->write = ape_mii_write; | |
| mdio_register(bus); | |
| And then define the mii_read and mii_write functions if you haven't already. | |
| Their syntax is straightforward: | |
| int mii_read(struct mii_dev *bus, int addr, int devad, int reg); | |
| int mii_write(struct mii_dev *bus, int addr, int devad, int reg, | |
| u16 val); | |
| The read function should read the register 'reg' from the phy at address 'addr' | |
| and return the result to its caller. The implementation for the write function | |
| should logically follow. |