[U-Boot] [RFC PATCH 1/1] doc: net: Rewrite network driver documentation

Andre Przywara andre.przywara at arm.com
Mon Nov 25 01:32:15 UTC 2019


doc/README.drivers.eth seems like a good source for understanding
U-Boot's network subsystem, but is only talking about legacy network
drivers. This is particularly sad as proper documentation would help in
porting drivers over to the driver model.

Rewrite the document to describe network drivers in the new driver model
world. Most driver callbacks are almost identical in their semantic, but
recv() differs in some important details.

Also keep some parts of the original text at the end, to help
understanding old drivers. Add some hints on how to port drivers over.

This also uses the opportunity to reformat the document in reST.

Signed-off-by: Andre Przywara <andre.przywara at arm.com>
---
 doc/README.drivers.eth | 438 ++++++++++++++++++++++++++++++-------------------
 1 file changed, 271 insertions(+), 167 deletions(-)

diff --git a/doc/README.drivers.eth b/doc/README.drivers.eth
index 1a9a23b51b..d1920ee47d 100644
--- a/doc/README.drivers.eth
+++ b/doc/README.drivers.eth
@@ -1,9 +1,3 @@
-!!! 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
 -----------------------
@@ -13,203 +7,313 @@ 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.
+Most exisiting 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			= &eth_ape_ops,
+		.priv_auto_alloc_size	= sizeof(struct eth_ape_dev),
+		.platdata_auto_alloc_size = sizeof(struct eth_ape_pdata),
+		.flags			= DM_FLAG_ALLOC_PRIV_DMA,
+	};
+
+struct eth_ape_dev 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);
+
+
+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.).
+
+====================
+ Callback functions
+====================
+
+The real work will be done in the callback function 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 is optional and will
+be 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.
 
-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).
+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 packet buffer can (and
+will!) be reused for subsequent calls to send().
+Alternatively you can copy the data to be send, then just queue the copied
+packet (for instance handing it over to a DMA engine), then return.
+
+The **recv** function polls for availability of a new packet. If none is
+available, return a negative error code, -EAGAIN is probably a good idea.
+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 neccessary 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:
-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;
+.. code-block:: c
 
-	dev = malloc(sizeof(*dev));
-	if (dev == NULL) {
-		free(priv);
-		return -ENOMEM;
-	}
+	(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()
 
-	/* setup whatever private state you need */
 
-	memset(dev, 0, sizeof(*dev));
-	sprintf(dev->name, "APE");
+================================
+ CONFIG_PHYLIB / CONFIG_CMD_MII
+================================
 
-	/*
-	 * if your device has dedicated hardware storage for the
-	 * MAC, read it and initialize dev->enetaddr with it
-	 */
-	ape_mac_read(dev->enetaddr);
+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.
 
-	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;
+In your driver's ``probe()`` function, add a call to mdio_alloc() and
+mdio_register() like so:
 
-	eth_register(dev);
+.. code-block:: c
 
-#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;
-}
+And then define the mii_read and mii_write functions if you haven't already.
+Their syntax is straightforward::
 
-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.
+	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 return value for this function should be as follows:
-< 0 - failure (hardware failure, not probe failure)
->=0 - number of interfaces detected
+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.
 
-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.
+========================
+ Legacy network drivers
+========================
 
- -----------
-  Callbacks
- -----------
+!!! WARNING !!!
 
-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);
+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 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 actual callback functions are fairly similar, the differences are:
 
-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;
-}
+- ``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.
 
-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.
+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.
 
-The write_hwaddr function should program the MAC address stored in dev->enetaddr
-into the Ethernet controller.
+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:
-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
---------------------------------
+.. code-block:: c
 
-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.
+	board_init()
+		eth_initialize()
+			board_eth_init() / cpu_eth_init()
+				driver_register()
+					initialize eth_device
+					eth_register()
 
-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;
+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;
 	}
 
-	bus->read = ape_mii_read;
-	bus->write = ape_mii_write;
-	mdio_register(bus);
+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.
 
-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 return value for this function should be as follows:
+< 0 - failure (hardware failure, not probe failure)
+>=0 - number of interfaces detected
 
-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.
+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.
-- 
2.14.5



More information about the U-Boot mailing list