Linux Phy 驱动解析

news2025/1/20 5:53:48

文章目录

  • 1. 简介
  • 2. phy_device
    • 2.1 mdio bus
    • 2.2 mdio device
    • 2.3 mdio driver
    • 2.4 poll task
      • 2.4.1 自协商配置
      • 2.4.2 link 状态读取
      • 2.4.3 link 状态通知
  • 3. phylink
    • 3.1 phylink_create()
    • 3.2 phylink_connect_phy()
    • 3.3 phylink_start()
    • 3.3 poll task
  • 参考资料

1. 简介

在调试网口驱动的过程中发现 phy 芯片的驱动框架结构还有点复杂,不仔细研究的话还不好搞懂,另外百度到的资料也不够全面,这篇就总结梳理一下这方面的知识。

我们知道一个 phy 驱动的原理是非常简单的,一般流程如下:

  • 1、用轮询/中断的方式通过 mdio 总线读取 phy 芯片的状态。
  • 2、在 phy link 状态变化的情况下,正确配置 mac 的状态。(例如:根据 phy 自协商的速率 10/100/1000M 把 mac 配置成对应速率)

下面就以 stmmac 网口驱动为例,展示一下 phy 驱动整个调用过程。整个 phy 驱动的主要调用流程如下图所示:

在这里插入图片描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FM9NMkuR-1671158852581)(images/phy/phy_drv_block.png)]

2. phy_device

首先每个 phy 芯片会创建一个 struct phy_device 类型的设备,对应的有 struct phy_driver 类型的驱动,这两者实际上是挂载在 mdio_bus_type 总线上的。

2.1 mdio bus

mdio 总线的定义:

struct bus_type mdio_bus_type = {
	.name		= "mdio_bus",
	.dev_groups	= mdio_bus_dev_groups,
	.match		= mdio_bus_match,
	.uevent		= mdio_uevent,
};

2.2 mdio device

网口驱动在初始化 probe() 时遍历 dts 的定义创建相应struct phy_device 类型的设备:

stmmac_dvr_probe()
`-| stmmac_mdio_register()
  `-| stmmac_mdio_register()
     `-| {
       |  new_bus = mdiobus_alloc();
       |  new_bus->read = &stmmac_xgmac2_mdio_read;  // mdio 读写函数
       |  new_bus->write = &stmmac_xgmac2_mdio_write;
       | 
       |  of_mdiobus_register(new_bus, mdio_node);
       `-| of_mdiobus_register_phy(mdio, child, addr);
          `-| get_phy_device()
             `-| get_phy_c22_id(bus, addr, &phy_id);
                `-| {
                  |  phy_reg = mdiobus_read(bus, addr, MII_PHYSID1); // 通过 mdio 总线读取 phy 芯片 id
                  |  phy_reg = mdiobus_read(bus, addr, MII_PHYSID2);
                  | }
               | phy_device_create(bus, addr, phy_id, is_c45, &c45_ids);
                `-| {
                  |  mdiodev->dev.bus = &mdio_bus_type;
                  |  mdiodev->dev.type = &mdio_bus_phy_type;
                  |  mdiodev->bus_match = phy_bus_match;
                  |  INIT_DELAYED_WORK(&dev->state_queue, phy_state_machine); // 这里就是 phy device 的轮询任务
                  | }
            | of_mdiobus_phy_device_register()
             `-| phy_device_register()
                `-| device_add()

2.3 mdio driver

mdio bus 会根据 struct phy_device 的 phy id 和 struct phy_driver 进行 match,如果没有找到对应驱动会使用通用驱动 genphy_driver

static struct phy_driver genphy_driver = {
	.phy_id		= 0xffffffff,
	.phy_id_mask	= 0xffffffff,
	.name		= "Generic PHY",
	.get_features	= genphy_read_abilities,
	.suspend	= genphy_suspend,
	.resume		= genphy_resume,
	.set_loopback   = genphy_loopback,
};

genphy_driver 为例 struct phy_device 的注册过程如下:

phy_init()
`-| phy_driver_register(&genphy_driver, THIS_MODULE);
   `-| {
     |  new_driver->mdiodrv.driver.bus = &mdio_bus_type;
     |  new_driver->mdiodrv.driver.probe = phy_probe;
     |  new_driver->mdiodrv.driver.remove = phy_remove;
     | }
     | driver_register()

其中一个关键点是 mdio driver 的 probe 函数是一个通用函数 phy_probe(),match 成功时会调用它读取状态寄存器来确定 phy 芯片的能力:

phy_probe()
`-| genphy_read_abilities()
   `-| {
     |  val = phy_read(phydev, MII_BMSR); // 读取 mdio 0x01 寄存器来确定 phy 的 10/100M 能力
     |  linkmode_mod_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported, val & BMSR_ANEGCAPABLE);
     |  linkmode_mod_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, phydev->supported, val & BMSR_100FULL);
     |  linkmode_mod_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT, phydev->supported, val & BMSR_100HALF);
     |  linkmode_mod_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT, phydev->supported, val & BMSR_10FULL);
     |  linkmode_mod_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT, phydev->supported, val & BMSR_10HALF);
     |  if (val & BMSR_ESTATEN) {
     |   val = phy_read(phydev, MII_ESTATUS); // 读取 mdio 0x0f 寄存器来确定 phy 的 1000M 能力
     |   linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, phydev->supported, val & ESTATUS_1000_TFULL);
     |   linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT, phydev->supported, val & ESTATUS_1000_THALF);
     |   linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT, phydev->supported, val & ESTATUS_1000_XFULL);
     |  }
     | }

2.4 poll task

上面 phy_device_create() 函数中创建了一个重要的 work phy_state_machine(),这个就是 phy_device 查询任务的主体,用来查询 phy 芯片的状态维护 phy 状态机。

在网口驱动启动时会启动这个 work:

net_device_ops->ndo_open()
`-| stmmac_open()
   `-| phylink_start()
      `-| phy_start()
         `-| phydev->state = PHY_UP;
           | phy_start_machine()
            `-| phy_trigger_machine()
               `-| phy_queue_state_machine(phydev, 0);
                  `-| mod_delayed_work(system_power_efficient_wq, &phydev->state_queue, jiffies);

phy_state_machine() 的核心逻辑如下所示:

void phy_state_machine(struct work_struct *work)
{

	old_state = phydev->state;

    /* (1) 状态机主体 */
	switch (phydev->state) {
    /* (1.1) 在 PHY_DOWN/PHY_READY 状态下不动作 */
	case PHY_DOWN:
	case PHY_READY:
		break;
    
    /* (1.2) 在 PHY_UP 状态下,表明网口被 up 起来,需要启动自协商并且查询自协商后的 link 状态
             如果自协商结果是 link up,进入 PHY_RUNNING 状态
             如果自协商结果是 link down,进入 PHY_NOLINK 状态
     */
	case PHY_UP:
		needs_aneg = true;

		break;

    /* (1.3) 在运行的过程中定时轮询 link 状态
             如果 link up,进入 PHY_RUNNING 状态
             如果 link down,进入 PHY_NOLINK 状态
     */
	case PHY_NOLINK:
	case PHY_RUNNING:
		err = phy_check_link_status(phydev);
		break;

	}

    /* (2) 如果需要,启动自协商配置 */
	if (needs_aneg)
		err = phy_start_aneg(phydev);

    /* (3) 如果 phy link 状态有变化,通知给对应网口 netdev */
	if (old_state != phydev->state) {
		phydev_dbg(phydev, "PHY state change %s -> %s\n",
			   phy_state_to_str(old_state),
			   phy_state_to_str(phydev->state));
		if (phydev->drv && phydev->drv->link_change_notify)
			phydev->drv->link_change_notify(phydev);
	}

    /* (4) 重新启动 work,周期为 1s */
	if (phy_polling_mode(phydev) && phy_is_started(phydev))
		phy_queue_state_machine(phydev, PHY_STATE_TIME);
}

标准的 mdio/mii 寄存器列表定义如下:

/* Generic MII registers. */
#define MII_BMCR		0x00	/* Basic mode control register */
#define MII_BMSR		0x01	/* Basic mode status register  */
#define MII_PHYSID1		0x02	/* PHYS ID 1                   */
#define MII_PHYSID2		0x03	/* PHYS ID 2                   */
#define MII_ADVERTISE		0x04	/* Advertisement control reg   */
#define MII_LPA			0x05	/* Link partner ability reg    */
#define MII_EXPANSION		0x06	/* Expansion register          */
#define MII_CTRL1000		0x09	/* 1000BASE-T control          */
#define MII_STAT1000		0x0a	/* 1000BASE-T status           */
#define	MII_MMD_CTRL		0x0d	/* MMD Access Control Register */
#define	MII_MMD_DATA		0x0e	/* MMD Access Data Register */
#define MII_ESTATUS		0x0f	/* Extended Status             */
#define MII_DCOUNTER		0x12	/* Disconnect counter          */
#define MII_FCSCOUNTER		0x13	/* False carrier counter       */
#define MII_NWAYTEST		0x14	/* N-way auto-neg test reg     */
#define MII_RERRCOUNTER		0x15	/* Receive error counter       */
#define MII_SREVISION		0x16	/* Silicon revision            */
#define MII_RESV1		0x17	/* Reserved...                 */
#define MII_LBRERROR		0x18	/* Lpback, rx, bypass error    */
#define MII_PHYADDR		0x19	/* PHY address                 */
#define MII_RESV2		0x1a	/* Reserved...                 */
#define MII_TPISTATUS		0x1b	/* TPI status for 10mbps       */
#define MII_NCONFIG		0x1c	/* Network interface config    */

2.4.1 自协商配置

具体启动 phy 自协商的代码流程如下:

phy_state_machine()
`-| phy_start_aneg()
   `-| phy_config_aneg()
      `-| genphy_config_aneg()
         `-| __genphy_config_aneg()
            `-| genphy_setup_master_slave() // (1) 如果是千兆网口,配置其 master/slave
               `-| {
                 |  phy_modify_changed(phydev, MII_CTRL1000,    // 配置 mdio 0x09 寄存器
                 |     (CTL1000_ENABLE_MASTER | CTL1000_AS_MASTER | CTL1000_PREFER_MASTER), ctl);
                 | }
              | genphy_config_advert() // (2) 设置本端的 advert 能力
               `-| {
                 |  linkmode_and(phydev->advertising, phydev->advertising, phydev->supported);
                 |  adv = linkmode_adv_to_mii_adv_t(phydev->advertising);
                 |  phy_modify_changed(phydev, MII_ADVERTISE,   // 10M/100M 能力配置到 mdio 0x04 寄存器
                 |       ADVERTISE_ALL | ADVERTISE_100BASE4 |
                 |       ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM, adv);
                 |  if (!(bmsr & BMSR_ESTATEN)) return changed;
                 |  adv = linkmode_adv_to_mii_ctrl1000_t(phydev->advertising);
                 |  phy_modify_changed(phydev, MII_CTRL1000,    // 1000M 能力配置到 mdio 0x09 寄存器
                 |       ADVERTISE_1000FULL | ADVERTISE_1000HALF, adv);
                 | }
              | genphy_check_and_restart_aneg()
               `-| genphy_restart_aneg() // (3) 启动 phy 自协商
                  `-| {
                    |  phy_modify(phydev, MII_BMCR, BMCR_ISOLATE,   // 配置 mdio 0x00 寄存器
                    |       BMCR_ANENABLE | BMCR_ANRESTART);
                    | }

2.4.2 link 状态读取

phy link 状态读取的代码流程如下:

phy_state_machine()
`-| phy_check_link_status()
   `-| phy_read_status()    // (1) 读取 link 状态 
      `-| genphy_read_status()
         `-| {
           |  genphy_update_link(phydev);   // (1.1) 更新 link 状态
           |  if (phydev->autoneg == AUTONEG_ENABLE && old_link && phydev->link) return 0;
           |  genphy_read_master_slave(phydev); // (1.2) 如果是千兆网口,更新本端和对端的 master/slave
           |  genphy_read_lpa(phydev);  // (1.3) 更新对端(link partner) 声明的能力
           |  if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete) {
           |    phy_resolve_aneg_linkmode(phydev);  // (1.4.1) 自协商模式,解析 link 结果
           |  } else if (phydev->autoneg == AUTONEG_DISABLE) {
           |    genphy_read_status_fixed(phydev); // (1.4.2) 固定模式,解析 link 结果
           |  }
           | }
     | if (phydev->link && phydev->state != PHY_RUNNING) {  // (2) link 状态 change 事件:变成 link up
     |   phydev->state = PHY_RUNNING;
     |   phy_link_up(phydev);   // link up 事件,通知给 phylink
     | } else if (!phydev->link && phydev->state != PHY_NOLINK) {  // (3) link 状态 change 事件:变成 link down
     |   phydev->state = PHY_NOLINK;
     |   phy_link_down(phydev); // link down 事件,通知给 phylink
     | }

2.4.3 link 状态通知

phy 的 link 状态变化怎么通知给 netdev,并且让 mac 做出相应的配置改变,这个是通过一个中介 phylink 来实现的。

phy_device 把 link 状态通知给 phylink 的流程如下:

phy_link_up()/phy_link_down()
`-| phydev->phy_link_change(phydev, true/false);
   `-| phylink_phy_change()
      `-| {
        |  pl->phy_state.speed = phydev->speed;     // (1) 把 `phy_device`  状态更新给 `phylink`
        |  pl->phy_state.duplex = phydev->duplex;
        |  pl->phy_state.interface = phydev->interface;
        |  pl->phy_state.link = up;
        |  phylink_run_resolve(pl);     // (2) 通知 `phylink` 的轮询任务启动
        | }

3. phylink

在 linux 内核中,以太网 mac 会被注册成 struct net_device,phy 芯片会被注册成 struct phy_devicephy_device 的状态怎么传递给 net_device,让其在 link 状态变化时做出对应的配置改变,这个任务就落在上述的 struct phylink 中介身上。

因为 phylink 只是一个中介,所以它不会创建对应的 device,它的核心在以下的几个函数当中。

3.1 phylink_create()

网口驱动在初始化 probe() 中创建 struct net_device 的同时也创建了 struct phylink

stmmac_dvr_probe()
`-| stmmac_phy_setup()
  `-| phylink_create(&priv->phylink_config, fwnode, mode, &stmmac_phylink_mac_ops);
     `-| {
       |  INIT_WORK(&pl->resolve, phylink_resolve);     // phylink 的轮询任务
       |  pl->mac_ops = mac_ops;
       |  timer_setup(&pl->link_poll, phylink_fixed_poll, 0);
       | }

这里的 stmmac_phylink_mac_ops 就包含了 mac 对 phy link 状态变化相应的相关函数:

static const struct phylink_mac_ops stmmac_phylink_mac_ops = {
	.validate = stmmac_validate,
	.mac_pcs_get_state = stmmac_mac_pcs_get_state,
	.mac_config = stmmac_mac_config,
	.mac_an_restart = stmmac_mac_an_restart,
	.mac_link_down = stmmac_mac_link_down,
	.mac_link_up = stmmac_mac_link_up,
};

3.2 phylink_connect_phy()

在网口驱动启动时会启动时,会连接 phylinkphy_device

net_device_ops->ndo_open()
`-| stmmac_open()
   `-| stmmac_init_phy()
      `-| phylink_of_phy_connect()
         `-| phy_attach_direct()
           | phylink_bringup_phy()
            `-| {
              |  phy->phylink = pl;
              |  phy->phy_link_change = phylink_phy_change; // (1) 设置 phy_device 的通知函数
              |  timer_setup(&pl->link_poll, phylink_fixed_poll, 0);
              | }

3.3 phylink_start()

在连接完成后,会同时启动 phylinkphy_device 的轮询任务:

net_device_ops->ndo_open()
`-| stmmac_open()
   `-| phylink_start()
      `-| mod_timer(&pl->link_poll, jiffies + HZ);  // (1) 启动 `phylink` 的轮询任务
         `-| phylink_fixed_poll()
            `-| phylink_run_resolve()
               `-| queue_work(system_power_efficient_wq, &pl->resolve);
        | phy_start()   // (2) 启动 `phy_device` 的轮询任务
         `-| phydev->state = PHY_UP;
           | phy_start_machine()
            `-| phy_trigger_machine()
               `-| phy_queue_state_machine(phydev, 0);
                  `-| mod_delayed_work(system_power_efficient_wq, &phydev->state_queue, jiffies);

3.3 poll task

phylink 的轮询任务就是查询 phy_device 更新过来的 link 状态,调用 stmmac_phylink_mac_ops 相关函数来同步配置 mac:

phylink_resolve()
`-| {
  |  link_state = pl->phy_state;
  |  phylink_link_up()/phylink_link_down()
   `-| pl->mac_ops->mac_link_up()
      `-| stmmac_mac_link_up()  // (1) 配置 mac 为相应状态

参考资料

1.以太网PHY寄存器分析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/93033.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从另外一个角度解释AUC

AUC到底代表什么呢,我们从另外一个角度解释AUC,我们先看看一个auc曲线 蓝色曲线下的面积(我的模型的AUC)比红线下的面积(理论随机模型的AUC)大得多,所以我的模型一定更好。 我的模型比随机模型好多少呢?理论随机模型只是对角线,…

加密与认证技术

加密与认证技术密码技术概述密码算法与密码体制的基本概念加密算法与解密算法秘钥的作用什么是密码密钥长度对称密码体系对称加密的基本概念典型的对称加密算法DES加密算法3DES加密算法非对称密码体系非对称加密基本概念密码技术概述 密码技术是保证网络安全的核心技术之一&am…

【windows Server 2019系列】 构建IIS服务器

个人名片: 对人间的热爱与歌颂,可抵岁月冗长🌞 Github👨🏻‍💻:念舒_C.ying CSDN主页✏️:念舒_C.ying 个人博客🌏 :念舒_C.ying Web服务器也称为WWW(World W…

电子厂测试题——难倒众多主播——大司马也才90分

一、选择题 1、1-2 ( ) A.1 B.3 C.-1 D.-3 2、|1-2|( ) A.1 B.3 C. -1 D.-3 3、1x2x3( ) A.5 B.6 C.7 D.8 4、3643( ) A.29 B.16 C.8 D.3 5、55x5( ) A.15 B.30 C.50 D.125 二、填空题(请填写阿拉伯数字) 6、110100 1000_______ 7、一个三角形砍去1个角&#…

Feign的两种最佳实践方式介绍

何谓最佳实践呢?就是企业中各种踩坑,最后总结出来的相对比较好的使用方式; 下面给大家介绍两种比较好的实践方案: 方式一(继承):给消费者的FeignClient和提供着的Controller定义一个统一的父接…

在逆变器中驱动和保护IGBT

在逆变器中驱动和保护IGBT 介绍 ACPL-339J是一款先进的1.0 A双输出,易于使用,智能的手机IGBT门驱动光耦合器接口。专为支持而设计MOSFET制造商的各种电流评级,ACPL-339J使它更容易为系统工程师支持不同的系统额定功率使用一个硬件平台通过…

全面解析若依框架(springboot-vue前后分离--后端部分)

1、 若依框架分解 - 启动配置 前端启动 # 进入项目目录 cd ruoyi-ui# 安装依赖 npm install# 强烈建议不要用直接使用 cnpm 安装,会有各种诡异的 bug,可以通过重新指定 registry 来解决 npm 安装速度慢的问题。 npm install --registryhttps://regist…

算法刷题打卡第47天:排序数组---归并排序

排序数组 难度:中等 给你一个整数数组 nums,请你将该数组升序排列。 示例 1: 输入:nums [5,2,3,1] 输出:[1,2,3,5]示例 2: 输入:nums [5,1,1,2,0,0] 输出:[0,0,1,1,2,5]归并排…

用CSS给健身的侣朋友做一个喝水记录本

前言 事情是这样的,由于七八月份的晚上时不时就坐在地摊上开始了喝酒撸串的一系列放肆的长肉肉项目。 这不,前段时间女朋友痛下决心(心血来潮)地就去报了一个健身的私教班,按照教练给的饮食计划中,其中有一…

卵巢早衰与微生物群,营养治疗新进展

卵巢早衰 卵巢早衰(premature ovarian insufficiency,简称POI)在生殖系统疾病中位居首位,这些疾病可能会损害多个功能系统,降低生活质量,最终剥夺女性患者的生育能力。 目前的激素替代疗法不能改善受孕或降…

NR PDSCH(七) DL SPS

非动态调度,除了PUSCH configured grant type 1和2的传输,还有PDSCH SPS 传输,两者的流程基本类似,也有些小区别。在实网并没有见过配置DL SPS PDSCH传输的log,但还是按顺序理一遍相关内容。 RRC/MAC 先看下MAC 38.32…

文件上传,还存储在应用服务器?

一般项目开发中都会有文件、图片、视频等文件上传并能够访问的场景。要实现这样的场景,要么把文件存储在应用服务器上,要么搭建文件服务来存储。但是这两种方式也有不少的缺点,增加运维的成本。 因此,追求用户体验的项目可能会考…

Tomcat安装配置全解

👌 棒棒有言:也许我一直照着别人的方向飞,可是这次,我想要用我的方式飞翔一次!人生,既要淡,又要有味。凡事不必太在意,一切随缘,缘深多聚聚,缘浅随它去。凡事…

数据库分库分表

文章目录为什么要分库分表?数据切分垂直切分水平切分(每个表的结构相同)范围拆分取模拆分(一般为业务主键)分库分表带来的问题数据倾斜问题热点问题事务问题聚合查询问题分页问题非分区业务查询分库分表实现或工具hash…

DSP篇--C6701功能调试系列之 UART串口测试

目录 1、原理 2、测试 调试的前期准备可以参考前面的博文:DSP篇--C6701功能调试系列之前期准备_nanke_yh的博客-CSDN博客 UART串口收发数据存在两种模式:通常的串口模式(McBSP in Serial Port Mode)和GPIO模式(McBS…

哈希表及其与Java类集的关系

目录 1.哈希表的概念 2.哈希冲突 3.如何避免哈希冲突? 3.1哈希函数设计 3.2 负载因子的调节 4.解决哈希冲突 4.1闭散列 4.1.1线性探测 4.1.2二次探测 4.2开散列(哈希桶) 5.HashMap 6.HashSet 1.哈希表的概念 假设有一组数据,要让你去搜索其中的一个关键码,这种场…

JWT快速入门及所需依赖

目录 1.JWT 1.1什么是JWT 1.2JWT的构成 jwt的头部 payload signature 1.3JWT快速入门案例 2Jwt认证(微服务) 2.1微服务下统一权限认证 2.2应用认证 3.无状态的JWT令牌如何实现续签功能? 3.1不允许改变Token令牌实现续签 3.2允许改…

计算机毕业设计django基于python大学生多媒体学习系统

项目介绍 随着计算机多媒体技术的发展和网络的普及。采用当前流行的B/S模式以及3层架构的设计思想通过Python技术来开发此系统的目的是建立一个配合网络环境的大学生多媒体学习系统的平台,这样可以有效地解决数据学习系统混乱的局面。 本文首先介绍了大学生多媒体学习系统的发…

eslint Parsing error: The keyword ‘export‘ is reserved

报错 原因 ECMAScript modules(import/export) 是 es6 的语法。 根据 eslint 官方文档 Configure language options ,eslint 默认使用 es5 语法: 解决 要让 eslint 知道我在使用 es6 的 modules 语法。有下面几种方法: 设置 env 为 es6&am…

喜讯 | 第三届国际科创节,企企通喜提两项大奖

近日,第三届国际科创节暨数服会STIF奖评选活动重磅揭晓,旨在向科技创新与数字化转型引领者致敬。企企通作为作为数字化采购平台领军者,凭借业内领先的技术实力与优秀的服务口碑,经过层层筛选和专业评审,企企通最终荣膺…