이더넷 PHY 드라이버 개발
이 문서는 리눅스 커널에서 이더넷 PHY 드라이버를 작성하고 보드를 bring-up하는 과정을 다룹니다. PHY 드라이버 골격 코드와 주요 콜백, Realtek/Marvell/Microchip 같은 실제 벤더 드라이버 소스 분석, Device Tree 설정과 보드 설계 포인트, 신호 무결성, 대표 장애 패턴과 트러블슈팅, PHY 펌웨어 로딩, ethtool 통계 해석, NIC(MAC) 드라이버와 PHY 통합까지 실전 중심으로 정리합니다. PHY의 하드웨어 동작 원리와 커널 프레임워크(phylib/phylink)는 이더넷 PHY (Physical Layer) 문서를 참고하세요.
PHY 드라이버 작성 가이드
새로운 PHY 칩을 지원하는 커널 드라이버를 작성하려면 drivers/net/phy/ 디렉터리에 벤더별 드라이버를 추가해야 합니다. 대부분의 구리 PHY는 genphy_* 헬퍼 함수만으로도 기본 동작이 가능하지만, RGMII 지연 설정, LED 제어, 온도 센서, downshift, EEE 정책 같은 벤더 고유 기능은 전용 콜백을 구현해야 합니다.
기본 골격 코드
PHY 드라이버의 최소 구조는 phy_driver 구조체 정의, module_phy_driver() 매크로, PHY ID 매칭 테이블로 구성됩니다. config_init()에서 하드웨어 초기 설정을 하고, read_status()에서 링크 상태를 해석하며, config_aneg()에서 광고 능력을 설정합니다.
#include <linux/module.h>
#include <linux/phy.h>
#include <linux/ethtool.h>
#define MYPHY_PHY_ID 0x001cc916
#define MYPHY_PHY_ID_MASK 0xfffffff0
/* 벤더 확장 레지스터 오프셋 */
#define MYPHY_REG_PAGE_SELECT 0x1f
#define MYPHY_REG_RGMII_DELAY 0x11
#define MYPHY_REG_LED_CTRL 0x18
#define MYPHY_REG_DOWNSHIFT 0x14
#define MYPHY_REG_TEMP 0x1a
/**
* config_init - PHY 하드웨어 초기 설정
*
* probe 직후와 resume 시 호출됩니다.
* RGMII 지연, LED 모드, downshift 정책 등 벤더 고유 설정을 적용합니다.
*/
static int myphy_config_init(struct phy_device *phydev)
{
int ret;
/* RGMII 지연 설정 (phy-mode에 따라) */
if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) {
ret = phy_modify_paged(phydev, 0xd08, MYPHY_REG_RGMII_DELAY,
0x0300, 0x0300);
if (ret < 0)
return ret;
}
/* LED0: 링크 상태, LED1: 활동 표시 */
ret = phy_write(phydev, MYPHY_REG_LED_CTRL, 0x61);
if (ret < 0)
return ret;
/* downshift: 3회 실패 후 한 단계 낮은 속도로 폴백 */
ret = phy_modify(phydev, MYPHY_REG_DOWNSHIFT, 0x001f, 0x0013);
if (ret < 0)
return ret;
return genphy_config_init(phydev);
}
/**
* read_status - 링크 상태 해석
*
* PHY 상태 머신이 주기적으로 호출합니다.
* 공통 genphy_read_status() 호출 후 벤더 고유 정보를 추가합니다.
*/
static int myphy_read_status(struct phy_device *phydev)
{
int ret;
ret = genphy_read_status(phydev);
if (ret)
return ret;
/* 벤더 레지스터에서 master/slave 결과 확인 (필요 시) */
/* 벤더 레지스터에서 downshift 발생 여부 확인 (필요 시) */
return 0;
}
/**
* config_aneg - 자동 협상 설정
*
* ethtool이 광고 능력을 변경하거나, 드라이버가 autoneg을 재시작할 때 호출됩니다.
*/
static int myphy_config_aneg(struct phy_device *phydev)
{
/* 벤더 고유 AN 설정이 필요하면 여기서 처리 */
return genphy_config_aneg(phydev);
}
/**
* handle_interrupt - PHY 인터럽트 처리
*
* PHY가 IRQ 라인을 지원하는 경우, 링크 변화를 인터럽트로 감지합니다.
*/
static irqreturn_t myphy_handle_interrupt(struct phy_device *phydev)
{
int irq_status;
irq_status = phy_read(phydev, 0x13); /* IRQ status, read clears */
if (irq_status < 0 || !(irq_status & 0x0001))
return IRQ_NONE;
phy_trigger_machine(phydev);
return IRQ_HANDLED;
}
/* PHY 드라이버 구조체 */
static struct phy_driver myphy_drivers[] = {
{
PHY_ID_MATCH_EXACT(MYPHY_PHY_ID),
.name = "My Company Example PHY",
.config_init = myphy_config_init,
.config_aneg = myphy_config_aneg,
.read_status = myphy_read_status,
.handle_interrupt = myphy_handle_interrupt,
.suspend = genphy_suspend,
.resume = myphy_config_init, /* resume = config_init 재적용 */
},
};
module_phy_driver(myphy_drivers);
static const struct mdio_device_id __maybe_unused myphy_tbl[] = {
{ MYPHY_PHY_ID, MYPHY_PHY_ID_MASK },
{ }
};
MODULE_DEVICE_TABLE(mdio, myphy_tbl);
MODULE_DESCRIPTION("My Company Example PHY driver");
MODULE_AUTHOR("Author Name");
MODULE_LICENSE("GPL");
주요 콜백 상세 설명
| 콜백 | 호출 시점 | 기본 구현 | 커스텀 필요 상황 | 주의점 |
|---|---|---|---|---|
probe | PHY 장치 발견 시 | phylib 코어 | 특수 초기화 (firmware 로드) | 슬립(Sleep) 가능 컨텍스트 |
config_init | attach 직후, resume 시 | genphy_config_init | strap override, delay bit, LED 모드 | resume에서 재호출 보장 필요 |
config_aneg | 광고 능력 변경 시 | genphy_config_aneg | 벤더 고유 AN 확장 (EEE, FEC) | restart AN 포함 여부 주의 |
read_status | 상태 머신 주기 또는 IRQ 후 | genphy_read_status | 벤더 레지스터에서 추가 상태 해석 | phydev 필드에 결과 반영 |
handle_interrupt | IRQ 발생 시 | 없음 | IRQ 지원 PHY | IRQ status read-to-clear 순서 |
suspend/resume | 시스템 PM | genphy_suspend/resume | WoL, EDPD, strap 재적용 | analog rail 상태에 따라 분기 |
get_features | PHY 능력 감지 | genphy_read_abilities | 표준 레지스터에 없는 능력 추가 | linkmode 비트맵(Bitmap) 정확히 설정 |
set_tunable | ethtool --set-phy-tunable | 없음 | downshift, EDPD 정책 | 범위 검증 필수 |
Kconfig와 Makefile 추가
# drivers/net/phy/Kconfig에 추가
config MYPHY_PHY
tristate "My Company PHY driver"
help
Support for My Company Example PHY.
This driver provides support for the MYPHY Gigabit Ethernet PHY.
# drivers/net/phy/Makefile에 추가
obj-$(CONFIG_MYPHY_PHY) += myphy.o
실제 벤더 PHY 드라이버 소스 분석
커널 트리에는 수십 종의 벤더 PHY 드라이버가 포함되어 있으며, 각각 벤더 고유의 페이지 전환 방식, 레지스터 배치, LED 제어 방식을 사용합니다. 이 절에서는 임베디드 보드와 서버 환경에서 가장 자주 사용되는 세 드라이버를 실제 커널 소스 기준으로 분석합니다. 코드 흐름을 따라가면 이전 절의 콜백 구조가 실제로 어떻게 구현되는지 이해할 수 있습니다.
Realtek RTL8211F 드라이버 분석
RTL8211F는 10/100/1000BASE-T 기가비트 PHY로, 라즈베리파이(Raspberry Pi), 로크칩(Rockchip) 기반 SBC, NXP i.MX 보드 등 수많은 임베디드 플랫폼에 탑재되어 있습니다. 소스 파일은 drivers/net/phy/realtek.c이며, 동일 파일에서 RTL8211B, RTL8211E, RTL8211FD, RTL822x 계열까지 함께 관리합니다.
PHY ID와 매칭
RTL8211F의 PHY ID는 0x001cc916이며, 하위 4비트는 하드웨어 리비전(Revision)에 따라 달라지므로 마스크(Mask)를 0xfffffff0으로 설정하여 리비전과 무관하게 매칭합니다. 커널 4.17 이후에는 PHY_ID_MATCH_MODEL() 매크로가 이 마스크 패턴을 표준화합니다.
config_init: RGMII 지연과 LED 설정
RTL8211F의 핵심 초기화는 페이지 0xd08(확장 레지스터 페이지)의 레지스터 0x11에서 RGMII TX/RX 지연 비트를 제어하는 것입니다. 레지스터 0x1f에 페이지 번호를 쓰면 현재 페이지가 전환되며, 작업 후 반드시 페이지 0으로 복원해야 합니다. 커널은 이 패턴을 phy_modify_paged() 헬퍼로 추상화하여 원자적으로 처리합니다.
LED 설정은 페이지 0xd04의 레지스터 0x10에서 수행하며, LED0(링크/속도 표시)와 LED1(활동 표시)의 점등 조건을 비트필드로 지정합니다. 일부 보드는 LED 극성이 반전되어 있어 active-low Device Tree 속성을 사용해야 합니다. RTL8211F-CG 이후 변종은 클럭 출력(CLK_OUT) 핀을 소프트웨어로 끌 수 있어, 불필요한 EMI 방출을 줄이기 위해 config_init에서 기본 비활성화합니다.
/* drivers/net/phy/realtek.c (커널 6.x 기준, 개념 요약) */
#define RTL8211F_PHY_ID 0x001cc916
#define RTL8211F_PAGE_SELECT 0x1f
/* 확장 레지스터 페이지 번호 */
#define RTL8211F_PAGE_RGMII_DELAY 0xd08
#define RTL8211F_REG_RGMII_DELAY 0x11
#define RTL8211F_TX_DELAY_BIT BIT(8)
#define RTL8211F_RX_DELAY_BIT BIT(3)
#define RTL8211F_PAGE_LED 0xd04
#define RTL8211F_REG_LED_CTRL 0x10
#define RTL8211F_LED0_LINK_1000 BIT(3)
#define RTL8211F_LED0_LINK_100 BIT(1)
#define RTL8211F_LED1_ACTIVE BIT(8)
/* 페이지 전환 헬퍼 (phy_modify_paged 내부 동작 요약) */
static int rtl8211f_page_write(struct phy_device *phydev, int page)
{
return phy_write(phydev, RTL8211F_PAGE_SELECT, page);
}
static int rtl8211f_config_init(struct phy_device *phydev)
{
int ret;
u16 delay_mask = 0, delay_val = 0;
/* phy-mode에 따라 TX/RX 지연 비트 결정 */
switch (phydev->interface) {
case PHY_INTERFACE_MODE_RGMII_ID:
delay_mask = RTL8211F_TX_DELAY_BIT | RTL8211F_RX_DELAY_BIT;
delay_val = RTL8211F_TX_DELAY_BIT | RTL8211F_RX_DELAY_BIT;
break;
case PHY_INTERFACE_MODE_RGMII_TXID:
delay_mask = RTL8211F_TX_DELAY_BIT;
delay_val = RTL8211F_TX_DELAY_BIT;
break;
case PHY_INTERFACE_MODE_RGMII_RXID:
delay_mask = RTL8211F_RX_DELAY_BIT;
delay_val = RTL8211F_RX_DELAY_BIT;
break;
default:
delay_mask = RTL8211F_TX_DELAY_BIT | RTL8211F_RX_DELAY_BIT;
delay_val = 0; /* 지연 없음 */
break;
}
/* phy_modify_paged: 페이지 전환 → 수정 → 페이지 0 복원을 원자적으로 수행 */
ret = phy_modify_paged(phydev, RTL8211F_PAGE_RGMII_DELAY,
RTL8211F_REG_RGMII_DELAY,
delay_mask, delay_val);
if (ret < 0)
return ret;
/* LED0 = 1G/100M 링크, LED1 = TX/RX 활동 */
ret = phy_modify_paged(phydev, RTL8211F_PAGE_LED,
RTL8211F_REG_LED_CTRL,
0x03ff,
RTL8211F_LED0_LINK_1000 | RTL8211F_LED0_LINK_100 |
RTL8211F_LED1_ACTIVE);
if (ret < 0)
return ret;
return genphy_config_init(phydev);
}
/* read_status는 genphy_read_status를 그대로 사용 */
static struct phy_driver rtl8211f_driver = {
PHY_ID_MATCH_MODEL(RTL8211F_PHY_ID),
.name = "RTL8211F Gigabit Ethernet",
.config_init = rtl8211f_config_init,
.read_status = genphy_read_status,
.suspend = genphy_suspend,
.resume = rtl8211f_config_init,
};
read_status와 페이지 전환 메커니즘
RTL8211F는 표준 레지스터(MII_BMSR, MII_LPA 등)로 링크 상태를 충분히 읽을 수 있어 read_status는 genphy_read_status를 그대로 사용합니다. 페이지 전환은 레지스터 0x1f에 원하는 페이지 번호를 기록하는 방식이며, 커널 phy_select_page() / phy_restore_page() API가 이를 표준화합니다. RTL8211F 드라이버는 .select_page 콜백을 구현하여 이 API와 연동합니다.
Marvell Alaska 88E1510 드라이버 분석
88E1510은 구리(Copper)/광(Fiber) 이중 미디어를 지원하는 기가비트 PHY로, 서버 NIC, 게이트웨이, 산업용 이더넷 장비에 광범위하게 사용됩니다. 소스 파일은 drivers/net/phy/marvell.c이며, 마블(Marvell) 드라이버는 커널 PHY 드라이버 중 가장 복잡한 구현 중 하나로 꼽힙니다. 페이지 전환에 레지스터 22(0x16)를 사용하는 점이 Realtek과 다릅니다.
PHY ID와 매칭
88E1510의 PHY ID는 0x01410dd0이며, 88E1518, 88E1540, 88E1543 같은 패밀리 멤버들은 PHY ID 하위 비트만 다르므로 드라이버는 마스크 0xfffffff0을 사용합니다. 광 전용 모드인 88E1112도 별도 엔트리로 등록되어 있으나 코어 초기화 함수를 공유합니다.
config_init: Fiber/Copper 감지와 RGMII 지연
88E1510의 config_init는 먼저 페이지 0으로 복귀한 뒤, 페이지 18(MISC 제어 페이지)에서 미디어 타입을 확인합니다. Fiber 모드로 감지되면 1000BASE-X 협상을 활성화하고, Copper 모드이면 표준 자동 협상 흐름을 유지합니다. RGMII 지연은 페이지 2(MAC 제어 페이지)의 레지스터 21(0x15)에서 설정하며, RX와 TX 지연이 독립 비트로 분리되어 있습니다. downshift는 페이지 0의 레지스터 16(0x10)에서 재시도 횟수를 설정합니다.
/* drivers/net/phy/marvell.c (커널 6.x 기준, 개념 요약) */
#define MII_88E1510_PHY_ID 0x01410dd0
#define MII_MARVELL_PHY_PAGE 22 /* 0x16 — Marvell 표준 페이지 선택 레지스터 */
/* 주요 페이지 번호 */
#define MII_MARVELL_COPPER_PAGE 0
#define MII_MARVELL_FIBER_PAGE 1
#define MII_MARVELL_MAC_PAGE 2
#define MII_MARVELL_MISC_PAGE 18
/* 페이지 2, 레지스터 21: RGMII 지연 설정 */
#define MII_88E1121_PHY_MAC_CTRL 21
#define MII_88E1121_PHY_MAC_RGMII_TX_DELAY BIT(4)
#define MII_88E1121_PHY_MAC_RGMII_RX_DELAY BIT(5)
/* 페이지 0, 레지스터 16: Copper 기능 제어 (downshift 등) */
#define MII_88E1011_PHY_SCR 16
#define MII_88E1011_PHY_SCR_DOWNSHIFT_EN BIT(11)
static int m88e1510_config_init(struct phy_device *phydev)
{
int err;
/* 항상 Copper 페이지(0)에서 시작 */
err = marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);
if (err < 0)
return err;
/* RGMII TX/RX 지연: MAC 페이지(2) 레지스터 21에서 설정 */
err = marvell_config_rgmii_delays(phydev);
if (err < 0)
return err;
/* LED 제어: 페이지 3, 레지스터 16 — 링크/활동 극성 */
err = marvell_config_led(phydev);
if (err < 0)
return err;
/* downshift: 4회 시도 후 폴백, 페이지 0 레지스터 16 */
err = phy_modify_paged(phydev, MII_MARVELL_COPPER_PAGE,
MII_88E1011_PHY_SCR,
0x7800,
MII_88E1011_PHY_SCR_DOWNSHIFT_EN | (4 - 1) << 12);
if (err < 0)
return err;
return marvell_config_init(phydev);
}
/**
* config_aneg: 다중 페이지 설정 (Copper + Fiber 각각)
* Fiber 페이지(1)에서 1000BASE-X AN 광고, Copper 페이지(0)에서 일반 AN 광고
*/
static int m88e1510_config_aneg(struct phy_device *phydev)
{
int err;
if (phydev->interface == PHY_INTERFACE_MODE_SGMII) {
/* SGMII: 페이지 1에서 Fiber AN 설정 */
err = marvell_set_page(phydev, MII_MARVELL_FIBER_PAGE);
if (err < 0)
return err;
err = genphy_config_aneg(phydev);
if (err < 0)
return err;
}
/* Copper 페이지(0) 복귀 후 표준 AN */
err = marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);
if (err < 0)
return err;
return genphy_config_aneg(phydev);
}
/**
* read_status: Copper 또는 Fiber 상태를 인터페이스에 따라 분리 읽기
*/
static int m88e1510_read_status(struct phy_device *phydev)
{
int err;
if (phydev->interface == PHY_INTERFACE_MODE_SGMII)
err = marvell_read_status_page(phydev, MII_MARVELL_FIBER_PAGE);
else
err = marvell_read_status_page(phydev, MII_MARVELL_COPPER_PAGE);
return err;
}
/* 인터럽트: 페이지 0 레지스터 18(IEVENT) 읽기로 IRQ 원인 확인 후 클리어 */
static irqreturn_t marvell_handle_interrupt(struct phy_device *phydev)
{
int irq_status;
irq_status = phy_read(phydev, MII_M1011_IEVENT);
if (irq_status < 0 || irq_status == 0)
return IRQ_NONE;
phy_trigger_machine(phydev);
return IRQ_HANDLED;
}
인터럽트 처리와 WoL
88E1510은 전용 IRQ 핀을 지원하며, 레지스터 19(0x13, Interrupt Enable)에서 링크 변화·속도 변화·downshift 발생 등 다양한 이벤트에 대해 인터럽트를 개별 활성화할 수 있습니다. 레지스터 18(0x12, Interrupt Status)을 읽으면 IRQ가 클리어됩니다. WoL(Wake-on-LAN) 기능은 페이지 17(Sleep 제어 페이지)에서 설정하며, set_wol / get_wol 콜백을 통해 ethtool과 연동합니다.
Microchip LAN8720A / LAN8742A 드라이버 분석
LAN8720A와 LAN8742A는 10/100BASE-T 전용 소형 PHY로, STM32, NXP LPC, Cortex-M MCU 기반 시스템과 저전력 IoT 장치에 주로 탑재됩니다. 소스 파일은 drivers/net/phy/smsc.c이며, 드라이버 규모가 작고 구조가 단순하여 PHY 드라이버 학습의 출발점으로 적합합니다.
PHY ID와 매칭
LAN8720A의 PHY ID는 0x0007c0f0, LAN8742A는 0x0007c130입니다. 두 칩 모두 SMSC(현 Microchip) 제품군이며 기본 동작은 동일합니다. 마스크 0xfffffff0을 사용해 리비전을 무시하고 매칭합니다.
config_init: EDPD와 RMII 클럭
LAN8720A는 기가비트 기능이 없으므로 config_init이 매우 단순합니다. 주요 설정은 두 가지입니다. 첫째, EDPD(Energy Detect Power Down)는 케이블이 연결되지 않았을 때 PHY를 저전력 감지 모드로 전환하는 기능입니다. 레지스터 17(EDPD 제어)의 EDPD 비트를 설정하면 유휴 전류를 수 mA에서 수십 μA로 낮출 수 있습니다. 둘째, RMII 기준 클럭(50 MHz)을 PHY가 공급할지 외부에서 입력받을지 레지스터 16(특수 제어)의 XTAL_EN 비트로 지정합니다. MCU가 클럭 소스를 가지지 않는 경우 PHY의 클럭 출력 핀을 RMII REF_CLK로 연결하고 이 비트를 설정해야 합니다.
/* drivers/net/phy/smsc.c (커널 6.x 기준, 개념 요약) */
#define LAN8720A_PHY_ID 0x0007c0f0
#define LAN8742A_PHY_ID 0x0007c130
/* 레지스터 17: EDPD (Energy Detect Power Down) */
#define LAN8720_REG_EDPD_CTRL 17
#define LAN8720_EDPD_EN BIT(13)
#define LAN8720_EDPD_TX_NLP_EN BIT(12) /* NLP 전송으로 원격 wake 지원 */
/* 레지스터 16: 특수 모드 제어 */
#define LAN8720_REG_MODE_CTRL 16
#define LAN8720_MODE_XTAL_EN BIT(15) /* PHY가 50 MHz 클럭 출력 */
static int lan8720_config_init(struct phy_device *phydev)
{
int ret;
/* EDPD 활성화: 케이블 미연결 시 저전력 감지 모드 */
ret = phy_set_bits(phydev, LAN8720_REG_EDPD_CTRL,
LAN8720_EDPD_EN | LAN8720_EDPD_TX_NLP_EN);
if (ret < 0)
return ret;
/* RMII 기준 클럭: Device Tree 속성이 있으면 PHY에서 클럭 출력 */
if (of_property_read_bool(phydev->mdio.dev.of_node,
"microchip,rmii-refclk-output")) {
ret = phy_set_bits(phydev, LAN8720_REG_MODE_CTRL,
LAN8720_MODE_XTAL_EN);
if (ret < 0)
return ret;
}
/* 기가비트 기능 없음: 표준 genphy_config_init으로 충분 */
return genphy_config_init(phydev);
}
static struct phy_driver smsc_phy_drivers[] = {
{
PHY_ID_MATCH_MODEL(LAN8720A_PHY_ID),
.name = "SMSC LAN8720A",
.config_init = lan8720_config_init,
.read_status = genphy_read_status,
.suspend = genphy_suspend,
.resume = lan8720_config_init,
},
{
PHY_ID_MATCH_MODEL(LAN8742A_PHY_ID),
.name = "Microchip LAN8742A",
.config_init = lan8720_config_init,
.config_aneg = genphy_config_aneg,
.read_status = genphy_read_status,
.suspend = genphy_suspend,
.resume = lan8720_config_init,
},
};
LAN8742A와 Wake-on-LAN 고려사항
LAN8742A는 LAN8720A에 비해 EDPD 타이머 설정과 WoL Magic Packet 감지 기능이 추가되었습니다. 단, WoL은 MAC 측에서 필터를 설정해야 하며, PHY 레벨에서는 원격 당사자에게 NLP(Normal Link Pulse)를 보내 PHY가 아직 살아있음을 알리는 역할만 담당합니다. LAN8742A는 별도 페이지 전환 메커니즘 없이 단일 주소 공간(페이지 0만 사용)으로 동작하므로, phy_modify_paged() 호출이 필요 없습니다.
벤더 PHY 드라이버 기능 비교
| 기능 | Realtek RTL8211F | Marvell 88E1510 | Microchip LAN8720A/42A |
|---|---|---|---|
| 지원 속도 | 10/100/1000M | 10/100/1000M (Fiber 포함) | 10/100M 전용 |
| 인터페이스 모드 | RGMII, RGMII-ID, RGMII-TXID, RGMII-RXID | RGMII, SGMII, 1000BASE-X | RMII, MII |
| 페이지 전환 레지스터 | 레지스터 0x1f (31번) | 레지스터 22 (0x16) | 없음 (단일 주소 공간) |
| RGMII 지연 설정 | 페이지 0xd08, 레지스터 0x11 | 페이지 2, 레지스터 21 | 해당 없음 (RMII 전용) |
| LED 제어 | 페이지 0xd04, 레지스터 0x10 | 페이지 3, 레지스터 16 | 특수 제어 레지스터 (레지스터 17) |
| 온도 센서 | 일부 변종(RTL8211F-VD) 지원 | 88E1540/88E1543 이상 지원 | 미지원 |
| 케이블 진단 (TDR) | 지원 (VCT via 페이지 전환) | 지원 (ethtool cable-test) | 미지원 |
| 인터럽트(IRQ) 지원 | 지원 (레지스터 0x12) | 지원 (레지스터 18, 19) | 지원 (제한적, 레지스터 30) |
| EEE (802.3az) | 지원 | 지원 | 미지원 (10/100 전용) |
| EDPD | 지원 (벤더 레지스터) | 지원 (페이지 0 레지스터 16) | 지원 (레지스터 17, 핵심 기능) |
| Fiber/Copper 이중 미디어 | 미지원 | 지원 (88E1510 핵심 특성) | 미지원 |
| 소스 파일 | drivers/net/phy/realtek.c |
drivers/net/phy/marvell.c |
drivers/net/phy/smsc.c |
phy_select_page()와 phy_restore_page()는 벤더별 .select_page 콜백을 통해 이 차이를 추상화합니다.Device Tree와 보드 설계 포인트
보드 bring-up에서는 커널 코드보다 Device Tree와 하드웨어 배선이 더 자주 문제를 일으킵니다. PHY 주소가 틀리거나, reset GPIO hold 시간이 부족하거나, 25MHz reference clock이 안정되기 전에 MDIO 접근이 시작되면 드라이버는 정상이어도 PHY를 못 찾습니다. 특히 RGMII는 내부 지연과 PCB trace delay를 어느 한 곳에서만 넣어야 합니다.
/* 보드 .dts 예시: 외부 RGMII PHY 연결 */
&emac {
pinctrl-names = "default";
pinctrl-0 = <&ext_rgmii_pins>;
phy-mode = "rgmii-id";
phy-handle = <&ext_rgmii_phy>;
phy-supply = <®_gmac_3v3>;
status = "okay";
};
&mdio {
ext_rgmii_phy: ethernet-phy@1 {
compatible = "ethernet-phy-ieee802.3-c22";
reg = <1>;
reset-gpios = <&pio 3 14 GPIO_ACTIVE_LOW>;
reset-assert-us = <15000>;
reset-deassert-us = <40000>;
};
};
strap pin과 reset 시퀀싱을 따로 점검하기
많은 PHY는 reset 해제 순간 strap pin을 샘플링해 주소, RGMII delay 기본값, LED 모드, SGMII/Fiber 선택 같은 초기 동작을 결정합니다. 이때 LED 핀과 strap 핀이 겸용인 설계가 흔해서, pull-up/pull-down 세기와 reset 폭이 기대와 조금만 어긋나도 PHY는 전혀 다른 모드로 부팅할 수 있습니다. 소프트웨어가 나중에 override할 수 있어도, 부팅 초기에 이미 잘못된 주소와 모드로 올라오면 MDIO 탐지부터 실패할 수 있습니다.
| strap 항목 | 잘못 샘플링되면 | 흔한 단서 | 대응 |
|---|---|---|---|
| PHY 주소 | 다른 주소로 부팅 | 기대 주소에서 0xffff | pull 저항과 reset 타이밍 재검토 |
| RGMII delay 기본값 | 내부 지연 on/off 반전 | 1G CRC 폭증 | strap 상태와 runtime override 둘 다 확인 |
| fiber / copper 선택 | 잘못된 매체 모드 | 링크 LED 동작이 이상함 | strap 조합과 보드 옵션 확인 |
| LED 모드 | LED 핀 동작 혼선 | 링크는 되는데 LED만 오작동 | strap 겸용 핀 배치 재검토 |
| 항목 | 왜 중요한가 | 실패 시 증상 |
|---|---|---|
phy-handle / PHY 주소 | 올바른 PHY 노드와 연결 | probe 실패, 잘못된 PHY 드라이버 매칭 |
phy-mode | MAC-PHY 인터페이스 계약 일치 | 링크 불안정, CRC, 속도 강등 |
| reset 타이밍 | strap pin, clock 안정화 보장 | PHY ID가 0xffff로 읽힘 |
| 전원 / regulator | 아날로그 블록 안정성 확보 | 온도 민감, 링크 flapping |
| reference clock | PMA/CDR 기준 클럭 | autoneg 실패, symbol error |
보드 Bring-up 체크리스트
외부 PHY가 달린 보드를 처음 올릴 때는 "드라이버가 로드됐는가"보다 "PHY가 물리적으로 살아 있는가"부터 확인해야 합니다. bring-up 초반에는 MDIO 읽기가 성공하는지만으로도 절반은 해결된 상태입니다. 이후에는 reset 타이밍, strap pin, 지연 보상, 상대편 링크 정책, 실제 트래픽 품질 순서로 범위를 좁히는 것이 좋습니다.
| 단계 | 무엇을 확인하나 | 성공 기준 | 실패 시 다음 행동 |
|---|---|---|---|
| 1. 전원 | PHY rail, analog rail, module power | 전압 안정, brown-out 없음 | regulator 순서와 power-good 재검토 |
| 2. 기준 클럭 | 25MHz/50MHz/refclk 존재 | 오실로스코프에서 안정적 파형 | clock source, pinmux 확인 |
| 3. reset/strap | reset 폭, strap 샘플링 타이밍 | 예상 PHY 주소와 모드로 부팅 | reset-assert/deassert 시간 조정 |
| 4. MDIO 스캔 | PHY ID 읽기, 버스 접근 | 정상 OUI/모델 읽힘 | PHY 주소, MDIO pull-up, bus mux 확인 |
| 5. 링크 정책 | phy-mode, advertisement, fixed-link | 예상 속도와 duplex 협상 | 양단 설정 비교 |
| 6. 트래픽 | CRC, drops, EEE/LPI | 오류 카운터 안정 | RGMII 지연, 케이블, EEE 검토 |
| 7. 스트레스 | 열, idle/wake, 장시간 안정성 | link flap 없음 | 전원 margin, 온도, 모듈 교체 테스트 |
외부 PHY가 없는 경우: fixed-link
스위치 CPU 포트나 백플레인처럼 외부 PHY가 없이 PCS/SerDes만 존재하는 경우에는 phy-handle 대신 fixed-link를 사용하기도 합니다. 이 경우 MDIO로 읽을 PHY가 없으므로, 링크 정책과 상태 전달을 MAC/phylink 계층에서 처리해야 합니다.
/* 외부 PHY 없이 고정 링크를 선언하는 예시 */
ð0 {
phy-mode = "sgmii";
fixed-link {
speed = <1000>;
full-duplex;
pause;
};
};
신호 무결성과 보드 레이아웃
PHY 문서가 소프트웨어 쪽으로만 흘러가기 쉽지만, 실제로는 보드 레이아웃이 링크 품질을 절반 이상 좌우합니다. 특히 RGMII 병렬 버스는 각 신호의 도착 시간 차이가 중요하고, SerDes는 임피던스와 return path, via stub, refclk 품질이 더 중요합니다. 같은 드라이버가 어떤 보드에서는 완벽하고 다른 보드에서는 CRC를 뿜는 이유가 여기에 있습니다.
중요한 점은 "숫자 하나"를 외우는 것이 아니라, PHY 데이터시트와 PCB stack-up, 커넥터 배치, 전원 분리 전략을 함께 보는 것입니다. RGMII는 지연 위치가 핵심이고, SGMII/USXGMII는 차동쌍 품질과 refclk 지터, 광 모듈 경로는 cage 접지와 전력 예산이 중요합니다.
| 항목 | 왜 중요한가 | 나빠질 때 보이는 현상 | 실무 점검 포인트 |
|---|---|---|---|
| RGMII 길이 매칭 | 125MHz DDR 타이밍 여유 확보 | 1G에서만 CRC, 100M은 정상 | 지연을 PCB와 PHY 중 한 곳만 부여 |
| refclk 품질 | PMA/CDR 기준 안정성 | 협상 불안정, symbol error | 클럭 소스, 전원 노이즈, jitter budget |
| SerDes 차동쌍 | 임피던스와 손실 관리 | block lock 불안정, BER 증가 | 차동 간격, via 수, return path 연속성 |
| magnetics 배치 | 구리 포트 절연과 잡음 억제 | 거리 민감, ESD 이후 링크 저하 | RJ45 근접 배치, center tap 전원/접지 검토 |
| 전원 decoupling | 아날로그 프런트엔드 안정화 | 온도 의존 링크 flap | analog/digital rail 분리와 근접 캐패시터 |
| SFP cage 접지 | EMI와 모듈 안정성 | 모듈별 편차, hot-plug 불안정 | cage 접지, 전력 예산, sideband pull-up |
대표 장애 패턴과 원인 분리
PHY 문제는 반복적으로 비슷한 모양으로 나타납니다. 아래 패턴을 외워 두면 "드라이버 버그인지, 보드 타이밍인지, 상대편 정책인지"를 빨리 구분할 수 있습니다.
패턴 1: 100M은 되는데 1G에서만 CRC가 폭증
가장 흔한 원인은 RGMII 지연 위치 오류입니다. 100M에서는 타이밍 margin이 커서 버티지만, 1G에서는 125MHz DDR 경계가 매우 빡빡해져 CRC와 carrier error가 늘어납니다. 이 경우 케이블을 바꿔도 개선이 거의 없고, phy-mode를 바꾸거나 PHY 내부 delay bit를 조정하면 즉시 달라집니다.
패턴 2: 수 분 간격으로 링크가 내려갔습니다 올라옵니다
EEE, 전원 품질, 열, 상대편 스위치 호환성 중 하나일 가능성이 큽니다. idle 상태에서만 재현되면 EEE를 먼저 끄고, 부하를 걸면 사라지면 전원 rail 또는 module thermal 문제일 가능성이 있습니다. dmesg에 일정 간격으로 link down/up이 반복되면 소프트웨어보다는 물리 계층이나 협상 정책 쪽을 더 의심하는 편이 맞습니다.
패턴 3: PHY ID가 0xffff 또는 0x0000으로 읽힌다
이 경우는 대부분 MDIO 자체가 실패하거나 PHY가 reset에서 아직 나오지 않았다는 뜻입니다. 잘못된 PHY 주소, MDC/MDIO pull-up 불량, reset deassert 지연 부족, refclk 부재를 우선 확인하세요. 드라이버를 아무리 바꿔도 PHY ID가 안 읽히면 소프트웨어 문제일 확률은 낮습니다.
패턴 4: SGMII와 1000BASE-X를 바꾸면 링크 LED만 반응하고 데이터는 안 흐릅니다
전기적 속도만 같다고 프로토콜 의미까지 같은 것은 아닙니다. SGMII는 speed code와 duplex 상태를 control word에 담고, 1000BASE-X는 Clause 37 의미를 따릅니다. 양단의 PCS 의미 체계가 다르면 block lock은 되더라도 유효 프레임 처리가 안 될 수 있습니다.
패턴 5: RJ45 SFP 모듈만 유난히 뜨겁고 링크 품질이 나쁘다
RJ45 SFP는 작은 폼팩터 안에 copper PHY와 magnetics 상당 부분을 넣기 때문에 열 여유가 매우 작습니다. 일부 스위치나 NIC는 전력 예산 때문에 copper SFP를 제한하기도 합니다. 광 모듈에서는 멀쩡한 포트가 copper SFP만 꽂으면 불안정해진다면 모듈 발열과 host cage 전원 budget을 의심하세요.
패턴 6: 부팅 직후는 정상인데 suspend/resume 뒤 링크가 나빠진다
resume 경로에서 PHY strap override, RGMII delay bit, host PCS mode, 모듈 sideband 초기화가 다시 적용되지 않는 경우가 많습니다. 특히 부팅 때만 실행한 config_init에 핵심 설정을 몰아 넣으면, resume 뒤에는 기본 strap 값으로 돌아가 CRC나 속도 강등이 다시 나타날 수 있습니다.
패턴 7: 같은 보드인데 슬롯이나 포트 하나만 간헐적으로 안 보입니다
이 경우는 PHY 드라이버보다 MDIO mux, shared reset, 주소 재사용 구조를 먼저 봐야 합니다. 특정 슬롯에서만 PHY ID가 0xffff로 튄다면 해당 슬롯의 mux 선택선, pull-up, hot-plug reset 타이밍이 가장 유력합니다. 동일한 드라이버가 다른 슬롯에서는 멀쩡하다는 사실이 중요한 단서입니다.
패턴 8: fixed-link로는 되는데 in-band status로 바꾸면 링크가 흐트러진다
보통은 host PCS가 speed code와 duplex 상태를 제대로 읽지 못하거나, 반대로 양단이 AN 의미를 서로 다르게 해석하고 있는 뜻입니다. fixed-link는 문제가 가려진 상태일 수 있으므로, 실제 목표 토폴로지가 in-band status라면 PCS와 SerDes 설정을 끝까지 맞춰야 합니다.
managed = "in-band-status"는 커널 내부에서 MLO_AN_INBAND 모드를 활성화하며, 이 모드에서는 PCS 드라이버의 pcs_get_state()가 실제로 SGMII control word 또는 Clause 37 base page를 해석해야 합니다.
PCS 드라이버가 이 콜백을 제대로 구현하지 않으면 fixed-link에서는 문제없던 포트가 in-band status로 전환하자마자 실패합니다.
세부 동작 원리는 In-Band vs Out-of-Band 링크 상태 결정과 phylink의 링크 상태 결정 경로 절을 참고하세요.
패턴 9: 광량은 정상인데 LOS가 계속 올라와 링크가 안 오른다
SFP 계열에서는 PHY 레지스터보다 cage sideband가 더 중요할 때가 있습니다. LOS, TX_FAULT, MOD_ABS pull-up 전압이 맞지 않거나 극성이 뒤집히면 광 모듈 자체는 정상이어도 host는 계속 fault 상태로 판단합니다. 이 경우 ethtool -m으로 DOM이 정상인데 링크만 안 오른다는 모순된 모습이 나옵니다.
트러블슈팅
PHY 장애는 "링크가 안 오른다", "링크는 오르는데 품질이 나쁘다", "부팅 직후만 실패합니다" 세 부류로 나누면 정리가 쉽습니다. 첫 번째는 Device Tree, reset, PHY 주소, 케이블, 상대편 광고 능력부터 확인합니다. 두 번째는 phy-mode, RGMII 지연, EEE, FEC, 온도, 모듈 품질로 좁힙니다. 세 번째는 부팅 순서와 전원 안정화, strap pin, clock ready 타이밍 문제일 가능성이 큽니다.
| 증상 | 가능한 원인 | 즉시 확인할 것 | 다음 조치 |
|---|---|---|---|
| PHY를 전혀 찾지 못함 | 잘못된 PHY 주소, reset 타이밍 부족, 전원 미인가 | dmesg, /sys/bus/mdio_bus/devices | PHY 주소와 reset GPIO, regulator 순서 점검 |
| 링크 업 안 됨 | 상대편 포트 정책, 케이블, advertisement 불일치 | ethtool eth0 | 양쪽 autoneg 정책과 케이블 등급 확인 |
| 링크 업 후 CRC 증가 | RGMII skew, 신호 품질, magnetics 문제 | ethtool -S eth0 | phy-mode와 보드 지연 위치 재검토 |
| 2.5G/5G가 1G로만 붙음 | 케이블 품질, 상대편 광고 제한 | supported/advertised link modes | Cat5e/Cat6 등급과 상대편 설정 확인 |
| 온도 올라가면 링크 flapping | 전원 품질, 모듈 열화, 아날로그 margin 부족 | 온도 로그, 모듈 DOM | 전원 rail, 방열, PHY 벤더 권장값 적용 |
# 1. 기본 링크 요약
ethtool eth0
# 2. NIC/드라이버 통계에서 물리 계층 오류 확인
ethtool -S eth0 | grep -E "crc|align|carrier|symbol|an|eee"
# 3. 현재 netdev가 어떤 PHY에 연결되어 있는지 추적
readlink -f /sys/class/net/eth0/phydev
# 4. 커널 로그에서 MDIO / PHY 이벤트 추적
dmesg | grep -i -E "phy|mdio|link|eee"
# 5. Device Tree의 phy-mode 확인
grep -R . /sys/firmware/devicetree/base | grep phy-mode
# 6. PHY 튜너블과 EDPD/downshift 확인
ethtool --show-phy-tunable eth0
# 7. 지원 시 케이블 TDR 진단
ethtool --cable-test-tdr eth0
# 8. SFP DOM / 광량 확인
ethtool -m eth0
커널 디버그 훅 활용
PHY 문제를 깊게 보려면 ethtool 출력만으로는 부족할 때가 있습니다. 이때는 동적 디버그, 드라이버별 register dump, 모듈 DOM, 장시간 링크 이벤트 로그를 조합해 "협상 실패인지, 품질 문제인지, 전원 복귀 문제인지"를 분리합니다. 특히 phylib는 공통 상태 머신을 쓰므로, 공통 로그와 벤더 드라이버 로그를 같이 보는 것이 좋습니다.
# 동적 디버그가 켜진 커널에서 PHY 공통 로그 활성화
echo 'file drivers/net/phy/* +p' > /sys/kernel/debug/dynamic_debug/control
# 특정 MAC 드라이버 로그도 함께 활성화
echo 'file drivers/net/ethernet/*/* +p' > /sys/kernel/debug/dynamic_debug/control
# 드라이버가 지원하면 MAC/PHY 레지스터 덤프
ethtool -d eth0
# 인터페이스 링크 이벤트 감시
ip monitor link
| 도구 | 무엇을 보기 좋나 | 언제 쓰나 |
|---|---|---|
ethtool | 링크 정책, 통계, EEE, FEC, 케이블 테스트 | 가장 먼저 |
dmesg | probe, reset, link flap, SFP 이벤트 | 부팅 직후 실패 |
| dynamic debug | phylib 상태 머신과 벤더 드라이버 상세 로그 | 간헐 재현, resume 이슈 |
ethtool -m | 광 모듈 DOM, 온도, RX/TX power | 광 링크 품질 문제 |
ethtool -d | 드라이버 제공 레지스터 덤프(Dump) | 벤더 드라이버 비교 |
실전 점검 순서
- PHY가 존재하는지
/sys/bus/mdio_bus/devices와 PHY ID부터 봅니다. - 링크 정책이 맞는지
ethtool eth0에서 speed, duplex, advertisement, pause를 확인합니다. - 오류가 물리 계층인지
ethtool -S에서 CRC, align, symbol, carrier 카운터를 봅니다. - 보드 타이밍 문제인지
phy-mode, RGMII delay, reset timing, clock 안정성을 점검합니다. - 상대편 의존 문제인지
스위치 포트, 모듈, 케이블, 강제 속도 설정을 바꿔 증상이 이동하는지 확인합니다.
PHY 펌웨어(Firmware) 로딩
일부 고성능 PHY는 리셋 후 부팅 과정에서 외부 펌웨어(Firmware)를 MDIO 버스를 통해 업로드받아야만 정상 동작합니다. 대표적으로 Aquantia(현 Marvell) AQR 시리즈 10G PHY와 일부 Marvell 88X 시리즈가 해당합니다. 이런 PHY는 내부에 DSP(Digital Signal Processor) 코어를 내장하고 있으며, DSP가 실행할 마이크로코드를 부팅 시 MDIO를 통해 기록해야만 10GBASE-T 등의 복잡한 선로 등화(Equalization) 알고리즘을 수행할 수 있습니다.
펌웨어 없이 PHY가 올라오면 드라이버 probe는 성공하더라도 링크 협상 자체가 시작되지 않습니다. 커널 로그에서 phy firmware not found 또는 request_firmware failed 메시지가 보인다면 이 단계가 실패한 것입니다.
커널 펌웨어 로딩 API
커널의 표준 펌웨어 로딩 인터페이스를 사용합니다. 펌웨어 파일은 /lib/firmware/ 하위에 위치하며, linux-firmware 저장소로 배포됩니다.
#include <linux/firmware.h>
/* 동기식 펌웨어 요청 (probe 시 가장 일반적으로 사용) */
int request_firmware(const struct firmware **fw,
const char *name,
struct device *device);
/* 비동기식 요청 (부트 타임 지연을 줄이고 싶을 때) */
int request_firmware_nowait(struct module *module,
bool uevent,
const char *name,
struct device *device,
gfp_t gfp,
void *context,
void (*cont)(const struct firmware *fw,
void *context));
/* 사용 후 반드시 해제 */
void release_firmware(const struct firmware *fw);
펌웨어 파일 경로 규칙: /lib/firmware/marvell/<chip>.bin 또는 /lib/firmware/aquantia/<chip>.cld 형식이 관례입니다. Device Tree에서 firmware-name 속성으로 경로를 지정하면 드라이버가 이 값을 우선적으로 사용합니다.
펌웨어 로딩 순서
/* Aquantia 스타일 PHY 펌웨어 로딩 개념 예시 */
static int aqr_firmware_load(struct phy_device *phydev)
{
const struct firmware *fw;
const char *fw_name;
int ret;
/* 1단계: Device Tree에서 firmware-name 읽기,
* 없으면 칩 기본 이름 사용 */
if (of_property_read_string(phydev->mdio.dev.of_node,
"firmware-name", &fw_name))
fw_name = "aquantia/aqr107.cld";
/* 2단계: 커널 펌웨어 서브시스템에서 파일 요청 */
ret = request_firmware(&fw, fw_name, &phydev->mdio.dev);
if (ret) {
phydev_err(phydev, "펌웨어 로드 실패: %s (%d)\n",
fw_name, ret);
return ret;
}
/* 3단계: PHY를 MDIO 부트로더 모드로 전환
* (칩별로 magic 레지스터 시퀀스가 다름) */
phy_write_mmd(phydev, MDIO_MMD_VEND1,
AQR_GLOBAL_BOOT_CTRL, AQR_BOOT_MODE_FLASH);
/* 4단계: 펌웨어 바이너리를 MDIO 쓰기로 업로드
* (일반적으로 Mailbox 레지스터 또는 특수 MMD 블록 사용) */
ret = aqr_upload_firmware(phydev, fw->data, fw->size);
if (ret) {
phydev_err(phydev, "펌웨어 업로드 실패 (%d)\n", ret);
goto out;
}
/* 5단계: PHY 재부팅(soft reset) 후 펌웨어 실행 대기 */
phy_write_mmd(phydev, MDIO_MMD_VEND1,
AQR_GLOBAL_SOFT_RESET, AQR_SOFT_RESET_BIT);
msleep(2000); /* 펌웨어 초기화 완료 대기 */
phydev_info(phydev, "펌웨어 로드 완료: %s (%zu bytes)\n",
fw_name, fw->size);
out:
release_firmware(fw);
return ret;
}
static int aqr_probe(struct phy_device *phydev)
{
int ret;
/* probe 단계에서 펌웨어 로드 */
ret = aqr_firmware_load(phydev);
if (ret)
return ret;
/* 이후 일반 초기화 진행 */
return 0;
}
Device Tree firmware-name 속성
/* Device Tree 예시 */
&mdio0 {
phy0: ethernet-phy@0 {
compatible = "ethernet-phy-id03a1.b4b3";
reg = <0>;
/* 기본 펌웨어 경로를 재정의할 때 사용 */
firmware-name = "aquantia/aqr107_custom.cld";
};
};
펌웨어가 필요한 주요 PHY
| 제조사 | 칩 시리즈 | 속도 | 펌웨어 경로 | 비고 |
|---|---|---|---|---|
| Aquantia / Marvell | AQR107, AQR108, AQR405, AQR411, AQR412 | 2.5G / 5G / 10G | aquantia/aqr107.cld |
CLD 포맷, 수백 KB |
| Marvell | 88X3310, 88X3340 | 10G | marvell/88X3310.bin |
SBUS 펌웨어 포함 |
| Broadcom | BCM84891 | 10G | broadcom/bcm84891.bin |
SPI NOR 선행 로딩 가능 |
| Intel / Maxlinear | GPY211, GPY215 | 2.5G | 내장 ROM (펌웨어 불필요) | 일부 보드는 NVM 업데이트 필요 |
펌웨어 로딩 문제 해결
펌웨어 관련 문제는 크게 세 가지 유형으로 나뉩니다.
- 파일 없음:
/lib/firmware/에 펌웨어 파일이 없습니다.linux-firmware패키지를 설치하거나 linux-firmware 저장소에서 직접 파일을 복사하세요. initramfs에 펌웨어를 포함시켜야 부팅 초기에 로드할 수 있습니다. - 버전 불일치: PHY 하드웨어 리비전(Revision)과 펌웨어 버전이 맞지 않으면 PHY가 reset 루프에 빠지거나 링크를 올리지 못합니다.
ethtool -m ethX로 PHY가 보고하는 펌웨어 버전을 확인하세요. - 부팅 속도 저하: 10G PHY 펌웨어는 수백 KB에서 수 MB에 달하며 MDIO(최대 2.5 Mbps)로 전송하면 수 초가 걸릴 수 있습니다.
request_firmware_nowait()로 비동기 로딩을 사용하면 네트워크가 필요한 시점까지 부팅을 지연시키지 않을 수 있습니다.
linux-firmware 저장소(git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git)를 통해 별도 배포됩니다. 배포판(Distro)에서는 linux-firmware 패키지로 제공되며, 임베디드 시스템에서는 rootfs 또는 initramfs에 펌웨어 파일을 직접 포함시켜야 합니다. 펌웨어 파일의 재배포 가능 여부는 각 칩 제조사의 EULA를 반드시 확인하시기 바랍니다.
ethtool 통계와 PHY 카운터 해석 실전 가이드
ethtool -S <interface> 명령은 드라이버가 노출하는 하드웨어 통계 카운터를 출력합니다. 이 카운터들은 NIC(Network Interface Card) MAC 계층과 PHY 계층에서 독립적으로 집계되므로, 어떤 카운터가 증가하는지 조합해서 보면 문제의 위치를 MAC인지 PHY인지, 더 나아가 케이블인지 상대방 장비인지까지 좁혀 낼 수 있습니다.
카운터 분류 개요
ethtool -S 출력은 드라이버마다 이름이 다르지만, 기능상으로 다음 세 범주로 분류할 수 있습니다.
- MAC Rx/Tx 카운터 — MAC이 직접 집계합니다. 프레임이 MAC에 도달한 이후의 오류(FCS, 길이, 정렬)를 반영합니다.
- PHY Rx/Tx 카운터 — PHY 내부 레지스터(RMON, Clause 45 PCS 등)에서 읽어옵니다. 케이블과 아날로그 경계에 가까운 오류를 반영합니다.
- 큐·흐름 제어 카운터 — DMA FIFO 언더런/오버런, TX 타임아웃, 링 버퍼 drop 등 드라이버 레벨 문제를 반영합니다.
PHY 관련 주요 카운터 상세
| 카운터 이름 | 의미 | 증가 원인 | 의심 위치 |
|---|---|---|---|
rx_crc_errors |
FCS(Frame Check Sequence) 불일치 프레임 수 | RGMII 스큐(skew) 오설정, 케이블 손상, EMI(Electromagnetic Interference) 간섭, SFP 모듈 불량 | PHY 아날로그 / RGMII 타이밍 |
rx_frame_errors |
정렬(alignment) 오류 — 길이가 8비트의 배수가 아닌 프레임 | 이더넷 프리앰블 손상, 링크 파트너의 타이밍 문제, 물리 계층 잡음 | PHY PCS / 케이블 |
rx_length_errors |
최소(64 B) 미만이거나 최대(1518/9000 B) 초과 프레임 | 프레임 잘림(Rx FIFO 오버런), Jumbo Frame 미설정, 링크 파트너 MTU 불일치 | MAC Rx FIFO / MTU 설정 |
carrier_errors / link_loss_count |
캐리어 감지 실패 횟수 — 링크 up/down 반복 | 케이블 접촉 불량, 전원 불안정(EEE 진입/복귀 실패), SGMII 협상 실패 | PHY 링크 상태 머신 / 케이블 |
symbol_errors |
PCS(Physical Coding Sublayer)가 복호화하지 못한 심볼 수 | PMA(Physical Medium Attachment) 손실 마진 부족, SerDes 이퀄라이저 미조정, 광 수신 출력 부족 | PHY PCS/PMA / 광 링크 예산 |
collisions |
충돌 감지 횟수 | Half-duplex로 동작 중인 경우 정상이나, Full-duplex에서 증가하면 Duplex 불일치(Duplex mismatch) 신호 | Auto-negotiation 실패 / Duplex 강제 설정 오류 |
ethtool -S 출력 예시와 주석
# Realtek RTL8211F PHY가 달린 인터페이스 예시
$ ethtool -S eth0
NIC statistics:
tx_packets: 4821093 # 정상 송신 프레임
rx_packets: 3927451 # 정상 수신 프레임
tx_errors: 0
rx_errors: 17 # ← 0이 아니면 추가 조사 필요
tx_dropped: 0
rx_dropped: 3 # FIFO 오버런 또는 소프트웨어 버퍼 부족
multicast: 1024
rx_crc_errors: 14 # ← RGMII 스큐 문제 의심, 타이밍 보정 확인
rx_frame_errors: 2 # ← PHY PCS 레벨 잡음
rx_length_errors: 1 # ← MTU 불일치 또는 FIFO 오버런
rx_missed_errors: 0
tx_aborted_errors: 0
tx_carrier_errors: 0
tx_fifo_errors: 0
tx_heartbeat_errors: 0
tx_window_errors: 0 # 0이 아니면 Half-duplex 충돌 창 문제
# PHY vendor-specific (드라이버마다 이름 다름)
phy_symbol_errors: 0
phy_rx_aligns: 0
phy_remote_rx_status: 1 # 원격 PHY Rx 정상
phy_local_rx_status: 1 # 로컬 PHY Rx 정상
MAC 문제와 PHY 문제 구별 방법
카운터만으로 MAC과 PHY 층을 완전히 분리하기는 어렵지만, 다음 원칙이 실용적입니다.
- rx_crc_errors만 증가할 때: 프레임은 완성됐으나 FCS가 틀렸다는 뜻이므로, 케이블이나 RGMII 타이밍이 원인일 가능성이 높습니다. MAC 내부 버그보다 PHY 또는 보드 배선 문제를 먼저 의심하세요.
- rx_frame_errors + rx_crc_errors 동시 증가: PHY의 비트 동기화 문제이거나, 케이블이 손상돼 프리앰블이 잘린 것입니다.
- rx_dropped + rx_missed_errors 증가, 오류 카운터 정상: 프레임 자체는 올바르지만 시스템이 소화하지 못하는 것이므로, MAC Rx FIFO나 CPU/DMA 처리 속도 문제입니다.
- collisions 증가 (Full-duplex 환경): Auto-negotiation이 실패해 한쪽은 Full-duplex, 다른 쪽은 Half-duplex로 동작하는 Duplex mismatch 상태입니다.
ethtool eth0으로 양단 모드를 즉시 확인하세요. - symbol_errors 증가: PCS/PMA 수준의 물리적 오류입니다. 1GbE 구리에서는 케이블 감쇠나 크로스토크(Crosstalk), SFP 광 링크에서는 수신 광 출력 부족(
ethtool -m eth0의 RX power 확인)을 점검하세요.
카운터 진단 흐름도
지속 모니터링 스크립트
일회성 조회 대신 카운터 변화량을 지속적으로 관찰하면 간헐적 오류도 포착할 수 있습니다.
# 방법 1: watch로 0이 아닌 카운터만 1초 간격 갱신
watch -n 1 'ethtool -S eth0 | grep -v ": 0$"'
# 방법 2: 변화량(delta) 추출 스크립트
#!/bin/bash
IFACE=${1:-eth0}
INTERVAL=${2:-5}
declare -A PREV
while IFS=': ' read -r key val; do
PREV["$key"]=$val
done < <(ethtool -S "$IFACE" | tail -n +2)
while true; do
sleep "$INTERVAL"
declare -A CURR
while IFS=': ' read -r key val; do
CURR["$key"]=$val
done < <(ethtool -S "$IFACE" | tail -n +2)
echo "=== $(date '+%T') delta (${INTERVAL}s) ==="
for key in "${!CURR[@]}"; do
delta=$(( CURR[$key] - ${PREV[$key]:-0} ))
if (( delta != 0 )); then
printf " %-40s %+d\n" "$key" "$delta"
fi
done
for key in "${!CURR[@]}"; do
PREV[$key]=${CURR[$key]}
done
done
# 방법 3: 파일에 기록 (후속 분석용)
while true; do
echo "$(date '+%F %T')" >> /tmp/eth0_stats.log
ethtool -S eth0 | grep -v ": 0$" >> /tmp/eth0_stats.log
sleep 60
done
증상 패턴과 근본 원인 매핑
| 증가하는 카운터 조합 | 의심 근본 원인 | 즉시 확인 명령 |
|---|---|---|
rx_crc_errors 단독 증가, 링크 안정 |
RGMII tx/rx delay 설정 오류 | cat /sys/bus/mdio_bus/devices/*/phy_id, DT rgmii-rxid 확인 |
rx_crc_errors + rx_frame_errors |
케이블 손상 또는 EMI 간섭 | 케이블 교체 후 비교, STP 차폐 케이블 사용 여부 확인 |
collisions Full-duplex 환경에서 증가 |
Duplex mismatch — 한쪽 강제 Half-duplex | ethtool eth0으로 Speed/Duplex 양단 확인 |
carrier_errors 지속 증가 |
케이블 접촉 불량 또는 EEE 진입/복귀 타임아웃 | ethtool --show-eee eth0, dmesg | grep eth0 |
symbol_errors 증가, SFP 광 링크 |
수신 광 출력 부족 또는 광섬유 굽힘 | ethtool -m eth0 (Rx power dBm 확인) |
rx_dropped + rx_missed_errors 증가 |
CPU 처리 지연 또는 Rx 링 버퍼 부족 | ethtool -g eth0, ethtool -G eth0 rx 4096 |
rx_crc_errors 급증 후 링크 down/up 반복 |
SFP 모듈 불량 또는 광 커넥터 오염 | SFP 모듈 교체, APC 클리너로 커넥터 청소 |
| 모든 오류 카운터 0, 처리량만 낮음 | 자동 협상 결과 속도 강등(예: 1G→100M) | ethtool eth0 | grep Speed |
PHY 전용 통계 (--phy-statistics)
Linux 4.16부터 ethtool --phy-statistics <interface> 옵션이 도입되어 PHY 드라이버가 구현한 경우 MAC 통계와 별도로 PHY 레지스터 기반 카운터를 조회할 수 있습니다.
# PHY 전용 통계 조회 (드라이버 지원 시)
$ ethtool --phy-statistics eth0
PHY statistics for eth0:
phy_receive_errors: 3 # PHY 내부 Rx 오류 (CRC, 심볼 등 PHY 자체 집계)
phy_duplex_mismatch: 0 # Duplex mismatch 감지 횟수 (일부 PHY만)
phy_fast_retrain_count: 2 # 2.5G BASE-T fast retrain 횟수
PHY 전용 통계는 드라이버가 ethtool_ops.get_phy_stats와 ethtool_ops.get_phy_sset_count를 구현해야 노출됩니다. Realtek RTL8211F, Marvell Alaska, Micrel KSZ 시리즈 등 일부 드라이버만 지원하며, 지원하지 않는 경우 "Operation not supported" 오류가 반환됩니다.
Clause 45 PHY라면 MMD 3(PCS)의 RMON 카운터 레지스터(3.0x1010~0x1019)를 직접 읽을 수도 있습니다.
# Clause 45 PHY RMON 카운터 직접 읽기 (phytool 필요)
# MMD 3, Register 0x1014 = Rx FCS Error Frame Counter
phytool read eth0/1/3:0x1014
Device Tree 실전 예제 모음
Device Tree(장치 트리)에서 이더넷 PHY를 올바르게 기술하는 것은 보드 bring-up의 핵심입니다. 이 절은 다양한 토폴로지별 완성된 예제를 제공하며, 각 속성의 용도와 자주 발생하는 실수를 함께 설명합니다.
RGMII + 외부 PHY (STM32MP1 + RTL8211F)
STM32MP1 SoC의 GMAC과 Realtek RTL8211F PHY를 RGMII 모드로 연결하는 가장 일반적인 예제입니다. RGMII 인터페이스는 1 Gbps에서 클럭 스큐가 매우 민감하므로 지연(delay) 속성을 정확히 기술해야 합니다.
ðernet0 {
status = "okay";
pinctrl-0 = <ðernet0_rgmii_pins_a>;
pinctrl-1 = <ðernet0_rgmii_sleep_pins_a>;
pinctrl-names = "default", "sleep";
phy-mode = "rgmii-id"; /* 내부 Tx+Rx 지연 모두 MAC 측에서 삽입 */
/* 대안: PHY 측 지연 사용 시 "rgmii-rxid" 또는 "rgmii-txid" */
max-speed = <1000>;
phy-handle = <&phy0>; /* mdio 노드 안의 phy 노드 참조 */
clocks = <&rcc ETH_MAC>, <&rcc ETH_TX>, <&rcc ETH_RX>;
clock-names = "stmmaceth", "mac-clk-tx", "mac-clk-rx";
mdio0: mdio {
#address-cells = <1>;
#size-cells = <0>;
compatible = "snps,dwmac-mdio";
phy0: ethernet-phy@1 {
compatible = "ethernet-phy-id001c.c916"; /* RTL8211F PHY ID */
reg = <1>; /* MDIO 주소 */
/* 하드웨어 reset GPIO */
reset-gpios = <&gpioa 12 GPIO_ACTIVE_LOW>;
reset-assert-us = <10000>; /* reset 인가 시간 10 ms */
reset-deassert-us = <30000>; /* reset 해제 후 안정화 30 ms */
/* EEE(Energy-Efficient Ethernet) 비활성화 예시 */
eee-broken-100tx;
/* 자동 협상 광고 속도 제한 */
max-speed = <1000>;
};
};
};
phy-mode에서 id 접미사는 어느 쪽에서 클럭 지연을 삽입하는지를 나타냅니다. rgmii-id는 MAC이 Tx와 Rx 모두 지연을 처리하므로 PHY의 내부 지연을 반드시 비활성화해야 합니다. rgmii-rxid는 PHY가 Rx 지연을 삽입하므로 MAC 쪽 Rx 지연을 끄고, rgmii-txid는 PHY가 Tx 지연을 삽입합니다. 두 곳이 모두 지연을 삽입하면 타이밍이 두 배가 돼 CRC 오류가 대량 발생합니다.
SGMII + SFP 케이지 (NXP LS1028A)
NXP LS1028A의 ENETC MAC을 SGMII 모드로 SFP 케이지에 연결하는 예제입니다. SFP 케이지는 별도의 sfp 노드로 기술하고, 모듈 감지·신호·전원 제어 GPIO를 각각 지정해야 합니다.
/* SFP 케이지 노드 (보드 최상위 레벨) */
sfp_eth0: sfp-eth0 {
compatible = "sff,sfp";
i2c-bus = <&i2c1>; /* SFP EEPROM I2C 버스 */
mod-def0-gpios = <&gpio3 5 GPIO_ACTIVE_LOW>; /* 모듈 삽입 감지 (MOD_ABS) */
los-gpios = <&gpio3 6 GPIO_ACTIVE_HIGH>;/* 수신 신호 손실 (LOS) */
tx-fault-gpios = <&gpio3 7 GPIO_ACTIVE_HIGH>;/* 송신 장애 (TX_FAULT) */
tx-disable-gpios = <&gpio3 8 GPIO_ACTIVE_HIGH>;/* 송신 레이저 끄기 (TX_DIS) */
/* 선택적: SFP 최대 전력 등급 */
maximum-power-milliwatt = <1000>;
};
/* ENETC MAC 노드 */
&enetc_port0 {
status = "okay";
phy-mode = "sgmii";
sfp = <&sfp_eth0>; /* SFP 케이지 연결 */
managed = "in-band-status"; /* SGMII in-band auto-negotiation 사용 */
/* phy-handle 없음: SFP 내 PHY는 자동 감지 */
};
SFP 케이지에 구리 SFP-T 모듈이 삽입되면 phylink가 자동으로 PHY 드라이버를 바인딩합니다. 광 모듈의 경우 managed = "in-band-status"가 SGMII 협상을 처리합니다. mod-def0-gpios가 없으면 모듈 hot-plug를 감지할 수 없으므로 권장합니다.
Fixed-link (PHY 없는 직결)
MAC과 스위치 칩, 또는 두 MAC을 직접 연결할 때는 Auto-negotiation이 없으므로 속도와 Duplex를 fixed-link 서브노드에 명시해야 합니다.
/* MAC ↔ 스위치 내부 포트 직결 예 */
&fec1 {
status = "okay";
phy-mode = "rgmii"; /* 지연 없는 순수 RGMII */
pinctrl-0 = <&pinctrl_fec1>;
pinctrl-names = "default";
fixed-link {
speed = <1000>; /* 링크 속도 Mbps */
full-duplex; /* Half-duplex이면 이 줄 제거 */
/* pause; 선택적: PAUSE 프레임 지원 표시 */
/* asym-pause; 선택적: 비대칭 PAUSE 지원 */
};
};
/* 참고: MDIO가 필요 없으므로 mdio 서브노드 불필요 */
/* 스위치 칩은 별도의 SPI/I2C/MDIO 연결로 제어 */
fixed-link 사용 시 주의: fixed-link와 phy-handle을 동시에 기술하면 충돌합니다. 또한 phy-mode에서 rgmii-id처럼 지연이 포함된 변형을 사용하면 MAC 드라이버가 하드웨어 지연을 삽입하려 시도하므로, 직결 구성에서는 rgmii(순수)를 사용하거나 보드 회로도에 지연 소자가 있는지 확인하세요.
MDIO 멀티플렉서 토폴로지
하나의 MDIO 버스에 물리적 멀티플렉서(Mux)를 달아 여러 버스를 파생하는 구성입니다. 멀티플렉서 선택 신호는 GPIO 또는 I2C 제어 레지스터로 구현할 수 있습니다.
/* GPIO 기반 MDIO 멀티플렉서 */
&mdio1 {
#address-cells = <1>;
#size-cells = <0>;
mdio_mux: mdio-mux@0 {
compatible = "mdio-mux-gpio";
mux-gpios = <&gpio1 20 GPIO_ACTIVE_HIGH /* 선택 비트 0 */
&gpio1 21 GPIO_ACTIVE_HIGH>; /* 선택 비트 1 */
#address-cells = <1>;
#size-cells = <0>;
mdio-parent-bus = <&mdio1>;
/* 채널 0: GPIO = 0b00 */
mdio_mux_0: mdio@0 {
reg = <0>;
#address-cells = <1>;
#size-cells = <0>;
phy_mux0: ethernet-phy@4 {
reg = <4>;
};
};
/* 채널 1: GPIO = 0b01 */
mdio_mux_1: mdio@1 {
reg = <1>;
#address-cells = <1>;
#size-cells = <0>;
phy_mux1: ethernet-phy@4 {
reg = <4>; /* 다른 버스이므로 주소 충돌 없음 */
};
};
/* 채널 2: GPIO = 0b10 */
mdio_mux_2: mdio@2 {
reg = <2>;
#address-cells = <1>;
#size-cells = <0>;
phy_mux2: ethernet-phy@4 {
reg = <4>;
};
};
};
};
/* MAC에서 멀티플렉서 뒤쪽 PHY 참조 */
ð0 {
phy-handle = <&phy_mux0>;
phy-mode = "rgmii-id";
};
듀얼 MAC과 공유 MDIO 버스
두 개의 MAC이 하나의 MDIO 버스를 공유하고 각각 다른 PHY를 제어하는 구성입니다. MDIO 버스는 한 MAC의 서브노드로 선언하고, 다른 MAC은 phy-handle로 동일 노드를 참조합니다.
/* MAC 0: MDIO 버스 소유 */
&gmac0 {
status = "okay";
phy-mode = "rgmii-id";
phy-handle = <&phy0>;
mdio_shared: mdio {
#address-cells = <1>;
#size-cells = <0>;
compatible = "snps,dwmac-mdio";
phy0: ethernet-phy@1 {
reg = <1>; /* MDIO 주소 1: gmac0용 PHY */
reset-gpios = <&gpio0 5 GPIO_ACTIVE_LOW>;
reset-assert-us = <10000>;
reset-deassert-us = <30000>;
};
phy1: ethernet-phy@2 {
reg = <2>; /* MDIO 주소 2: gmac1용 PHY */
reset-gpios = <&gpio0 6 GPIO_ACTIVE_LOW>;
reset-assert-us = <10000>;
reset-deassert-us = <30000>;
};
};
};
/* MAC 1: 공유 MDIO 버스의 PHY를 참조 */
&gmac1 {
status = "okay";
phy-mode = "rgmii-id";
phy-handle = <&phy1>;
/* 자체 mdio 서브노드 없음 — gmac0의 mdio 버스 공유 */
};
PHY 패키지 (4포트 통합 PHY)
Marvell 88E1512 4포트나 Realtek RTL8367RB 같이 단일 칩에 여러 PHY가 내장된 경우 ethernet-phy-package 노드로 공유 자원을 묶어야 합니다.
/* 4포트 PHY 패키지 예 (Marvell 88E1510 quad) */
&mdio {
#address-cells = <1>;
#size-cells = <0>;
phy_package: ethernet-phy-package@0 {
#address-cells = <1>;
#size-cells = <0>;
reg = <0>; /* 패키지 기준 MDIO 주소 */
/* 패키지 전체 하드웨어 reset: 모든 포트에 동시 적용 */
reset-gpios = <&gpio2 10 GPIO_ACTIVE_LOW>;
reset-assert-us = <10000>;
reset-deassert-us = <50000>;
/* 포트 0: MDIO 주소 0 */
phy_pkg0: ethernet-phy@0 {
reg = <0>;
};
/* 포트 1: MDIO 주소 1 */
phy_pkg1: ethernet-phy@1 {
reg = <1>;
};
/* 포트 2: MDIO 주소 2 */
phy_pkg2: ethernet-phy@2 {
reg = <2>;
};
/* 포트 3: MDIO 주소 3 */
phy_pkg3: ethernet-phy@3 {
reg = <3>;
};
};
};
/* 각 MAC이 패키지 내 PHY를 개별 참조 */
ð0 { phy-handle = <&phy_pkg0>; phy-mode = "rgmii-id"; }
ð1 { phy-handle = <&phy_pkg1>; phy-mode = "rgmii-id"; }
ð2 { phy-handle = <&phy_pkg2>; phy-mode = "rgmii-id"; }
ð3 { phy-handle = <&phy_pkg3>; phy-mode = "rgmii-id"; }
커널의 phy_package_join() / phy_package_leave() API는 패키지 내 여러 PHY 드라이버가 글로벌 초기화를 한 번만 수행하도록 조율합니다. PHY 드라이버는 phy_package_init_once()를 사용해 공유 레지스터 초기화가 중복 실행되지 않도록 해야 합니다.
자주 혼동하는 DT 속성 비교표
| 속성 쌍 | 속성 A | 속성 B | 차이 및 올바른 사용 |
|---|---|---|---|
phy-mode vs phy-connection-type |
phy-mode = "rgmii-id"현재 표준 속성명 |
phy-connection-type = "rgmii-id"구형 속성명 (deprecated) |
두 이름은 동일한 의미입니다. 신규 DTS에서는 반드시 phy-mode를 사용하세요. 구형 BSP를 포팅할 때 phy-connection-type을 만나면 phy-mode로 교체하세요. |
phy-handle vs MDIO 자식 노드 직접 기술 |
phy-handle = <&phy0>;별도 선언된 PHY 노드를 phandle로 참조 |
MAC 노드 아래 mdio 서브노드 안에 ethernet-phy@N 직접 선언 |
두 방법 모두 유효합니다. phy-handle은 MDIO를 공유하거나 PHY 노드를 여러 곳에서 참조할 때 필요합니다. 단독 구성에서는 직접 선언도 가능하지만, phy-handle 방식이 더 명확합니다. |
managed vs in-band-status |
managed = "in-band-status"속성 이름이 managed, 값이 "in-band-status" |
in-band-status;구형 불리언 속성 (일부 BSP에서 사용) |
managed = "in-band-status"가 현재 표준입니다. SGMII/1000BASE-X 링크에서 PHY 없이 in-band(대역 내) 협상을 사용할 때 기술합니다. 이 속성이 있으면 phylink가 외부 PHY 없이도 링크 상태를 추적합니다. |
fixed-link vs phy-handle |
fixed-link { speed = <1000>; full-duplex; }; |
phy-handle = <&phyN>; |
fixed-link는 Auto-negotiation이 없는 직결 구성에서만 사용합니다. 실제 PHY가 붙어있으면 반드시 phy-handle을 사용하세요. 둘을 동시에 기술하면 동작이 정의되지 않습니다. |
reset-gpios 위치 |
PHY 노드(ethernet-phy@N) 안에 기술 |
MAC 노드나 보드 최상위에 기술 | PHY reset GPIO는 반드시 ethernet-phy@N 노드 안에 기술해야 phylib가 자동으로 reset 시퀀스를 제어합니다. MAC 노드나 최상위에 기술하면 커널이 인식하지 못합니다. |
rx-internal-delay-ps vs rxc-skew-ps |
rx-internal-delay-ps = <2000>;PHY 내부 Rx 지연을 피코초 단위로 설정 (신규 방식) |
rxc-skew-ps = <1860>;구형 Micrel/KSZ 방식 속성 |
신규 보드에서는 rx-internal-delay-ps / tx-internal-delay-ps를 사용하세요. 두 속성이 공존하면 rx-internal-delay-ps가 우선합니다. 지원 범위는 PHY 드라이버 코드에서 확인하세요. |
NIC(MAC) 드라이버 개발자를 위한 PHY 통합 실전 가이드
PHY 드라이버를 직접 작성하는 것과, MAC(NIC) 드라이버에서 PHY를 올바르게 연결하고 운용하는 것은 별개의 과제입니다. 이 절은 첫 MAC 드라이버를 작성하거나 기존 드라이버에 PHY 지원을 추가하는 초급 개발자가 반드시 알아야 할 사항을 다룹니다. PHY 드라이버 자체는 PHY 드라이버 작성 가이드, MAC 드라이버 구조는 Network Device 드라이버 문서를 참고하세요.
net_device_ops 구현체)를 뜻합니다. PHY 칩 자체의 벤더 드라이버가 아니라, MAC 쪽에서 PHY를 찾고 연결하고 링크 변화에 대응하는 코드를 작성하는 관점입니다.
MDIO 버스 등록: MAC 드라이버의 첫 번째 책임
MAC 드라이버가 PHY를 사용하려면 먼저 mii_bus를 할당하고 등록해야 합니다. 이 과정이 없으면 커널은 PHY를 찾을 수 없습니다. MDIO 버스 등록은 MAC의 probe() 안에서 수행하며, remove()에서 반드시 해제해야 합니다.
/* MAC 드라이버의 MDIO 버스 등록 전체 흐름 */
static int my_mac_mdio_init(struct my_priv *priv)
{
struct mii_bus *bus;
struct device_node *mdio_np;
int ret;
bus = mdiobus_alloc();
if (!bus)
return -ENOMEM;
bus->name = "my-mac-mdio";
snprintf(bus->id, MII_BUS_ID_SIZE, "%s",
dev_name(priv->dev));
bus->read = my_mdio_read; /* 하드웨어 MDIO read 구현 */
bus->write = my_mdio_write; /* 하드웨어 MDIO write 구현 */
bus->parent = priv->dev;
bus->priv = priv;
/* Device Tree에서 mdio 서브노드 찾기 */
mdio_np = of_get_child_by_name(priv->dev->of_node, "mdio");
if (mdio_np) {
/* DT 기반 등록: PHY 주소를 DT에서 자동 해석 */
ret = of_mdiobus_register(bus, mdio_np);
of_node_put(mdio_np);
} else {
/* DT 없이 전체 주소 스캔 (비추천, 레거시용) */
ret = mdiobus_register(bus);
}
if (ret) {
mdiobus_free(bus);
return ret;
}
priv->mii_bus = bus;
return 0;
}
/* remove에서 반드시 해제 */
static void my_mac_mdio_fini(struct my_priv *priv)
{
if (priv->mii_bus) {
mdiobus_unregister(priv->mii_bus);
mdiobus_free(priv->mii_bus);
}
}
mii_bus->mdio_lock이 자동으로 관리하므로 콜백 안에서 별도 락을 잡지 마세요.
PHY 연결 패턴: phylib 직접 연결 vs phylink
MAC 드라이버에서 PHY를 연결하는 방법은 크게 두 가지입니다. 어떤 패턴을 선택하느냐에 따라 구현해야 하는 콜백과 링크 변화 처리 방식이 완전히 달라집니다.
| 항목 | phylib 직접 연결 | phylink 사용 |
|---|---|---|
| 연결 함수 | of_phy_connect() / phy_connect_direct() | phylink_create() + phylink_of_phy_connect() |
| 링크 변화 콜백 | adjust_link(net_device *) | mac_link_up() / mac_link_down() / mac_config() |
| 시작/정지 | phy_start() / phy_stop() | phylink_start() / phylink_stop() |
| ethtool 연결 | phy_ethtool_get_link_ksettings() 등 수동 연결 | phylink_ethtool_ksettings_get() 등 일괄 제공 |
| 해제 | phy_disconnect() | phylink_disconnect_phy() + phylink_destroy() |
| 적합한 경우 | 외부 PHY 1개, 단순 RGMII/MII | SFP, in-band, PCS 분리, fixed-link |
패턴 A: phylib 직접 연결 (단순 구성)
/* probe에서 PHY 연결 */
static int my_mac_probe(struct platform_device *pdev)
{
struct my_priv *priv = ...;
struct device_node *phy_np;
int ret;
/* 1단계: MDIO 버스 등록 */
ret = my_mac_mdio_init(priv);
if (ret)
return ret;
/* 2단계: DT에서 phy-handle 찾기 */
phy_np = of_parse_phandle(pdev->dev.of_node, "phy-handle", 0);
if (!phy_np) {
dev_err(&pdev->dev, "phy-handle not found in DT\n");
ret = -ENODEV;
goto err_mdio;
}
/* 3단계: phy-mode 파싱 */
ret = of_get_phy_mode(pdev->dev.of_node, &priv->phy_mode);
if (ret) {
priv->phy_mode = PHY_INTERFACE_MODE_RGMII; /* 폴백 기본값 */
}
/* 4단계: PHY 연결 — adjust_link 콜백 등록 */
priv->phydev = of_phy_connect(priv->ndev, phy_np,
&my_adjust_link, 0,
priv->phy_mode);
of_node_put(phy_np);
if (!priv->phydev) {
dev_err(&pdev->dev, "PHY connect failed\n");
ret = -ENODEV;
goto err_mdio;
}
/* 5단계: 지원 속도 제한 (하드웨어 능력에 맞게) */
phy_set_max_speed(priv->phydev, SPEED_1000);
phy_remove_link_mode(priv->phydev,
ETHTOOL_LINK_MODE_1000baseT_Half_BIT);
phy_attached_info(priv->phydev);
return 0;
err_mdio:
my_mac_mdio_fini(priv);
return ret;
}
패턴 B: phylink 사용 (권장, 확장 가능)
/* phylink MAC 콜백 구현 */
static void my_mac_config(struct phylink_config *config,
unsigned int mode,
const struct phylink_link_state *state)
{
struct my_priv *priv = container_of(config, struct my_priv,
phylink_config);
/* 인터페이스 모드 변경 시 MAC 클럭 소스/PCS 설정 조정 */
if (state->interface != priv->current_interface) {
my_mac_set_interface_mode(priv, state->interface);
priv->current_interface = state->interface;
}
}
static void my_mac_link_up(struct phylink_config *config,
struct phy_device *phy,
unsigned int mode,
phy_interface_t interface,
int speed, int duplex,
bool tx_pause, bool rx_pause)
{
struct my_priv *priv = container_of(config, struct my_priv,
phylink_config);
/* ▼ 반드시 구현해야 할 핵심 작업 */
my_mac_set_speed(priv, speed); /* MAC 클럭 분주 변경 */
my_mac_set_duplex(priv, duplex); /* full/half 모드 설정 */
my_mac_set_pause(priv, tx_pause, rx_pause); /* flow control */
my_mac_enable_tx_rx(priv); /* TX/RX 활성화 */
}
static void my_mac_link_down(struct phylink_config *config,
unsigned int mode,
phy_interface_t interface)
{
struct my_priv *priv = container_of(config, struct my_priv,
phylink_config);
my_mac_disable_tx_rx(priv); /* TX/RX 비활성화 */
}
static const struct phylink_mac_ops my_phylink_mac_ops = {
.mac_config = my_mac_config,
.mac_link_up = my_mac_link_up,
.mac_link_down = my_mac_link_down,
};
/* probe에서 phylink 초기화 */
static int my_mac_probe_phylink(struct my_priv *priv)
{
struct phylink_config *plcfg = &priv->phylink_config;
plcfg->dev = &priv->ndev->dev;
plcfg->type = PHYLINK_NETDEV;
/* MAC이 지원하는 인터페이스 모드 선언 */
__set_bit(PHY_INTERFACE_MODE_RGMII_ID,
plcfg->supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_SGMII,
plcfg->supported_interfaces);
/* MAC 능력 선언 */
phy_interface_set_rgmii(plcfg->supported_interfaces);
plcfg->mac_capabilities = MAC_SYM_PAUSE | MAC_ASYM_PAUSE |
MAC_10 | MAC_100 | MAC_1000FD;
priv->phylink = phylink_create(plcfg, dev_fwnode(priv->dev),
priv->phy_mode,
&my_phylink_mac_ops);
if (IS_ERR(priv->phylink))
return PTR_ERR(priv->phylink);
return phylink_of_phy_connect(priv->phylink,
priv->dev->of_node, 0);
}
adjust_link / mac_link_up 올바르게 구현하기
NIC 드라이버 초보자가 가장 자주 실수하는 부분이 링크 변화 콜백입니다. PHY가 속도/듀플렉스/pause를 재협상하면 MAC 하드웨어 레지스터를 반드시 갱신해야 합니다. 이 콜백을 빈 함수로 두거나, speed만 바꾸고 duplex나 flow control을 빠뜨리면 실제 운영에서 미묘한 장애가 발생합니다.
/* phylib adjust_link 콜백: 반드시 구현할 4가지 작업 */
static void my_adjust_link(struct net_device *ndev)
{
struct my_priv *priv = netdev_priv(ndev);
struct phy_device *phydev = ndev->phydev;
bool changed = false;
/* ① 링크 다운 처리 */
if (!phydev->link) {
if (priv->link_up) {
priv->link_up = false;
my_mac_disable_tx_rx(priv);
netif_carrier_off(ndev);
netdev_info(ndev, "Link down\n");
}
return;
}
/* ② 속도 변경 반영 */
if (priv->speed != phydev->speed) {
my_mac_set_speed(priv, phydev->speed);
priv->speed = phydev->speed;
changed = true;
}
/* ③ 듀플렉스 변경 반영 */
if (priv->duplex != phydev->duplex) {
my_mac_set_duplex(priv, phydev->duplex);
priv->duplex = phydev->duplex;
changed = true;
}
/* ④ Flow control 반영 */
if (priv->pause != phydev->pause ||
priv->asym_pause != phydev->asym_pause) {
my_mac_set_pause(priv, phydev->pause, phydev->asym_pause);
priv->pause = phydev->pause;
priv->asym_pause = phydev->asym_pause;
changed = true;
}
/* 링크 업 전환 */
if (!priv->link_up) {
priv->link_up = true;
my_mac_enable_tx_rx(priv);
netif_carrier_on(ndev);
changed = true;
}
if (changed)
phy_print_status(phydev);
}
adjust_link를 { }로 두면 PHY가 1G→100M으로 재협상해도 MAC은 여전히 1G 클럭을 사용합니다. 결과적으로 "링크 업인데 CRC가 폭증하는" 증상이 나타나며, RGMII delay 문제와 쉽게 혼동됩니다. mac_link_up()도 마찬가지입니다.
ethtool PHY 관련 콜백 연결
MAC 드라이버는 ethtool에서 PHY 관련 명령이 들어오면 phylib 또는 phylink의 헬퍼로 위임해야 합니다. 이 연결을 빠뜨리면 ethtool eth0이 정보를 보여주지 못하거나 ethtool -s eth0 speed 100 같은 명령이 동작하지 않습니다.
| ethtool 기능 | phylib 사용 시 | phylink 사용 시 |
|---|---|---|
| 속도/듀플렉스 조회 | phy_ethtool_get_link_ksettings() | phylink_ethtool_ksettings_get() |
| 속도/듀플렉스 설정 | phy_ethtool_set_link_ksettings() | phylink_ethtool_ksettings_set() |
| AN 재시작 | phy_ethtool_nway_reset() | phylink_ethtool_nway_reset() |
| pause 조회/설정 | 직접 구현 | phylink_ethtool_get/set_pauseparam() |
| EEE 조회/설정 | phy_ethtool_get/set_eee() | phylink_ethtool_get/set_eee() (커널 6.x+) |
/* phylib 사용 시 ethtool ops 연결 예시 */
static const struct ethtool_ops my_ethtool_ops = {
.get_link_ksettings = phy_ethtool_get_link_ksettings,
.set_link_ksettings = phy_ethtool_set_link_ksettings,
.nway_reset = phy_ethtool_nway_reset,
.get_link = ethtool_op_get_link,
/* ... MAC 고유 ops ... */
};
/* phylink 사용 시 ethtool ops 연결 예시 */
static const struct ethtool_ops my_ethtool_ops_phylink = {
.get_link_ksettings = phylink_ethtool_ksettings_get,
.set_link_ksettings = phylink_ethtool_ksettings_set,
.nway_reset = phylink_ethtool_nway_reset,
.get_pauseparam = phylink_ethtool_get_pauseparam,
.set_pauseparam = phylink_ethtool_set_pauseparam,
.get_link = ethtool_op_get_link,
/* ... MAC 고유 ops ... */
};
open/close에서의 PHY 시작·정지 순서
인터페이스가 올라갈 때(ndo_open)와 내려갈 때(ndo_stop)의 PHY 관련 호출 순서는 미묘하지만 중요합니다. 순서가 뒤바뀌면 링크 변화 콜백이 이미 해제된 리소스에 접근하거나, 큐가 활성화된 상태에서 MAC 설정이 바뀌는 문제가 생깁니다.
/* ndo_open: 올바른 시작 순서 */
static int my_ndo_open(struct net_device *ndev)
{
struct my_priv *priv = netdev_priv(ndev);
int ret;
/* ① MAC 하드웨어 초기화 (DMA, 링 버퍼) */
ret = my_mac_hw_init(priv);
if (ret)
return ret;
/* ② IRQ 등록 */
ret = my_mac_request_irqs(priv);
if (ret)
goto err_hw;
/* ③ NAPI 활성화 */
napi_enable(&priv->napi);
/* ④ PHY 상태 머신 시작 — 여기서부터 adjust_link가 호출될 수 있음 */
phy_start(ndev->phydev);
/* phylink 사용 시: phylink_start(priv->phylink); */
/* ⑤ 네트워크 큐 시작 */
netif_tx_start_all_queues(ndev);
return 0;
err_hw:
my_mac_hw_fini(priv);
return ret;
}
/* ndo_stop: 올바른 정지 순서 (open의 역순) */
static int my_ndo_stop(struct net_device *ndev)
{
struct my_priv *priv = netdev_priv(ndev);
/* ① 네트워크 큐 정지 */
netif_tx_stop_all_queues(ndev);
netif_carrier_off(ndev);
/* ② PHY 정지 — 더 이상 adjust_link가 호출되지 않음 */
phy_stop(ndev->phydev);
/* phylink 사용 시: phylink_stop(priv->phylink); */
/* ③ NAPI 비활성화 */
napi_disable(&priv->napi);
/* ④ IRQ 해제 */
my_mac_free_irqs(priv);
/* ⑤ MAC 하드웨어 정지 */
my_mac_hw_fini(priv);
return 0;
}
remove에서의 정리 순서
MAC 드라이버의 remove()(또는 disconnect())에서는 PHY 관련 자원을 올바른 순서로 해제해야 합니다. 특히 unregister_netdev()는 내부적으로 ndo_stop()을 호출할 수 있으므로, PHY disconnect와 MDIO bus 해제는 그 이후에 수행해야 합니다.
/* remove에서의 정리 순서 */
static void my_mac_remove(struct platform_device *pdev)
{
struct my_priv *priv = platform_get_drvdata(pdev);
/* ① netdev 해제 — 내부에서 ndo_stop() 호출 가능 */
unregister_netdev(priv->ndev);
/* ② PHY 연결 해제 */
if (priv->ndev->phydev)
phy_disconnect(priv->ndev->phydev);
/* phylink 사용 시:
* phylink_disconnect_phy(priv->phylink);
* phylink_destroy(priv->phylink);
*/
/* ③ MDIO 버스 해제 — PHY disconnect 이후에! */
my_mac_mdio_fini(priv);
/* ④ 기타 하드웨어 자원 해제 */
free_netdev(priv->ndev);
}
phylink_destroy()를 phylink_disconnect_phy() 이후에 호출해야 합니다.
Deferred Probe 처리
Device Tree 기반 시스템에서는 PHY가 아직 probe되지 않았을 때 MAC 드라이버의 of_phy_connect()가 실패할 수 있습니다. 이 경우 -EPROBE_DEFER를 반환하면 커널이 나중에 다시 probe를 시도합니다. 이 처리를 빠뜨리면 부팅 순서에 따라 PHY가 연결되지 않는 간헐적 문제가 발생합니다.
/* deferred probe 처리 패턴 */
static int my_mac_probe(struct platform_device *pdev)
{
struct device_node *phy_np;
struct phy_device *phydev;
phy_np = of_parse_phandle(pdev->dev.of_node, "phy-handle", 0);
if (!phy_np)
return -ENODEV;
phydev = of_phy_find_device(phy_np);
of_node_put(phy_np);
if (!phydev) {
/* PHY가 아직 등록되지 않음 — 나중에 재시도 요청 */
return -EPROBE_DEFER;
}
/* phydev를 사용한 이후에는 참조를 해제 */
put_device(&phydev->mdio.dev);
/* 이후 of_phy_connect()로 정식 연결 진행 */
...
}
phy-mode 선택과 흔한 실수
MAC 드라이버 초보자가 가장 많이 겪는 문제가 phy-mode 설정 오류입니다. 올바른 모드를 선택하지 않으면 링크는 올라와도 데이터가 깨지거나 속도가 제한될 수 있습니다.
| phy-mode | 의미 | 지연 보상 위치 | 흔한 실수 |
|---|---|---|---|
rgmii | 지연 없음 (PCB에서 보상) | PCB 트레이스 길이 | PHY도 MAC도 지연을 넣지 않아 CRC 폭증 |
rgmii-id | PHY가 TX/RX 모두 지연 추가 | PHY 내부 | PCB에서 이미 보상했는데 이중 지연 |
rgmii-txid | PHY가 TX만 지연 추가 | PHY TX + PCB RX | TX/RX를 혼동하여 반대로 설정 |
rgmii-rxid | PHY가 RX만 지연 추가 | PHY RX + PCB TX | TX/RX를 혼동하여 반대로 설정 |
sgmii | SerDes 기반 1.25 Gbps | 해당 없음 | 1000BASE-X와 혼동, control word 해석 차이 |
mii / rmii | 10/100M 전용 | PHY/MAC/PCB 조합 | ref_clk 방향(입력/출력) 미설정 |
phy-mode를 선택하세요. 확인이 안 되면 rgmii, rgmii-id, rgmii-txid, rgmii-rxid 네 가지를 순서대로 시도하면서 ethtool -S로 CRC 에러 카운터를 비교하는 것이 가장 빠른 방법입니다.
suspend/resume에서 PHY 재초기화
시스템 suspend/resume 시 PHY는 전원이 차단되거나 리셋될 수 있습니다. MAC 드라이버의 resume 경로에서 PHY가 정상 상태로 돌아왔는지 확인하고, 필요시 재초기화해야 합니다.
/* suspend/resume에서 PHY 처리 */
static int my_mac_suspend(struct device *dev)
{
struct my_priv *priv = dev_get_drvdata(dev);
if (netif_running(priv->ndev)) {
netif_device_detach(priv->ndev);
phy_stop(priv->ndev->phydev);
/* phylink 사용 시: phylink_stop(priv->phylink); */
my_mac_hw_fini(priv);
}
return 0;
}
static int my_mac_resume(struct device *dev)
{
struct my_priv *priv = dev_get_drvdata(dev);
if (netif_running(priv->ndev)) {
my_mac_hw_init(priv);
/* PHY가 resume 콜백에서 config_init을 재실행함
* → strap, delay, LED 등 벤더 설정이 복원됨
* MAC 드라이버는 phy_start()만 호출하면 됨 */
phy_start(priv->ndev->phydev);
/* phylink 사용 시: phylink_start(priv->phylink); */
netif_device_attach(priv->ndev);
}
return 0;
}
resume 콜백이 config_init을 다시 호출하지 않는 드라이버가 간혹 있습니다. 이 경우 RGMII delay bit, LED 모드, downshift 정책이 strap 기본값으로 복귀하여 부팅 직후와 다른 동작을 보입니다. PHY 드라이버의 resume 구현을 확인하고, 필요하면 MAC 드라이버에서 PHY soft reset 후 재연결을 시도하세요.
초보 NIC 드라이버 개발자의 흔한 실수 TOP 10
아래는 커널 메일링 리스트와 실무에서 반복적으로 보고되는 PHY 관련 실수를 정리한 것입니다. 새 드라이버를 작성하거나 기존 드라이버를 수정할 때 체크리스트로 활용하세요.
| # | 실수 | 증상 | 올바른 처리 |
|---|---|---|---|
| 1 | adjust_link/mac_link_up을 빈 함수로 둠 | 속도 재협상 후 CRC 폭증, "링크 업인데 통신 불가" | speed, duplex, pause를 MAC 레지스터에 반드시 반영 |
| 2 | phy-mode 불일치 | 1G에서만 CRC 에러, 100M에서는 정상 | 보드 회로도 확인, PHY 데이터시트의 지연 보상 위치 맞추기 |
| 3 | MDIO bus 등록 전에 PHY 연결 시도 | probe 실패, NULL 포인터 | of_mdiobus_register() → of_phy_connect() 순서 보장 |
| 4 | deferred probe 미처리 | 부팅 순서에 따라 간헐적 PHY 연결 실패 | -EPROBE_DEFER 반환으로 재시도 허용 |
| 5 | phy_disconnect() 전에 MDIO bus 해제 | 커널 oops, use-after-free | 반드시 PHY disconnect → MDIO unregister → MDIO free 순서 |
| 6 | ethtool PHY ops 미연결 | ethtool eth0에서 속도/듀플렉스 정보 없음 | phy_ethtool_* 또는 phylink_ethtool_* 헬퍼 연결 |
| 7 | phy_start() 호출 누락 | 인터페이스 up 해도 링크 상태 머신 미가동, 항상 링크 다운 | ndo_open에서 phy_start() 반드시 호출 |
| 8 | resume에서 PHY 재초기화 누락 | suspend/resume 후 링크 불안정 또는 속도 강등 | resume 경로에서 phy_start() 재호출, PHY 드라이버 resume 콜백 확인 |
| 9 | phy_set_max_speed() 미호출 | MAC이 100M만 지원하는데 PHY가 1G 광고 → 상대편과 1G 협상 → 프레임 손실 | MAC 하드웨어 능력에 맞게 최대 속도 제한 |
| 10 | fixed-link인데 phy-handle 기술 | 존재하지 않는 PHY를 탐색하느라 probe 지연 또는 실패 | PHY 없는 직결 구성은 fixed-link만 사용, 둘을 동시에 기술하지 않기 |
첫 보드 Bring-up 시 최소 동작 검증 절차
새 MAC 드라이버를 작성한 후, 또는 새 보드에 기존 드라이버를 포팅한 후 PHY 동작을 확인하는 최소 절차입니다.
- PHY ID 확인 —
dmesg | grep -i phy로 PHY가 정상 탐지되었는지 확인합니다. ID가0xffff또는0x0000이면 MDIO 버스 자체의 문제입니다.# 정상 출력 예시 my-mac-mdio: probed my-mac 1c30000.ethernet eth0: PHY [my-mac-mdio:01] driver [RTL8211F] (irq=POLL) my-mac 1c30000.ethernet eth0: attached PHY driver (mii_bus:phy_addr=my-mac-mdio:01, irq=POLL) - 링크 업 확인 — 케이블 연결 후
ip link show eth0에서LOWER_UP표시와carrier상태를 확인합니다.ip link show eth0 # 정상: <BROADCAST,MULTICAST,UP,LOWER_UP> # 비정상: <BROADCAST,MULTICAST,UP> (LOWER_UP 없음 = 링크 없음) cat /sys/class/net/eth0/carrier # 1: 링크 업, 0: 링크 다운 - 속도/듀플렉스 확인 —
ethtool eth0으로 협상된 속도와 듀플렉스가 기대값인지 확인합니다.ethtool eth0 # Speed: 1000Mb/s, Duplex: Full, Auto-negotiation: on - ping 테스트 — 작은 패킷과 큰 패킷 모두 테스트합니다.
ping -c 10 192.168.1.1 ping -c 10 -s 1472 192.168.1.1 # MTU 근처 크기 - 에러 카운터 확인 —
ethtool -S eth0으로 CRC, carrier, frame 에러가 증가하지 않는지 확인합니다.ethtool -S eth0 | grep -iE 'err|crc|drop|miss|carrier' # 모든 에러 카운터가 0 또는 증가하지 않아야 정상 - 속도 강제 변경 테스트 — 100M 강제 설정 후 복귀하여
adjust_link/mac_link_up콜백이 올바르게 동작하는지 확인합니다.ethtool -s eth0 speed 100 duplex full autoneg off ping -c 5 192.168.1.1 ethtool -S eth0 | grep -iE 'err|crc' ethtool -s eth0 autoneg on # 원복 - 링크 플랩 테스트 — 케이블 분리/재연결을 3~5회 반복하면서
dmesg에 링크 up/down 로그가 정상 출력되는지, 복구 후 통신이 되는지 확인합니다. - 대역폭 측정 —
iperf3로 실제 throughput이 링크 속도의 90% 이상인지 확인합니다. 현저히 낮으면 DMA, NAPI, 또는 PHY 설정 문제일 수 있습니다.# 서버 측 iperf3 -s # 클라이언트 측 iperf3 -c 192.168.1.1 -t 30 # 1G 링크에서 ~940 Mbps 이상이면 정상
참고자료
- 이더넷 PHY (Physical Layer)
- 이더넷 (Ethernet)
- Network Device 드라이버 (net_device)
- ethtool
- Device Tree
- DSA Tagging
- Linux kernel networking PHY documentation
- 이더넷 PHY (Physical Layer) — PHY 하드웨어 계층, MDIO, phylib/phylink 프레임워크
- Network Device 드라이버 — MAC 쪽 링크 콜백과 큐 제어 계약 이해
- ethtool — 운영 환경에서 링크 정책과 통계를 읽는 방법