PHY driver debugging -- PHY device driver

1. Preface

Kernel version: linux 4.9.225. Take freescale as an example. (some contents need to be modified and supplemented, which may not be accurate)

2. General

In the previous article, the controller driver uses the connection mode of platform bus. In this section, the PHY device driver is based on the connection mode of device, driver and bus.

Its drive involves the following important parts:

  • Bus - sturct mii_bus (mii stand for media independent interface)
  • Device - struct phy_device
  • Driver - struct phy_driver

The creation and registration of phy devices have been described in detail in the probe function of the controller in the previous article (Note: unlike i2c/spi, PHY devices have a board_info function to add devices, but directly read the registers in PHY < according to IEEE regulations, the contents of the first 16 registers of phy chip must be fixed >), which will not be described in this section; This section mainly explains the remaining MDIO in the bus, device and driver models_ Bus and the driver corresponding to PHY equipment.

3. mdio_bus

3.1 entry function of bus registration

# linux-4.9.225\drivers\net\phy\phy_device.c
static int __init phy_init(void)
{
	int rc;

	rc = mdio_bus_init(); //mdio_bus registration
	if (rc)
		return rc;

	rc = phy_drivers_register(genphy_driver,ARRAY_SIZE(genphy_driver), THIS_MODULE); //Universal PHY driver
	if (rc)
		mdio_bus_exit();

	return rc;
}

subsys_initcall(phy_init); 

subsys_ The function of initcall (phy_init) is very important. This line determines that the kernel will call this function at startup. After registration, a general PHY driver will be registered. For the mechanism of intcall, please refer to the topic I wrote earlier: linux kernel link script vmlinux The sequel of LDS analysis - initcall mechanism (13)

3.2 bus registration function - mdio_bus_init parsing

# linux-4.9.225\drivers\net\phy\mdio_bus.c
static struct class mdio_bus_class = {
	.name		= "mdio_bus",
	.dev_release	= mdiobus_release,
};

static int mdio_bus_match(struct device *dev, struct device_driver *drv)
{
	struct mdio_device *mdio = to_mdio_device(dev);

	if (of_driver_match_device(dev, drv))
		return 1;

	if (mdio->bus_match)
		return mdio->bus_match(dev, drv);

	return 0;
}

struct bus_type mdio_bus_type = {
	.name		= "mdio_bus",     //Bus name
	.match		= mdio_bus_match, //Functions used to match devices and drivers on the bus
	.pm		= MDIO_BUS_PM_OPS,
};
EXPORT_SYMBOL(mdio_bus_type);

int __init mdio_bus_init(void)
{
	int ret;

	ret = class_register(&mdio_bus_class); //Register the device class (in the linux device model, I will talk about the concept of this class in detail)
	if (!ret) {
		ret = bus_register(&mdio_bus_type);//Bus registration
		if (ret)
			class_unregister(&mdio_bus_class);
	}

	return ret;
}

among
(1) class_ After register (& mdio_bus_class) is executed, there will be the following device classes:

  • /sys/class/mdio_bus

(2)bus_ After register (& mdio_bus_type) is executed, there will be the following bus types:

  • /sys/bus/mdio_bus

3.3 analysis of match function in bus

/**
 * mdio_bus_match - determine if given MDIO driver supports the given
 *		    MDIO device
 * @dev: target MDIO device
 * @drv: given MDIO driver
 *
 * Description: Given a MDIO device, and a MDIO driver, return 1 if
 *   the driver supports the device.  Otherwise, return 0. This may
 *   require calling the devices own match function, since different classes
 *   of MDIO devices have different match criteria.
 */
static int mdio_bus_match(struct device *dev, struct device_driver *drv)
{
	struct mdio_device *mdio = to_mdio_device(dev);

	if (of_driver_match_device(dev, drv))
		return 1;

	if (mdio->bus_match)               //Implement matching functions
		return mdio->bus_match(dev, drv);

	return 0;
}

4. Device driver registration

In phy_ Not only MDIO is registered in init function_ If phy.bus is configured as a general-purpose driver, it does not need to register the function of phy.bus as a special driver. However, if phy.bus is configured as a general-purpose driver, it does not need to register the function of phy.bus. About the knowledge of general PHY driver, there are a lot of explanations on the Internet. This section will not repeat the description.

For the mainstream PHY brands in the market, there are generally corresponding drivers in the drivers\net\phy directory of the kernel source code. This section mainly takes realtek RTL8211F as an example to describe the driver of phy. The code is as follows:

# linux-4.9.225\drivers\net\phy\realtek.c
static struct phy_driver realtek_drvs[] = {
	......
	, {
		.phy_id		= 0x001cc916,
		.name		= "RTL8211F Gigabit Ethernet",
		.phy_id_mask	= 0x001fffff,
		.features	= PHY_GBIT_FEATURES,
		.flags		= PHY_HAS_INTERRUPT,
		.config_aneg	= &genphy_config_aneg,
		.config_init	= &rtl8211f_config_init,
		.read_status	= &genphy_read_status,
		.ack_interrupt	= &rtl8211f_ack_interrupt,
		.config_intr	= &rtl8211f_config_intr,
		.suspend	= genphy_suspend,
		.resume		= genphy_resume,
	},
};

module_phy_driver(realtek_drvs);                           //Register PHY driver

static struct mdio_device_id __maybe_unused realtek_tbl[] = {
	{ 0x001cc912, 0x001fffff },
	{ 0x001cc914, 0x001fffff },
	{ 0x001cc915, 0x001fffff },
	{ 0x001cc916, 0x001fffff },
	{ }
};

MODULE_DEVICE_TABLE(mdio, realtek_tbl);

4.1 phy driver registration

(1) PHY devices of the same brand have many different models. In order to support the driver of phy that can register multiple models at one time, the kernel is in include \ Linux \ PHY H provides a macro module for registering PHY drivers_ phy_ driver. The macro is defined as follows:

# linux-4.9.225\include\linux\phy.h

#define phy_module_driver(__phy_drivers, __count)			\
static int __init phy_module_init(void)					\
{									\
	return phy_drivers_register(__phy_drivers, __count, THIS_MODULE); \
}	

#define module_phy_driver(__phy_drivers)				\
	phy_module_driver(__phy_drivers, ARRAY_SIZE(__phy_drivers))

(2) Where phy_ driver_ The definition of register is as follows (note that there are some changes with the old kernel)

/**
 * phy_driver_register - register a phy_driver with the PHY layer
 * @new_driver: new phy_driver to register
 * @owner: module owning this PHY
 */
int phy_driver_register(struct phy_driver *new_driver, struct module *owner)
{
	int retval;

	new_driver->mdiodrv.flags |= MDIO_DEVICE_IS_PHY;
	new_driver->mdiodrv.driver.name = new_driver->name;//Drive name
	new_driver->mdiodrv.driver.bus = &mdio_bus_type;   //Drive mounted bus
	new_driver->mdiodrv.driver.probe = phy_probe;      //The probe function that is called after the PHY device and the driver match. 
	new_driver->mdiodrv.driver.remove = phy_remove;
	new_driver->mdiodrv.driver.owner = owner;

	retval = driver_register(&new_driver->mdiodrv.driver); //Register device with linux device model framework_ Driver drive
	if (retval) {
		pr_err("%s: Error %d in registering driver\n",
		       new_driver->name, retval);

		return retval;
	}

	pr_debug("%s: Registered new driver\n", new_driver->name);

	return 0;
}

int phy_drivers_register(struct phy_driver *new_driver, int n,
			 struct module *owner)
{
	int i, ret = 0;

	for (i = 0; i < n; i++) {
		ret = phy_driver_register(new_driver + i, owner);//Register all phy drivers in the array
		if (ret) {
			while (i-- > 0)
				phy_driver_unregister(new_driver + i);
			break;
		}
	}
	return ret;
}

4.2 MODULE_ DEVICE_ The role of the table macro

4.2.1 use of C language macro definition ## connector and # connector

1 . ## Connection symbol
The function of "##" concatenation symbol is to join two tokens in the macro definition with parameters to form a new substring. But it cannot be the first or last substring. The so-called token refers to the smallest syntax unit that the compiler can recognize.

Simply put, "##" is a separate connection method. Its function is to separate first and then make a forced connection. Among them, the function of separation is similar to that of spaces.

We know that in ordinary macro definitions, the preprocessor usually interprets the component segment flag with spaces, compares each separated segment with the previous definition, and the same one is replaced. If separated by spaces, there are some spaces between the replaced segments. If we don't want these spaces, we can replace them by adding some "##". For example:

#define example(name, type)   name_##type##_type

"Name" and the first "" and the second "" and the second "type" are not separated, so the preprocessor will separate name_##type##type is interpreted as three paragraphs: "name", "type", and "_type". Only "type" appears in front of the macro, so it can be replaced by the macro.

2 . # Symbol
A single "#" means: replace this variable and enclose it in double quotation marks. For example, a macro definition__ stringify_1(x) :

# linux-4.9.225\include\linux\stringify.h
#define  __stringify_1(x)   #x

So__ stringify_ 1 (realtek_tbl) < = equivalent to = > "Realtek"_ tbl"

4.2.2 alias function

The function defined by alias will be used as the alias of another function.
The official description of gcc is as follows: 5.24 Declaring Attributes of Functions:

alias ("target")
The alias attribute causes the declaration to be emitted as an alias for another symbol, which must be specified. For instance,

void __f () { /* Do something. */; }
void f () __attribute__ ((weak, alias ("__f")));

declares f' to be a weak alias for__f'. In C++, the mangled name for the target must be used. It is an error if `__f' is not defined in the same translation unit.

4.2.3 attribute of specified variable --- usage of unused

unused indicates that the function or variable may not be used. This attribute can prevent the compiler from generating warning messages. The official description of gcc is as follows: 5.31 Specifying Attributes of Variables:
unused
This attribute, attached to a variable, means that the variable is meant to be possibly unused. GCC will not produce a warning for this variable.

4.2.4 MODULE_DEVICE_TABLE parsing

MODULE_ DEVICE_ The table macro is defined in / include / Linux / module H, as follows:

/* Creates an alias so file2alias.c can find device table. */
#define MODULE_DEVICE_TABLE(type, name)					\
extern const typeof(name) __mod_##type##__##name##_device_table		\
  __attribute__ ((unused, alias(__stringify(name))))

After expanding this macro according to the code, you will find:
Generated a_ mod_type__name_device_table, where type is the type and name is the name of the driver. When compiling the kernel, these symbols are placed in a separate area. When the kernel is running, the user can dynamically load the driver through the type (tpye) and the name in the device table corresponding to the type. After finding this symbol in the table, the driver can be loaded quickly.

MODULE_ DEVICE_ The first parameter of table is the type of device. If it is a PHY device, it will naturally be MDIO (if it is a PCI device, it will be PCI). The latter parameter is the equipment table. The last element of the equipment table is empty and used to identify the end.

4.2.5 MODULE_DEVICE_TABLE(mdio, realtek_tbl) parsing (to be verified and modified later)

1. Definitions

/**
 * struct mdio_device_id - identifies PHY devices on an MDIO/MII bus
 * @phy_id: The result of
 *     (mdio_read(&MII_PHYSID1) << 16 | mdio_read(&PHYSID2)) & @phy_id_mask
 *     for this PHY type
 * @phy_id_mask: Defines the significant bits of @phy_id.  A value of 0
 *     is used to terminate an array of struct mdio_device_id.
 */
struct mdio_device_id {
	__u32 phy_id;
	__u32 phy_id_mask;
};

static struct mdio_device_id __maybe_unused realtek_tbl[] = {
	{ 0x001cc912, 0x001fffff },
	{ 0x001cc914, 0x001fffff },
	{ 0x001cc915, 0x001fffff },
	{ 0x001cc916, 0x001fffff },
	{ }
};

MODULE_DEVICE_TABLE(mdio, realtek_tbl);

2 . open:

#define MODULE_DEVICE_TABLE(mdio, realtek_tbl)					\
extern const struct mdio_device_id __mod_mdio__realtek_tbl_device_table		\
  __attribute__ ((unused, "realtek_tbl")))

Generate a file named__ mod_mdio__realtek_tbl_device_table. When the kernel is built, the depmod program will search for symbols in all modules__ mod_mdio__realtek_tbl_device_table, extract the data (device list) from the module and add it to the mapping file / lib / modules / kernel_ VERSION/modules. In mdiomap, when depmod is finished, all MDIO devices and their module names are listed in the file. When the driver is needed, it is driven by modules Mdiomap file to find the appropriate driver.

5. Relationship between device drive and controller drive

Keywords: Linux network data structure

Added by KirstyBurgoine on Sat, 05 Mar 2022 18:06:19 +0200