|
@@ -0,0 +1,321 @@
|
|
|
+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.
|
|
|
+
|
|
|
+Most existing drivers do already - and new network driver MUST - use the
|
|
|
+U-Boot core driver model. Generic information about this can be found in
|
|
|
+doc/driver-model/design.rst, this document will thus focus on the network
|
|
|
+specific code parts.
|
|
|
+Some drivers are still using the old Ethernet interface, differences between
|
|
|
+the two and hints about porting will be handled at the end.
|
|
|
+
|
|
|
+Driver framework
|
|
|
+------------------
|
|
|
+
|
|
|
+A network driver following the driver model must declare itself using
|
|
|
+the UCLASS_ETH .id field in the U-Boot driver struct:
|
|
|
+
|
|
|
+.. code-block:: c
|
|
|
+
|
|
|
+ U_BOOT_DRIVER(eth_ape) = {
|
|
|
+ .name = "eth_ape",
|
|
|
+ .id = UCLASS_ETH,
|
|
|
+ .of_match = eth_ape_ids,
|
|
|
+ .ofdata_to_platdata = eth_ape_ofdata_to_platdata,
|
|
|
+ .probe = eth_ape_probe,
|
|
|
+ .ops = ð_ape_ops,
|
|
|
+ .priv_auto_alloc_size = sizeof(struct eth_ape_priv),
|
|
|
+ .platdata_auto_alloc_size = sizeof(struct eth_ape_pdata),
|
|
|
+ .flags = DM_FLAG_ALLOC_PRIV_DMA,
|
|
|
+ };
|
|
|
+
|
|
|
+struct eth_ape_priv contains runtime per-instance data, like buffers, pointers
|
|
|
+to current descriptors, current speed settings, pointers to PHY related data
|
|
|
+(like struct mii_dev) and so on. Declaring its size in .priv_auto_alloc_size
|
|
|
+will let the driver framework allocate it at the right time.
|
|
|
+It can be retrieved using a dev_get_priv(dev) call.
|
|
|
+
|
|
|
+struct eth_ape_pdata contains static platform data, like the MMIO base address,
|
|
|
+a hardware variant, the MAC address. ``struct eth_pdata eth_pdata``
|
|
|
+as the first member of this struct helps to avoid duplicated code.
|
|
|
+If you don't need any more platform data beside the standard member,
|
|
|
+just use sizeof(struct eth_pdata) for the platdata_auto_alloc_size.
|
|
|
+
|
|
|
+PCI devices add a line pointing to supported vendor/device ID pairs:
|
|
|
+
|
|
|
+.. code-block:: c
|
|
|
+
|
|
|
+ static struct pci_device_id supported[] = {
|
|
|
+ { PCI_DEVICE(PCI_VENDOR_ID_APE, 0x4223) },
|
|
|
+ {}
|
|
|
+ };
|
|
|
+
|
|
|
+ U_BOOT_PCI_DEVICE(eth_ape, supported);
|
|
|
+
|
|
|
+It is also possible to declare support for a whole class of PCI devices::
|
|
|
+
|
|
|
+ { PCI_DEVICE_CLASS(PCI_CLASS_SYSTEM_SDHCI << 8, 0xffff00) },
|
|
|
+
|
|
|
+Device probing and instantiation will be handled by the driver model framework,
|
|
|
+so follow the guidelines there. The probe() function would initialise the
|
|
|
+platform specific parts of the hardware, like clocks, resets, GPIOs, the MDIO
|
|
|
+bus. Also it would take care of any special PHY setup (power rails, enable
|
|
|
+bits for internal PHYs, etc.).
|
|
|
+
|
|
|
+Driver methods
|
|
|
+----------------
|
|
|
+
|
|
|
+The real work will be done in the driver method functions the driver provides
|
|
|
+by defining the members of struct eth_ops:
|
|
|
+
|
|
|
+.. code-block:: c
|
|
|
+
|
|
|
+ struct eth_ops {
|
|
|
+ int (*start)(struct udevice *dev);
|
|
|
+ int (*send)(struct udevice *dev, void *packet, int length);
|
|
|
+ int (*recv)(struct udevice *dev, int flags, uchar **packetp);
|
|
|
+ int (*free_pkt)(struct udevice *dev, uchar *packet, int length);
|
|
|
+ void (*stop)(struct udevice *dev);
|
|
|
+ int (*mcast)(struct udevice *dev, const u8 *enetaddr, int join);
|
|
|
+ int (*write_hwaddr)(struct udevice *dev);
|
|
|
+ int (*read_rom_hwaddr)(struct udevice *dev);
|
|
|
+ };
|
|
|
+
|
|
|
+An up-to-date version of this struct together with more information can be
|
|
|
+found in include/net.h.
|
|
|
+
|
|
|
+Only start, stop, send and recv are required, the rest are optional and are
|
|
|
+handled by generic code or ignored if not provided.
|
|
|
+
|
|
|
+The **start** function initialises the hardware 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 enetaddr member
|
|
|
+of the generic struct eth_pdata (which would be the first member of your
|
|
|
+own platdata struct). 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). The packet buffer can (and
|
|
|
+will!) be reused for subsequent calls to send(), so it must be no longer
|
|
|
+used when the send() function returns. The easiest way to achieve this is
|
|
|
+to wait until the transmission is complete. Alternatively, if supported by
|
|
|
+the hardware, just waiting for the buffer to be consumed (by some DMA engine)
|
|
|
+might be an option as well.
|
|
|
+Another way of consuming the buffer could be to copy the data to be send,
|
|
|
+then just queue the copied packet (for instance handing it over to a DMA
|
|
|
+engine), and return immediately afterwards.
|
|
|
+In any case you should leave the state such that the send function can be
|
|
|
+called multiple times in a row.
|
|
|
+
|
|
|
+The **recv** function polls for availability of a new packet. If none is
|
|
|
+available, it must return with -EAGAIN.
|
|
|
+If a packet has been received, make sure it is accessible to the CPU
|
|
|
+(invalidate caches if needed), then write its address to the packetp pointer,
|
|
|
+and return the length. If there is an error (receive error, too short or too
|
|
|
+long packet), return 0 if you require the packet to be cleaned up normally,
|
|
|
+or a negative error code otherwise (cleanup not necessary or already done).
|
|
|
+The U-Boot network stack will then process the packet.
|
|
|
+
|
|
|
+If **free_pkt** is defined, U-Boot will call it after a received packet has
|
|
|
+been processed, so the packet buffer can be freed or recycled. Typically you
|
|
|
+would hand it back to the hardware to acquire another packet. free_pkt() will
|
|
|
+be called after recv(), for the same packet, so you don't necessarily need
|
|
|
+to infer the buffer to free from the ``packet`` pointer, but can rely on that
|
|
|
+being the last packet that recv() handled.
|
|
|
+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 use any
|
|
|
+buffer you wish.
|
|
|
+
|
|
|
+The **stop** 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 start() function), so make sure it can handle this sort of thing.
|
|
|
+
|
|
|
+The (optional) **write_hwaddr** function should program the MAC address stored
|
|
|
+in pdata->enetaddr into the Ethernet controller.
|
|
|
+
|
|
|
+So the call graph at this stage would look something like:
|
|
|
+
|
|
|
+.. code-block:: c
|
|
|
+
|
|
|
+ (some net operation (ping / tftp / whatever...))
|
|
|
+ eth_init()
|
|
|
+ ops->start()
|
|
|
+ eth_send()
|
|
|
+ ops->send()
|
|
|
+ eth_rx()
|
|
|
+ ops->recv()
|
|
|
+ (process packet)
|
|
|
+ if (ops->free_pkt)
|
|
|
+ ops->free_pkt()
|
|
|
+ eth_halt()
|
|
|
+ ops->stop()
|
|
|
+
|
|
|
+
|
|
|
+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.
|
|
|
+
|
|
|
+In your driver's ``probe()`` function, add a call to mdio_alloc() and
|
|
|
+mdio_register() like so:
|
|
|
+
|
|
|
+.. code-block:: c
|
|
|
+
|
|
|
+ bus = mdio_alloc();
|
|
|
+ if (!bus) {
|
|
|
+ ...
|
|
|
+ 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.
|
|
|
+
|
|
|
+................................................................
|
|
|
+
|
|
|
+Legacy network drivers
|
|
|
+------------------------
|
|
|
+
|
|
|
+!!! WARNING !!!
|
|
|
+
|
|
|
+This section below describes 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, as described above.
|
|
|
+
|
|
|
+The actual callback functions are fairly similar, the differences are:
|
|
|
+
|
|
|
+- ``start()`` is called ``init()``
|
|
|
+- ``stop()`` is called ``halt()``
|
|
|
+- The ``recv()`` function must loop until all packets have been received, for
|
|
|
+ each packet it must call the net_process_received_packet() function,
|
|
|
+ handing it over the pointer and the length. Afterwards it should free
|
|
|
+ the packet, before checking for new data.
|
|
|
+
|
|
|
+For porting an old driver to the new driver model, split the existing recv()
|
|
|
+function into the actual new recv() function, just fetching **one** packet,
|
|
|
+remove the call to net_process_received_packet(), then move the packet
|
|
|
+cleanup into the ``free_pkt()`` function.
|
|
|
+
|
|
|
+Registering the driver and probing a device is handled very differently,
|
|
|
+follow the recommendations in the driver model design documentation for
|
|
|
+instructions on how to port this over. For the records, the old way of
|
|
|
+initialising a network driver is as follows:
|
|
|
+
|
|
|
+Old network driver registration
|
|
|
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
+
|
|
|
+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:
|
|
|
+
|
|
|
+.. code-block:: c
|
|
|
+
|
|
|
+ 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:
|
|
|
+
|
|
|
+.. code-block:: c
|
|
|
+
|
|
|
+ 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.
|