I am going to begin at the drivers themselves, where you see code like this:
U_BOOT_DRIVER(eth_sun8i_emac) = {
.name = "eth_sun8i_emac",
.id = UCLASS_ETH,
.of_match = sun8i_emac_eth_ids,
.of_to_plat = sun8i_emac_eth_of_to_plat,
.probe = sun8i_emac_eth_probe,
.ops = &sun8i_emac_eth_ops,
.priv_auto = sizeof(struct emac_eth_dev),
.plat_auto = sizeof(struct sun8i_eth_pdata),
.flags = DM_FLAG_ALLOC_PRIV_DMA,
};
or maybe like this:
U_BOOT_PHY_DRIVER(genphy) = {
.uid = 0xffffffff,
.mask = 0xffffffff,
.name = "Generic PHY",
.features = PHY_GBIT_FEATURES | SUPPORTED_MII |
SUPPORTED_AUI | SUPPORTED_FIBRE |
SUPPORTED_BNC,
.config = genphy_config,
.startup = genphy_startup,
.shutdown = genphy_shutdown,
};
We can use ctags to easily discover where these macros are defined:
In dm/device.h --
#define U_BOOT_DRIVER(__name) \
ll_entry_declare(struct driver, __name, driver)
In include/phy.h --
/**
* U_BOOT_PHY_DRIVER() - Declare a new U-Boot driver
* @__name: name of the driver
*/
#define U_BOOT_PHY_DRIVER(__name) \
ll_entry_declare(struct phy_driver, __name, phy_driver)
and in include/linker_lists.h
#define ll_entry_declare_list(_type, _name, _list) \
_type _u_boot_list_2_##_list##_2_##_name[] __aligned(4) \
__attribute__((unused)) \
__section("__u_boot_list_2_"#_list"_2_"#_name)
This file merits careful study.
It provides "ll_entry_start(_type,_list)" along with "ll_entry_end(_type,_list)'
and a gang of other "ll_entry_*" macros.
__u_boot_list ALIGN(8) : {
KEEP(*(SORT(__u_boot_list*)));
. = ALIGN(CONSTANT(COMMONPAGESIZE));
__end_rodata = .;
}
We have u-boot.map from my recent build for the H5 based Orange Pi PC 2 and
we can search/examine it:
grep _list u-boot.map | grep eth ./u-boot.map: __u_boot_list_2_driver_2_eth_bootdev ./u-boot.map: __u_boot_list_2_driver_2_eth_sun8i_emac ./u-boot.map: __u_boot_list_2_driver_2_eth_usb grep _list u-boot.map | grep eth ./u-boot.map: __u_boot_list_2_phy_driver_1 ./u-boot.map: __u_boot_list_2_phy_driver_2_genphy ./u-boot.map: __u_boot_list_2_phy_driver_3 ./u-boot.map: __u_boot_list_2_uclass_driver_2_phyWe get much more than just the above, but I have pruned it to illustrate just what seems to be of current interest.
doc/develop/driver-model doc/develop/driver-model/design.rst doc/develop/driver-model/ethernet.rst doc/api/linker_lists.rst
I'll note again that having a list is somewhat silly. Virtually every board (including the H5 board I am now working with) have just one ethernet interface and that one ethernet interface has just one PHY chip. But we have a fancy general driver scheme and we are ready for exotic things.
My question is just where all of this starts up. Where is the code that finds that driver and puts it to use? Oddly enough there is no command such as "eth" that would be parallel to "mii" or "mdio" for the phy interfaces. This is too bad. On boot up, U-boot will announce:
eth0: ethernet@1c30000But it does not initialize or try to use the interface. You have to execute some network related command like "ping" or "dhcp' before U-boot gets busy doing autonegotiation.
So, I thought it might be interesting to try to trace the "ping" command to where it digs up the driver from the driver list.
Like all user commands, the ping command is supported by code in one
of the files in the "cmd" directory. Usually you would find ping.c,
but in this case the code is in cmd/net.c in function do_ping().
This calls net_loop(PING) -- also in net/net.c.
This calls net_init() -- also in net/net.c.
This calls net_init_loop() -- also in net/net.c.
This calls eth_get_dev() -- in net/eth-uclass.c
At this point it would be useful to study the driver-model design document. The concept is a hierarchy like this:
Uclass - a kind of device (like ethernet) Driver - a specific driver. Device - a particular device that uses that driverThe code in eth_get_dev() calls eth_get_uclass_priv() -- also in net/eth-uclass.c This calls uclass_get(UCLASS_ETH, &uc) -- which is in drivers/core/uclass.c
This call is an important discovery. It will call uclass_find(UCLASS_ETH), which is also in drivers/core/uclass.c. It references a pointer "gd" (a pointer to "global data") and expects to find dm_root and then loops through the list that points to, returning the first match for an "id" of UCLASS_ETH. The routine uclass_get() has fallback code to get driver info from "platdata" -- which is an old scheme predating the current "DM" driver model -- but that case does not apply to us.
Two things are worth noting here. One is that the driver list has been copied into the "global data" structure. The other is that there is no provision for using anything other than the first ethernet device in the list. This is not entirely surprising. In the case of hardware with more than one ethernet interface, one of them will need to be designated as "primary" and will be the only one that U-Boot knows or cares about.
With that rant over, let's look at the sun8i_emac.c driver for the h5. We find a routine sun8i_mdio_init(). It is called by sun8i_emac_eth_probe(). It sets up a "mii_dev" structure, with pointers to read() and write() routines and then calls mdio_register().
The code for mdio_register() is in common/miiphyutil.c It adds our structure to a list named "mii_devs" and it sets "current_mii" to the first thing added to the list. Something important to note here is that the ethernet "probe" routine must be called when U-boot starts, because the U-boot "mii" and "mdio" commands know about mdio things from the very beginning, even before we try a ping.
Back in that probe routine, we see this code:
sun8i_mdio_init(dev->name, dev); priv->bus = miiphy_get_dev_by_name(dev->name);Here "dev->name" comes from "dev" which is the argument to sun8i_emac_eth_probe(struct udevice *dev).
The code for miiphy_get_dev_by_name() is in common/miiphyutil.c also.
It searches the list mii_devs, looking for a match by name.
It seems awfully likely that it is going to find the one and only
thing we just added to the list. The first match wins.
It returns a pointer to the mii_dev structure.
The probe routine then calls sun8i_phy_init(priv, dev).
The code there is as follows:
static int
sun8i_phy_init(struct emac_eth_dev *priv, void *dev)
{
struct phy_device *phydev;
phydev = phy_connect(priv->bus, priv->phyaddr, dev, priv->interface);
if (!phydev)
return -ENODEV;
priv->phydev = phydev;
phy_config(priv->phydev);
return 0;
}
Here "priv->bus" is the mii_dev structure we just set up and relocated.
Values for phyaddr and interface are obtained by calls:
offset = fdtdec_lookup_phandle(gd->fdt_blob, node, "phy-handle");
if (offset >= 0)
priv->phyaddr = fdtdec_get_int(gd->fdt_blob, offset, "reg", -1);
pdata->phy_interface = dev_read_phy_mode(dev);
priv->interface = pdata->phy_interface;
The fdt routines are in lib/fdtdec.c and lib/fdtdec_common.c.
FDT is the flattened device tree, so it is fetching this from the device tree.
We see this:
&emac {
pinctrl-names = "default";
pinctrl-0 = <&emac_rgmii_pins>;
phy-supply = <®_gmac_3v3>;
phy-handle = <&ext_rgmii_phy>;
phy-mode = "rgmii-id";
status = "okay";
};
&external_mdio {
ext_rgmii_phy: ethernet-phy@1 {
compatible = "ethernet-phy-ieee802.3-c22";
reg = <1>;
};
};
The call to dev_read_phy_mode() is even more interesting.
This is in drivers/core/read.c and looks like so:
phy_interface_t dev_read_phy_mode(const struct udevice *dev)
{
return ofnode_read_phy_mode(dev_ofnode(dev));
}
Here "ofnode" is another way of getting at the device tree.
It ultimately fetches the value of "phy-mode", i.e "rgmii-id".
Both phy_connect() and phy_config() are in drivers/net/phy/phy.c
If you look at phy_connect() you will be wondering what values are set for various
compile time CONFIG options. The thing to do is to examine the file ".config" in
the U-boot build root. It turns out that none of the special config options
tested here are set, so this routine will call phy_find_by_mask(bus, mask)
with mask set to 1<
Have any comments? Questions?
Drop me a line!