1. 以太网开关不是“点一下就完事”的UI操作
很多人第一次在Android设备上尝试启用以太网时,会下意识打开设置里的“网络与互联网”→“以太网”,勾选“启用以太网”——然后发现毫无反应。插着网线,状态栏没图标,ip addr查不到eth0,ping不通局域网设备。这不是你设备坏了,也不是网线有问题,而是你误把一个跨层协同的系统级流程当成了普通开关。
Android的以太网开关,本质上是一条从Java层UI触发、经由Framework服务调度、最终落到底层Linux内核驱动和网络栈的完整链路。它不像Wi-Fi那样有标准化的HAL接口和成熟的厂商适配层,而是高度依赖SoC厂商对Linux内核以太网子系统的裁剪、驱动实现质量,以及OEM对Android Framework层的定制补丁。我最早在一台基于RK3399的工业平板上调试这个功能时,就卡在了第三步:Settings里能点开开关,但点击后没有任何日志输出,连logcat -s ConnectivityService都静默无声。后来翻源码才发现,那台设备的EthernetManager根本没被初始化——因为厂商在SystemServer启动时跳过了EthernetService的注册逻辑,理由是“该设备不支持有线网络”。
关键词“Android”“以太网”“开关流程”背后的真实含义,其实是:一次从用户意图到物理网卡通电、链路建立、IP获取、路由注入的全栈贯通验证。它涉及至少四个关键层级:
- 应用层(Settings App):提供UI入口,调用
EthernetManagerAPI; - Framework层(EthernetManager / EthernetService):负责状态管理、广播通知、与ConnectivityService联动;
- HAL层(可选,部分新平台引入):抽象硬件控制,但多数老平台直接走内核ioctl;
- Kernel层(drivers/net/ethernet/... + net/ipv4/):真正执行PHY上电、MAC初始化、链路状态机、ARP/ICMP处理。
而热搜词里反复出现的“stm32车载以太网”“fpga三速以太网”“mt8072ie与fx5uj通讯”,恰恰说明这个流程的复杂性正在被放大——当以太网不再只是PC或路由器的标配,而是嵌入到车载ECU、工业PLC、边缘AI盒子中时,Android作为上层OS,必须和底层定制硬件深度咬合。这时候,“开关流程”就不再是Android单方面的事,而是软硬协同的契约兑现过程。
所以,别再只盯着Settings里的那个滑块。它只是一个触发器,真正的战场在logcat的滚动日志里,在dmesg的驱动加载信息中,在/sys/class/net/eth0/下的每一个属性文件里。接下来,我会带你一层一层剥开这个流程,不是照着AOSP代码念注释,而是告诉你每一层实际调试时最该盯住哪几行日志、哪个文件、哪条命令,以及为什么这些地方出问题,其他地方再怎么改都是白费劲。
2. Settings UI背后的三次关键调用:从点击到Binder通信
当你在Settings里点击“启用以太网”开关时,表面上只是SwitchPreferenceCompat状态切换,但背后发生了三次关键的跨进程调用,缺一不可。这三次调用构成了整个流程的“引信”,一旦其中任何一次失败,后续所有动作都不会发生。我见过太多案例,开发者只查EthernetService日志,却忽略了最前端的调用链断裂。
2.1 第一次调用:Settings App内部状态同步与API封装
Settings App本身并不直接操作网络,它通过EthernetManager这个系统服务代理来完成。关键代码位于packages/apps/Settings/src/com/android/settings/network/EthernetSettings.java中:
private void updateEthernetState(boolean enabled) { if (mEthernetManager == null) { Log.w(TAG, "EthernetManager is null, skip enable/disable"); return; } try { // 这里是第一次关键调用:封装参数并准备Binder请求 mEthernetManager.setEthEnabled(enabled); } catch (RemoteException e) { Log.e(TAG, "Failed to set ethernet enabled: " + enabled, e); // 注意:这里catch住异常后,UI可能仍显示为“已启用”,造成严重误导 } }这段代码看似简单,但藏着两个极易被忽略的坑:
mEthernetManager的初始化时机。它是在onCreate()中通过getSystemService(Context.ETHERNET_SERVICE)获取的。如果设备厂商在SystemServer中压根没注册EthernetService(就像我前面提到的RK3399平板),那么这里返回的就是null,Log.w会打印警告,但UI毫无感知。这是第一道防线,也是最容易被跳过的检查点。你可以在Settings启动时加一句adb shell dumpsys ethernet,如果返回No service found,那就不用往下看了。setEthEnabled(true)的参数传递。注意,这个API接受的是boolean,但底层驱动往往需要更精细的控制,比如指定PHY地址、MII/RMII模式、速率协商方式。AOSP原生实现里,这个boolean最终会被映射成一个固定的SIOCETHTOOLioctl命令。但如果你的硬件需要先配置PHY寄存器才能UP网卡,那么仅靠这个true/false是远远不够的——这就引出了后续的HAL层或Vendor Service定制需求。
提示:调试时,务必在
updateEthernetState方法开头加一行Log.d(TAG, "Setting ethernet to: " + enabled),并配合adb logcat -s EthernetSettings实时观察。如果这条日志都不出现,说明问题出在UI层:可能是Preference绑定错误、Fragment未正确加载,或者mEthernetManager初始化失败。
2.2 第二次调用:Binder跨进程进入EthernetService
EthernetManager.setEthEnabled()的实现位于frameworks/base/core/java/android/net/EthernetManager.java,它只是一个轻量级封装,真正的逻辑在EthernetService中。其核心是通过Binder机制将调用转发:
// frameworks/base/services/core/java/com/android/server/ethernet/EthernetService.java public void setEthEnabled(boolean enabled) { enforceCallingPermission(); // 关键:这里开始真正的业务逻辑 mHandler.obtainMessage(EVENT_SET_ETHERNET_ENABLED, enabled ? 1 : 0, 0).sendToTarget(); }mHandler是EthernetService的主线程Handler,EVENT_SET_ETHERNET_ENABLED消息被投递后,会在handleMessage()中被处理。这里就是第二次关键调用的发生地——它把UI层的粗粒度指令,转化成了Framework层的状态机事件。
这个转换过程至关重要。EthernetService内部维护着一个mState变量(STATE_DISABLED/STATE_ENABLING/STATE_ENABLED),而setEthEnabled(true)并不会立即UP网卡,而是先将状态设为STATE_ENABLING,然后触发一系列异步操作:检查硬件可用性、读取默认配置、通知ConnectivityService准备接管。很多“点了没反应”的问题,就卡在这个状态机卡死在STATE_ENABLING上。为什么?因为下一步要调用mNetworkInterface.up(),而这个方法可能因驱动未就绪而阻塞或抛出异常。
注意:
EthernetService的日志TAG是EthernetService,不是EthernetManager。调试时必须同时抓取adb logcat -s EthernetService -s ConnectivityService。如果看到EVENT_SET_ETHERNET_ENABLED日志,但紧接着没有up interface eth0或link up相关输出,基本可以断定是驱动层或内核配置问题。
2.3 第三次调用:与ConnectivityService的深度耦合
EthernetService在将状态设为STATE_ENABLING后,做的第一件事不是直接操作网卡,而是向ConnectivityService注册一个NetworkRequest:
// 在handleMessage中处理EVENT_SET_ETHERNET_ENABLED if (enabled) { // 构造一个NetworkRequest,告诉ConnectivityService:“我要一个以太网网络” NetworkRequest request = new NetworkRequest.Builder() .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET) .build(); mConnectivityManager.requestNetwork(request, mNetworkCallback); }这是整个流程中最容易被误解的一环。很多人以为EthernetService自己就能把网卡UP起来,其实不然。Android的网络连接管理是中心化的,ConnectivityService才是真正的“交通警察”。EthernetService只是个“报备单位”,它告诉ConnectivityService:“我这边硬件准备好了,你可以按规则给我分配IP、设置路由了。”
因此,第三次调用的本质,是一次跨服务的策略协商。ConnectivityService收到请求后,会检查当前网络策略(比如是否允许以太网、是否有更高优先级的网络如Wi-Fi正在连接)、读取res/xml/ethernet_config.xml中的配置(MTU、DNS、静态IP等),最后才调用NetworkAgent的sendNetworkScore()和notifyNetworkConnected()。只有这时,EthernetService才会收到回调,进而执行真正的mNetworkInterface.up()。
实操经验:如果你的设备
dumpsys connectivity里看不到Ethernet相关的NetworkAgent,或者adb shell ip link show eth0显示DOWN,但logcat里又有requestNetwork成功日志,那问题一定出在ConnectivityService的策略匹配环节。常见原因是ethernet_config.xml缺失或格式错误,或者NetworkCapabilities的transport type被硬编码为TRANSPORT_WIFI。
这三次调用,环环相扣,像一条精密的齿轮链。少了一颗齿,整个传动就失效。而它们共同指向一个事实:Android以太网开关,从来就不是一个孤立的功能,它是整个Android网络架构的一分子,必须放在ConnectivityService的全局视角下理解。
3. EthernetService的核心状态机与驱动交互真相
EthernetService不是简单的“开关控制器”,它是一个严格遵循状态机模型的网络服务组件。它的核心逻辑藏在frameworks/base/services/core/java/com/android/server/ethernet/EthernetService.java的handleMessage()方法中,而这个状态机的每一次跃迁,都直连底层驱动的生死。
3.1 状态机全景:从DISABLED到ENABLED的七步跃迁
EthernetService定义了五个核心状态:
| 状态常量 | 含义 | 触发条件 | 关键动作 |
|---|---|---|---|
STATE_DISABLED | 初始态,服务未启用 | 进程启动时 | 不做任何事 |
STATE_ENABLING | 正在启用中 | setEthEnabled(true) | 检查硬件、构造NetworkRequest、注册Callback |
STATE_ENABLED | 已启用,但链路未通 | NetworkCallback.onAvailable() | 调用mNetworkInterface.up() |
STATE_LINK_CONNECTED | 物理链路已通(Link UP) | mLinkMonitor检测到/sys/class/net/eth0/carrier == "1" | 发送EVENT_LINK_CONNECTED广播 |
STATE_OBTAINING_IP | 正在获取IP(DHCP或静态) | mIpManager.startProvisioning()成功 | 等待onProvisioned()回调 |
你以为STATE_ENABLED就是终点?错。它只是“软件层面认为可以用了”,但物理世界还没点头。真正的“可用”标志,是STATE_LINK_CONNECTED——这意味着PHY芯片已经检测到网线另一端的信号,并完成了自动协商(Auto-negotiation),确定了速率(10/100/1000Mbps)和双工模式(Half/Full)。这一步完全由Linux内核驱动完成,EthernetService只是被动监听/sys/class/net/eth0/carrier这个sysfs节点的变化。
我曾经在一个使用RTL8211E PHY的项目中,遇到过STATE_ENABLED永远无法跃迁到STATE_LINK_CONNECTED的情况。logcat里反复打印Waiting for link...,dmesg里却有rtl8211e phy: link up。排查了三天,最后发现是/sys/class/net/eth0/carrier的权限问题:内核驱动创建该节点时用了0444权限,而EthernetService运行在system_server进程,UID为1000,无法读取。解决方案极其简单:在init.rc里加一行chmod 0644 /sys/class/net/eth0/carrier。这个教训告诉我:状态机的每一步,都依赖于一个具体的、可验证的Linux文件系统节点或内核事件。脱离了这个基础,再漂亮的Java代码也是空中楼阁。
3.2 驱动交互:不是ioctl,而是sysfs与uevent的双重奏
EthernetService与底层驱动的交互,远比想象中更“Linux原生”。它几乎不使用传统的ioctl系统调用去控制网卡,而是采用两种更轻量、更可靠的方式:
sysfs文件读写:用于查询和设置硬件状态。
/sys/class/net/eth0/carrier:读取值为1表示Link UP,0表示Link DOWN。EthernetService内部有一个LinkMonitor线程,就是不断轮询这个文件。/sys/class/net/eth0/operstate:读取值为up/down/unknown,反映内核网络栈对该接口的操作状态。/sys/class/net/eth0/device/power/wakeup:设置为enabled可让网卡在suspend状态下唤醒系统(用于Wake-on-LAN)。
Netlink socket监听uevent:用于接收内核主动推送的事件。
- 当PHY芯片检测到链路变化时,内核会通过
netlink发送NETLINK_ROUTE消息,类型为RTM_NEWLINK或RTM_DELLINK。 EthernetService通过NetworkManagementSocketTagger类监听这些事件,比轮询carrier文件更及时、更节能。
- 当PHY芯片检测到链路变化时,内核会通过
这种设计体现了Android对Linux内核的尊重:不重复造轮子,而是充分利用内核已有的、经过充分测试的基础设施。这也解释了为什么很多“自定义以太网驱动”的失败案例,根源不在Java层,而在驱动本身没有正确导出carrier节点,或者没有正确发送NETLINK事件。
实操技巧:调试驱动交互,最有效的方法是三管齐下:
adb shell cat /sys/class/net/eth0/carrier—— 看物理层是否就绪;adb shell cat /sys/class/net/eth0/operstate—— 看网络栈是否UP;adb shell cat /proc/net/dev—— 看eth0的收发包计数是否在增长(RX/TX列)。
如果carrier是1,operstate是up,但/proc/net/dev里eth0的计数始终为0,那问题一定在MAC层或PHY层的电气连接上,比如网线水晶头没压好、RJ45接口虚焊、PHY供电不足。
3.3 网络配置的落地:从XML到IP地址的生成路径
当STATE_LINK_CONNECTED达成后,EthernetService会调用mIpManager.startProvisioning(),正式启动IP地址获取流程。这里的IpManager是ConnectivityService的一部分,它根据/etc/ethernet_config.xml的配置决定是走DHCP还是静态IP。
一个典型的ethernet_config.xml长这样:
<NetworkSettings> <NetworkTemplate> <TransportType>ETHERNET</TransportType> <DefaultRoute>true</DefaultRoute> <UseStaticIp>false</UseStaticIp> <StaticIp> <IpAddress>192.168.1.100</IpAddress> <Gateway>192.168.1.1</Gateway> <Netmask>255.255.255.0</Netmask> <DnsServers>8.8.8.8,114.114.114.114</DnsServers> </StaticIp> <DhcpTimeoutMs>30000</DhcpTimeoutMs> <Mtu>1500</Mtu> </NetworkTemplate> </NetworkSettings>关键点在于<UseStaticIp>标签。如果为false,IpManager会启动DhcpClient;如果为true,则直接调用NetworkInterface.setInetAddresses()。但这里有个致命陷阱:ethernet_config.xml的路径和加载时机。它必须放在/system/etc/目录下,且在SystemServer启动EthernetService之前就被Resources类加载。如果OEM把配置文件放错了位置(比如/vendor/etc/),或者文件名拼写错误(ethernet_config.xmlvsethernet-config.xml),IpManager就会回退到默认配置,导致DHCP超时或静态IP不生效。
我曾在一个高通平台项目中,发现ethernet_config.xml被错误地放在了/odm/etc/,而SystemServer的Resources只扫描/system/etc/和/vendor/etc/。结果就是,无论怎么改XML内容,设备始终走DHCP,且超时时间固定为30秒。解决方法是在SystemServer.java的startOtherServices()里,手动添加一行resources.addAssetPath("/odm/etc/");——但这属于hack,正规做法是让OEM把文件放到正确路径。
这个细节再次印证:Android以太网开关流程,是Framework、Vendor、Kernel三方契约的总和。任何一个环节的微小偏差,都会让整个流程在某个状态上停滞不前。
4. 内核驱动层的硬核真相:PHY、MAC与DMA的三角关系
如果说Framework层是“指挥官”,那么内核驱动层就是“前线士兵”。EthernetService的状态机再完美,也得靠驱动把电信号变成数据包。而Android设备上的以太网驱动,绝非一块标准网卡驱动那么简单,它是一个由PHY芯片、MAC控制器、DMA引擎构成的精密三角,任何一个角出问题,整个链路就瘫痪。
4.1 PHY芯片:物理层的“守门人”
PHY(Physical Layer Transceiver)是真正接触网线的芯片,负责将数字信号转换为模拟电信号(Tx),并将接收到的模拟信号还原为数字信号(Rx)。在Android设备中,常见的PHY有:
- Realtek RTL8211E/F/G:成本低,广泛用于消费级平板和盒子;
- Marvell 88E1510/1518:性能强,多见于工业级设备;
- Microchip LAN8720A/LAN8742A:低功耗,常用于电池供电的IoT设备。
PHY芯片的初始化,是整个以太网流程的第一道门槛。它需要被正确配置寄存器,才能完成自动协商(Auto-negotiation),确定速率和双工模式。这个配置过程,通常由MAC控制器的驱动代码完成,通过MDIO总线(Management Data Input/Output)进行读写。
一个典型的PHY初始化序列如下(以RTL8211E为例):
// drivers/net/phy/realtek.c static int rtl8211e_config_init(struct phy_device *phydev) { // 步骤1:复位PHY phy_write(phydev, 0x00, 0x9000); // 写入复位位 msleep(10); // 步骤2:使能自动协商 phy_write(phydev, 0x00, 0x3000); // 0x3000 = 1000BASE-T + 100BASE-TX + 10BASE-T phy_write(phydev, 0x04, 0x01e1); // Advertise all capabilities // 步骤3:启动自动协商 phy_write(phydev, 0x00, 0x3100); // 0x3100 = AN Enable + Restart AN return 0; }这个序列必须在MAC驱动probe()函数中被调用。如果OEM的驱动代码漏掉了phy_config_init(),或者写错了寄存器地址(比如把0x00写成0x01),那么PHY就永远处于“未初始化”状态,carrier文件永远是0,dmesg里会打印phy_read failed或link down。
调试PHY的黄金法则:
dmesg | grep -i "phy\|rtl\|marvell"。如果看到phy xxx: probed,说明PHY被识别;如果看到phy xxx: failed to read register,那就是初始化失败。此时,你需要反编译OEM的内核模块(.ko文件),找到对应的PHY驱动代码,确认config_init函数是否被调用。
4.2 MAC控制器:数据链路层的“调度中心”
MAC(Media Access Control)控制器是SoC的一部分,负责实现以太网帧的封装/解封装、CRC校验、冲突检测(CSMA/CD)等。在ARM SoC中,常见的MAC IP核有:
- Synopsys DesignWare MAC:最通用,RK、Allwinner、NXP i.MX系列广泛使用;
- Cadence MACB/GEM:Xilinx Zynq、Microchip SAMA5D2使用;
- Qualcomm Atheros QCA953x/QCA955x:高通自家方案。
MAC驱动的核心任务,是初始化DMA(Direct Memory Access)通道,并将内存中的sk_buff(socket buffer)与DMA描述符环(Descriptor Ring)关联起来。这个过程极其脆弱,一个参数配错,就会导致收发包完全失灵。
以DesignWare MAC为例,关键初始化步骤包括:
- DMA描述符环分配:在DMA可访问的内存区域(通常是CMA池)分配一组
struct dwmac_dma_desc结构体,每个描述符包含buf_addr(数据缓冲区地址)、length、status等字段。 - 中断注册:注册
rx_irq和tx_irq,当DMA完成收包或发包时,触发中断。 - PHY连接:通过
phy_connect()将MAC与PHY实例绑定,建立struct phy_device指针。
其中,DMA缓冲区地址的正确性是生死线。ARM架构要求DMA缓冲区必须是物理连续的,且地址需满足Cache一致性要求(dma_alloc_coherent())。如果OEM在dwmac_probe()中错误地使用了kmalloc()分配缓冲区,那么CPU写入的数据,DMA引擎可能读不到,反之亦然。现象就是:ip link show eth0显示UP,carrier是1,但ping任何地址都Destination Host Unreachable,/proc/net/dev里RX计数为0。
实操诊断:
adb shell cat /proc/interrupts | grep eth。如果eth0对应的中断计数(rx_irq列)在ping时完全不增长,说明DMA收包中断没触发,问题100%在MAC驱动的中断注册或DMA配置上。
4.3 DMA引擎:内存与网卡间的“高速公路”
DMA(Direct Memory Access)引擎是MAC和内存之间的桥梁。它允许网卡绕过CPU,直接读写内存,极大提升吞吐量。但在嵌入式系统中,DMA配置是出了名的“玄学”区域。
一个典型的DMA配置失误场景:
- Cache一致性问题:CPU修改了
sk_buff->data,但没执行__clean_dcache_area(),导致DMA读到的是旧缓存数据; - 地址映射错误:
dma_map_single()返回的dma_addr被错误地当作虚拟地址使用; - 描述符环大小不匹配:驱动申请了128个描述符,但硬件寄存器里配置的环大小是64,导致DMA越界。
这些问题不会导致内核崩溃,但会让网络表现得“时好时坏”:有时能ping通,但scp大文件必丢包;有时iperf3测速只有10Mbps,远低于标称的100Mbps。这是因为DMA错误具有随机性和偶发性,很难复现。
我的经验是:遇到这种“玄学”问题,第一反应不是看Framework日志,而是直接上perf工具:
# 在设备上安装perf(需内核开启CONFIG_PERF_EVENTS) adb shell perf record -e 'irq:irq_handler_entry' -g -- sleep 10 adb shell perf script | grep -i "eth\|dma"如果irq_handler_entry事件里,eth0的中断处理函数(如stmmac_interrupt)调用次数极少,而/proc/interrupts里计数却很高,那就说明中断被屏蔽了,或者request_irq()时IRQF_SHARED标志没设对。
内核驱动层的调试,没有捷径。它要求你同时懂Linux内核网络子系统、ARM体系结构、SoC datasheet,以及OEM提供的BSP代码。但一旦打通这一层,你会发现,Framework层的所有“诡异行为”,都有一个清晰、确定的物理世界原因。
5. 全流程实操排错:从Settings点击到ping通的逐层验证清单
理论讲得再透,不如一张可执行的排错清单。下面是我十年间,从RK3288到高通SM8450,从工业平板到车载IVI,总结出的Android以太网开关全流程验证表。它不是按“先查A再查B”的线性顺序,而是按“现象→定位层级→验证命令→修复方向”的矩阵式结构,确保你能在5分钟内锁定问题根源。
5.1 现象:Settings里开关无响应,点击后UI状态不改变
| 定位层级 | 验证命令 | 预期输出 | 问题定位 | 修复方向 |
|---|---|---|---|---|
| 应用层 | adb shell dumpsys ethernet | No service found或Ethernet service not ready | EthernetService未注册 | 检查SystemServer.java,确认mActivityManagerService.systemReady()后是否调用了startEthernetService();检查Android.mk是否编译了EthernetService |
| Framework层 | adb logcat -s EthernetSettings -s EthernetService | 无updateEthernetState日志,或mEthernetManager is null | EthernetManager初始化失败 | 检查Settings的AndroidManifest.xml,确认<uses-permission android:name="android.permission.INTERNET" />已声明;检查res/values/config.xml,确认config_ethernet_interface_name值为eth0 |
| HAL层(若存在) | `adb shell getprop | grep ethernet` | ro.ethernet.enable=0或persist.ethernet.enable=0 | HAL层禁用标志 |
注意:
dumpsys ethernet是最快捷的初筛工具。如果它直接报错,说明问题在最顶层,无需往下深挖。
5.2 现象:Settings开关能切换,但状态栏无图标,ip addr查不到eth0
| 定位层级 | 验证命令 | 预期输出 | 问题定位 | 修复方向 |
|---|---|---|---|---|
| Framework层 | adb logcat -s EthernetService -s ConnectivityService | EVENT_SET_ETHERNET_ENABLED后无requestNetwork日志 | EthernetService未向ConnectivityService发起请求 | 检查EthernetService.java中mConnectivityManager是否为null;确认Context.CONNECTIVITY_SERVICE权限已获取 |
| Kernel层 | adb shell ls /sys/class/net/ | 无eth0目录 | 内核未识别网卡 | `dmesg |
| 驱动层 | adb shell cat /proc/net/dev | 无eth0行 | 驱动未成功注册网络设备 | `dmesg |
关键洞察:
/sys/class/net/下没有eth0,是内核驱动层的铁证。此时看dmesg比看任何Java日志都有效。
5.3 现象:ip link show eth0显示UP,但ping不通,/proc/net/dev中RX为0
| 定位层级 | 验证命令 | 预期输出 | 问题定位 | 修复方向 |
|---|---|---|---|---|
| PHY层 | adb shell cat /sys/class/net/eth0/carrier | 0 | PHY未Link UP | `dmesg |
| MAC层 | adb shell cat /sys/class/net/eth0/operstate | down | MAC未UP | adb shell ip link set eth0 up,如果报错Operation not permitted,说明CAP_NET_ADMIN权限缺失,需在sepolicy中添加allow system_server net_admin |
| DMA层 | adb shell cat /proc/interrupts | grep eth | rx_irq计数为0 | DMA收包中断未触发 | `dmesg |
经验之谈:当
carrier是1,operstate是up,但RX为0时,90%的问题在DMA中断。此时,perf record比logcat更有价值。
5.4 现象:能ping通网关,但无法上网,DNS解析失败
| 定位层级 | 验证命令 | 预期输出 | 问题定位 | 修复方向 |
|---|---|---|---|---|
| IP配置层 | adb shell ip route show | 无默认路由(default via x.x.x.x dev eth0) | ConnectivityService未注入路由 | adb shell dumpsys connectivity,查找Ethernet网络的NetworkAgent是否处于CONNECTED状态;检查ethernet_config.xml中<DefaultRoute>true</DefaultRoute>是否设置 |
| DNS层 | adb shell getprop net.eth0.dns1 | 为空或错误IP | DNS未正确设置 | adb shell setprop net.eth0.dns1 8.8.8.8;或修改ethernet_config.xml中的<DnsServers>字段 |
| 防火墙层 | adb shell iptables -t filter -L OUTPUT | 有DROP规则拦截eth0流量 | SELinux或iptables策略阻止 | adb shell su -c "iptables -t filter -D OUTPUT -o eth0 -j DROP";检查sepolicy中net_admin是否被限制 |
最后一公里:网络通了,但应用打不开网页,往往是DNS或路由问题。
getprop和ip route是最快的验证手段。
这张表,我把它贴在实验室的白板上,每次遇到以太网问题,就按表索骥。它不教你原理,只告诉你“下一步该敲什么命令”,因为真正的调试,从来不是靠猜,而是靠证据链。而证据,就藏在dmesg、/sys/class/net/、/proc/net/dev这些最原始的Linux接口里。
6. OEM定制与跨平台适配:从RK3399到高通SM8450的实战差异
AOSP的以太网框架是理想化的参考实现,但现实世界里,每一家OEM的定制都像一道独特的密码。我参与过的项目,从瑞芯微RK3399、全志H616、到高通SM8450,甚至恩智浦i.MX8MQ,它们的以太网适配方案,几乎没有两片相同的叶子。理解这些差异,是避免“一套方案打天下”式踩坑的关键。
6.1 RK3399平台:双PHY与USB转以太网的混合迷宫
RK3399 SoC原生集成一个GMAC(Gigabit MAC),但OEM为了降低成本,常常外挂一个RTL8211E PHY,同时又通过USB3.0接口连接一个ASIX AX88179 USB-Ethernet适配器,形成“双以太网”方案。这带来了三个独特问题:
接口命名冲突:内核可能将RTL8211E识别为
eth0,而USB网卡识别为enp0s17u1u1,但EthernetService默认只认eth0。解决方案是在BoardConfig.mk中添加BOARD_ETHERNET_DEVICE_NAME := enp0s17u1u1,并修改ethernet_config.xml中的<InterfaceName>字段。PHY供电时序:RK3399的GMAC需要
vddio和vdd10两路供电,而OEM的电源树设计,可能导致PHY在MAC初始化完成前就上电,引发phy_read超时。修复方法是在dts文件中,为PHY节点添加regulator-always-on;和regulator-boot-on;属性,并确保vddio的enable-gpios在MAC的reset-gpios之后触发。USB网卡热插拔:USB-Ethernet不支持
carrier检测