1. 前言

 内核版本:linux 4.9.225,以freescale为例。(部分内容待修改和补充,不一定准确)

2. 概述

上一篇文章讲了控制器的驱动使用的是platform总线的连接方式,本节要讲的PHY设备驱动是基于device、driver、bus的连接方式

其驱动涉及如下几个重要部分:

  • 总线 - sturct mii_bus (mii stand for media independent interface)
  • 设备 - struct phy_device
  • 驱动 - struct phy_driver

关于PHY设备的创建和注册已经在上一篇控制器的probe函数中有过详细的描述(需要注意的是:phy设备不像i2c/spi有一个board_info函数进行设备的添加,而是直接读取phy中的寄存器<根据IEEE的规定,PHY芯片的前16个寄存器的内容必须是固定的>),本节就不再描述;本节主要讲解总线、设备、驱动模型中剩下的mdio_bus总线PHY设备对应的驱动

3. mdio_bus总线

3.1 总线注册的入口函数

# linux-4.9.225\drivers\net\phy\phy_device.c
static int __init phy_init(void)
{
int rc; rc = mdio_bus_init(); //mdio_bus总线的注册
if (rc)
return rc; rc = phy_drivers_register(genphy_driver,ARRAY_SIZE(genphy_driver), THIS_MODULE); //通用PHY驱动
if (rc)
mdio_bus_exit(); return rc;
} subsys_initcall(phy_init);

subsys_initcall(phy_init) 这行的作用非常重要,这一行就决定了内核在启动的时候会调用该函数,注册完了之后紧接着又注册一个通用的PHY驱动。关于intcall的机制请参见我之前写的专题:linux内核链接脚本vmlinux.lds分析续篇之 --- initcall机制(十三)

3.2 总线注册函数--- mdio_bus_init解析

# 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", //总线名称
.match = mdio_bus_match, //用来匹配总线上设备和驱动的函数
.pm = MDIO_BUS_PM_OPS,
};
EXPORT_SYMBOL(mdio_bus_type); int __init mdio_bus_init(void)
{
int ret; ret = class_register(&mdio_bus_class); //注册设备类 (在linux设备模型中,我再仔细讲这个类的概念)
if (!ret) {
ret = bus_register(&mdio_bus_type);//总线注册
if (ret)
class_unregister(&mdio_bus_class);
} return ret;
}

其中

(1) class_register(&mdio_bus_class)执行后会有以下设备类:

  • /sys/class/mdio_bus

(2)bus_register(&mdio_bus_type)执行后会有以下总线类型:

  • /sys/bus/mdio_bus

3.3 总线中的match函数解析

/**
* 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) //实现匹配的函数
return mdio->bus_match(dev, drv); return 0;
}

4. 设备驱动的注册

在phy_init函数中不仅注册了mdio_bus总线,还注册了一个通用的PHY驱动作为缺省的内核PHY驱动,但是如果PHY芯片的内部寄存器和802.3定义的并不一样或者需要特殊的功能配置以实现更强的功能,这就需要专有的驱动。关于通用PHY驱动的知识,网上有一大堆讲解,本节就不再重复的去描述。

对于市场上存在的主流PHY品牌,一般在内核源码 drivers\net\phy目录下都有对应的驱动。本节主要以realtek RTL8211F为例,讲述PHY的驱动,代码如下:

# 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); //注册PHY驱动 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驱动的注册

(1)同一品牌的PHY设备有多种不同的型号,内核为了支持一次可以注册多个型号的PHY的驱动,在include\linux\phy.h中提供了用于注册PHY驱动的宏module_phy_driver。该宏的定义如下:

# 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)其中phy_driver_register定义如下(注意这里与老版本内核有一定的改动)

/**
* 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;//驱动名称
new_driver->mdiodrv.driver.bus = &mdio_bus_type; //驱动挂载的总线
new_driver->mdiodrv.driver.probe = phy_probe; //PHY设备和驱动匹配后调用的probe函数
new_driver->mdiodrv.driver.remove = phy_remove;
new_driver->mdiodrv.driver.owner = owner; retval = driver_register(&new_driver->mdiodrv.driver); //向linux设备模型框架中注册device_driver驱动
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);//注册数组中所有的phy驱动
if (ret) {
while (i-- > 0)
phy_driver_unregister(new_driver + i);
break;
}
}
return ret;
}

4.2 MODULE_DEVICE_TABLE宏的作用

4.2.1 C语言宏定义##连接符和#符的使用

1 . ## 连接符号

"##" 连接符号其功能是在带参数的宏定义中将两个子串(token)联接起来,从而形成一个新的子串。但它不可以是第一个或者最后一个子串。所谓的子串(token)就是指编译器能够识别的最小语法单元。

简单的说,“##”是一种分隔连接方式,它的作用是先分隔,然后进行强制连接。其中,分隔的作用类似于空格

我们知道在普通的宏定义中,预处理器一般把空格解释成分段标志,并把分隔后的每一段和前面的定义比较,相同的就被替换。如果采用空格来分隔,被替换后段与段之间存在一些空格。如果我们不希望出现这些空格,就可以通过添加一些 “##”来替代空格。例如:

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

"name"和第一个 " "之间,以及第2个""和第二个 "type" 之间没有被分隔,所以预处理器会把name_##type##type解释成3段:"name"、"type"、以及"_type",其中只有"type"是在宏前面出现过的,所以它可以被宏替换。

2 . # 符号

单独的一个 "#" 则表示: 替换这个变量后,再加双引号引起来。例如,宏定义 __stringify_1(x) :

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

那么 __stringify_1(realtek_tbl) <=等价于=> ”realtek_tbl"

4.2.2 alias函数

alias定义的函数将作为另一个函数的别名。

gcc官方的说明部分内容如下: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 指定变量的属性 - - - unused的用法

unused 表示该函数或变量可能不使用,这个属性可以避免编译器产生警告信息。在gcc官方的说明部分内容如下: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解析

MODULE_DEVICE_TABLE宏定义在 /include/linux/module.h中,如下:

/* 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))))

根据代码把这个宏展开之后会发现:

生成了一个 _mod_type__name_device_table 的符号表,其中type为类型,name是这个驱动的名称。在内核编译的时候将这部分符号单独放置在一个区域。当内核运行的时,用户可以通过类型(tpye)和类型对应的设备表中名称(name)中动态的加载驱动,在表中查找到了这个符号之后可以迅速的加载驱动。

MODULE_DEVICE_TABLE的第一个参数是设备的类型,如果是PHY设备,那自然是MDIO(如果是PCI设备,那将是pci)。后面一个参数是设备表,这个设备表的最后一个元素是空的,用于标识结束。

4.2.5 MODULE_DEVICE_TABLE(mdio, realtek_tbl)解析(待验证,后续再来修改)

1. 定义

/**
* 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 . 展开

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

生成一个名为__mod_mdio__realtek_tbl_device_table,内核构建时,depmod程序会在所有模块中搜索符号__mod_mdio__realtek_tbl_device_table,把数据(设备列表)从模块中抽出,添加到映射文件 /lib/modules/KERNEL_VERSION/modules.mdiomap 中,当depmod结束之后,所有的MDIO设备连同他们的模块名字都被该文件列出。在需要驱动的时候,由modules.mdiomap 文件来找寻恰当的驱动程序。

5. 设备驱动与控制器驱动之间的关系图

PHY驱动调试之 ---PHY设备驱动(三)的更多相关文章

  1. Linux中总线设备驱动模型及平台设备驱动实例

    本文将简要地介绍Linux总线设备驱动模型及其实现方式,并不会过多地涉及其在内核中的具体实现,最后,本文将会以平台总线为例介绍设备和驱动程序的实现过程. 目录: 一.总线设备驱动模型总体介绍及其实现方 ...

  2. MPU6050带字符驱动的i2c从设备驱动1

    开干: 1.闲言碎语 这个驱动,越写觉的越简单,入门难,入门之后感觉还好.Linux开发还是比较友好的. 2.编写MPU6050带字符驱动的i2c从设备驱动 要实现的功能就是,将MPU6050作为字符 ...

  3. 【Linux高级驱动】linux设备驱动模型之平台设备驱动机制

    [1:引言: linux字符设备驱动的基本编程流程] 1.实现模块加载函数  a.申请主设备号    register_chrdev(major,name,file_operations);  b.创 ...

  4. LCD驱动分析(一)字符设备驱动框架分析

    参考:S3C2440 LCD驱动(FrameBuffer)实例开发<一>   S3C2440 LCD驱动(FrameBuffer)实例开发<二> LCD驱动也是字符设备驱动,也 ...

  5. 【linux驱动分析】misc设备驱动

    misc设备驱动.又称混杂设备驱动. misc设备驱动共享一个设备驱动号MISC_MAJOR.它在include\linux\major.h中定义:         #define MISC_MAJO ...

  6. Linux驱动编写(块设备驱动代码)

    [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 按照ldd的说法,linux的设备驱动包括了char,block,net三种设备.char设备 ...

  7. Samsung_tiny4412(驱动笔记03)----字符设备驱动基本操作及调用流程

    /*********************************************************************************** * * 字符设备驱动基本操作及 ...

  8. Linux设备驱动编程之复杂设备驱动

    这里所说的复杂设备驱动涉及到PCI.USB.网络设备.块设备等(严格意义而言,这些设备在概念上并不并列,例如与块设备并列的是字符设备,而PCI.USB设备等都可能属于字符设备),这些设备的驱动中又涉及 ...

  9. Linux gadget驱动分析3------复合设备驱动

    windows上面对usb复合设备的识别需要下面条件. “ 如果设备满足下列要求,则总线驱动程序还会报告 USB\COMPOSITE 的兼容标识符: 设备描述符的设备类字段 (bDeviceClass ...

  10. 【Linux 驱动】简单字符设备驱动架构(LED驱动)

    本文基于icool210开发板,内核版本:linux2.6.35: 驱动代码: (1)头文件:led.h #ifndef __LED_H__ #define __LED_H__ #define LED ...

随机推荐

  1. KingbaseES R6 集群主机锁冲突导致的主备切换案例

    ​ 案例说明: 主库在业务高峰期间,客户执行建表等DDL操作,主库产生"AccessExclusiveLock "锁,导致大量的事务产生锁冲突,大量的会话堆积,客户端session ...

  2. 【读书笔记】C#高级编程 第六章 数组

    (一)同一类型和不同类型的多个对象 如果需要使用同一类型的多个对象,就可以使用数组或集合(后面章讲). 如果需要使用不同类型的多个对象,可以使用Tuple(元组)类型. (二)简单数组 如果需要使用同 ...

  3. dp-位移模型(数字三角形演变)

    由数字三角形问题演变而来下面的题: https://www.cnblogs.com/sxq-study/p/12303589.html 一:规定位移方向 题目: Hello Kitty想摘点花生送给她 ...

  4. saas 服务多语言 SDK

    简介 saas 服务一般提供了一个文档来描述提供的 OpenAPI,然会每个用户根据文档自己实现签名.调用等功能的封装,这部分会出现各种各样的问题,因此用户更希望平台直接提供一个 SDK. darab ...

  5. Linux查看文件内容和压缩文件命令

    查看文件内容 一些重要的目录: /usr/share/    帮助文档.说明文档/var/log/       日志/var/run/       运行中程序/var/tmp       临时文件 根 ...

  6. docker bridge 到 k8s pod 跨节点网络通信机制演进

  7. 【持久层框架】- SpringData - JPA

    SpringData - JPA 生命不息,写作不止 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 一个有梦有戏的人 @怒放吧德德 分享学习心得,欢迎指正,大家一起学习成长! JPA简 ...

  8. python+request+pymysql+pytest数据驱动

    一.pymysql简单使用 1.安装mysql 下载地址:https://www.mysql.com/,安装教程这里不做介绍了,网上一大推. 2.安装pymysql库 在Terminal终端输入:pi ...

  9. 面试突击91:MD5 加密安全吗?

    MD5 是 Message Digest Algorithm 的缩写,译为信息摘要算法,它是 Java 语言中使用很广泛的一种加密算法.MD5 可以将任意字符串,通过不可逆的字符串变换算法,生成一个唯 ...

  10. Selenium+Python系列(二) - 元素定位那些事

    一.写在前面 今天一实习生小孩问我,说哥你自动化学了多久才会的,咋学的? 自学三个月吧,真的是硬磕呀,当时没人给讲! 其实,学什么都一样,真的就是你想改变的决心有多强罢了. 二.元素定位 这部分内容可 ...