📺 B站 嵌入式孙老师博主个人介绍

📘 博主书籍-京东购买链接*:Yocto项目实战教程

📘 加博主微信,进技术交流群jerrydev



从零理解 Linux 网卡驱动:以 RK3576 + RTL8211F 为例,把 MAC、PHY、MDIO、RGMII、stmmac 和设备树一次讲清楚

很多人第一次接触以太网驱动时,会天然地把“网卡驱动”理解成一个单独的 C 文件:
写几个寄存器,能发包,能收包,就算完成了。

但真上板后很快就会发现,以太网驱动根本不是这么回事。

你看到的“eth0”“网线插上了”“能不能拿到 IP”“为什么只有 100M”“为什么是 169.254.x.x”“为什么日志里有 Generic PHY / RTL8211F / stmmac / MDIO / RGMII / tx_delay / rx_delay”——这些现象,其实来自一整条分层链路,不是一个函数决定的。

如果把整个系统画成一条链,大概是这样:

应用层
  ↓
socket / TCP/IP 协议栈
  ↓
Linux net_device
  ↓
MAC 驱动(stmmac / dwmac / SoC glue)
  ↓
MDIO 总线 + PHY 驱动(realtek.c)
  ↓
RGMII / RMII / SGMII 等 MAC-PHY 接口
  ↓
外部 PHY(RTL8211F)
  ↓
磁性器件 / RJ45 / 网线
  ↓
对端交换机 / 路由器 / 电脑

理解这条链之后,你再去看驱动、设备树、日志,就会一下子变得非常清晰。

这篇文章就围绕这个思路展开。


一、先把“网卡驱动”拆开:它其实不是一个驱动,而是三层协作

在这里插入图片描述

在 Linux 里,以太网能工作,通常至少涉及三层:

1. MAC 驱动

MAC 是 SoC 里集成的以太网控制器。
在 RK3576 这类平台上,MAC 不直接等于“整个网卡”,它只是数据链路层里靠近 CPU 的那一部分。

它主要负责:

  • 发送和接收以太网帧
  • DMA 描述符管理
  • 中断
  • NAPI 轮询
  • 网卡的 ndo_openndo_stopndo_start_xmit
  • 和 Linux 网络子系统对接

你当前这套平台里,核心 MAC 驱动不是你完全手写的,而是 Synopsys DesignWare MAC(stmmac / dwmac) 这一套通用框架。

也就是说,大部分发送、接收、DMA、NAPI 逻辑,实际上已经在 stmmac 里写好了
你并不是从零发明一个以太网驱动,而是站在成熟内核框架上做 SoC 适配。


2. PHY 驱动

PHY 是外部芯片,比如你这里的 RTL8211F

PHY 不负责 IP,不负责 socket,不负责 DMA。
它负责的是更底层的“电气”和“链路协商”:

  • 检测网线插拔
  • 自协商速度和双工
  • 10/100/1000M 切换
  • Link Up / Link Down
  • RGMII 内部延时
  • 低功耗模式(例如 ALDPS)
  • 时钟输出
  • 读取 PHY ID
  • MDIO/MDC 寄存器访问

Linux 里这部分通常由 PHYLIB 框架统一管理,而具体厂商的 PHY,例如 Realtek,就放在 drivers/net/phy/realtek.c 里。

也就是说:

  • MAC 驱动 关注“怎么收发帧”
  • PHY 驱动 关注“物理链路是否建立、速度是多少、时序怎么配”

3. 设备树(DTS)

设备树不是驱动代码,但在嵌入式平台里它和驱动是同等重要的。

为什么?

因为驱动只认识“逻辑设备”,但板子到底怎么接线、PHY 地址多少、RGMII 模式是什么、reset GPIO 在哪、时钟怎么接、延时参数是多少,这些都不是驱动源码能自己猜出来的,必须由板级描述告诉它。

所以设备树的任务是把板级硬件信息说清楚,例如:

  • phy-mode = "rgmii-rxid";
  • reg = <0x1>;
  • snps,reset-gpio = <...>;
  • tx_delay = <...>;
  • rx_delay = <...>;
  • phy-handle = <&rgmii_phy0>;

如果设备树错了,驱动再优秀也没用。


二、以你当前平台为例:RK3576 + RTL8211F 到底是怎么工作的

你当前这套板子,整体结构可以这样理解:

RK3576 GMAC0 (MAC)
   │
   ├── MDIO/MDC  →  RTL8211F 的管理接口
   ├── RGMII TX/RX/CLK → RTL8211F 的数据接口
   │
RTL8211F (PHY)
   │
   ├── 磁性器件 / RJ45
   │
对端交换机 / 路由器 / PC

这里最容易混淆的是两条线:

1. MDIO/MDC

这是一条“管理总线”,速度不高。
它不是拿来传以太网数据的,它是拿来:

  • 读取 PHY ID
  • 读取 link 状态
  • 设置 10/100/1000
  • 配 RGMII delay
  • 配低功耗、时钟输出等

驱动里经常出现的:

  • phy@1
  • reg = <0x1>
  • PHY_ID_MATCH_EXACT(...)
  • mdiobus
  • phy_read()
  • phy_write()

本质都属于这条链。


2. RGMII

RGMII 才是 MAC 和 PHY 之间真正传数据的高速接口。
这条链上有:

  • TXD[3:0]
  • RXD[3:0]
  • TX_CTL
  • RX_CTL
  • TXC
  • RXC

如果这条线的时序、引脚、延时配置不对,最常见现象不是“完全没设备”,而是:

  • 只能 100M
  • 千兆不稳定
  • 丢包
  • CRC 错误
  • 10/100 能上,1000 上不去
  • Link Up 但 DHCP 拿不到地址
  • RX 为 0,TX 有值

这也是为什么 phy-modetx_delayrx_delay、PHY 内部 delay 如此关键。


三、Linux 启动后,以太网驱动的完整工作流程

下面按时间顺序,把驱动启动的过程走一遍。


第一步:内核解析设备树,创建平台设备

内核启动后,会先读取 DTB,发现有一个 GMAC 节点,比如:

&gmac0 {
	phy-mode = "rgmii-rxid";
	clock_in_out = "output";
	snps,reset-gpio = <&gpio2 RK_PB5 GPIO_ACTIVE_LOW>;
	snps,reset-active-low;
	snps,reset-delays-us = <0 20000 100000>;
	phy-handle = <&rgmii_phy0>;
	status = "okay";
};

这一步内核还没有“开始发网包”,只是知道:
“这里有个以太网控制器,地址在某个寄存器区,挂了一个外部 PHY。”


第二步:MAC 驱动 probe

接着平台驱动 probe,也就是 stmmac / dwmac 的 Rockchip glue 开始接管这个设备。

它会做这些事情:

  • 映射寄存器
  • 申请时钟
  • 申请 reset
  • 解析 phy-mode
  • 解析 tx_delay / rx_delay
  • 配置 pinctrl
  • 初始化 DMA
  • 注册 net_device
  • 创建或连接 MDIO 总线

这一层最重要的一点是:
它并不直接知道板上 PHY 是谁,它只负责把“MAC 控制器”先带起来。


第三步:创建 MDIO 总线,识别 PHY

然后 MAC 驱动会通过 MDIO 去扫描 PHY。

例如设备树里:

&mdio0 {
	rgmii_phy0: phy@1 {
		reg = <0x1>;
	};
};

这表示 PHY 地址是 1。

驱动会通过 MDIO 读 PHY 的标准寄存器:

  • PHYIDR1
  • PHYIDR2

从而拿到一个唯一 PHY ID,比如 RTL8211F 对应的 ID。

如果识别成功,内核就会在 PHY 驱动表里查找谁能匹配这个 ID。


第四步:PHY 驱动匹配

drivers/net/phy/realtek.c 里,通常会有类似这样的驱动表:

static struct phy_driver realtek_drvs[] = {
	{
		PHY_ID_MATCH_EXACT(0x001cc916),
		.name           = "RTL8211F Gigabit Ethernet",
		.config_init    = rtl8211f_config_init,
		.config_aneg    = genphy_config_aneg,
		.read_status    = genphy_read_status,
		.suspend        = genphy_suspend,
		.resume         = genphy_resume,
	},
};

这段代码的含义非常重要:

  • PHY_ID_MATCH_EXACT(...):告诉内核,这个驱动认哪个 PHY
  • config_init:PHY 初始化时要做什么
  • config_aneg:如何做自协商
  • read_status:如何读链路状态

如果这里匹配不上,就可能回退成 Generic PHY
这时系统也许还能工作,但厂商特有配置就可能丢失,比如:

  • RGMII delay
  • EEE
  • 低功耗
  • clkout
  • page 切换寄存器处理

这就是为什么“能看到网口”不等于“驱动正确”。


第五步:PHY 初始化

匹配到 RTL8211F 后,会进入 rtl8211f_config_init()

这一层通常干什么?

  • 配置 page select
  • 配置 RGMII internal delay
  • 配置 EEE
  • 配置 ALDPS
  • 配置时钟输出
  • 配置 LED 行为
  • 做厂商寄存器的初始化

这一步非常像“上电后的板级调参”。

如果这里 delay 配错了,就可能出现一种典型现象:

  • 驱动看起来都正常
  • PHY 也识别正确
  • 但千兆协商不上
  • 最后掉到 100M 甚至 10M

所以很多人以为“驱动没问题了”,其实只是 probe 通过了,时序未必正确


第六步:网卡被注册成 eth0

前面 MAC 和 PHY 都搞定后,系统就会创建 eth0

这时候你用 ifconfig -aip link 就能看到它。
但注意:

看得到 eth0,并不代表网络通。

这只是说明:

  • net_device 已经注册了
  • 驱动对象已经存在了

真正的数据路径,还要看 ndo_open、NAPI、DMA、PHY link 状态。


第七步:ifconfig up / systemd-networkd / dhcpcd 拉起接口

当网络管理器把接口拉起来时,会调用驱动的 ndo_open

典型动作包括:

  • 申请中断
  • 初始化 DMA ring
  • 启动 RX/TX engine
  • 启动 NAPI
  • phy_start() 或 phylink start
  • 允许 MAC 开始收发

到这里,才真正进入可收发包状态。


四、网卡驱动最核心的四个入口函数

很多人问,从零看网卡驱动,应该抓哪些核心函数?

我建议先盯住下面四个:

  1. ndo_open
  2. ndo_stop
  3. ndo_start_xmit
  4. poll(NAPI)

只要把这四个看明白,整个数据平面就通了。


1. ndo_open:网卡启动

大致伪代码是这样:

static int myeth_open(struct net_device *ndev)
{
	struct my_priv *priv = netdev_priv(ndev);

	init_dma_desc(priv);
	request_irq(priv->irq, myeth_interrupt, 0, ndev->name, ndev);
	napi_enable(&priv->napi);

	start_rx_dma(priv);
	start_tx_dma(priv);
	enable_mac_rx_tx(priv);

	phy_start(ndev->phydev);
	netif_start_queue(ndev);

	return 0;
}

逻辑很直白:

  • 把 DMA ring 准备好
  • 开中断
  • 开 NAPI
  • 开 MAC 的收发
  • 启动 PHY
  • 允许上层发送队列启动

2. ndo_stop:网卡关闭

这是反方向动作:

static int myeth_stop(struct net_device *ndev)
{
	struct my_priv *priv = netdev_priv(ndev);

	netif_stop_queue(ndev);
	phy_stop(ndev->phydev);

	disable_mac_rx_tx(priv);
	stop_rx_dma(priv);
	stop_tx_dma(priv);

	napi_disable(&priv->napi);
	free_irq(priv->irq, ndev);

	return 0;
}

3. ndo_start_xmit:发包主路径

这是应用层发包最终到驱动的入口之一。

static netdev_tx_t myeth_start_xmit(struct sk_buff *skb, struct net_device *ndev)
{
	struct my_priv *priv = netdev_priv(ndev);
	dma_addr_t dma;

	dma = dma_map_single(priv->dev, skb->data, skb->len, DMA_TO_DEVICE);
	fill_tx_desc(priv, dma, skb->len);
	kick_tx_dma(priv);

	return NETDEV_TX_OK;
}

这段代码真正做的事只有三件:

  • 把 skb 映射成 DMA 地址
  • 填 TX 描述符
  • 通知硬件开始发

注意,网卡驱动不是一个字节一个字节发,而是通过 DMA 描述符让硬件自己搬运。


4. NAPI poll:收包主路径

收包一般不是在中断里把所有包都处理完,而是中断里只做“通知”,真正收包在 poll 里做:

static int myeth_poll(struct napi_struct *napi, int budget)
{
	struct my_priv *priv = container_of(napi, struct my_priv, napi);
	int work_done = 0;

	while (work_done < budget && rx_desc_has_packet(priv)) {
		struct sk_buff *skb = build_skb_from_rx_desc(priv);
		skb->protocol = eth_type_trans(skb, priv->ndev);
		napi_gro_receive(napi, skb);
		work_done++;
	}

	if (work_done < budget) {
		napi_complete_done(napi, work_done);
		enable_rx_irq(priv);
	}

	return work_done;
}

这里面最关键的理解是:

  • 中断只是提醒“来包了”
  • NAPI poll 才负责真正从 RX ring 取包
  • 拿到包后交给协议栈

这就是 Linux 高性能网卡驱动的基本套路。


五、PHY 驱动最核心的逻辑是什么

相比 MAC 驱动,PHY 驱动通常代码短很多,但很容易看不懂。

原因是它不是“完整数据通路驱动”,它主要是“寄存器配置 + 状态机驱动”。

最常见的 PHY 驱动核心函数有这几个:

  • probe
  • config_init
  • config_aneg
  • read_status
  • suspend
  • resume

其中最关键的是 config_initread_status


1. config_init:初始化 PHY 的特殊功能

以 RTL8211F 为例,它可能需要在这里做:

  • 根据 phy-mode 配 RGMII delay
  • 开/关 ALDPS
  • 开/关 EEE
  • 开/关 clkout
  • 配 LED

例如你可以把它理解为这种伪代码:

static int rtl8211f_config_init(struct phy_device *phydev)
{
	switch (phydev->interface) {
	case PHY_INTERFACE_MODE_RGMII:
		disable_tx_delay();
		disable_rx_delay();
		break;
	case PHY_INTERFACE_MODE_RGMII_ID:
		enable_tx_delay();
		enable_rx_delay();
		break;
	case PHY_INTERFACE_MODE_RGMII_RXID:
		disable_tx_delay();
		enable_rx_delay();
		break;
	case PHY_INTERFACE_MODE_RGMII_TXID:
		enable_tx_delay();
		disable_rx_delay();
		break;
	}

	disable_eee_if_needed();
	configure_clkout_if_needed();
	configure_low_power_if_needed();

	return 0;
}

这段逻辑特别重要,因为它决定的是板子是否稳定跑千兆


2. read_status:读取链路状态

这个函数一般做:

  • Link Up / Link Down
  • Speed = 10 / 100 / 1000
  • Duplex = half / full
  • Pause 能力

它最后会把状态写回 phydev,MAC 驱动再根据这个结果调整硬件。


六、设备树到底写什么才叫“把板子描述清楚了”

很多初学者写网卡 DTS 时,容易写成“能编译过去就行”。
但真正有价值的 DTS,不是能编译,而是能完整描述板级事实

一个比较合理的 Ethernet DTS,至少包含三部分。


1. GMAC 节点

&gmac0 {
	phy-mode = "rgmii-rxid";
	clock_in_out = "output";

	snps,reset-gpio = <&gpio2 RK_PB5 GPIO_ACTIVE_LOW>;
	snps,reset-active-low;
	snps,reset-delays-us = <0 20000 100000>;

	pinctrl-names = "default";
	pinctrl-0 = <&eth0m0_miim
		     &eth0m0_tx_bus2
		     &eth0m0_rx_bus2
		     &eth0m0_rgmii_clk
		     &eth0m0_rgmii_bus
		     &ethm0_clk0_25m_out>;

	tx_delay = <0x30>;
	rx_delay = <0x0>;

	phy-handle = <&rgmii_phy0>;
	status = "okay";
};

这里每一项都不是装饰:

  • phy-mode:定义 MAC/PHY 延时模型
  • reset-gpio:PHY 上电复位
  • pinctrl-0:把 SoC 引脚切到以太网功能
  • tx_delay/rx_delay:MAC 侧延时参数
  • phy-handle:连接到哪一个 PHY

2. MDIO / PHY 节点

&mdio0 {
	rgmii_phy0: ethernet-phy@1 {
		compatible = "ethernet-phy-id001c.c916",
			     "ethernet-phy-ieee802.3-c22";
		reg = <0x1>;
		clocks = <&cru REFCLKO25M_GMAC0_OUT>;
		phy-supply = <&vcc_3v3_phy>;
		realtek,clkout-disable;
		max-speed = <1000>;
	};
};

这部分的作用是:

  • reg:PHY 的 MDIO 地址
  • compatible:帮助绑定正确驱动
  • clocks:PHY 的参考时钟
  • phy-supply:供电控制
  • max-speed:限制速度上限
  • realtek,xxx:厂商私有配置

3. pinctrl 节点

这是最容易被忽视的一层。
驱动写得再漂亮,如果 pinctrl 没配到正确复用,接口根本不可能好。

所以以太网 pinctrl 至少要保证:

  • MDIO/MDC
  • TX/RX 数据线
  • TXC/RXC
  • 参考时钟
  • reset pin(如果是 GPIO)

七、为什么“能看到 eth0”不等于“驱动没问题”

这个误区非常常见。

看到 eth0 只说明两件事:

  1. net_device 已经注册了
  2. MAC 驱动 probe 至少没死

但下面这些问题依然可能存在:

  • PHY 其实绑定成 Generic PHY 了
  • RGMII delay 错了
  • 只有 100M,没有 1000M
  • 链路一直 downshift
  • RX 一直 0
  • DHCP 拿不到
  • 169.254.x.x fallback

这就是为什么调试以太网时,必须同时看:

  • dmesg
  • ethtool
  • ifconfig / ip -s link
  • 设备树
  • PHY 驱动匹配情况
  • 板级原理图

单看一个现象,往往会误判。


八、你当前这套驱动里,真正关键的代码点有哪些

如果把“当前工程里的关键代码”提炼出来,其实就几处。


1. PHY ID 匹配

这是 PHY 驱动入口。

{
	PHY_ID_MATCH_EXACT(0x001cc916),
	.name = "RTL8211F Gigabit Ethernet",
	.config_init = rtl8211f_config_init,
}

如果这里没有,或者 ID 错了,就很容易退回 Generic PHY。


2. rtl8211f_config_init()

这是 PHY 的“板级初始化大脑”。

这里通常做:

  • 根据 phy-mode 决定延时
  • 配厂商扩展寄存器
  • 关/开 EEE
  • 配 ALDPS
  • 配 clkout

这部分代码写对了,日志通常会从“Generic PHY + 100M”进化到“能正确识别 RTL8211F 并开始稳定协商”。


3. GMAC 的设备树

这个部分决定:

  • 复位
  • 时钟
  • 引脚
  • 延时
  • PHY 连接

如果这个节点错了,PHY 驱动未必报错,但链路质量很可能不正常。


4. 网络管理器配置

这部分虽然不是驱动源码,但在板上经常会误导判断。

比如:

  • 驱动已经好了
  • 但 DHCP 没回来
  • 系统给了 169.254.x.x

这时如果不了解网络层,就会误以为“驱动还有问题”。

其实 169.254.x.x 往往只是:

  • DHCP 失败后的 fallback
  • 或者 dhcpcd / networkd 两个管理器抢网卡

所以驱动调通后,还要把 rootfs 侧网络服务收敛成一套。


九、如果“从零开始写驱动”,到底应该怎么干

这个问题必须讲现实一点。

真正从零写一套网卡驱动,代价非常大

因为你要自己处理:

  • DMA ring
  • cache coherent
  • NAPI
  • GRO
  • checksum offload
  • TSO
  • ethtool
  • phylib
  • PM
  • error recovery

这对大多数项目并不划算。

工业界真正的做法是:

复用成熟 MAC 核心,只写 glue 和板级配置。

对你这个平台,正确策略是:

  1. 复用 stmmac
  2. realtek.c 做 RTL8211F PHY
  3. 写好 SoC glue / DTS
  4. 补必要的厂商特性
  5. 调板级时序

也就是说,真正“从零”的部分,其实主要是:

  • 设备树
  • SoC glue
  • 少量 PHY 特化代码
  • 调试脚本与日志

而不是自己重写一个完整以太网子系统。


十、一个合理的开发顺序

如果我要从零把一块新板的 Ethernet 带起来,我会按下面顺序做。

第一步:确认硬件连接

先看原理图:

  • MAC 接哪个 PHY
  • MDIO 地址多少
  • reset pin 在哪
  • 参考时钟怎么来
  • RGMII 还是 RMII
  • LED/ALDPS/clkout strap 怎么接
  • 磁性器件怎么接
  • RJ45 是否带灯
  • 千兆四对线是否完整

不做这一步,后面都是猜。


第二步:写 DTS

先把最基本的节点写出来:

  • gmac0
  • mdio0
  • phy@1
  • pinctrl

先让系统能识别出 eth0 和 PHY。


第三步:确认 PHY 驱动是否正确匹配

dmesg

  • RTL8211F Gigabit Ethernet
  • 还是 Generic PHY

如果还是 Generic PHY,先别碰 DHCP,先把 PHY 驱动绑定搞定。


第四步:确认 Link Up / Speed / Duplex

ethtool eth0

  • 是否 Link detected
  • Speed 是多少
  • 对端 advertised 什么
  • 本端 advertised 什么

这一层解决的是“物理链路是否真的正常”。


第五步:确认 TX/RX 是否都正常

看统计:

  • RX packets
  • TX packets
  • errors
  • crc
  • frame

如果 TX 有、RX 没有,就别去想 IP,先回头查物理层。


第六步:最后才看 DHCP / IP

只有前面都正常了,才看:

  • dhcpcd
  • systemd-networkd
  • 静态 IP
  • 路由
  • DNS

这个顺序很重要。
很多人恰恰反过来,一开始就盯 DHCP,结果越调越乱。


十一、为什么网卡驱动调试经常会卡在“100M / 10M / 169.254”这种现象里

因为这几个现象非常有迷惑性。

1. 只能 100M

不代表驱动没加载。
常见原因是:

  • 千兆四对线没全通
  • RGMII delay 不对
  • 磁性器件或 RJ45 接线有问题
  • PHY 特殊配置没生效

2. 能 Link Up,但拿不到 DHCP

不代表 DHCP 服务一定坏了。
也可能是:

  • TX 正常但 RX 不正常
  • 对端回包收不到
  • 实际链路质量差,广播包丢失

3. 是 169.254.x.x

这通常不是“系统给错了 IP”,而是:

  • DHCP 没成功
  • 自动退回到 IPv4 link-local

这时你如果把注意力全放在 IP 上,就会错过真正的物理链路问题。


十二、最后做一个全局总结

如果只用一句话总结 Linux 以太网驱动,那就是:

网卡驱动不是一个驱动文件,而是一条从设备树、MAC、DMA、NAPI、MDIO、PHY、RGMII、RJ45 一直到网络服务的完整链路。

对当前这套 RK3576 + RTL8211F 而言,真正关键的点有四个:

第一,MAC 驱动不是从零写

你复用的是 stmmac / dwmac。
所以核心收发逻辑、DMA、NAPI,已经有成熟框架,不需要从零发明。

第二,PHY 驱动决定链路层行为

realtek.c 里的 RTL8211F 匹配和 rtl8211f_config_init(),决定了 PHY 是否被正确识别,以及 RGMII delay、EEE、clkout、低功耗等行为。

第三,设备树决定板级事实

设备树不是配角,它是驱动能否正确工作的“前提条件”。
phy-moderegreset-gpiotx_delayrx_delayphy-handle,每一项都可能直接决定链路是否稳定。

第四,网络管理器只是最后一层

eth0 出现了、Link Up 了,不代表 IP 就一定对。
DHCP、169.254、networkd、dhcpcd 这些属于更上层,只能在驱动和链路先正常的前提下再处理。


结尾:如果让我们从零做一版“最小可工作”的网卡支持,最少要有这些东西

最后把最小集合列出来,作为一份真正可落地的清单。

代码层最少要有

1. MAC 驱动

  • probe
  • open / stop
  • start_xmit
  • interrupt + NAPI
  • DMA 描述符管理
  • net_device_ops

2. PHY 驱动

  • PHY ID match
  • config_init
  • read_status
  • autoneg 支持

3. 设备树

  • gmac node
  • mdio node
  • phy node
  • pinctrl
  • reset / clock / delay

4. rootfs 网络配置

  • 选一个网络管理器
  • DHCP 或静态 IP
  • 不要多个服务同时抢接口


📺 B站 嵌入式孙老师博主个人介绍

📘 博主书籍-京东购买链接*:Yocto项目实战教程

📘 加博主微信,进技术交流群jerrydev


Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐