mirror of
				https://xff.cz/git/u-boot/
				synced 2025-10-31 18:35:42 +01:00 
			
		
		
		
	These docs are useful for developers, not users. Move them under that section. Suggested-by: Heinrich Schuchardt <xypron.glpk@gmx.de> Signed-off-by: Simon Glass <sjg@chromium.org>
		
			
				
	
	
		
			322 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
	
	
			
		
		
	
	
			322 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
	
	
| 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,
 | |
| 		.of_to_plat	= eth_ape_of_to_plat,
 | |
| 		.probe			= eth_ape_probe,
 | |
| 		.ops			= ð_ape_ops,
 | |
| 		.priv_auto	= sizeof(struct eth_ape_priv),
 | |
| 		.plat_auto = 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
 | |
| 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 plat_auto.
 | |
| 
 | |
| 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 plat 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(struct bd_info *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.
 |