이더넷 PHY (Physical Layer)

이더넷 PHY는 MAC이 만든 프레임을 실제 전기·광 신호로 바꾸고, 반대로 선로에서 들어온 신호를 디지털 비트스트림으로 복원하는 계층입니다. 이 문서는 PHY의 역할과 계층 경계, MDIO 레지스터(Register) 접근, Clause 22/45와 MMD, phylib/phylink 수명주기, 분리형 PCS와 phylink_pcs, SerDes/SFP, EEE·downshift·EDPD·FEC 같은 기능 튜닝, Device Tree와 보드 bring-up, 신호 무결성(Integrity), 링크 장애 디버깅(Debugging) 절차를 커널 드라이버와 하드웨어 설계 관점에서 최대한 상세히 정리합니다.

전제 조건: 이더넷 (Ethernet) 문서를 먼저 읽고, 커널 쪽 연결은 Network Device 드라이버 (net_device) 문서를 함께 보세요. PHY는 L1 개념만 알면 끝나는 것이 아니라 MAC 드라이버, Device Tree, 보드 지연(Latency) 보상, 링크 협상 정책까지 연결해서 봐야 실제 문제를 풀 수 있습니다.
일상 비유: 이 개념은 통역기와 배선 기사를 합쳐 놓은 역할과 비슷합니다. MAC이 만든 디지털 프레임을 외부 선로가 이해할 수 있는 전기·광 신호로 번역하고, 케이블 특성에 맞춰 타이밍과 신호 품질까지 조정해야 하므로 단순한 포트 확장 칩보다 훨씬 많은 상태 머신과 아날로그 제약을 가집니다.

핵심 요약

  • PHY — MAC과 실제 매체 사이에서 부호화, 클럭 복구, 신호 송수신, 링크 감지를 담당하는 계층입니다.
  • MDIO — MAC이나 SoC가 PHY 레지스터를 읽고 쓰는 관리 버스(Bus)입니다. Clause 22와 Clause 45 두 주소 모델이 있습니다.
  • phylib — 커널의 공통 PHY 프레임워크로, phy_devicephy_driver를 통해 링크 상태 머신을 운영합니다.
  • phylink — MAC, PHY, PCS, SFP, fixed-link를 하나의 정책 계층으로 묶어 주는 상위 프레임워크입니다.
  • phy-mode — RGMII, SGMII, 1000BASE-X 같은 MAC-PHY 인터페이스 모드를 뜻하며, 이 설정이 틀리면 링크는 올라와도 CRC 오류와 속도 강등이 생길 수 있습니다.

단계별 이해

  1. PHY가 어디까지 담당하는지 구분하기
    MAC은 프레임을 만들고, PHY는 그 프레임을 선로에 맞는 신호로 바꿉니다. PCS/PMA/PMD 경계를 먼저 잡아야 로그와 보드 문제를 분리할 수 있습니다.
  2. MDIO로 상태를 읽는 법 익히기
    링크 업 여부, PHY ID, 광고 능력, RGMII 지연 같은 핵심 정보는 대부분 MDIO 레지스터에서 확인합니다. PHY 디버깅의 기본은 레지스터 관찰입니다.
  3. 커널 수명주기 이해하기
    probe에서 attach하고, open에서 phy_start() 혹은 phylink_start()를 호출하며, 링크 변화 시 MAC 재설정 콜백(Callback)이 들어오는 흐름을 따라가야 합니다.
  4. 보드와 링크 장애를 함께 점검하기
    phy-mode, reset GPIO, 클럭, 전원, 케이블, 상대편 광고 능력을 같이 봐야 합니다. PHY 문제는 드라이버 버그와 보드 타이밍 문제가 자주 섞여 나타납니다.
문서 관계: 이더넷 (Ethernet) 문서는 전체 프로토콜과 역사, 프레임, 오프로드까지 넓게 다룹니다. 이 문서는 그중 PHY만 깊게 파고들며, MAC 드라이버 계약은 Network Device 드라이버, 보드 기술은 Device Tree, 운영 제어는 ethtool 문서와 연결해서 읽는 구성을 전제로 합니다.

개요

이더넷 PHY의 핵심 임무는 세 가지입니다. 첫째, MAC이 제공한 병렬 또는 직렬 디지털 데이터를 선로 규격에 맞는 전기·광 신호로 변환합니다. 둘째, 상대편 장치와 링크 속도, 듀플렉스, pause, FEC 같은 능력을 협상합니다. 셋째, 온도·전압·신호 품질 변화에 따라 CDR(Clock and Data Recovery), equalization, amplitude 설정을 조정해 실제 배선 위에서 동작하도록 만듭니다.

즉 PHY는 단순한 "포트"가 아니라, 아날로그 회로와 디지털 상태 머신이 결합된 독립 장치입니다. 링크가 안 올라오거나 CRC가 증가할 때는 소프트웨어만 볼 일이 아니라, PHY 내부 지연 보상, reference clock, magnetics, 케이블 품질, 상대편 포트의 협상 정책까지 같이 봐야 합니다.

실무 감각: Link detected: yes가 찍힌다고 PHY 문제가 끝난 것이 아닙니다. 링크 업 이후에도 CRC 증가, carrier error, EEE 복귀 지연, 잘못된 speed/duplex 합의, RGMII skew 오류가 남을 수 있으므로 "업 여부"와 "링크 품질"을 반드시 분리해서 봐야 합니다.

MAC과 PHY의 경계

문제를 빨리 좁히려면 "어디까지가 MAC이고 어디부터 PHY인가"를 먼저 구분해야 합니다. MAC은 프레임 헤더, 주소 필터, pause 처리, DMA와 큐를 담당하고, PHY는 부호화/직렬화(Serialization)/클럭 복구/매체 송수신을 맡습니다. 1G 이하 임베디드 보드에서는 MAC과 외부 PHY가 RGMII로 연결되는 경우가 많고, 서버 NIC에서는 MAC/PCS가 SoC나 NIC 내부에 있고 외부 모듈은 PMD 역할만 하는 경우도 흔합니다.

구성 요소주요 책임대표 증상점검 포인트
MAC프레임 조립, 주소 필터, DMA, pause, 통계queue hang, TX timeout, 잘못된 offloadnet_device, DMA 링, IRQ, NAPI
MAC-PHY 인터페이스RGMII/SGMII 등 디지털 링크속도 강등, CRC, 불안정한 링크phy-mode, skew, clock, in-band status
PHY PCS/PMA부호화, AN, 직렬화, CDR링크 미협상, symbol errorMDIO 레지스터, 광고 능력, 벤더 확장 레지스터
PMD/매체전기·광 신호, magnetics, 커넥터거리 민감, 케이블 교체 시 개선케이블, 모듈, 커넥터, 온도, 전원 품질
MAC과 PHY의 경계 SoC / NIC MAC 프레임과 DMA MDIO 관리 버스 RGMII / SGMII / 1000BASE-X / USXGMII PHY PCS 부호화, AN 페이지 처리 PMA SerDes, CDR, equalization PMD 전기·광 송수신, MDI/laser Magnetics / 모듈 RJ45 magnetics, SFP cage 절연과 매체 적응 케이블 / 원격 PHY UTP, DAC, 광섬유, 백플레인 상대편 광고 능력과 품질 데이터 경로 MDIO/MDC MDI
MAC은 프레임과 큐를 다루고, PHY는 부호화·클럭 복구·매체 송수신을 담당합니다. MDIO는 데이터 경로가 아니라 PHY 관리 경로입니다.

PHY 내부 하위 계층

이더넷 PHY를 깊게 이해하려면 PHY를 단일 칩으로 보지 말고 PCS, PMA, PMD, MDI로 나눠 생각해야 합니다. 1000BASE-T 같은 구리 PHY는 PCS에서 부호화와 auto-negotiation control word 처리를 하고, PMA/PMD에서 echo cancellation, NEXT/FEXT 제거, adaptive equalization, timing recovery 같은 DSP 처리를 수행합니다. 반면 광 링크는 PMD가 레이저 드라이버, CDR, 광 수신기와 더 강하게 결합됩니다.

즉 "링크가 안 붙는다"는 하나의 증상도 PCS 문제인지, PMA의 CDR lock 실패인지, PMD의 광 세기 부족인지, MDI 쪽 배선 문제인지에 따라 전혀 다른 디버깅 절차가 필요합니다. 초기에 이 구분을 해 두면 드라이버와 보드 설계 문제를 훨씬 빠르게 분리할 수 있습니다.

하위 계층주요 역할구리 PHY 구현 예광/SerDes 구현 예문제 징후
PCS부호화, 스크램블링, AN control word 처리1000BASE-T 4D-PAM5 심볼 처리 준비8B/10B, 64B/66B, block lock협상 실패, block lock 불가
PMA직렬화, 클럭 복구, lane 정렬ADC/DAC와 DSP 사이 timing recoverySerDes, CDR, lane deskew링크 flap, symbol error
PMD실제 매체 송수신magnetics, hybrid, echo cancellationlaser/TIA, 광 세기, DAC/AOC거리 민감, 온도 민감, CRC 증가
MDI커넥터와 매체 접점RJ45, 트위스트 페어LC 커넥터, backplane, DAC케이블 교체 시 증상 변화
PHY 내부 TX/RX 파이프라인 TX 경로 MAC 데이터 PCS 스크램블, 부호화 PMA SerDes, timing PMD / 아날로그 프런트엔드 DAC, pre-emphasis, line driver 매체 RX 경로 매체 입력 PMD / 아날로그 프런트엔드 ADC, AGC, echo/NEXT/FEXT 제거 PMA CDR, lane 정렬 PCS 디코드, block lock MAC 입력 1000BASE-T 구리 PHY는 RX 경로에서 echo cancellation, 적응형 equalization, timing recovery가 매우 중요합니다.
PHY 내부에서는 단순 신호 증폭만 일어나는 것이 아니라, TX/RX 각각에서 부호화와 아날로그 보정, DSP가 길게 이어집니다.
왜 이 분리가 중요한가: block lock, PCS status, symbol error는 주로 PCS/PMA 경계 문제를 시사하고, 짧은 케이블에서만 좋아지거나 온도에 따라 증상이 달라지면 PMD 쪽 margin 부족일 가능성이 큽니다.

MDIO와 표준 레지스터

MDIO(Management Data Input/Output)는 PHY를 설정하고 상태를 읽는 가장 기본적인 관리 경로입니다. 10/100/1000 PHY에서는 Clause 22가, 고속 PHY와 복잡한 서브블록을 가진 장치에서는 Clause 45가 많이 쓰입니다. Clause 22는 5비트 PHY 주소와 5비트 레지스터 번호만 다루지만, Clause 45는 MMD(devad) 개념을 도입해 PMA/PCS/AN 등 기능 블록별 레지스터 공간을 분리합니다.

MDIO 물리 인터페이스

MDIO 버스는 두 개의 신호선으로 구성됩니다. MDC(Management Data Clock)는 MAC(또는 MDIO 컨트롤러)이 생성하는 단방향 클럭이고, MDIO(Management Data I/O)는 양방향 데이터 선입니다. 전기적으로 MDIO 선은 open-drain 방식이며 외부 pull-up 저항(1.5 kΩ ~ 10 kΩ)이 필요합니다. MDC의 최대 주파수는 IEEE 802.3에서 2.5 MHz로 규정하지만, 많은 PHY가 12.5 MHz 이상도 허용합니다. 보드 설계 시 pull-up 저항 값과 배선 길이가 잘못되면 특정 PHY만 간헐적으로 읽히지 않는 문제가 발생합니다.

신호방향특성설계 포인트
MDCMAC → PHY (단방향)클럭, 최대 2.5 MHz (IEEE 표준)rise/fall time 10 ns 이내 권장, 다중 PHY 시 fan-out 고려
MDIO양방향 (open-drain)데이터, 클럭 상승 엣지에 샘플링pull-up 1.5 kΩ~10 kΩ, 배선 길이 짧게 유지
MDIO 물리 인터페이스 MAC / MDIO 컨트롤러 MDC 생성, 프레임 구성 MDIO 드라이브/샘플 Vdd (3.3 V / 2.5 V / 1.8 V) pull-up R MDC (단방향 클럭) MDIO (양방향 데이터, open-drain) PHY (slave) 주소 매칭, 레지스터 응답 turn-around 후 데이터 구동 트랜잭션 흐름 MAC이 MDC 클럭을 구동하며 MDIO 선에 프레임(Preamble + ST + OP + PHYAD + REGAD + TA + Data)을 직렬 전송합니다. 읽기 시 turn-around(TA) 구간에서 MAC은 MDIO를 해제(Hi-Z)하고, PHY가 선을 구동해 16비트 데이터를 돌려줍니다.
MDIO는 MDC(클럭) + MDIO(데이터)의 2선 인터페이스이며, MDIO는 open-drain이므로 외부 pull-up이 필수입니다.

MDIO 프레임 포맷

하나의 MDIO 트랜잭션은 직렬(serial)로 전송되는 프레임으로 구성됩니다. Clause 22 프레임은 총 64비트(프리앰블(Preamble) 32비트 + 프레임 32비트)이며, Clause 45는 주소 프레임과 데이터 프레임을 두 번에 나눠 보내 16비트 레지스터 주소 공간을 확보합니다.

Clause 22 MDIO 프레임 포맷 읽기 (Read) 프레임 Preamble 32 × 1 ST 01 OP 10 (Read) PHYAD 5비트 (PHY 주소) REGAD 5비트 (레지스터 주소) TA Z + 0 DATA 16비트 (PHY → MAC) Idle 쓰기 (Write) 프레임 Preamble 32 × 1 ST 01 OP 01 (Write) PHYAD 5비트 (PHY 주소) REGAD 5비트 (레지스터 주소) TA 1 + 0 DATA 16비트 (MAC → PHY) Idle 필드 상세 Preamble: 연속 1 (32비트) — 버스 동기화 및 PHY wake-up, 일부 PHY는 축약 허용 ST: Start of Frame — Clause 22는 01, Clause 45는 00으로 구분 OP: Operation — Read=10, Write=01 (Clause 22) / Address=00, Read=11, Write=01 (Clause 45) TA: Turn-Around — 읽기 시 MAC이 MDIO를 Hi-Z로 놓고 PHY가 선을 잡는 전환 구간 (2비트)
Clause 22 프레임은 Preamble(32비트) + ST(2) + OP(2) + PHYAD(5) + REGAD(5) + TA(2) + DATA(16) = 총 64비트입니다. 읽기 시 TA 구간에서 버스 방향이 전환됩니다.
Clause 45 MDIO 프레임 포맷 1단계: 주소 프레임 (Address) Preamble 32 × 1 ST 00 OP 00 (Addr) PRTAD 5비트 (포트 주소) DEVAD 5비트 (MMD 번호) TA 1 + 0 ADDRESS 16비트 (레지스터 주소) 2단계: 읽기/쓰기 프레임 (Read/Write) Preamble 32 × 1 ST 00 OP 11/01 PRTAD 5비트 (포트 주소) DEVAD 5비트 (MMD 번호) TA Z+0/10 DATA 16비트 (읽기/쓰기 값) Clause 22 vs Clause 45 핵심 차이 ST 필드: Clause 22 = 01, Clause 45 = 00 → PHY가 프레임 타입을 즉시 구분합니다. 주소 확장: Clause 22는 REGAD 5비트(32개), Clause 45는 2단계 트랜잭션으로 16비트(65,536개) 레지스터 접근 가능합니다. OP 코드: Clause 45는 Address(00), Write(01), Read(11), Post-Read-Inc-Address(10) 4가지를 지원합니다.
Clause 45는 주소 프레임으로 레지스터 위치를 먼저 지정한 뒤, 별도의 읽기/쓰기 프레임으로 데이터를 주고받는 2단계 구조입니다.
Clause 45의 Post-Read-Increment-Address: OP 코드 10을 사용하면 현재 주소의 데이터를 읽은 뒤 내부 주소 카운터가 자동으로 +1 됩니다. 연속된 레지스터를 빠르게 읽어야 하는 PCS lane status 같은 진단에서 유용하며, 주소 프레임을 매번 다시 보내지 않아도 됩니다.

리눅스 커널 MDIO API

리눅스 커널에서 MDIO 버스는 struct mii_bus로 추상화됩니다. MAC 드라이버나 전용 MDIO 컨트롤러 드라이버가 이 구조체를 등록하고, readwrite 콜백을 통해 실제 하드웨어 접근을 수행합니다. PHY 드라이버는 phy_read() / phy_write()를 통해 간접적으로 이 버스를 사용하며, Clause 45 접근은 phy_read_mmd() / phy_write_mmd()로 분리되어 있습니다.

/* MDIO 버스 등록 — MAC 또는 MDIO 컨트롤러 드라이버 */
struct mii_bus *bus = mdiobus_alloc();
bus->name   = "my-mdio";
bus->read   = my_mdio_read;    /* Clause 22 read 콜백 */
bus->write  = my_mdio_write;   /* Clause 22 write 콜백 */
bus->read_c45  = my_mdio_c45_read;  /* Clause 45 read 콜백 (v5.18+) */
bus->write_c45 = my_mdio_c45_write; /* Clause 45 write 콜백 (v5.18+) */
bus->parent = &pdev->dev;
bus->phy_mask = 0;  /* 0이면 모든 주소 스캔, 비트마스크로 특정 주소 제외 */
mdiobus_register(bus);

/* PHY 드라이버에서 레지스터 접근 */
int val = phy_read(phydev, MII_BMSR);            /* Clause 22 */
int pcs = phy_read_mmd(phydev, MDIO_MMD_PCS, 0x0001); /* Clause 45 */
phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x8000, 0x0001);  /* Clause 45 쓰기 */

/* Clause 22만 지원하는 PHY에서 간접 Clause 45 접근 (MII_MMD_CTRL/DATA) */
phy_write(phydev, MII_MMD_CTRL, devad);
phy_write(phydev, MII_MMD_DATA, reg);
phy_write(phydev, MII_MMD_CTRL, devad | MII_MMD_CTRL_NOINCR);
val = phy_read(phydev, MII_MMD_DATA);
주의: 커널 v5.18 이전에는 read_c45/write_c45 콜백이 없었고, Clause 45 접근도 read/write 콜백에 특수 인코딩(MII_ADDR_C45 플래그)을 섞어 전달했습니다. 레거시 드라이버를 다룰 때는 이 차이를 인지해야 합니다.

mii_bus 수명주기와 등록 API

struct mii_bus는 MDIO 컨트롤러를 커널에 등록하는 핵심 구조체입니다. 할당부터 해제까지의 전체 수명주기와 각 필드의 역할을 이해해야 PHY 탐색, 주소 마스킹, reset 타이밍 같은 실무 문제를 올바르게 처리할 수 있습니다.

/* mii_bus 주요 필드 (include/linux/mdio.h, include/linux/phy.h) */
struct mii_bus {
    const char  *name;       /* 버스 이름 (sysfs에 표시) */
    char        id[MII_BUS_ID_SIZE]; /* 고유 ID (Device Tree에서 자동 생성) */
    struct device *parent;   /* 부모 디바이스 (MAC 또는 MDIO 컨트롤러) */
    void        *priv;       /* 드라이버 전용 데이터 */

    /* Clause 22 콜백: 반드시 구현해야 합니다 */
    int (*read)(struct mii_bus *bus, int addr, int regnum);
    int (*write)(struct mii_bus *bus, int addr, int regnum, u16 val);

    /* Clause 45 콜백 (v5.18+): NULL이면 Clause 45 미지원 */
    int (*read_c45)(struct mii_bus *bus, int addr, int devad, int regnum);
    int (*write_c45)(struct mii_bus *bus, int addr, int devad, int regnum, u16 val);

    /* PHY reset 콜백: 버스 등록 시 한 번 호출됩니다 */
    int (*reset)(struct mii_bus *bus);

    u32  phy_mask;   /* 비트가 1인 주소는 스캔에서 제외됩니다 */
    u32  phy_ignore; /* (v6.1+) phy_mask와 유사, 별도 용도 */

    struct mdio_device *mdio_map[PHY_MAX_ADDR]; /* 주소별 장치 맵 */
    struct mutex mdio_lock;  /* 버스 접근 직렬화 */
};
/* 완전한 mii_bus 등록/해제 수명주기 */
static int my_mdio_read(struct mii_bus *bus, int addr, int regnum)
{
    struct my_priv *priv = bus->priv;
    u32 cmd;

    /* MDIO 컨트롤러 레지스터에 읽기 명령 구성 */
    cmd = MDIO_CMD_READ | (addr << 5) | regnum;
    writel(cmd, priv->regs + MDIO_CMD_REG);

    /* 완료 대기 — 보통 수 us, 최대 수백 us */
    if (readl_poll_timeout(priv->regs + MDIO_STATUS_REG,
                          cmd, cmd & MDIO_DONE, 10, 10000))
        return -ETIMEDOUT;

    return readl(priv->regs + MDIO_DATA_REG) & 0xffff;
}

static int my_mdio_write(struct mii_bus *bus, int addr, int regnum, u16 val)
{
    struct my_priv *priv = bus->priv;
    u32 cmd;

    cmd = MDIO_CMD_WRITE | (addr << 5) | regnum;
    writel(val, priv->regs + MDIO_DATA_REG);
    writel(cmd, priv->regs + MDIO_CMD_REG);

    return readl_poll_timeout(priv->regs + MDIO_STATUS_REG,
                              cmd, cmd & MDIO_DONE, 10, 10000)
           ? -ETIMEDOUT : 0;
}

static int my_probe(struct platform_device *pdev)
{
    struct mii_bus *bus;
    struct my_priv *priv;

    /* mdiobus_alloc_size(): priv 크기를 인자로 받아 한 번에 할당합니다 */
    bus = mdiobus_alloc_size(sizeof(*priv));
    if (!bus)
        return -ENOMEM;

    priv = bus->priv;     /* mdiobus_alloc_size가 priv 포인터를 설정합니다 */
    priv->regs = devm_ioremap_resource(&pdev->dev, ...);

    bus->name    = "my-soc-mdio";
    bus->parent  = &pdev->dev;
    bus->read    = my_mdio_read;
    bus->write   = my_mdio_write;
    bus->phy_mask = 0;  /* 모든 주소 스캔 */

    /* Device Tree 기반이면 of_mdiobus_register()로 DT 노드에서 PHY를 탐색합니다 */
    snprintf(bus->id, MII_BUS_ID_SIZE, "%s", pdev_name(pdev));
    return of_mdiobus_register(bus, pdev->dev.of_node);
}

static void my_remove(struct platform_device *pdev)
{
    struct mii_bus *bus = platform_get_drvdata(pdev);

    mdiobus_unregister(bus);  /* PHY 장치들을 모두 해제합니다 */
    mdiobus_free(bus);        /* mii_bus 메모리를 해제합니다 */
}
함수역할사용 시점
mdiobus_alloc()mii_bus 구조체 할당 (priv 없음)priv가 불필요할 때
mdiobus_alloc_size(sz)mii_bus + sz 바이트 priv 할당드라이버별 데이터가 필요할 때
mdiobus_register(bus)버스 등록 + 전체 주소 스캔비 DT 환경에서 사용
of_mdiobus_register(bus, np)DT 기반 등록 (노드에서 PHY 정보 파싱)Device Tree 기반 시스템
devm_mdiobus_register(dev, bus)devres 관리 등록 (remove에서 자동 해제)드라이버 remove 단순화
mdiobus_unregister(bus)등록 해제, 연결된 PHY 제거드라이버 remove 시
mdiobus_free(bus)메모리 해제unregister 후 호출
실무 팁: devm_mdiobus_register()를 사용하면 remove 콜백에서 mdiobus_unregister()를 직접 호출하지 않아도 됩니다. 다만 mdiobus_alloc()은 devres가 아니므로, devm_mdiobus_alloc_size()(v6.3+)와 함께 쓰는 것이 가장 깔끔합니다.

MDIO 버스 락킹과 직접 접근 API

MDIO 버스는 mdio_lock 뮤텍스로 보호됩니다. PHY 드라이버가 사용하는 phy_read()/phy_write()는 내부에서 이 락을 잡고 풀지만, 여러 레지스터를 원자적으로 읽거나 써야 하는 경우에는 락을 직접 관리해야 합니다.

/* 버스 레벨 접근 — 락 자동 관리 (일반적인 경우) */
int val = mdiobus_read(bus, phy_addr, regnum);      /* 락 잡기 → read → 풀기 */
mdiobus_write(bus, phy_addr, regnum, val);           /* 락 잡기 → write → 풀기 */

/* 버스 레벨 접근 — 락 없음 (호출자가 이미 락을 잡은 상태) */
int val = __mdiobus_read(bus, phy_addr, regnum);
__mdiobus_write(bus, phy_addr, regnum, val);

/* PHY 레벨 접근 — 락 자동 관리 */
int val = phy_read(phydev, regnum);                   /* 내부에서 mdio_lock 획득 */
phy_write(phydev, regnum, val);

/* 여러 레지스터를 원자적으로 접근해야 할 때 — 직접 락 관리 */
mutex_lock(&phydev->mdio.bus->mdio_lock);

/* 락 보유 중에는 __mdiobus_read/write 사용 */
int page = __mdiobus_read(phydev->mdio.bus, phydev->mdio.addr, 0x1f);
__mdiobus_write(phydev->mdio.bus, phydev->mdio.addr, 0x1f, 0x0a43);
val = __mdiobus_read(phydev->mdio.bus, phydev->mdio.addr, 0x19);
__mdiobus_write(phydev->mdio.bus, phydev->mdio.addr, 0x1f, page); /* 원래 페이지 복원 */

mutex_unlock(&phydev->mdio.bus->mdio_lock);
주의: phy_read()__mdiobus_read()를 혼용하면 데드락이 발생합니다. phy_read()는 내부에서 mdio_lock을 잡으므로, 이미 락을 잡은 상태에서 호출하면 같은 뮤텍스를 두 번 잡게 됩니다. 락 보유 중에는 반드시 __mdiobus_read()/__mdiobus_write()를 사용하세요.

레지스터 수정 헬퍼 API

PHY 레지스터의 특정 비트만 변경하는 작업은 매우 빈번합니다. 매번 read-modify-write를 직접 구현하면 코드가 반복되고 실수가 생기므로, 커널은 phy_modify() 계열의 헬퍼를 제공합니다. 이 함수들은 내부에서 락을 잡고, 읽기-수정-쓰기를 원자적으로 수행합니다.

/* phy_modify(phydev, regnum, mask, set)
 * 동작: val = phy_read(reg); val = (val & ~mask) | set; phy_write(reg, val);
 * 반환값: 변경 전 원래 값 (음수면 에러) */

/* 예: BMCR에서 AN enable 비트를 끄고 강제 100M full duplex 설정 */
phy_modify(phydev, MII_BMCR,
           BMCR_ANENABLE | BMCR_SPEED100 | BMCR_FULLDPLX,  /* 클리어할 비트 마스크 */
           BMCR_SPEED100 | BMCR_FULLDPLX);                    /* 세팅할 비트 */

/* phy_set_bits(): 특정 비트만 세팅 (mask = set = bits) */
phy_set_bits(phydev, MII_BMCR, BMCR_ANENABLE | BMCR_ANRESTART);

/* phy_clear_bits(): 특정 비트만 클리어 (mask = bits, set = 0) */
phy_clear_bits(phydev, MII_BMCR, BMCR_PDOWN);

/* Clause 45 MMD 버전도 동일 패턴입니다 */
phy_modify_mmd(phydev, MDIO_MMD_PCS, 0x0001, mask, set);
phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, 0x8000, BIT(3));
phy_clear_bits_mmd(phydev, MDIO_MMD_AN, 0x0010, BIT(0));
함수동작용도
phy_modify(dev, reg, mask, set)(read & ~mask) | set → write복수 비트를 한 번에 변경
phy_set_bits(dev, reg, bits)read | bits → write특정 비트 켜기
phy_clear_bits(dev, reg, bits)read & ~bits → write특정 비트 끄기
phy_modify_mmd(dev, devad, reg, mask, set)Clause 45 read-modify-writeMMD 레지스터 수정
phy_set_bits_mmd(dev, devad, reg, bits)Clause 45 비트 세팅MMD 비트 켜기
phy_clear_bits_mmd(dev, devad, reg, bits)Clause 45 비트 클리어MMD 비트 끄기
실무 팁: phy_modify()는 반환값이 변경 전 원래 레지스터 값입니다. 이를 활용하면 "이전 상태를 확인하면서 동시에 변경"하는 패턴을 한 번의 호출로 처리할 수 있습니다. 에러 시에는 음수가 반환되므로 반드시 ret < 0 검사를 해야 합니다.

벤더 페이지 전환 API

Clause 22만 지원하는 PHY에서도 벤더 확장 레지스터에 접근해야 하는 경우가 흔합니다. 대부분의 벤더 PHY(Realtek, Marvell, Microchip 등)는 "페이지 전환 레지스터"(보통 reg 0x1f 또는 reg 0x16)를 통해 레지스터 공간을 확장합니다. 커널은 이를 안전하게 처리하기 위해 phy_read_paged() 계열 API를 제공합니다.

/* PHY 드라이버가 페이지 전환 방식을 선언합니다 (probe 시) */
static int my_phy_read_page(struct phy_device *phydev)
{
    return __phy_read(phydev, 0x1f);  /* 현재 페이지 번호 반환 */
}

static int my_phy_write_page(struct phy_device *phydev, int page)
{
    return __phy_write(phydev, 0x1f, page);  /* 페이지 전환 */
}

/* phy_driver 구조체에서 콜백 등록 */
static struct phy_driver my_phy_drivers[] = {{
    .phy_id      = 0x001cc840,
    .phy_id_mask = 0xffffff00,
    .name        = "My Vendor PHY",
    .read_page   = my_phy_read_page,
    .write_page  = my_phy_write_page,
    /* ... 기타 콜백 ... */
}};

/* 페이지 전환 API 사용 — 자동으로 페이지 전환 → 접근 → 원래 페이지 복원 */
int val = phy_read_paged(phydev, 0x0a43, 0x19);   /* 페이지 0x0a43, 레지스터 0x19 읽기 */
phy_write_paged(phydev, 0x0d08, 0x15, 0x1234);    /* 페이지 0x0d08, 레지스터 0x15 쓰기 */
phy_modify_paged(phydev, 0x0a46, 0x14, BIT(5), 0); /* 페이지 0x0a46에서 bit 5 클리어 */

/* 여러 레지스터를 같은 페이지에서 접근할 때 — 수동 페이지 선택 */
int old_page = phy_select_page(phydev, 0x0a43);  /* 락 획득 + 페이지 전환 */
if (old_page >= 0) {
    val = __phy_read(phydev, 0x19);               /* 락 보유 중이므로 __ 접두사 사용 */
    __phy_write(phydev, 0x1a, 0x00ff);
}
phy_restore_page(phydev, old_page, val);          /* 원래 페이지 복원 + 락 해제 */
함수동작락 관리
phy_read_paged(dev, page, reg)페이지 전환 → read → 복원내부에서 자동
phy_write_paged(dev, page, reg, val)페이지 전환 → write → 복원내부에서 자동
phy_modify_paged(dev, page, reg, mask, set)페이지 전환 → modify → 복원내부에서 자동
phy_select_page(dev, page)락 획득 + 페이지 전환호출자가 restore 필수
phy_restore_page(dev, old_page, ret)페이지 복원 + 락 해제select_page와 쌍
주의: phy_select_page()phy_restore_page()는 반드시 쌍으로 사용해야 합니다. phy_select_page()mdio_lock을 잡은 채로 반환하므로, 반드시 phy_restore_page()로 락을 풀어야 합니다. 이 구간에서 phy_read()(락을 잡는 함수)를 호출하면 데드락이 발생합니다 — __phy_read()를 사용하세요.

MDIO 디바이스 모델과 mdio_driver

모든 PHY(phy_device)는 mdio_device를 내장하고 있으며, mdio_device는 MDIO 버스 위의 일반 장치입니다. PHY가 아닌 MDIO 장치(예: 이더넷 스위치, SFP 모듈 관리 칩, retimer)를 다룰 때는 mdio_driver를 사용합니다.

/* mdio_device: MDIO 버스 위 모든 장치의 기본 구조체 */
struct mdio_device {
    struct device   dev;
    struct mii_bus *bus;         /* 소속 MDIO 버스 */
    int            addr;         /* MDIO 주소 (0..31) */
    int            flags;        /* MDIO_DEVICE_FLAG_* */
    struct gpio_desc *reset_gpio; /* reset GPIO (DT에서 파싱) */
    unsigned int   reset_assert_delay;   /* reset assert 유지 시간 (us) */
    unsigned int   reset_deassert_delay; /* reset deassert 후 대기 시간 (us) */
};

/* phy_device는 mdio_device를 내장합니다 */
struct phy_device {
    struct mdio_device mdio;     /* 첫 번째 멤버 — 구조체 캐스팅 가능 */
    struct phy_driver *drv;
    u32  phy_id;
    int  speed, duplex;
    int  link;
    /* ... 수십 개의 상태 필드 ... */
};

/* PHY가 아닌 MDIO 장치를 위한 드라이버 등록 예시 */
static int my_switch_probe(struct mdio_device *mdiodev)
{
    /* mdiodev->bus, mdiodev->addr를 사용해 레지스터 접근 */
    int id = mdiobus_read(mdiodev->bus, mdiodev->addr, 0x00);
    dev_info(&mdiodev->dev, "switch ID: 0x%04x\n", id);
    return 0;
}

static const struct of_device_id my_switch_of_match[] = {
    { .compatible = "my,switch-chip" },
    { }
};

static struct mdio_driver my_switch_driver = {
    .mdiodrv.driver = {
        .name           = "my-switch",
        .of_match_table = my_switch_of_match,
    },
    .probe = my_switch_probe,
};
mdio_module_driver(my_switch_driver);

MDIO API 실전 패턴

PHY 드라이버와 MAC 드라이버에서 가장 자주 사용되는 MDIO 접근 패턴들을 정리합니다. 각 패턴은 실제 커널 드라이버에서 반복적으로 나타나는 코드를 단순화한 것입니다.

/* ━━━━━━ 패턴 1: PHY ID 읽기와 검증 ━━━━━━ */
static u32 read_phy_id(struct phy_device *phydev)
{
    int id1 = phy_read(phydev, MII_PHYSID1);  /* reg 0x02 */
    int id2 = phy_read(phydev, MII_PHYSID2);  /* reg 0x03 */

    if (id1 < 0 || id2 < 0)
        return 0xffffffff;    /* 읽기 실패 — 버스 미연결 가능 */

    return (id1 << 16) | id2;   /* 예: RTL8211F = 0x001cc916 */
}

/* ━━━━━━ 패턴 2: 소프트 리셋과 완료 대기 ━━━━━━ */
static int my_phy_soft_reset(struct phy_device *phydev)
{
    int ret;

    phy_set_bits(phydev, MII_BMCR, BMCR_RESET);

    /* BMCR_RESET 비트가 자동으로 0이 될 때까지 대기 (최대 500ms) */
    ret = phy_read_poll_timeout(phydev, MII_BMCR, ret,
                                !(ret & BMCR_RESET),
                                1000, 500000, false);
    if (ret)
        phydev_err(phydev, "soft reset timeout\n");

    return ret;
}

/* ━━━━━━ 패턴 3: 링크 상태 확인 (래치 비트 고려) ━━━━━━ */
static bool check_link_status(struct phy_device *phydev)
{
    int bmsr;

    /* 첫 번째 읽기: 래치된 값을 소모합니다 */
    bmsr = phy_read(phydev, MII_BMSR);
    if (bmsr < 0)
        return false;

    /* 두 번째 읽기: 현재 링크 상태가 반영됩니다 */
    bmsr = phy_read(phydev, MII_BMSR);
    return bmsr >= 0 && (bmsr & BMSR_LSTATUS);
}

/* ━━━━━━ 패턴 4: AN 재시작과 완료 대기 ━━━━━━ */
static int restart_and_wait_an(struct phy_device *phydev)
{
    int ret;

    /* AN enable + restart AN 비트를 동시에 세팅합니다 */
    phy_set_bits(phydev, MII_BMCR, BMCR_ANENABLE | BMCR_ANRESTART);

    /* AN complete 비트 대기 — 케이블 길이와 상대편 PHY에 따라 수 초 */
    ret = phy_read_poll_timeout(phydev, MII_BMSR, ret,
                                ret & BMSR_ANEGCOMPLETE,
                                50000, 5000000, false);
    return ret;
}

/* ━━━━━━ 패턴 5: loopback 설정 (PHY 내부 루프백) ━━━━━━ */
static int set_phy_loopback(struct phy_device *phydev, bool enable)
{
    if (enable)
        return phy_set_bits(phydev, MII_BMCR, BMCR_LOOPBACK);
    else
        return phy_clear_bits(phydev, MII_BMCR, BMCR_LOOPBACK);
}

/* ━━━━━━ 패턴 6: 벤더 확장 레지스터로 RGMII 지연 설정 ━━━━━━ */
/* Realtek RTL8211F 기준 예시 */
static int rtl8211f_set_rgmii_delay(struct phy_device *phydev, bool tx, bool rx)
{
    u16 val = 0;

    if (tx) val |= BIT(1);   /* TX delay enable */
    if (rx) val |= BIT(0);   /* RX delay enable */

    return phy_modify_paged(phydev, 0x0d08, 0x11,
                            BIT(1) | BIT(0), val);
}

/* ━━━━━━ 패턴 7: Clause 45 EEE 광고 능력 설정 ━━━━━━ */
static int set_eee_advertisement(struct phy_device *phydev, bool enable_1g)
{
    u16 adv = 0;

    if (enable_1g)
        adv = MDIO_EEE_1000T;

    /* MMD 7 (AN), 레지스터 0x003c: EEE Advertisement */
    return phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, adv);
}
MDIO API 계층 구조 PHY 드라이버 / MAC 드라이버 사용자 공간: ethtool, sysfs, mdio-tools PHY 레벨 API phy_read() / phy_write() phy_modify() / phy_set_bits() phy_read_paged() MMD (Clause 45) API phy_read_mmd() / phy_write_mmd() phy_modify_mmd() phy_set_bits_mmd() genphy 헬퍼 genphy_read_status() genphy_config_aneg() genphy_soft_reset() 페이지 전환 API phy_select_page() phy_restore_page() read_page / write_page mdio_lock 뮤텍스 (직렬화 계층) phy_read → mutex_lock → bus->read → mutex_unlock 버스 레벨 API mdiobus_read() / __mdiobus_read() mdiobus_c45_read() 하드웨어 콜백 bus->read() / bus->write() bus->read_c45() / bus->write_c45()
PHY 드라이버는 phy_read() 등 상위 API를 사용하고, 이 API들은 내부에서 mdio_lock을 거쳐 하드웨어 콜백에 도달합니다.

MDIO 에러 처리와 디버깅 패턴

MDIO 접근은 실패할 수 있습니다. 버스 타임아웃, PHY 미응답(0xffff), reset 미완료 등이 흔한 원인입니다. 커널 MDIO API는 에러 시 음수를 반환하고, 일부 PHY는 미연결 주소에서 0xffff를 돌려줍니다. 올바른 에러 처리 패턴을 따르지 않으면 잘못된 레지스터 값으로 PHY를 설정해 버리는 사고가 생깁니다.

/* 올바른 에러 처리 패턴 */
static int my_phy_config(struct phy_device *phydev)
{
    int ret;

    /* phy_read()는 에러 시 음수, 성공 시 0..0xffff */
    ret = phy_read(phydev, MII_BMSR);
    if (ret < 0) {
        phydev_err(phydev, "BMSR read failed: %d\n", ret);
        return ret;
    }

    /* 0xffff는 PHY 미연결 또는 버스 문제를 의미할 수 있습니다 */
    if (ret == 0xffff) {
        phydev_warn(phydev, "BMSR all-ones — PHY not responding\n");
        return -ENODEV;
    }

    /* phy_modify()는 에러 시 음수, 성공 시 변경 전 원래 값 */
    ret = phy_modify(phydev, MII_BMCR, BMCR_PDOWN, 0);
    if (ret < 0)
        return ret;

    /* phy_write()는 에러 시 음수, 성공 시 0 */
    ret = phy_write(phydev, MII_ADVERTISE, ADVERTISE_ALL | ADVERTISE_CSMA);
    return ret;
}

/* 디버깅 시 레지스터 덤프 패턴 */
static void dump_phy_regs(struct phy_device *phydev)
{
    int i, val;

    phydev_info(phydev, "=== PHY register dump (Clause 22) ===\n");
    for (i = 0; i < 32; i++) {
        val = phy_read(phydev, i);
        if (val < 0)
            phydev_info(phydev, "  reg[0x%02x] = ERROR (%d)\n", i, val);
        else
            phydev_info(phydev, "  reg[0x%02x] = 0x%04x\n", i, val);
    }
}
증상가능한 원인확인 방법
모든 레지스터가 0xffffPHY 전원 미인가, reset 미해제, 주소 불일치전원/reset GPIO 확인, DT reg 속성과 PHY 스트랩 핀 대조
모든 레지스터가 0x0000MDIO 데이터 라인 단선(pull-down 상태)오실로스코프로 MDIO 파형 확인
읽기 타임아웃 (-ETIMEDOUT)MDC 클럭 미공급, 컨트롤러 클럭 게이팅SoC 클럭 트리 확인, MDC 핀 오실로스코프 측정
특정 주소만 응답 없음PHY 스트랩 핀 설정 오류, mux 선택 누락다른 주소 스캔, 스트랩 핀 설계도 확인
읽기 값이 매번 다름버스 신호 무결성 문제, pull-up 부족MDC/MDIO 파형 품질 확인, 2.2kΩ pull-up 유무
실무 팁: 커널 부팅 시 mdio_bus 드라이버가 PHY를 스캔하는 로그는 dmesg | grep -i mdio로 확인할 수 있습니다. PHY가 아예 보이지 않으면 MDIO 컨트롤러 드라이버 로드 여부(lsmod | grep mdio)부터 확인하고, ls /sys/bus/mdio_bus/devices/로 커널이 인식한 MDIO 장치 목록을 조회하세요.
모드주소 구조적용 범위특징
Clause 22PHY 주소 5비트 + 레지스터 5비트주로 1G 이하 PHY구조가 단순하고 기본 상태 조회에 적합
Clause 45PHY 주소 + MMD(devad) + 16비트 레지스터10G 이상, 복합 PHYPMA/PCS/AN/FEC를 블록별로 분리해 접근
MDIO 접근 흐름 MAC 드라이버 phy_read(), phy_write() MDIO 컨트롤러 MDC 생성, 프레임 전송 Clause 22 PHY addr + reg 0x00..0x1f Clause 45 PHY addr + devad + reg PMA / PCS / AN / vendor MMD PHY 레지스터 BMCR / BMSR PHYID1 / PHYID2 AN 광고 / 링크 상태 RGMII delay / EEE / 벤더 확장 관리 API 단순 주소 모델 MMD 분리
Clause 22는 간단한 32개 레지스터 공간을, Clause 45는 기능 블록별 MMD 주소 공간(Address Space)을 제공합니다.
레지스터Clause 22 주소의미실무 활용
BMCR0x00reset, speed select, duplex, autoneg enable강제 속도/듀플렉스 설정 여부 확인
BMSR0x01link, autoneg capable, autoneg complete링크 업과 협상 완료 여부 점검
PHYID10x02벤더 OUI 상위 비트PHY 모델 식별
PHYID20x03OUI 하위 비트 + 모델/리비전드라이버 매칭 검증
ANAR / ANLPAR0x04 / 0x05광고 능력 / 상대편 응답속도, pause, duplex 불일치 원인 파악
ANER0x06AN expansion (parallel detect, page received)상대편이 AN을 지원하는지 확인
GBCR / GBSR0x09 / 0x0A1000BASE-T control/statusmaster/slave 역할, 1G 광고/결과
GBESR0x0Fextended status (1000BASE-T/X 지원 여부)PHY가 1G 이상을 지원하는지 확인

BMCR과 BMSR 비트 맵

BMCR(Basic Mode Control Register, 0x00)과 BMSR(Basic Mode Status Register, 0x01)은 모든 Clause 22 PHY가 반드시 구현해야 하는 핵심 레지스터입니다. BMCR은 PHY 동작을 제어하고, BMSR은 현재 상태와 PHY의 능력을 보고합니다. 특히 BMSR의 링크 상태 비트(bit 2)는 래치(Latch) 동작을 하는 PHY가 많아서, 링크가 한 번이라도 끊어지면 0으로 래치되고 다시 읽어야 현재 상태가 반영됩니다.

BMCR — Basic Mode Control Register (0x00) 15 Reset 14 Loopback 13 Speed[0] 12 AN Enable 11 Power Down 10 Isolate 9 Restart AN 8 Duplex 7 COL Test 6 Speed[1] 5:0 Reserved BMCR 주요 비트 해석 bit 15 (Reset): 1을 쓰면 PHY 소프트 리셋, 완료되면 자동으로 0으로 돌아갑니다. 리셋 후 모든 레지스터가 기본값으로 돌아갑니다. bit 13,6 (Speed): {6,13} = 00→10M, 01→100M, 10→1G. AN이 켜져 있으면 이 필드 대신 AN 결과가 적용됩니다. bit 12 (AN Enable): 1이면 Auto-Negotiation 활성. 0으로 끄면 Speed/Duplex 비트가 강제 적용됩니다. BMSR — Basic Mode Status Register (0x01) 15 100BASE-T4 14 100-FD 13 100-HD 12 10-FD 11 10-HD 10:7 Rsvd 6 MF Pre 5 AN Done 4 Rmt Fault 3 AN Able 2 Link (Latch) 1 Jabber 0 Ext Cap
BMCR의 Speed 비트는 {bit6, bit13} 조합으로 해석하며, BMSR bit 2(Link)는 래치되므로 두 번 읽어야 현재 상태가 됩니다.
# 네트워크 인터페이스가 어떤 PHY에 연결되었는지 확인
readlink -f /sys/class/net/eth0/phydev

# MDIO 버스에 보이는 PHY 목록과 ID 확인
grep . /sys/bus/mdio_bus/devices/*/phy_id

# 기본 링크 정보와 광고 능력 확인
ethtool eth0

# 드라이버가 찍는 PHY 로그 확인
dmesg | grep -i -E "phy|mdio|link"
주의: BMSR의 링크 비트는 래치 동작을 하는 PHY가 많아서 한 번 읽어서 끝내면 오판할 수 있습니다. PHY 디버깅에서는 같은 레지스터를 두 번 읽거나, 드라이버가 이미 해석한 ethtool 출력과 함께 비교하는 습관이 필요합니다.

Clause 45와 MMD 주소 공간

Clause 45의 핵심은 PHY를 하나의 납작한 레지스터 맵으로 보지 않고, 기능 블록별 MMD(Manageable Device)로 나누는 데 있습니다. 10G 이상 PHY나 복합 SerDes는 PMA/PMD, PCS, Auto-Negotiation, vendor-specific 블록이 분리되어 있어야 디버깅과 기능 확장이 가능합니다. 리눅스 커널도 이를 mdiobus_c45_read() 같은 API와 MDIO_MMD_* 매크로(Macro)로 반영합니다.

devadMMD 이름대표 내용실무 활용
1PMA/PMDsignal detect, PMD control, optical/copper 특성광 세기, lane signal, 전기 특성 확인
2WISWAN 인터페이스 하위 계층특수 WAN PHY에서 사용
3PCSblock lock, fault, PCS statusSerDes lock, alignment, fault 분리
4PHY XSPHY 확장 SerDes 계층고속 lane 상태 확인
5DTE XSDTE 쪽 확장 SerDes호스트 측 lane 진단
6TCtransmission convergence특정 고속 PHY 구현에서 사용
7Auto-NegotiationAN control/status, next page고속 AN 실패 원인 분석
30/31Vendor 1/2벤더 확장 기능RGMII delay, downshift, 온도 센서, patch RAM
/* 개념 예시: Clause 45 MMD 직접 읽기 */
struct mii_bus *bus = phydev->mdio.bus;
int phy_addr = phydev->mdio.addr;

/* PCS status 1 */
int pcs_stat = mdiobus_c45_read(bus, phy_addr, MDIO_MMD_PCS, 0x0001);

/* AN status 1 */
int an_stat = mdiobus_c45_read(bus, phy_addr, MDIO_MMD_AN, 0x0001);

/* 벤더 확장 레지스터 */
int vend = mdiobus_c45_read(bus, phy_addr, MDIO_MMD_VEND1, 0x8050);
Clause 45 MMD 구조 호스트 PHY addr + devad + reg MMD 1 PMA/PMD MMD 3 PCS MMD 7 Auto-Negotiation MMD 30/31 Vendor 레지스터 예시 PMA/PMD: signal detect, optical power PCS: block lock, lane fault, alignment AN: base page, next page, FEC advertisement Vendor: RGMII delay, downshift, strap status Clause 22 only PHY에서는 이 구조가 보이지 않거나 벤더 페이지 전환으로 유사하게 흉내 내기도 합니다.
Clause 45는 PMA/PMD, PCS, AN, vendor 확장을 MMD로 나눠 관리해 고속 PHY 진단을 가능하게 합니다.
주의: 일부 PHY는 Clause 22만 지원하지만 내부적으로는 벤더 페이지(Page) 전환 레지스터를 통해 사실상 MMD 비슷한 구조를 제공합니다. 이 경우 페이지 전환을 원복하지 않으면 다음 상태 읽기가 엉뚱한 페이지를 읽어 버려 디버깅이 더 어려워집니다.

MDIO 버스 토폴로지(Topology)와 다중 PHY

실제 보드에서는 PHY가 하나만 있는 경우보다, 하나의 MDIO 버스에 여러 PHY와 스위치 내부 포트 PHY, retimer, 별도 PCS, SFP 관리 장치가 함께 매달리는 경우가 더 많습니다. MDIO는 I2C처럼 주소 기반 공유 버스이므로, 주소 충돌과 버스 길이, pull-up, reset 순서를 동시에 관리해야 합니다. 보드 bring-up에서 PHY 하나는 보이고 나머지는 안 보인다면 드라이버보다 먼저 버스 토폴로지를 확인해야 합니다.

특히 스위치 칩은 외부 포트의 copper PHY와 별개로 내부 포트용 PHY 또는 PCS를 또 가지고 있을 수 있습니다. 이 경우 운영체제는 "인터페이스 하나당 PHY 하나"라고 단순화해서 볼 수 없고, MDIO 버스 아래에 여러 개의 mdio_devicephy_device가 계층적으로 배치됩니다. 주소가 겹치거나 reset 순서가 꼬이면 전혀 다른 장치에 레지스터를 쓰는 사고가 발생할 수 있습니다.

토폴로지구성 예장점흔한 문제
단일 버스 + 단일 PHYSoC MAC ↔ 외부 RJ45 PHY가장 단순, bring-up 쉬움주소 실수, reset 타이밍
단일 버스 + 다중 PHY듀얼 포트 보드, 4포트 PHY배선 단순화주소 충돌, 전원 순서, 버스 부하
버스 뒤에 스위치/DSACPU MAC ↔ 스위치 ↔ 포트 PHY들포트 확장 용이내부 MDIO 주소 맵 이해 필요
MDIO mux/게이트백플레인 슬롯별 PHY 선택주소 재사용 가능mux 선택 상태와 reset 순서 꼬임
MAC PCS + 외부 PHY 혼합host PCS는 MAC 내부, copper PHY는 외부SerDes와 copper 분리 가능PCS와 PHY 상태를 따로 해석해야 함
/* 개념 예시: 하나의 MDIO 버스에 다중 PHY가 매달린 경우 */
&mdio0 {
    #address-cells = <1>;
    #size-cells = <0>;

    phy0: ethernet-phy@1 {
        reg = <1>;
    };

    phy1: ethernet-phy@2 {
        reg = <2>;
    };

    switch0: ethernet-switch@10 {
        reg = <0x10>;
    };
};
MDIO 버스 토폴로지 SoC MAC / MDIO 컨트롤러 mii_bus, read/write/reset MDC / MDIO shared bus PHY @1 RJ45 port 0 PHY @2 RJ45 port 1 MDIO mux 슬롯별 선택 스위치 내부 MDIO 포트 PHY / PCS Retimer / 별도 PCS host lane 보정 슬롯 PHY @1 재사용 mux 뒤에서만 유효 실패 포인트 주소 충돌 pull-up 부족 mux 선택 누락
MDIO는 단일 PHY만 다루는 버스가 아니라, 여러 PHY와 PCS가 공존하는 공유 관리 평면일 수 있습니다.

MDIO mux 프레임워크

MDIO 주소 공간이 5비트(0~31)로 제한되기 때문에, 대규모 장비에서는 MDIO mux(멀티플렉서(Multiplexer))로 버스를 분할합니다. 리눅스 커널은 mdio-mux 프레임워크를 제공하며, GPIO mux(mdio-mux-gpio), MMIO mux(mdio-mux-mmio), I2C mux 등 다양한 구현이 있습니다. mux 뒤의 각 세그먼트(Segment)는 독립적인 MDIO 버스로 취급되므로, 같은 PHY 주소를 세그먼트마다 재사용할 수 있습니다.

MDIO mux에 의한 버스 세그먼트 분리 부모 MDIO 버스 mii_bus (MDC + MDIO) MDIO mux mdio-mux-gpio mdio-mux-mmio mdio-mux-multiplexer select()/deselect() 콜백 세그먼트 0 PHY @1, PHY @2 세그먼트 1 PHY @1, PHY @3 세그먼트 2 PHY @1 (주소 재사용) 슬롯 0 RJ45 ×2 1G copper PHY 슬롯 1 SFP + RJ45 10G + 1G PHY 슬롯 2 RJ45 ×1 2.5G PHY 각 세그먼트는 독립 mii_bus로 등록되어, PHY 주소 @1이 세그먼트마다 별개 장치를 가리킵니다. mux select() 중에만 해당 세그먼트의 MDIO 트래픽이 물리 버스에 전달됩니다.
MDIO mux는 GPIO나 MMIO로 버스 세그먼트를 선택하며, 각 세그먼트의 PHY 주소는 독립적으로 관리됩니다.
/* Device Tree 예시: GPIO 기반 MDIO mux */
mdio-mux {
    compatible = "mdio-mux-gpio";
    gpios = <&gpio0 3 0>, <&gpio0 4 0>;  /* 2비트 선택선 → 4개 세그먼트 */
    mdio-parent-bus = <&mdio0>;
    #address-cells = <1>;
    #size-cells = <0>;

    mdio_seg0: mdio@0 {
        reg = <0>;
        #address-cells = <1>;
        #size-cells = <0>;
        phy_slot0: ethernet-phy@1 { reg = <1>; };
    };

    mdio_seg1: mdio@1 {
        reg = <1>;
        #address-cells = <1>;
        #size-cells = <0>;
        phy_slot1: ethernet-phy@1 { reg = <1>; }; /* 같은 주소 재사용 */
    };
};
주의: MDIO mux의 select() 콜백 자체도 MDIO 트랜잭션이 필요할 수 있습니다(예: I2C mux). 이 경우 mux 선택과 실제 PHY 접근 사이에 락(lock) 순서가 꼬이면 데드락(deadlock)이 발생합니다. 커널의 mdio_mux_init()는 이를 고려해 부모 버스 락을 잡은 채 select를 호출하므로, select 내부에서 같은 부모 버스의 MDIO 접근을 시도하면 안 됩니다.

PHY 탐지와 링크 수명주기

커널 관점에서 PHY 수명주기는 "발견, attach, 광고 설정, 상태 머신 시작, 링크 변화 통지, stop"의 반복입니다. probe 단계에서는 PHY 노드를 찾고 드라이버를 매칭합니다. 인터페이스가 올라갈 때는 phy_start() 또는 phylink_start()가 상태 머신을 돌리고, 링크가 변하면 드라이버는 speed, duplex, pause, interface mode를 MAC에 다시 적용해야 합니다.

  1. 발견phy-handle 또는 firmware node를 통해 PHY를 찾고 ID를 읽어 드라이버를 매칭합니다.
  2. attachof_phy_connect() 또는 phylink 연결 함수로 MAC과 PHY를 연결합니다.
  3. 정책 설정 — 지원 속도, EEE, pause, advertisement, in-band status 정책을 설정합니다.
  4. 상태 머신 시작 — 인터페이스 up 시 PHY state machine이 주기적으로 링크를 확인합니다.
  5. 링크 변화 통지adjust_link 혹은 mac_link_up() 경로에서 MAC 레지스터가 재설정됩니다.
  6. 정지 — 인터페이스 down 시 queue 정지 후 PHY polling 또는 IRQ를 멈춥니다.
/* 개념 예시: phylib attach와 링크 수명주기 */
static void my_adjust_link(struct net_device *ndev)
{
    struct phy_device *phydev = ndev->phydev;

    if (!phydev->link) {
        my_mac_link_down(ndev);
        return;
    }

    my_mac_link_up(ndev, phydev->speed, phydev->duplex);
}

static int my_probe(struct platform_device *pdev)
{
    struct net_device *ndev = platform_get_drvdata(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_connect(ndev, phy_np, &my_adjust_link, 0,
                            PHY_INTERFACE_MODE_RGMII_ID);
    of_node_put(phy_np);
    if (!phydev)
        return -ENODEV;

    phy_set_max_speed(phydev, SPEED_1000);
    phy_attached_info(phydev);
    return 0;
}

static int my_open(struct net_device *ndev)
{
    phy_start(ndev->phydev);
    netif_start_queue(ndev);
    return 0;
}

static int my_stop(struct net_device *ndev)
{
    netif_stop_queue(ndev);
    phy_stop(ndev->phydev);
    return 0;
}
PHY 링크 수명주기 reset / probe PHY ID 읽기 드라이버 매칭 광고 능력 설정 speed, pause, EEE Auto-Negotiation FLP / in-band status link up read_status() speed / duplex / pause MAC 재설정 clock, duplex, pause 정상 TX/RX 시작 링크 통계와 오류 감시 실패 시 재협상 반복
PHY 상태 머신은 링크 상태를 주기적으로 읽고, 링크가 변할 때 MAC 재설정을 요청합니다.

PHY 상태 머신 내부 상태와 전이 규칙

커널 phylibphy_state 열거형(enum)으로 PHY의 수명 주기를 관리합니다. 각 상태는 phy_state_machine() 폴링 루프와 phy_start()/phy_stop() 등의 제어 호출에 의해 전이됩니다. 상태를 정확히 이해하면 드라이버 개발 시 발생하는 링크 불안정, 무한 재시도, 장치 미해제 같은 문제를 빠르게 진단할 수 있습니다.

phy_state 열거형 정의

/* include/linux/phy.h (v6.x 기준, 일부 커널 버전에서 이름이 다를 수 있음) */
enum phy_state {
    PHY_DOWN        = 0,  /* PHY가 비활성화됨 — 드라이버 미연결 또는 phy_detach() 완료 */
    PHY_READY       = 1,  /* phy_attach() 완료, phy_start() 대기 중 */
    PHY_HALTED      = 2,  /* phy_stop() 호출됨 — 폴링 중단, MAC 링크 다운 알림 완료 */
    PHY_ERROR       = 3,  /* config_init / read_status 등에서 복구 불가 오류 발생 */
    PHY_UP          = 4,  /* phy_start() 호출됨 — config_init 수행 후 협상 대기 */
    PHY_RUNNING     = 5,  /* 링크 UP — MAC이 속도/듀플렉스로 설정 완료 */
    PHY_NOLINK      = 6,  /* 협상 완료 후 링크 신호 없음 — 재협상 또는 케이블 대기 */
    PHY_CABLETEST   = 7,  /* ethtool cable test 진행 중 — 완료 후 PHY_UP 또는 PHY_HALTED로 복귀 */
};

각 상태의 상세 설명

다음은 각 상태가 실제로 의미하는 바와 전이 조건입니다.

상태 전이 다이어그램

PHY_DOWN attach 전 / detach 후 phy_attach() PHY_READY start() 대기 phy_start() PHY_UP config_init/aneg link UP PHY_RUNNING 링크 UP, TX/RX 정상 링크 단절 PHY_NOLINK 링크 신호 없음 링크 복구 phy_stop() PHY_HALTED 폴링 중단, 링크 다운 phy_start() phy_detach() PHY_ERROR 복구 불가 오류 config_init 실패 read_status 실패 오류 처리 후 중단 PHY_CABLETEST 케이블 진단 중 cable_test_start() 진단 완료 범례 (Legend) 정상 전이 오류/중단 경로 진단 경로 phy_stop() 경로
PHY 상태 머신의 8가지 상태와 전이 경로입니다. 실선은 정상 경로, 점선은 오류 또는 중단 경로를 나타냅니다.

phy_state_machine() 핵심 폴링 루프

phy_state_machine()PHY_POLL 모드에서 주기적으로 호출되며(기본 PHY_STATE_TIME = 1 Hz), 인터럽트 모드에서는 인터럽트 핸들러가 대신 트리거합니다. 현재 상태를 읽고 다음 상태를 결정하는 구조입니다.

/* drivers/net/phy/phy.c — 핵심 로직 요약 (실제 코드 단순화) */
void phy_state_machine(struct work_struct *work)
{
    struct delayed_work *dwork = to_delayed_work(work);
    struct phy_device *phydev =
        container_of(dwork, struct phy_device, state_queue);
    bool needs_aneg = false, do_suspend = false;
    enum phy_state old_state;
    int err = 0;

    mutex_lock(&phydev->lock);
    old_state = phydev->state;

    switch (phydev->state) {
    case PHY_DOWN:
    case PHY_READY:
        break;  /* 폴링 비활성 — 아무 동작 없음 */

    case PHY_UP:
        needs_aneg = true;  /* config_aneg() 호출 예약 */
        break;

    case PHY_NOLINK:
    case PHY_RUNNING:
        err = phy_check_link_status(phydev);  /* read_status() → adjust_link() */
        break;

    case PHY_HALTED:
        do_suspend = true;
        break;

    case PHY_ERROR:
        phy_error(phydev);  /* 오류 상태 유지 또는 리셋 시도 */
        break;

    default:
        break;
    }

    mutex_unlock(&phydev->lock);

    if (needs_aneg)
        err = phy_start_aneg(phydev);  /* config_init → config_aneg 순서로 호출 */

    if (do_suspend)
        phy_suspend(phydev);

    if (err == -ENODEV)
        phy_error(phydev);

    /* 다음 폴링 스케줄 (인터럽트 모드가 아닐 때만) */
    if (phy_polling_mode(phydev) && phy_is_started(phydev))
        phy_queue_state_machine(phydev, PHY_STATE_TIME);
}

phy_start() / phy_stop() 흐름

phy_start()phy_stop()은 MAC 드라이버가 인터페이스를 올리거나 내릴 때 직접 호출하는 진입점입니다. 두 함수는 뮤텍스(mutex)로 보호된 상태에서 phydev->state를 변경하고 phy_state_machine()을 즉시 스케줄합니다.

/* drivers/net/phy/phy.c */
void phy_start(struct phy_device *phydev)
{
    mutex_lock(&phydev->lock);

    if (phydev->state != PHY_READY &&
        phydev->state != PHY_HALTED) {
        WARN_ONCE(true, "phy_start() called in unexpected state %d\n",
                   phydev->state);
        mutex_unlock(&phydev->lock);
        return;
    }

    if (phydev->state == PHY_HALTED) {
        phydev->state = PHY_UP;
        phy_resume(phydev);    /* 드라이버 resume 훅 호출 */
    } else {
        phydev->state = PHY_UP;
    }

    phy_queue_state_machine(phydev, 0);  /* 즉시 state_machine 트리거 */
    mutex_unlock(&phydev->lock);
}
EXPORT_SYMBOL(phy_start);

void phy_stop(struct phy_device *phydev)
{
    mutex_lock(&phydev->lock);

    if (!phy_is_started(phydev) && phydev->state != PHY_DOWN) {
        WARN_ONCE(true, "phy_stop() called in unexpected state\n");
        mutex_unlock(&phydev->lock);
        return;
    }

    phydev->state = PHY_HALTED;
    phy_process_queued_link_change(phydev);  /* 펜딩 링크 이벤트 처리 */
    mutex_unlock(&phydev->lock);

    phy_stop_machine(phydev);      /* 폴링 워크큐 취소 */
    phy_disable_interrupts(phydev);
    phy_link_down(phydev);         /* MAC adjust_link(link=0) 호출 */
}
EXPORT_SYMBOL(phy_stop);

상태 전이 조건 요약 테이블

전이 (From → To) 트리거 호출 컨텍스트 주의 사항
DOWN → READY phy_attach_direct() MAC 드라이버 probe 또는 open() config_init()은 아직 미호출 상태
READY → UP phy_start() net_device.ndo_open() 내부 phy_start() 전에 phy_connect()가 완료되어야 합니다.
UP → RUNNING read_status()에서 link=1 감지 phy_state_machine() 폴링 루프 adjust_link() 콜백으로 MAC에 통보됩니다.
RUNNING → NOLINK read_status()에서 link=0 감지 phy_state_machine() 폴링 루프 케이블 단선, 원격 장치 다운, 신호 손실 등이 원인입니다.
NOLINK → RUNNING read_status()에서 link=1 재감지 phy_state_machine() 폴링 루프 Auto-Negotiation 재협상이 필요할 수 있습니다.
RUNNING/UP → HALTED phy_stop() net_device.ndo_stop() 내부 폴링이 즉시 중단되고 MAC에 링크 다운이 통보됩니다.
HALTED → UP phy_start() net_device.ndo_open() phy_resume()가 내부에서 호출됩니다.
HALTED → DOWN phy_detach() MAC 드라이버 remove 반드시 HALTED 상태에서만 detach해야 합니다.
UP/RUNNING → ERROR config_init() 또는 read_status() 오류 phy_state_machine() 드라이버는 phy_error()를 호출해 이 상태로 진입시킵니다.
RUNNING → CABLETEST phy_start_cable_test() ethtool 명령 또는 직접 호출 진단 완료 후 phy_start_cable_test_tdr_notify()로 결과 전달됩니다.
흔한 실수와 주의 사항
  • phy_start() 전 config_init() 미완료: phy_start()는 내부적으로 phy_state_machine()을 통해 config_init()을 호출합니다. MAC 드라이버가 직접 config_init()을 호출하면 이중 초기화가 발생할 수 있습니다.
  • PHY_READY 상태에서 phy_stop() 호출: phy_stop()PHY_READY 상태에서는 동작하지 않습니다. 반드시 phy_start() 이후, 즉 PHY_UP 이상 상태에서 호출해야 합니다.
  • phy_detach() 타이밍: phy_detach()는 반드시 phy_stop() 이후 PHY_HALTED 또는 PHY_DOWN 상태에서만 안전합니다. 링크가 살아있는 상태에서 detach하면 use-after-free 버그가 발생할 수 있습니다.
  • PHY_ERROR 상태 무시: phy_error()가 호출되면 폴링 루프는 계속 실행되지만 상태를 복구하지 않습니다. MAC 드라이버는 adjust_link() 콜백에서 phydev->state == PHY_ERROR를 확인하고 상위 계층에 알려야 합니다.
  • 인터럽트 모드와 폴링 모드 혼용: PHY_POLL 플래그 없이 phy_attach()하면 인터럽트 구동 방식이 기본값입니다. 인터럽트 핀이 올바르게 연결되지 않은 하드웨어에서는 링크 이벤트가 전달되지 않아 PHY_NOLINK 상태에 무한히 머무를 수 있습니다. 이 경우 phy_attach()MDIO_BUS_PHY_IRQ_POLL을 명시적으로 설정해야 합니다.
  • PHY_CABLETEST 중 링크 이벤트 수신: 케이블 진단 중에는 phy_state_machine()이 링크 상태를 확인하지 않으므로, 진단이 완료될 때까지 링크 변경 이벤트가 MAC으로 전달되지 않습니다.

리눅스 커널 객체 모델

리눅스에서 PHY 관련 코드를 읽을 때는 mii_bus, mdio_device, phy_device, phy_driver, phylink, net_device가 어떤 관계인지 먼저 잡아야 합니다. mii_bus는 실제 MDIO 컨트롤러이고, phy_device는 그 버스 위에 매달린 PHY 인스턴스, phy_driver는 특정 PHY ID에 매칭되는 동작 구현, phylink는 MAC과 PHY 사이 정책 오케스트레이터입니다.

객체대표 소유자핵심 역할어디서 자주 보나
mii_busMAC/SoC 드라이버MDIO read/write/reset 제공MDIO 컨트롤러 probe
mdio_deviceMDIO 코어버스 위 디바이스 공통 래퍼PHY 이외 MDIO 장치
phy_devicephylib링크 상태, 광고 능력, 인터페이스 모드 보유phydev->link, speed, duplex
phy_driver벤더 PHY 드라이버config_init, config_aneg, read_status 구현drivers/net/phy/
phylinkMAC 드라이버MAC/PHY/PCS/SFP 정책 통합SFP, fixed-link, in-band status
net_deviceMAC 드라이버유저스페이스와 커널 네트워크 스택(Network Stack) 진입점(Entry Point)eth0 링크 관리
/* 개념 예시: PHY 관련 커널 객체의 대표 필드 */
struct mii_bus {
    char id[MII_BUS_ID_SIZE];
    int (*read)(...);
    int (*write)(...);
    int (*reset)(...);
    struct device *parent;
};

struct phy_device {
    struct mdio_device mdio;
    phy_interface_t interface;
    int link;
    int speed;
    int duplex;
    int pause;
    int asym_pause;
    int irq;
    linkmode_t supported;
    linkmode_t advertising;
    linkmode_t lp_advertising;
};

struct phy_driver {
    u32 phy_id;
    u32 phy_id_mask;
    const char *name;
    int (*config_init)(...);
    int (*config_aneg)(...);
    int (*read_status)(...);
    irqreturn_t (*handle_interrupt)(...);
};
리눅스 PHY 객체 관계 mii_bus read/write/reset phy_device link, speed, advertising phy_driver 벤더 구현 phylink 정책과 상태 조정 net_device MAC 드라이버 SFP / PCS / fixed-link 선택적 토폴로지 버스 위 인스턴스 정책 연결
phylib는 버스와 PHY 인스턴스를 관리하고, phylink는 그 위에서 MAC과 SFP/PCS/fixed-link 정책까지 묶습니다.

PHY 드라이버 콜백을 어디까지 구현해야 하나

많은 PHY는 genphy_config_aneg(), genphy_read_status() 같은 공통 헬퍼만으로도 동작합니다. 하지만 RGMII 지연, downshift, EEE, LED, strap override, 온도 센서, FEC 정책처럼 벤더 확장 기능이 필요한 경우에는 전용 콜백 구현이 필요합니다.

콜백언제 필요한가대표 작업
config_initprobe 직후 초기 설정strap 보정, 지연 모드, 기본 LED 정책
config_aneg광고 능력 커스터마이즈EEE/FEC/downshift/속도 제한
read_status링크 상태 해석이 벤더별로 다름speed/duplex/pause, master/slave 반영
handle_interruptIRQ 기반 PHY링크 변화 인터럽트(Interrupt) 처리
suspend/resume저전력 운용 필요WoL, EEE, analog power gating
/* 개념 예시: 벤더 페이지 전환 레지스터 접근 패턴 */
static int myphy_set_rgmii_delay(struct phy_device *phydev)
{
    int oldpage, ret;

    oldpage = phy_select_page(phydev, 0xd08);
    if (oldpage < 0)
        return oldpage;

    ret = phy_modify(phydev, 0x11, 0x0300, 0x0200);
    return phy_restore_page(phydev, oldpage, ret);
}

genphy 헬퍼 함수 상세 분석

대부분의 PHY 드라이버는 벤더 전용 레지스터 접근이 필요한 일부 콜백만 직접 구현하고, 나머지는 커널이 제공하는 genphy_* 계열 함수에 위임합니다. 이 함수들은 IEEE 802.3 표준 레지스터(BMCR, BMSR, ANAR, ANLPAR, GBCR, GBSR)만을 사용해 동작하므로, 표준 Clause 22를 준수하는 PHY라면 드라이버 없이도 링크를 올릴 수 있습니다. 이 절에서는 각 함수가 어떤 레지스터를 어떤 순서로 다루는지 실제 커널 로직에 근거해 상세히 살펴봅니다.

genphy_config_aneg() — Auto-Negotiation 광고 설정

genphy_config_aneg()phy_deviceadvertising 링크모드(Linkmode) 비트맵을 읽어 ANAR(Auto-Negotiation Advertisement Register, 레지스터 4번)와 GBCR(1000BASE-T Control Register, 레지스터 9번)에 기록한 뒤, BMCR(Basic Mode Control Register, 레지스터 0번)에서 재시작 비트를 세트합니다. PHY가 AN을 지원하지 않는 경우에는 강제 속도/듀플렉스(duplex) 경로로 폴백합니다.

/* drivers/net/phy/phy_device.c 참조 — 개념 재현 */
int genphy_config_aneg(struct phy_device *phydev)
{
    int err, changed = 0;

    /* (1) 1000BASE-T 마스터/슬레이브(Master/Slave) 광고 — 레지스터 9 */
    if (genphy_config_eee_advert(phydev) &&
        linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
                          phydev->advertising)) {
        err = genphy_config_advert(phydev);
        if (err < 0)
            return err;
        changed |= err;
    }

    if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
                          phydev->advertising)) {
        /* (2) BMCR 에 AN_ENABLE + RESTART_AN 세트 */
        if (changed) {
            err = phy_modify(phydev, MII_BMCR,
                              BMCR_ANRESTART | BMCR_ANENABLE,
                              BMCR_ANENABLE | BMCR_ANRESTART);
            if (err < 0)
                return err;
        }
    } else {
        /* (3) AN 비활성: 강제 속도/듀플렉스 적용 */
        err = genphy_setup_forced(phydev);
        if (err < 0)
            return err;
    }
    return 0;
}

/* genphy_config_advert: ANAR(reg4)와 GBCR(reg9)를 advertising 비트맵으로 갱신 */
static int genphy_config_advert(struct phy_device *phydev)
{
    int err, adv, changed = 0;

    /* ANAR: 10/100 half/full + pause 비트 */
    adv = ethtool_adv_to_mii_adv_t(
            linkmode_adv_to_lcl_adv_t(phydev->advertising));
    err = phy_modify_changed(phydev, MII_ADVERTISE,
                              ADVERTISE_ALL | ADVERTISE_100BASE4 |
                              ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM,
                              adv);
    if (err < 0)
        return err;
    changed |= err;

    /* GBCR(reg9): 1000BASE-T half/full 광고 */
    if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
                          phydev->advertising) ||
        linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
                          phydev->advertising)) {
        adv = ethtool_adv_to_mii_ctrl1000_t(
                linkmode_adv_to_lcl_adv_t(phydev->advertising));
        err = phy_modify_changed(phydev, MII_CTRL1000,
                                  CTL1000_ENABLE_MASTER |
                                  CTL1000_AS_MASTER |
                                  ADVERTISE_1000FULL |
                                  ADVERTISE_1000HALF, adv);
        if (err < 0)
            return err;
        changed |= err;
    }
    return changed;
}

주목할 점은 phy_modify_changed()가 기존 레지스터 값과 비교해 실제로 비트가 달라졌을 때만 쓰기를 수행하고 1을 반환한다는 것입니다. 광고 내용이 변하지 않았다면 불필요한 AN 재시작을 생략해 링크 순간 끊김을 방지합니다.

genphy_read_status() — 링크 상태 판독

genphy_read_status()는 링크가 올라온 후 커널 폴러(Poller) 또는 인터럽트 핸들러에서 주기적으로 호출되어, PHY 레지스터를 읽고 phydevlink, speed, duplex, pause 필드를 갱신합니다. MAC 드라이버는 이 필드를 보고 MAC 속도를 재설정합니다.

int genphy_read_status(struct phy_device *phydev)
{
    int err;

    /* (1) BMSR 읽기 — sticky 비트이므로 두 번 읽어 래치 해제 */
    err = genphy_update_link(phydev);
    if (err)
        return err;

    /* 링크가 없으면 속도/듀플렉스 정보는 의미 없음 */
    phydev->speed = SPEED_UNKNOWN;
    phydev->duplex = DUPLEX_UNKNOWN;
    phydev->pause = 0;
    phydev->asym_pause = 0;

    if (phydev->autoneg == AUTONEG_ENABLE && phydev->link) {
        /* (2) ANLPAR(reg5): 파트너 광고 읽기 */
        err = genphy_read_lpa(phydev);
        if (err < 0)
            return err;

        /* (3) GBSR(reg10): 1000BASE-T 마스터/슬레이브 결과 */
        if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
                              phydev->supported) ||
            linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
                              phydev->supported)) {
            err = genphy_read_1000t_status(phydev);
            if (err < 0)
                return err;
        }

        /* (4) 협상 결과로 speed/duplex/pause 결정 */
        phy_resolve_aneg_linkmode(phydev);
    } else if (phydev->autoneg == AUTONEG_DISABLE) {
        /* (5) 강제 모드: BMCR 비트로 speed/duplex 직접 판독 */
        err = genphy_read_status_fixed(phydev);
        if (err < 0)
            return err;
    }
    return 0;
}

genphy_update_link()는 BMSR(Basic Mode Status Register, 레지스터 1번)의 비트 2(Link Status)를 확인합니다. 이 비트는 스티키(sticky) 비트여서 링크 다운 이벤트를 기록하고 나면 읽어야 클리어되므로, 래치 해제를 위해 BMSR을 두 번 연속으로 읽습니다. phy_resolve_aneg_linkmode()는 자신의 advertising과 파트너의 lp_advertising 비트맵을 AND 연산해 최대 공통 모드를 선택합니다.

genphy_suspend() / genphy_resume() — 전원 관리

genphy_suspend()는 BMCR의 비트 11(Power Down)을 세트해 PHY 아날로그 회로를 저전력 상태로 전환합니다. genphy_resume()는 해당 비트를 클리어해 정상 동작 상태로 복귀시킵니다. 드라이버가 이 콜백을 오버라이드하는 일반적인 이유는 WoL(Wake-on-LAN) 활성화 여부를 확인하거나, Power Down 시에도 유지해야 하는 벤더 레지스터 상태가 있을 때입니다.

int genphy_suspend(struct phy_device *phydev)
{
    return phy_set_bits(phydev, MII_BMCR, BMCR_PDOWN);
}
EXPORT_SYMBOL(genphy_suspend);

int genphy_resume(struct phy_device *phydev)
{
    return phy_clear_bits(phydev, MII_BMCR, BMCR_PDOWN);
}
EXPORT_SYMBOL(genphy_resume);

/* 오버라이드 예시: WoL이 활성화된 경우 Power Down 생략 */
static int myphy_suspend(struct phy_device *phydev)
{
    if (phy_interrupt_is_valid(phydev) && phydev->wol_enabled)
        return 0;  /* WoL 활성: Power Down 금지 */
    return genphy_suspend(phydev);
}

genphy_soft_reset() — PHY 소프트 리셋

genphy_soft_reset()은 BMCR의 비트 15(Reset)를 세트하고, PHY 내부가 리셋을 완료해 해당 비트를 자동으로 클리어할 때까지 폴링합니다. 최대 대기 시간은 500ms이며, 이 기간을 초과하면 -ETIMEDOUT을 반환합니다. 리셋 직후에는 PHY가 EEPROM 스트랩(strap) 또는 내부 기본값으로 레지스터를 복원하므로, 이후에 벤더 초기화 코드를 반드시 재실행해야 합니다.

int genphy_soft_reset(struct phy_device *phydev)
{
    u16 res = BMCR_RESET;
    int err;

    if (phydev->autoneg == AUTONEG_ENABLE)
        res |= BMCR_ANRESTART;

    /* BMCR bit15 = 1 → PHY 리셋 개시 */
    err = phy_modify(phydev, MII_BMCR, BMCR_ISOLATE, res);
    if (err < 0)
        return err;

    /* PHY가 리셋을 완료하면 bit15를 스스로 클리어함 */
    err = phy_poll_reset(phydev);
    if (err)
        return err;

    /* 리셋 후 일부 PHY는 추가 안정화 시간이 필요 */
    msleep(1);
    return 0;
}
EXPORT_SYMBOL(genphy_soft_reset);

phy_poll_reset() 내부는 최대 100ms 간격으로 BMCR bit15를 읽으면서 클리어를 기다립니다. 재시도 횟수 초과 시 -ETIMEDOUT을 반환합니다. 하드웨어 리셋 핀(reset-gpios)이 붙어 있는 경우에는 mdio_device_reset()이 GPIO를 토글하고, 그 다음 단계로 genphy_soft_reset()이 호출됩니다.

genphy_read_abilities() / genphy_c45_read_abilities() — PHY 능력 탐지

genphy_read_abilities()는 PHY 탐지 직후 phy_driver.get_features 콜백 위치에서 호출되어 phydev->supported 링크모드 비트맵을 채웁니다. BMSR의 능력 비트(10BASE-T, 100BASE-TX, 1000BASE-T, AN 지원)를 읽어 표준 비트맵으로 변환하고, GBSR(레지스터 10번)도 추가로 확인합니다.

int genphy_read_abilities(struct phy_device *phydev)
{
    int val;

    linkmode_set_bit_array(phy_basic_ports_array,
                            ARRAY_SIZE(phy_basic_ports_array),
                            phydev->supported);

    /* BMSR(reg1): 기본 속도/듀플렉스 능력 비트 */
    val = phy_read(phydev, MII_BMSR);
    if (val < 0)
        return val;
    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);

    /* ESTATUS(reg15): 기가비트(Gigabit) 능력 확인 */
    if (val & BMSR_ESTATEN) {
        val = phy_read(phydev, MII_ESTATUS);
        if (val < 0)
            return val;
        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);
    }
    return 0;
}
EXPORT_SYMBOL(genphy_read_abilities);

Clause 45 PHY를 위한 genphy_c45_read_abilities()는 PMA/PMD MMD(레지스터 1.4, 1.7, 1.8 등)를 읽어 10GBASE-T, 25GBASE-KR, 40GBASE-CR4 같은 상위 속도 비트까지 채웁니다. PHY 드라이버가 get_features를 오버라이드하지 않으면 phylib이 Clause 22/45 여부를 자동으로 판단해 둘 중 하나를 기본으로 호출합니다.

/* Clause 45 PHY 능력 탐지 — MMD 1(PMA/PMD), MMD 3(PCS) 참조 */
int genphy_c45_read_abilities(struct phy_device *phydev)
{
    int val;

    /* PMA/PMD Ctrl2(1.7): 10G 능력 비트 */
    val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_CTRL2);
    if (val < 0)
        return val;

    linkmode_mod_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
                     phydev->supported,
                     (val & MDIO_PMA_CTRL2_10GBTR));

    /* PCS Status2(3.8): PCS 능력 확인 */
    val = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_PCS_STAT2);
    if (val < 0)
        return val;
    linkmode_mod_bit(ETHTOOL_LINK_MODE_10000baseR_FEC_BIT,
                     phydev->supported,
                     (val & MDIO_PCS_STAT2_10GBR));

    return 0;
}

genphy 함수 참조 테이블

함수역할 요약주요 레지스터호출 시점오버라이드 필요 조건
genphy_config_aneg()AN 광고 비트맵 → ANAR/GBCR 기록, AN 재시작BMCR(0), ANAR(4), GBCR(9)config_aneg 콜백EEE/FEC/downshift 광고, 벤더 AN 페이지 필요 시
genphy_read_status()BMSR 링크 확인, ANLPAR/GBSR 읽기, speed/duplex 결정BMSR(1), ANLPAR(5), GBSR(10)read_status 콜백, 폴러/인터럽트벤더 확장 속도(2.5G 등), EEE, master/slave 재해석 시
genphy_suspend()BMCR PDOWN 세트 → PHY 저전력BMCR(0) bit11시스템 suspend, ethtool --set-priv-flagsWoL 활성 시 Power Down 생략, 벤더 전용 저전력 모드
genphy_resume()BMCR PDOWN 클리어 → 정상 복귀BMCR(0) bit11시스템 resumeresume 후 벤더 레지스터 재초기화 필요 시
genphy_soft_reset()BMCR Reset 비트 세트 후 클리어 폴링BMCR(0) bit15드라이버 probe, config_init리셋 후 즉시 벤더 레지스터 복원이 필요한 경우
genphy_read_abilities()BMSR/ESTATUS 읽어 supported 비트맵 채움BMSR(1), ESTATUS(15)get_features 콜백, PHY 탐지 직후벤더 전용 능력 레지스터, 2.5G/5G 확장 속도 지원 시
genphy_c45_read_abilities()MMD PMA/PCS 레지스터로 10G+ 능력 탐지MMD1.4, MMD1.7, MMD3.8Clause 45 PHY의 get_features25G/40G 이상, 벤더 MMD 확장 속도 필요 시
genphy_config_init()supportedadvertising 복사 (기본 초기화)config_init 콜백 (폴백)거의 항상 오버라이드: strap 보정, RGMII 지연, LED 등

ethtool 명령에서 MDIO 레지스터까지의 호출 체인

유저스페이스 ethtool -s eth0 speed 1000 ethtool 커널 핸들러 ethtool_set_link_ksettings() phylib 상태 머신 phy_start_aneg() phy_driver 디스패치 drv->config_aneg(phydev) genphy 헬퍼 함수 계층 genphy_config_aneg() genphy_config_advert() genphy_setup_forced() phy_modify_changed() 상태 판독 경로 (폴러/IRQ) genphy_read_status() genphy_update_link() → BMSR 2회 읽기 MDIO 레지스터 접근 phy_read / phy_write() phy_read_mmd() mii_bus->read / write() phylib 폴러(100ms 주기) 또는 PHY 인터럽트에서 직접 호출
ethtool 명령 한 줄이 phy_driver 디스패치를 거쳐 genphy 헬퍼로 전달되고, 최종적으로 mii_bus read/write를 통해 PHY 칩 내부 레지스터에 접근합니다. 상태 판독 경로는 설정 경로와 독립적으로 폴러 또는 인터럽트로부터 호출됩니다.
genphy 사용 vs. 커스텀 구현 실전 지침
  • genphy만으로 충분한 경우: 표준 Clause 22를 완전히 준수하고, 특수 기능(RGMII 지연 조정, downshift, EEE 강제, 온도 알람, LED 매핑)이 없는 단순 PHY입니다. 이때는 .config_aneg = genphy_config_aneg, .read_status = genphy_read_status로 지정하거나 phy_driver 구조체에서 해당 필드를 NULL로 두면 phylib이 자동으로 genphy를 폴백으로 사용합니다.
  • 반드시 커스텀 구현이 필요한 경우: RGMII/SGMII 인터페이스 지연을 PHY 레지스터로 설정해야 할 때, 2.5G/5G/10G 같이 표준 BMSR로는 표현되지 않는 속도를 지원할 때, 하드웨어 EEE 협상 알고리즘이 표준과 다를 때, 벤더 페이지 전환이 필요한 레지스터에 접근해야 할 때입니다.
  • 혼합 패턴 (가장 흔한 형태): config_initconfig_aneg는 벤더 구현, read_statusgenphy_read_status() 직접 호출 후 벤더 필드만 덧붙이는 방식입니다. 이렇게 하면 표준 협상 로직을 재사용하면서 추가 필드만 보강할 수 있습니다.
  • 디버깅 팁: 링크는 올라오지만 속도가 SPEED_UNKNOWN으로 남아 있다면, read_status가 genphy를 사용하고 있고 PHY가 비표준 속도 레지스터를 쓰는 상황입니다. ethtool -d eth0으로 레지스터 덤프(Dump)를 확인하고 BMSR/ANLPAR 비트를 직접 점검하세요.

Auto-Negotiation

Auto-Negotiation은 "링크 업" 자체보다 "어떤 모드로 링크를 올릴지"를 결정하는 규칙입니다. 구리 PHY에서는 FLP(Fast Link Pulse) 기반 Clause 28이 기본이고, SerDes 기반 인터페이스에서는 Clause 37이나 Clause 73의 in-band status가 많이 쓰입니다. 현대 장비는 속도뿐 아니라 pause, FEC, EEE, master/slave 역할까지 협상에 포함할 수 있습니다.

IEEE 802.3 PHY Clause 계보와 적용 범위 Clause 22 MDIO 기본 레지스터 접근 5비트 PHY + 5비트 reg 10M ~ 1G copper/fiber Clause 28 구리 Auto-Negotiation FLP burst → base page 10/100/1000BASE-T Clause 37 1000BASE-X SerDes AN 8B/10B in-band config 1G fiber, SGMII (변형) Clause 45 확장 MDIO (MMD 구조) 5비트 devad + 16비트 reg 10G 이상, 복합 PHY Clause 40 1000BASE-T AN 확장 master/slave + 1G 능력 광고 GBCR(0x09) / GBSR(0x0A) Clause 73 백플레인/고속 AN 48비트 DME page + Next Page 10G/25G/40G/100G KR/CR Clause 72/93 Link Training equalizer 계수 최적화 Clause 73 AN 뒤 자동 진입 관리→협상 SerDes AN 확장 관리 1G 확장 고속 AN Training Clause 관계 요약 Clause 22/45는 관리 인터페이스(MDIO 레지스터 접근 방식)이고, Clause 28/37/73은 Auto-Negotiation 프로토콜입니다. 구리(10/100/1000M): Clause 22 관리 + Clause 28 AN + Clause 40(1G 확장). 대부분의 임베디드 보드가 이 조합입니다. SerDes(1G fiber/SGMII): Clause 22 관리 + Clause 37 in-band AN. SFP 1G 모듈, MAC-PHY 간 SGMII에서 사용합니다. 고속(10G+): Clause 45 관리 + Clause 73 AN + Clause 72/93 Link Training. 백플레인, DAC, 광 모듈에서 사용합니다.
Clause 22/45는 레지스터 접근 방식(관리 인터페이스), Clause 28/37/73은 협상 프로토콜(Auto-Negotiation)입니다. 속도 범위와 매체에 따라 조합이 달라집니다.
규격주요 대상교환 방식실무 포인트
Clause 2810/100BASE-TFLP burst기본 페이지로 speed/duplex/pause 광고
Clause 371000BASE-X, SGMII8B/10B in-bandSerDes 링크와 MAC 상태 동기화
Clause 73백플레인, 10G 이상base page + next pageFEC, lane 특성, 고속 backplane와 결합

Clause 28: FLP 기반 Auto-Negotiation 상세

Clause 28은 10/100/1000BASE-T 구리 이더넷에서 사용하는 Auto-Negotiation 규격입니다. 양단의 PHY가 FLP(Fast Link Pulse) 버스트를 교환하여 지원하는 속도, duplex, pause 능력을 알리고, 공통적으로 가능한 최고 모드를 선택합니다. FLP 버스트는 33개의 펄스로 구성되며, 그중 17개의 클럭 펄스 사이에 끼워진 16개의 데이터 펄스가 하나의 Link Code Word(16비트)를 운반합니다.

Clause 28 FLP 버스트 구조 FLP 버스트: 33 펄스 = 17 클럭(C) + 16 데이터(D) 0V C D0 C D1 C D2 ... C D15 C 데이터 펄스 유무로 0/1 구분 (펄스 있음=1, 없음=0) Clause 28 Base Page (ANAR, reg 0x04) S[4:0] Selector A[4:0] Technology 5 10-T 6 10-T FD 7 100TX 8 100TX FD 9 100T4 10 Pause 11 Asym Pause 12 Rsvd 13 Rmt Fault 14 Ack 15 Next Page 우선순위 결정 규칙 (Highest Common Denominator) 양단이 광고한 능력의 교집합에서 우선순위가 가장 높은 모드가 선택됩니다: 1000BASE-T FD > 1000BASE-T HD > 100BASE-TX FD > 100BASE-T4 > 100BASE-TX HD > 10BASE-T FD > 10BASE-T HD 1000BASE-T 확장 (Clause 40, GBCR reg 0x09) bit 8: 1000BASE-T HD 광고, bit 9: 1000BASE-T FD 광고, bit 12:11: master/slave 설정 (port type + 난수 비교로 역할 결정)
FLP 버스트의 데이터 펄스가 Base Page의 각 비트를 운반합니다. 1000BASE-T 능력은 별도 레지스터(GBCR, 0x09)로 확장 광고됩니다.
Master/Slave 결정: 1000BASE-T에서는 양단 PHY 중 하나가 master(송신 클럭 기준), 나머지가 slave가 됩니다. GBCR(0x09)의 bit 12:11로 "prefer master/slave" 또는 "manual force"를 설정할 수 있으며, 양쪽 모두 force master로 설정하면 협상이 실패합니다. NIC-to-switch 연결에서 master/slave 충돌로 링크가 flap하는 경우가 드물지 않으며, ethtool -s eth0 master-slave forced-master로 디버깅할 수 있습니다.

NLP에서 FLP로: Auto-Negotiation의 탄생 배경

Auto-Negotiation이 등장하기 전, 10BASE-T PHY는 링크 유지를 위해 NLP(Normal Link Pulse)를 사용했습니다. NLP는 16ms 간격으로 전송되는 단일 전압 펄스(100ns 폭)로, "나는 살아 있다"는 단순 신호에 불과하며 어떤 정보도 운반하지 않습니다. 10BASE-T 시대에는 속도가 하나뿐이었으므로 이것만으로도 충분했습니다.

100BASE-TX가 등장하면서 문제가 생겼습니다. 10M 포트와 100M 포트가 같은 RJ45 커넥터로 연결될 수 있게 되자, 케이블을 꽂는 순간 양단이 어떤 속도로 통신할지 자동으로 합의하는 메커니즘이 필요해졌습니다. IEEE 802.3u(1995)는 기존 NLP 타이밍 슬롯 사이에 데이터 펄스를 끼워 넣어 16비트 정보를 운반하는 FLP(Fast Link Pulse) 버스트를 정의했고, 이것이 Clause 28 Auto-Negotiation의 물리적 기반입니다.

구분NLPFLP
목적링크 존재 확인(Link Integrity)능력 광고 + 링크 존재 확인
펄스 구조단일 펄스, 16ms 간격17 클럭 + 16 데이터 = 33 펄스 버스트
정보량0비트 (유무만 의미)16비트 (Link Code Word)
버스트 간격16ms ± 8ms16ms ± 8ms (버스트 단위)
하위 호환NLP만 인식하는 10M PHY는 FLP를 NLP로 해석 → 10M 링크 성립
하위 호환의 핵심: FLP 버스트의 클럭 펄스 간격은 NLP와 동일합니다. 따라서 AN을 모르는 구형 10BASE-T PHY는 FLP 내의 클럭 펄스를 NLP로 인식하여 10M 링크를 올립니다. 이것이 "자동 협상이 없는 장치와도 연결된다"는 Clause 28의 핵심 설계 원칙입니다.

FLP 물리 신호의 상세 구조

FLP 버스트 하나는 정확히 2ms 안에 33개의 펄스를 담고 있습니다. 홀수 번째 펄스(1, 3, 5, …, 33번)가 클럭 펄스이고, 짝수 번째 위치(2, 4, 6, …, 32번)가 데이터 펄스 슬롯입니다. 데이터 슬롯에 펄스가 있으면 비트 1, 없으면 비트 0으로 해석합니다.

파라미터IEEE 규격값설명
클럭 펄스 폭100ns ± 20nsNLP와 동일한 폭
클럭-데이터 간격62.5µs ± 7µs클럭 직후 데이터 슬롯까지의 간격
클럭-클럭 간격125µs ± 14µs인접 클럭 펄스 사이 간격
버스트 길이≈ 2ms33 펄스 × 62.5µs
버스트 간격16ms ± 8ms연속 FLP 버스트 사이
펄스 진폭CAT-5에서 ≈ 1V 피크차동 신호 기준

이 타이밍은 선로 위의 감쇠와 반사를 고려하여 정의되었습니다. 클럭-데이터 간격이 62.5µs인 이유는, 이보다 좁으면 매체 위의 반사 신호와 실제 데이터 펄스를 구분하기 어렵고, 이보다 넓으면 버스트 전체 시간이 길어져 협상 지연이 증가하기 때문입니다.

FLP 버스트 타이밍 상세 0V C₁ D₀=1 C₂ (없음) D₁=0 C₃ D₂=1 C₄ ··· C₁₇ 62.5µs 125µs (클럭 간격) 버스트 총 길이 ≈ 2ms (33 펄스) 다음 버스트 16ms ± 8ms 클럭 펄스 (항상 존재) 데이터 펄스 (있으면 1, 없으면 0) 펄스 폭: 100ns ± 20ns (차동 ≈ 1V peak)
FLP 버스트 내부의 타이밍 구조입니다. 클럭 펄스 간격 125µs, 클럭-데이터 간격 62.5µs가 핵심 파라미터이며, 데이터 펄스의 유무가 각 비트를 결정합니다.

Clause 28 Auto-Negotiation 상태 머신

IEEE 802.3 Clause 28은 Auto-Negotiation의 동작을 7개 주요 상태로 정의합니다. 이 상태 머신은 PHY 하드웨어 내부에서 실행되며, 커널이 BMCR의 AN Enable/Restart 비트를 세트하면 PHY가 자율적으로 상태를 전이합니다. 소프트웨어는 BMSR의 AN Complete 비트를 폴링하거나 인터럽트를 기다려 완료를 감지합니다.

Clause 28 Auto-Negotiation 상태 머신 AN_ENABLE BMCR.AN_ENABLE = 1 TRANSMIT_DISABLE 송신 정지, 수신 감시 ABILITY_DETECT FLP 송신 시작, 상대 base page 대기 ACK_DETECT Ack 비트 세트하여 응답 COMPLETE_ACK 양단 Ack 확인, consistency check NEXT_PAGE_WAIT Next Page 교환 (선택적) AN_GOOD_CHECK link_good 타이머 대기 AN_GOOD ✓ 협상 완료, BMSR.AN_COMPLETE = 1 PARALLEL_DETECT FLP 없이 신호 감지 → 폴백 AN 미지원 시 상대 base page 수신 상대 Ack 수신 NP 비트 = 1 추가 NP 교환 NP 없음 또는 NP 완료 link_good 타이머 만료 FLP 미수신, 신호만 감지 AN RESTART (BMCR bit 9)
Clause 28 상태 머신의 핵심 흐름입니다. 정상 경로는 ABILITY_DETECT → ACK_DETECT → COMPLETE_ACK → AN_GOOD이며, 상대가 FLP를 보내지 않으면 PARALLEL_DETECT로 폴백합니다.
상태진입 조건동작탈출 조건
AN_ENABLEBMCR.AN_ENABLE = 1PHY 내부 AN 회로 활성화즉시 ABILITY_DETECT로 전이
TRANSMIT_DISABLEAN 미지원 또는 링크 실패송신 정지, 수신만 감시FLP/NLP 감지 시 ABILITY_DETECT
ABILITY_DETECTAN 시작자신의 Base Page를 FLP로 반복 송신상대의 Base Page 수신(3회 일관)
ACK_DETECT상대 Base Page 수신Ack 비트를 세트한 Base Page 송신상대의 Ack 비트 수신
COMPLETE_ACK양단 Ack 교환일관성 검사(3회 동일 수신)NP 비트 확인 → NEXT_PAGE_WAIT 또는 AN_GOOD_CHECK
NEXT_PAGE_WAITNP 비트 = 1Next Page 교환 (EEE, OUI 등)NP 교환 완료 → AN_GOOD_CHECK
AN_GOOD_CHECK모든 페이지 교환 완료link_good 타이머 가동타이머 만료 → AN_GOOD
AN_GOODlink_good 확인BMSR.AN_COMPLETE = 1, 링크 업 보고링크 실패 또는 AN 재시작
일관성 검사(Consistency Check): ACK_DETECT와 COMPLETE_ACK에서 PHY는 상대의 Base Page를 최소 3회 연속 동일하게 수신해야 유효한 것으로 인정합니다. 이는 선로 위의 일시적 노이즈로 인한 비트 오류가 잘못된 협상 결과를 만드는 것을 방지합니다. 이 검사 때문에 AN 완료까지 최소 3 × 16ms = 48ms가 소요됩니다.

Base Page 비트 필드 완전 해부

Clause 28 Base Page(ANAR, 레지스터 0x04)는 16비트이며, FLP 버스트 하나가 이 16비트를 통째로 운반합니다. 상대편이 보내는 Base Page는 ANLPAR(레지스터 0x05)에서 읽을 수 있습니다. 각 비트 필드의 역할을 정확히 이해해야 협상 결과를 해석할 수 있습니다.

비트필드설명기본값
4:0Selector Field프로토콜 식별자. IEEE 802.3은 00001, IEEE 802.9(ISLAN 16T)은 00010. 사실상 00001만 사용됩니다.00001
510BASE-T10Mbps half-duplex 지원1
610BASE-T FD10Mbps full-duplex 지원1
7100BASE-TX100Mbps half-duplex 지원1
8100BASE-TX FD100Mbps full-duplex 지원1
9100BASE-T4100Mbps 4쌍(pair) 모드 (구식, 거의 사용하지 않음)0
10PAUSE대칭(symmetric) pause 프레임 지원PHY별
11ASM_DIR (Asymmetric Pause)비대칭 pause 지원 방향PHY별
12Reserved예약 비트, 항상 00
13Remote Fault상대편에게 "내 쪽에 오류가 있다"는 신호0
14Acknowledge상대의 Base Page를 정상 수신했다는 확인상태 머신 제어
15Next Page추가 페이지(Next Page)가 뒤따른다는 표시0 (NP 미사용 시)
Selector Field의 의미: Selector가 00001(IEEE 802.3)일 때만 비트 5~9의 기술(Technology) 필드가 위 표의 의미를 가집니다. 만약 Selector가 다른 값이면 기술 필드의 해석도 달라집니다. 실무에서는 00001 외의 값을 볼 일이 거의 없지만, ethtool 결과가 이상할 때 ANLPAR을 직접 읽어 Selector를 확인하면 원인을 좁힐 수 있습니다.
# Base Page (ANAR, reg 4) 직접 읽기
phytool read eth0/0/4
# 예: 0x05e1 = 0000 0101 1110 0001
#   Selector=00001(802.3), 10T=1, 10T-FD=1, 100TX=1, 100TX-FD=1, Pause=0

# 상대편 Base Page (ANLPAR, reg 5) 읽기
phytool read eth0/0/5
# 양단의 교집합에서 최고 우선순위가 결과 모드

# ethtool로도 확인 가능
ethtool eth0 | grep -A20 "Advertised link modes"

우선순위 결정 알고리즘 (Priority Resolution)

양단이 Base Page를 교환한 후, 공통으로 지원하는 모드의 교집합에서 IEEE 802.3이 정의한 고정 우선순위 표에 따라 최고 모드가 선택됩니다. 이 우선순위는 PHY 하드웨어에 내장되어 있으며 소프트웨어로 변경할 수 없습니다.

우선순위기술속도Duplex비고
1 (최고)1000BASE-T FD1000MFullClause 40 확장 레지스터(GBCR 0x09)로 광고
21000BASE-T HD1000MHalf사실상 미사용 (대부분 PHY가 미지원)
3100BASE-T2 FD100MFull2쌍 100M, 규격만 존재
4100BASE-TX FD100MFull가장 흔한 100M 모드
5100BASE-T2 HD100MHalf규격만 존재
6100BASE-T4100MHalf4쌍 CAT-3, 레거시(Legacy)
7100BASE-TX HD100MHalfhalf-duplex 100M
810BASE-T FD10MFull
9 (최저)10BASE-T HD10MHalf최소 공통 분모(Fallback)
Full Duplex가 항상 Half보다 높은 이유: Half-duplex는 CSMA/CD 충돌(Collision)이 발생하므로, 같은 속도라면 full-duplex가 실질 처리량이 2배 이상입니다. 따라서 100BASE-TX FD(우선순위 4)가 100BASE-TX HD(우선순위 7)보다 높습니다. 간혹 "속도는 높아야 하는데 왜 duplex까지 관여하느냐"라고 오해하지만, duplex 불일치는 속도 저하보다 훨씬 심각한 오동작을 유발합니다.

우선순위 결정은 커널 코드에서는 genphy_read_status()가 협상 완료 후 결과를 읽어오는 방식으로 반영됩니다. PHY 하드웨어가 이미 최고 공통 모드를 선택하여 동작 중이므로, 소프트웨어는 그 결과를 BMSR, ANLPAR, GBSR 레지스터에서 읽어 phydev->speed, phydev->duplex에 기록할 뿐입니다.

Next Page 프로토콜

Base Page 16비트만으로는 모든 능력을 표현할 수 없습니다. EEE(Energy Efficient Ethernet), 추가적인 기술 광고, OUI(Organizationally Unique Identifier) 기반 벤더 확장 정보를 전달하려면 Next Page 메커니즘이 필요합니다. Base Page의 비트 15(NP)를 1로 세트하면, 상태 머신은 COMPLETE_ACK 이후 NEXT_PAGE_WAIT 상태로 진입하여 추가 페이지를 교환합니다.

비트필드설명
10:0Message/Unformatted CodeMessage Page일 때는 메시지 코드, Unformatted Page일 때는 자유 데이터
11Toggle연속 페이지 동기화용 토글 비트 (매 페이지마다 반전)
12Ack2이전 페이지의 메시지를 수신·처리 완료했다는 확인
13Message Page1 = Message Page (코드 해석), 0 = Unformatted Page (원시 데이터)
14Ack상대 Next Page 수신 확인
15Next Page1 = 추가 페이지 있음, 0 = 마지막 페이지

Next Page 교환은 양단의 NP 비트가 모두 0이 될 때까지 반복됩니다. 한 쪽에 더 보낼 페이지가 없어도 상대가 NP=1을 보내면 Null Page(모두 0)로 응답해야 합니다. 이 프로토콜은 양방향이므로, 순서가 어긋나면 AN이 재시작됩니다.

EEE 광고와 Next Page: 10/100/1000BASE-T에서 EEE 능력은 Base Page에 비트가 없으므로 반드시 Next Page를 통해 교환합니다. ethtool --show-eee eth0에서 "Advertised EEE link modes"가 비어 있다면, PHY가 Next Page를 보내지 않거나 상대가 NP를 지원하지 않는 것일 수 있습니다. 커널에서는 genphy_config_eee_advert()가 EEE 광고를 MDIO MMD 레지스터(Clause 45 접근)에 설정하며, 이 정보가 Next Page 교환에 반영됩니다.
# Next Page 교환 레지스터 확인 (Clause 22)
# ANNPTR (reg 7) — 로컬 Next Page 송신
phytool read eth0/0/7

# ANNPRR (reg 8) — 상대편 Next Page 수신
phytool read eth0/0/8

# EEE 광고 레지스터 (Clause 45, MMD 7 reg 60)
phytool read eth0/7/60
# bit 1: 100BASE-TX EEE, bit 2: 1000BASE-T EEE

# 상대편 EEE 능력 (MMD 7 reg 61)
phytool read eth0/7/61

Pause 프레임 해석 알고리즘 (Annex 28B)

Auto-Negotiation에서 pause 능력은 Base Page의 비트 10(PAUSE)과 비트 11(ASM_DIR)으로 교환됩니다. 양단의 이 두 비트 조합으로 실제 흐름 제어(Flow Control) 방향이 결정되는데, 이 해석 규칙은 IEEE 802.3 Annex 28B에 정의되어 있으며 직관적이지 않으므로 주의가 필요합니다.

로컬 PAUSE로컬 ASM_DIR상대 PAUSE상대 ASM_DIR로컬→상대 Pause상대→로컬 Pause
00XX불가불가
0111불가가능
101X가능가능 (대칭)
1101가능불가
1110가능가능 (대칭)
1111가능가능 (대칭)

핵심 포인트는 다음과 같습니다:

실무 함정: 대부분의 NIC 드라이버는 기본적으로 PAUSE=1, ASM_DIR=1을 광고합니다. 그런데 일부 임베디드 보드의 MAC 드라이버는 pause 처리 로직을 구현하지 않으면서도 광고만 켜 두는 경우가 있습니다. 이 경우 스위치가 해당 포트에 pause 프레임을 보내면 MAC이 이를 무시하여 패킷 손실이 발생합니다. ethtool -A eth0 rx off tx off로 명시적으로 꺼야 안전합니다.
/* 커널 내부 pause 해석 — drivers/net/phy/phy_device.c 참조 */
void phy_resolve_aneg_pause(struct phy_device *phydev)
{
    /* ANAR(reg 4)의 pause 비트와 ANLPAR(reg 5)의 pause 비트를 비교 */
    if (phydev->duplex == DUPLEX_FULL) {
        bool lcl_pause  = linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT,
                                              phydev->advertising);
        bool lcl_asym   = linkmode_test_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT,
                                              phydev->advertising);
        bool rmt_pause  = linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT,
                                              phydev->lp_advertising);
        bool rmt_asym   = linkmode_test_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT,
                                              phydev->lp_advertising);

        /* IEEE 802.3 Annex 28B 테이블 구현 */
        phydev->pause = lcl_pause && rmt_pause;
        phydev->asym_pause = lcl_pause ^ rmt_pause;
    }
}

1000BASE-T Master/Slave 결정 메커니즘 상세 (Clause 40)

1000BASE-T는 4쌍 모두를 동시에 사용하는 양방향 통신이므로, 한쪽이 master(타이밍 기준 송신), 다른 쪽이 slave(타이밍 추종 수신) 역할을 맡아야 합니다. 이 역할은 신호의 부호화(PAM-5)에서 클럭 복구 기준점을 결정하므로, 양쪽이 같은 역할을 선택하면 통신이 불가능합니다.

Master/slave 결정은 GBCR(Gigabit Control Register, 레지스터 0x09)의 비트 12:11로 설정하며, 4가지 모드가 있습니다:

bit 12 (MS Manual)bit 11 (MS Value)의미사용 시나리오
00자동, multiport(slave 선호)스위치 포트 (기본값)
01자동, single-port(master 선호)NIC (기본값)
10강제 slave디버깅용
11강제 master디버깅용

자동 모드에서의 결정 과정은 다음과 같습니다:

  1. 양단이 GBCR의 bit 11(MS Value)을 교환합니다. 하나가 1(master 선호)이고 다른 하나가 0(slave 선호)이면 즉시 역할이 결정됩니다.
  2. 양쪽 모두 같은 값(둘 다 master 선호 또는 둘 다 slave 선호)이면, 각 PHY가 생성한 10비트 난수(seed)를 비교하여 큰 쪽이 master가 됩니다.
  3. 난수까지 같으면(확률 1/1024) AN이 재시작됩니다.
1000BASE-T Master/Slave 결정 흐름 AN 시작: GBCR bit 12:11 교환 bit 12 = 1? (강제 모드?) bit 11 값으로 즉시 결정 양쪽 모두 강제 master → AN 실패! 아니오 양단 bit 11 동일? bit 11=1 → master 결정 다름 같음 10비트 난수(seed) 비교 큰 쪽 = master 동일(1/1024 확률) → AN 재시작
Master/slave 결정 흐름입니다. 자동 모드에서는 preference 비트 → 난수 비교 순서로 역할을 결정합니다. 강제 모드 충돌은 링크 실패의 흔한 원인입니다.
Master/Slave 충돌 사례: 일부 저가형 스위치가 모든 포트를 "force master"로 설정하는 경우가 있습니다. 이런 스위치에 NIC를 연결하면 NIC도 기본값이 "prefer master(single-port)"이므로, 난수 비교로 해결되기는 하지만 케이블 품질이 나쁘면 seed 교환 자체가 실패하여 링크가 반복적으로 flap합니다. ethtool eth0에서 master-slave cfg: preferred-master / master-slave status:로 현재 상태를 확인하세요.
# master/slave 상태 확인
ethtool eth0 | grep -i "master-slave"
# master-slave cfg:  preferred-master
# master-slave status:  master

# 강제 slave로 변경 (디버깅)
ethtool -s eth0 master-slave forced-slave

# GBCR(reg 0x09) 직접 확인
phytool read eth0/0/9
# bit 12:11 확인, bit 9: 1000FD 광고, bit 8: 1000HD 광고

# GBSR(reg 0x0A) — 협상 결과
phytool read eth0/0/10
# bit 14: master/slave config fault (양쪽 강제 동일 시 1)
# bit 13: local=master(1)/slave(0)

Auto-Negotiation 타이밍 파라미터

AN 완료까지 걸리는 시간은 고정값이 아니라, 매체 조건, PHY 구현, 교환할 페이지 수에 따라 달라집니다. 아래는 IEEE 802.3이 정의한 타이밍과 실측치입니다.

파라미터규격값실측 범위설명
break_link_timer≥ 75ms75~100msAN 시작 시 기존 링크를 끊고 대기하는 시간
autoneg_wait_timer1000ms (최소)1~3초AN 미완료 시 parallel detect로 전환하기 전 대기
link_fail_inhibit_timer750~1000msPHY 구현별AN 완료 후 실제 링크 업 전까지 안정화 대기
FLP 교환 최소 시간≈ 100ms50~200msBase Page 교환 + Ack (3회 일관)
Next Page 1쌍 교환≈ 50ms48~100ms3회 일관 × 16ms
1000BASE-T 링크 훈련500ms~2초master/slave + equalizer 수렴
AN 전체 (10/100M)1~3초케이블 삽입부터 링크 업까지
AN 전체 (1000BASE-T)2~5초equalizer 훈련 포함
AN 전체 (2.5G/5GBASE-T)3~8초고속일수록 훈련 시간 증가
1000BASE-T가 유독 느린 이유: 10/100M AN은 FLP 교환만으로 끝나지만, 1000BASE-T는 AN 완료 후 추가로 PHY Training 단계가 필요합니다. 4쌍 양방향 PAM-5 부호화에서 echo cancellation과 NEXT/FEXT 제거 필터의 계수가 수렴해야 하므로, 케이블이 길거나 품질이 나쁘면 이 훈련 시간이 크게 늘어납니다. dmesg에서 "Link is Up" 메시지의 타임스탬프를 보면 보드별 AN 소요 시간을 측정할 수 있습니다.

커널의 Auto-Negotiation 구현 흐름

리눅스 커널에서 AN은 크게 세 단계로 나뉩니다: 광고 설정, AN 시작, 결과 판독. 이 흐름은 phylib 상태 머신과 ethtool 인터페이스를 통해 제어됩니다.

커널 Auto-Negotiation 구현 흐름 사용자 공간 ethtool -s eth0 autoneg on ethtool -s eth0 advertise 0x... ethtool eth0 (결과 읽기) ethtool 커널 계층 ethnl_set_linkmodes() ethnl_get_linkmodes() phylib / PHY 드라이버 ① config_aneg() ② genphy_config_advert() ③ BMCR AN_RESTART ④ phy_state_machine() 폴링 ⑤ read_status() 호출 ⑥ adjust_link() 콜백 PHY 하드웨어 ANAR(reg4) + GBCR(reg9) → FLP 송신 → 상태 머신 자율 실행 → BMSR.AN_COMPLETE → ANLPAR(reg5) + GBSR(reg0A) 결과 저장 인터럽트(link_change) 또는 폴링으로 커널에 완료 통보 MDIO 쓰기 IRQ/폴링 핵심 요약 ① config_aneg: phydev->advertising → ANAR/GBCR 레지스터에 기록 + BMCR AN_RESTART ② phy_state_machine: 1초 간격 폴링(또는 IRQ)으로 BMSR.AN_COMPLETE 감시 → read_status → MAC에 adjust_link 통보
커널에서 AN은 ethtool → phylib → PHY 하드웨어 → 인터럽트/폴링 → read_status → MAC 콜백 순서로 진행됩니다.
/* AN 전체 흐름 요약 — 관련 커널 함수 */

/* 1단계: 광고 설정 + AN 시작 */
phy_ethtool_set_link_ksettings()
  → phydev->autoneg = AUTONEG_ENABLE;
  → linkmode_copy(phydev->advertising, ...);
  → phy_start_aneg(phydev);
    → phydev->drv->config_aneg(phydev);  /* 대부분 genphy_config_aneg */genphy_config_advert(phydev);   /* ANAR, GBCR 기록 */phy_modify(phydev, MII_BMCR, ..., BMCR_ANRESTART);

/* 2단계: 상태 머신 폴링 (1초 주기) */
phy_state_machine()
  → phydev->drv->read_status(phydev);  /* 대부분 genphy_read_status */genphy_update_link(phydev);       /* BMSR.LINK_STATUS 확인 */genphy_read_lpa(phydev);          /* ANLPAR, GBSR 판독 */genphy_read_master_slave(phydev); /* GBSR bit 14:13 판독 */
    → phydev->speed, phydev->duplex 갱신

/* 3단계: MAC 통보 */
phy_adjust_link(phydev->attached_dev);
  → MAC 드라이버의 adjust_link() 콜백
  → 속도 변경, duplex 변경, pause 설정 반영
genphy_update_link()의 이중 판독: BMSR의 LINK_STATUS 비트(bit 2)는 latch-low 특성을 가집니다. 즉 링크가 잠깐이라도 끊겼으면 0으로 래치되고, 다음 판독에서야 현재 상태를 반영합니다. 따라서 genphy_update_link()는 첫 번째 읽기 결과가 0이면 한 번 더 읽어서 현재 실제 링크 상태를 확인합니다. 이 동작을 모르면 "링크가 살아 있는데 커널이 down으로 보고한다"는 오해가 생길 수 있습니다.

광고 비트 제어와 속도 제한 전략

AN을 유지하면서 특정 속도만 광고하는 것은, AN을 완전히 끄는 것보다 안전한 속도 제한 방법입니다. AN을 끄면 parallel detect 위험이 생기지만, 광고 비트만 조절하면 AN 프로토콜은 정상 동작하면서 원하는 속도로 합의할 수 있습니다.

# 현재 광고 확인
ethtool eth0 | grep -A5 "Advertised link modes"

# 100M Full만 광고 (AN은 유지)
ethtool -s eth0 autoneg on advertise 0x008

# 1000M Full만 광고
ethtool -s eth0 autoneg on advertise 0x020

# 100M + 1000M Full 광고 (10M 제외)
ethtool -s eth0 autoneg on advertise 0x028

# 모든 지원 속도 광고 복원
ethtool -s eth0 autoneg on advertise 0x03f

# advertise 비트맵 의미 (ethtool 비트)
#   0x001 = 10baseT Half
#   0x002 = 10baseT Full
#   0x004 = 100baseT Half
#   0x008 = 100baseT Full
#   0x010 = 1000baseT Half
#   0x020 = 1000baseT Full
#   0x1000 = 2500baseT Full (NBASE-T)
#   0x2000 = 5000baseT Full (NBASE-T)
실전 팁 — 불안정한 1G 링크 해결: 케이블 품질이 나빠서 1G에서 CRC가 폭증하지만 케이블 교체가 불가능할 때, AN을 끄고 100M으로 강제하는 대신 advertise 0x008(100M Full만)로 설정하면 AN 프레임워크는 그대로 유지되면서 100M Full-duplex로 안전하게 합의합니다. Pause 협상도 정상 동작합니다.

Auto-Negotiation 실패 패턴과 체계적 디버깅

AN 관련 문제는 크게 네 가지 범주로 나뉩니다. 각 범주별 증상과 원인, 점검 순서를 정리합니다.

범주 1: AN 자체가 완료되지 않음

증상가능한 원인점검 방법
BMSR.AN_COMPLETE가 영구적으로 0상대편 PHY 비활성 또는 AN 미지원phytool read eth0/0/1 (BMSR), 상대편 전원/설정 확인
AN 무한 재시작 (링크 flap)master/slave 충돌, 심한 노이즈ethtool eth0 | grep master, GBSR bit 14(config fault) 확인
AN 시간 초과 후 parallel detect상대편 AN off, FLP 미송신상대편 autoneg 설정 확인

범주 2: AN 완료되나 기대와 다른 속도

증상가능한 원인점검 방법
1G 가능한데 100M로 링크상대편이 1G 미광고, 케이블 4쌍 미결선ANLPAR(reg5) 읽기, 케이블 테스터로 쌍 확인
2.5G 포트가 1G로만 링크상대편 NBASE-T 미지원, CAT-5 케이블ethtool eth0 | grep "Link partner"
100M만 가능 (10M로도 안 내림)한쪽 광고 마스크가 100M만 허용양단 advertise 비트맵 비교

범주 3: AN 완료되나 duplex mismatch

증상가능한 원인점검 방법
late collision 폭증, 처리량 1/10 이하한쪽 autoneg off → 상대 parallel detect → half duplex양단 duplex 비교, ethtool -S eth0 | grep collision
간헐적 패킷 손실 (큰 프레임에서)duplex mismatch + TCP 재전송으로 숨겨진 상태netstat -s | grep retrans, ethtool eth0 | grep Duplex

범주 4: AN 완료되나 pause 관련 문제

증상가능한 원인점검 방법
대역폭 충분한데 간헐적 지연불필요한 pause 프레임 수신ethtool -S eth0 | grep pause
pause 협상 실패한쪽이 PAUSE=0/ASM_DIR=0 광고ethtool -a eth0, ANAR/ANLPAR 비트 10:11 비교
# AN 문제 체계적 디버깅 순서

# 1. 로컬 PHY 상태 확인
ethtool eth0
# Speed, Duplex, Autoneg, Link detected, master-slave status 확인

# 2. 광고/상대편 능력 비교
ethtool eth0 | grep -E "Supported|Advertised|Link partner"

# 3. 핵심 레지스터 직접 판독
phytool read eth0/0/0   # BMCR: AN enable? speed? duplex?
phytool read eth0/0/1   # BMSR: AN complete? link status?
phytool read eth0/0/4   # ANAR: 로컬 광고
phytool read eth0/0/5   # ANLPAR: 상대 광고
phytool read eth0/0/6   # ANER: AN 확장 상태
phytool read eth0/0/9   # GBCR: 1G 광고 + master/slave
phytool read eth0/0/10  # GBSR: 1G 결과 + config fault

# 4. 카운터로 증상 수치화
ethtool -S eth0 | grep -E "crc|collision|error|pause|drop"

# 5. 링크 flap 감시
ip -ts monitor link dev eth0

# 6. dmesg에서 PHY 이벤트 확인
dmesg | grep -i "link\|phy\|eth0"

NBASE-T (2.5G/5G/10GBASE-T) Auto-Negotiation 고려사항

NBASE-T(IEEE 802.3bz)는 기존 CAT-5e/6 케이블에서 1G 이상의 속도를 제공하기 위해 개발되었습니다. AN 관점에서 NBASE-T는 기존 Clause 28/40 프레임워크를 확장하여 2.5G와 5G 능력을 광고합니다.

속도케이블 요구광고 방법Master/Slave
2.5GBASE-TCAT-5e (100m)Clause 45 MMD 7, reg 32 bit 7Clause 40 확장 (동일 메커니즘)
5GBASE-TCAT-5e (100m, 일부 CAT-6 권장)Clause 45 MMD 7, reg 32 bit 8Clause 40 확장
10GBASE-TCAT-6a (100m) / CAT-6 (55m)Clause 45 MMD 7, reg 32 bit 12Clause 40 확장

NBASE-T AN의 핵심 차이점:

# 2.5G/5G 광고 레지스터 확인 (Clause 45, MMD 7 reg 32)
phytool read eth0/7/32
# bit 7: 2.5GBASE-T 광고, bit 8: 5GBASE-T 광고

# 상대편 NBASE-T 능력 (MMD 7 reg 33)
phytool read eth0/7/33

# ethtool로 확인
ethtool eth0 | grep "2500\|5000\|10000"

# downshift 설정 확인 (PHY 벤더별)
# Realtek: phytool read eth0/0/20 (page 0xa43)
# Marvell: phytool read eth0/7/0x8001

EEE와 Auto-Negotiation의 상호작용

EEE(Energy Efficient Ethernet, IEEE 802.3az)는 AN의 Next Page 메커니즘을 통해 협상되지만, 실제 EEE 능력 레지스터는 Clause 45 MMD 7에 별도로 존재합니다. AN 과정에서 EEE가 어떻게 처리되는지 이해하면 EEE 관련 링크 불안정 문제를 풀 수 있습니다.

  1. AN 시작 전: 커널은 genphy_config_eee_advert()를 호출하여 MMD 7 레지스터 60(EEE Advertisement)에 지원 모드를 기록합니다.
  2. AN 진행 중: PHY 하드웨어가 Base Page 교환 후 Next Page를 통해 EEE 능력을 교환합니다. 이 과정은 PHY 내부에서 자동으로 처리됩니다.
  3. AN 완료 후: MMD 7 레지스터 61(EEE LP Ability)에서 상대편 EEE 능력을 확인할 수 있습니다. 양단 모두 지원하는 속도에서만 EEE가 활성화됩니다.
  4. LPI(Low Power Idle) 진입: 링크 업 이후 트래픽이 없으면 PHY가 LPI 모드로 진입하여 전력을 절감합니다. 복귀 시간(wake time)은 100BASE-TX에서 30µs, 1000BASE-T에서 16.5µs입니다.
EEE와 링크 불안정: EEE가 활성화되면 LPI 진입/복귀 과정에서 일시적인 신호 변동이 발생합니다. 케이블이 길거나 노이즈가 심한 환경에서는 이 변동이 링크 flap이나 CRC 증가로 나타날 수 있습니다. 문제 발생 시 ethtool --set-eee eth0 eee off로 EEE를 끄고 증상이 개선되는지 확인하세요. 임베디드 장비에서는 EEE를 기본적으로 끄는 것이 일반적입니다.

Auto-Negotiation 관련 레지스터 종합 참조표

AN 디버깅에 필요한 모든 레지스터를 한 곳에 모았습니다. Clause 22(기본)와 Clause 45(확장)로 구분합니다.

레지스터번호이름핵심 비트용도
— Clause 22 기본 레지스터 —
BMCR0x00Basic Mode Controlbit 12: AN Enable, bit 9: AN RestartAN 시작/정지 제어
BMSR0x01Basic Mode Statusbit 5: AN Complete, bit 3: AN Ability, bit 2: Link StatusAN 완료 여부, 링크 상태
ANAR0x04AN Advertisementbit 10:11: Pause, bit 5~9: Technology로컬 광고 설정
ANLPAR0x05AN Link Partner Ability(ANAR과 동일 구조)상대편 광고 판독
ANER0x06AN Expansionbit 4: Parallel Detect Fault, bit 1: Page ReceivedAN 확장 상태
ANNPTR0x07AN Next Page Transmitbit 15: NP, bit 13: MP, bit 10:0: CodeNext Page 송신
ANNPRR0x08AN Next Page Receive(ANNPTR과 동일 구조)Next Page 수신
GBCR0x091000BASE-T Controlbit 12:11: MS 설정, bit 9: 1G FD, bit 8: 1G HD1G 광고 + master/slave
GBSR0x0A1000BASE-T Statusbit 14: MS Config Fault, bit 13: Master/Slave1G 협상 결과
— Clause 45 확장 레지스터 (MMD 7: AN) —
AN Control7.0AN Controlbit 12: AN Enable, bit 9: AN RestartClause 45 AN 제어
AN Status7.1AN Statusbit 5: AN Complete, bit 3: AN AbilityClause 45 AN 상태
EEE Adv7.60EEE Advertisementbit 1: 100TX EEE, bit 2: 1G EEE, bit 3: 10G EEEEEE 광고
EEE LP7.61EEE LP Ability(EEE Adv와 동일 구조)상대편 EEE 능력
NBASE-T Adv7.3210G/NBASE-T AN Controlbit 7: 2.5G, bit 8: 5G, bit 12: 10GNBASE-T 광고
NBASE-T LP7.3310G/NBASE-T AN Status(7.32와 동일 구조)상대편 NBASE-T 능력
레지스터 판독 순서: AN 문제를 디버깅할 때는 항상 BMSR → ANAR → ANLPAR → GBCR → GBSR 순서로 읽는 것을 권장합니다. BMSR로 AN 완료 여부를 먼저 확인하고, 양단 광고(ANAR vs ANLPAR)를 비교하고, 1G 환경이면 GBSR의 master/slave 상태와 config fault를 점검합니다. 이 5개 레지스터만 읽어도 AN 관련 문제의 90%를 진단할 수 있습니다.
강제 설정의 함정: 한쪽만 autoneg off로 고정하면 상대편이 평행 감지(parallel detect)로 링크를 올리면서 duplex를 잘못 해석할 수 있습니다. 이런 경우 링크는 올라오지만 late collision, carrier error, 심한 처리량(Throughput) 저하가 발생합니다. 양쪽 정책을 반드시 짝으로 맞추세요.
# 현재 링크 협상 결과
ethtool eth0

# 자동 협상 끄고 강제 설정
ethtool -s eth0 speed 1000 duplex full autoneg off

# EEE 상태 확인
ethtool --show-eee eth0

# 링크 업/다운 이벤트 실시간 감시
ip monitor link

협상 디버깅에서 중요한 것은 "로컬이 무엇을 광고했고 상대편이 무엇을 응답했는가"입니다. 단순히 속도 결과만 보면 중간 원인을 놓칩니다. 예를 들어 2.5G 포트가 1G로만 붙는다면 케이블 품질 문제일 수도 있지만, 상대편의 advertisement mask가 1G까지만 켜져 있는 정책 문제일 수도 있습니다.

Auto-Negotiation 협상 흐름 비교: Clause 28, 37, 73 Auto-Negotiation 협상 흐름 비교 Clause 28 (10/100/1000BASE-T) 로컬 PHY 원격 PHY FLP burst FLP burst Base Page 교환 (speed/duplex/pause) Next Page (EEE, master/slave 등) 최고 공통 모드 선택 실패 시 → Parallel Detect (상대 강제 모드) Clause 37 (1000BASE-X/SGMII) 로컬 PCS 원격 PCS 8B/10B config 8B/10B config Config Reg 교환 (pause, duplex) SGMII: speed/duplex control word Link Up + In-Band 동기 속도 고정 (1G), pause/duplex만 협상 Clause 73 (백플레인/10G+) 로컬 AN 원격 AN DME page DME page Base Page (speed/FEC/lane 광고) Next Page 체인 (확장 능력) Link Training 진입 equalization 튜닝 후 데이터 전송 시작 Parallel Detect (폴백) 상대편이 AN을 지원하지 않으면 수신 신호만으로 속도를 추정 → duplex를 half로 잘못 설정할 위험이 있음
세 가지 Auto-Negotiation 규격은 적용 매체와 교환 방식이 다르지만, 모두 "양단이 능력을 공개하고 최고 공통 모드를 선택한다"는 원칙을 따릅니다.

Parallel Detect와 강제 모드의 위험

Auto-Negotiation이 실패하거나 상대편이 AN을 지원하지 않을 때, PHY는 Parallel Detect로 폴백(Fallback)합니다. 이 모드는 상대편이 보내는 신호의 패턴만으로 속도를 감지하되, duplex 정보는 교환하지 않습니다. 결과적으로 한쪽은 full duplex, 다른 쪽은 half duplex로 동작하는 duplex mismatch가 발생할 수 있습니다.

로컬 설정상대편 설정결과증상
autoneg onautoneg on정상 협상없음
autoneg onautoneg off (1G full)parallel detect → 1G halflate collision, 처리량 저하
autoneg off (1G full)autoneg off (1G full)강제 1G full동작하지만 pause 미협상
autoneg off (100M full)autoneg onparallel detect → 100M halfduplex mismatch
핵심 규칙: AN을 끄려면 반드시 양쪽 모두 같은 speed/duplex로 강제 설정해야 합니다. 한쪽만 끄면 다른 쪽이 parallel detect로 빠지면서 duplex mismatch가 거의 확실하게 발생합니다. 이 오류는 링크가 정상적으로 올라오기 때문에 문제를 인지하기까지 시간이 걸리는 경우가 많습니다.
# 양단 광고 능력 비교 — 로컬
ethtool eth0 | grep -E "Supported|Advertised|Link partner"

# 특정 속도만 광고하도록 제한 (AN 유지)
ethtool -s eth0 autoneg on advertise 0x020   # 1000baseT Full만

# master/slave 역할 확인 (1000BASE-T 이상)
ethtool eth0 | grep -i "master"

Auto-Negotiation Off: 강제 모드 상세 동작과 조건

Auto-Negotiation을 비활성화하면 PHY는 BMCR(Basic Mode Control Register)의 비트 12(AN Enable)를 0으로 클리어하고, 비트 13(Speed Selection MSB), 비트 6(Speed Selection LSB), 비트 8(Duplex Mode)의 조합으로 속도와 듀플렉스를 직접 지정합니다. FLP(Fast Link Pulse) 교환이 중단되므로 상대편과 어떤 능력 정보도 주고받지 않습니다.

BMCR 강제 모드 비트 구성

AN을 끄면 BMCR의 속도/듀플렉스 비트가 직접 PHY 동작을 결정합니다. AN Enable(비트 12)이 1일 때는 이 비트들이 무시되지만, 0이 되면 즉시 유효해집니다.

BMCR 비트이름의미
bit 13 + bit 6Speed Selection0010 Mbps
bit 13 + bit 6Speed Selection01100 Mbps
bit 13 + bit 6Speed Selection101000 Mbps
bit 8Duplex Mode0Half Duplex
bit 8Duplex Mode1Full Duplex
bit 12AN Enable0강제 모드 활성 (위 비트 유효)
bit 9Restart AN무시됨AN이 꺼져 있으면 효과 없음

커널 구현: genphy_setup_forced()

사용자가 ethtool -s eth0 autoneg off speed 1000 duplex full을 실행하면, 커널은 phy_ethtool_sset()phy_start_aneg()config_aneg() 경로를 타고, phydev->autoneg == AUTONEG_DISABLE이면 genphy_setup_forced()를 호출합니다. 이 함수는 BMCR에서 AN Enable 비트를 클리어하고 speed/duplex 비트를 직접 세트합니다.

/* drivers/net/phy/phy_device.c — 강제 모드 레지스터 설정 */
int genphy_setup_forced(struct phy_device *phydev)
{
    u16 ctl = 0;

    phydev->pause = 0;
    phydev->asym_pause = 0;

    /* speed 비트 설정 */
    if (phydev->speed == SPEED_1000)
        ctl |= BMCR_SPEED1000;
    else if (phydev->speed == SPEED_100)
        ctl |= BMCR_SPEED100;
    /* SPEED_10이면 두 비트 모두 0 */

    /* duplex 비트 설정 */
    if (phydev->duplex == DUPLEX_FULL)
        ctl |= BMCR_FULLDPLX;

    /* BMCR 쓰기: AN Enable 클리어, speed/duplex 적용 */
    return phy_modify(phydev, MII_BMCR,
                        BMCR_ANENABLE | BMCR_SPEED1000 |
                        BMCR_SPEED100 | BMCR_FULLDPLX, ctl);
}

phy_sanitize_settings()genphy_setup_forced() 호출 전에 실행되어, 요청된 speed/duplex 조합이 PHY의 supported 비트맵에 존재하는지 검증합니다. 지원하지 않는 조합이면 가능한 최고 속도로 자동 하향 조정합니다.

/* drivers/net/phy/phy.c — 강제 모드 설정값 검증 */
void phy_sanitize_settings(struct phy_device *phydev)
{
    const struct phy_setting *setting;

    /* speed/duplex 조합이 supported에 있는지 확인 */
    setting = phy_find_valid(phydev->speed, phydev->duplex,
                              phydev->supported);
    if (setting) {
        phydev->speed = setting->speed;
        phydev->duplex = setting->duplex;
    } else {
        /* 유효한 조합이 없으면 최저 속도로 폴백 */
        phydev->speed = SPEED_UNKNOWN;
        phydev->duplex = DUPLEX_UNKNOWN;
    }
}

속도별 강제 모드 제약 조건

모든 이더넷(Ethernet) 속도가 동일하게 강제 모드를 지원하는 것은 아닙니다. 물리 계층의 특성에 따라 속도별로 제약 사항이 크게 다릅니다.

속도강제 모드 지원제약 및 주의사항
10BASE-T완전 지원NLP(Normal Link Pulse)만 사용하므로 AN 없이도 안정적으로 동작합니다. Manchester 인코딩은 클럭 복원이 간단하여 양단 강제 설정이 가장 문제없는 속도입니다.
100BASE-TX완전 지원MLT-3 인코딩과 4B5B 코딩으로 별도의 훈련 과정 없이 동작합니다. 양단 강제 설정 시 가장 많이 사용되는 속도입니다.
1000BASE-T제한적 지원Master/Slave 역할 결정이 필요합니다. 정상적인 AN에서는 Clause 28의 확장으로 master/slave를 자동 결정하지만, AN을 끄면 양단 모두 PHY가 기본값(보통 slave)으로 고정됩니다. 양쪽 모두 같은 역할이면 링크가 올라오지 않습니다. 한쪽은 반드시 master, 다른 쪽은 slave로 수동 설정해야 합니다.
2.5GBASE-T / 5GBASE-TPHY 벤더 의존NBASE-T Alliance(현 MGBASE-T) 표준은 AN을 전제로 설계되었습니다. 일부 PHY는 강제 모드를 벤더 특정 레지스터로 지원하지만, 표준 BMCR 비트만으로는 2.5G/5G 속도를 지정할 수 없습니다(BMCR speed 비트 조합에 해당 값이 없음).
10GBASE-T실질적 불가DSP 기반 128-DSQ 변조와 LDPC 코딩으로 반드시 링크 트레이닝(Link Training)이 필요합니다. AN 없이는 equalizer 계수를 교환할 수 없어 물리적으로 링크를 올릴 수 없습니다. Clause 55는 AN을 필수(mandatory)로 규정합니다.
25G/40G/100G+불가Clause 73 AN과 Clause 72 링크 트레이닝이 필수입니다. SerDes 기반 인터페이스에서 equalizer 튜닝 없이는 BER(Bit Error Rate)이 허용 범위를 초과합니다.
1000BASE-T 강제 모드의 Master/Slave 함정: 1000BASE-T는 4쌍 전이중 전송으로 동작하며, master가 송신 클럭의 기준이 됩니다. AN을 끄면 GBCR(레지스터 9)의 bit 12(Manual Master/Slave Enable)를 1로 세트하고, bit 11(Master/Slave Value)로 역할을 지정해야 합니다. 이 설정을 빠뜨리면 양쪽 모두 slave가 되어 클럭 동기화에 실패하고 링크가 올라오지 않습니다.
# 1000BASE-T 강제 모드 — 양단 설정 예시
# 장비 A (master로 설정)
ethtool -s eth0 speed 1000 duplex full autoneg off
phytool write eth0/0/9 0x1800   # GBCR: manual M/S enable + master

# 장비 B (slave로 설정)
ethtool -s eth0 speed 1000 duplex full autoneg off
phytool write eth0/0/9 0x1000   # GBCR: manual M/S enable + slave

# master/slave 상태 확인
ethtool eth0 | grep -i "master"

강제 모드에서 상실되는 기능

AN을 끄면 FLP 교환이 중단되므로, AN의 Base Page와 Next Page를 통해 협상되던 여러 기능이 자동으로 비활성화됩니다. 이 영향은 단순한 속도/듀플렉스 고정보다 훨씬 광범위합니다.

기능AN 활성 시AN 비활성(강제 모드) 시영향
Pause (흐름 제어)Base Page bit 10/11로 협상협상 불가, 양단 모두 비활성수신 버퍼 오버플로 시 프레임 드롭 증가. 특히 버스트 트래픽이 많은 환경에서 패킷 손실이 악화됩니다.
Asymmetric PauseASM_DIR 비트로 방향 협상협상 불가단방향 흐름 제어가 불가능합니다. DCB(Data Center Bridging) 환경에서는 PFC도 영향을 받습니다.
EEE (Energy Efficient Ethernet)Next Page 또는 LLDP로 협상협상 불가, EEE 비활성PHY가 LPI(Low Power Idle) 상태에 진입하지 않아 유휴 시 전력 소모가 증가합니다. 서버/데이터센터에서는 의도적으로 EEE를 끄는 경우가 많으므로 이것이 오히려 장점일 수 있습니다.
Master/Slave 역할preference + 난수로 자동 결정수동 설정 필수 (1G 이상)수동 설정을 빠뜨리면 링크 자체가 올라오지 않습니다.
DownshiftPHY가 자동으로 낮은 속도 재시도비활성 (고정 속도)케이블 품질이 나빠도 지정 속도에서만 재시도하므로 링크 실패가 지속됩니다.
Remote Fault 통지Base Page bit 13으로 전달전달 불가상대편 PHY 오류를 감지할 수 없어 장애 원인 추적이 어려워집니다.
Next Page 확장 능력FEC, 추가 속도, OUI 메시지전달 불가고속 환경에서 FEC 협상이 안 되면 BER이 증가합니다.

강제 모드를 사용해야 하는 정당한 사유

대부분의 환경에서 AN on이 권장되지만, AN을 끄는 것이 올바른 선택인 구체적인 시나리오가 있습니다.

시나리오사유설정 시 주의사항
관리형 스위치의 정책 강제네트워크 관리자가 포트별 속도/듀플렉스를 중앙에서 통제할 때. 협상 결과의 불확실성을 제거합니다.반드시 양단을 같은 값으로 설정합니다. 스위치 포트와 서버 NIC 양쪽 모두 변경해야 합니다.
구형 장비 호환AN을 지원하지 않거나 구현이 불완전한 레거시(Legacy) PHY와 연결할 때. 일부 초기 100BASE-TX PHY는 AN 상태 머신에 버그가 있습니다.100M full duplex 이하로 강제합니다. 1G 이상은 구형 장비에서 지원하지 않을 가능성이 높습니다.
미디어 컨버터 경유Copper-to-Fiber 미디어 컨버터가 AN을 중계하지 못하는 경우. 컨버터가 AN을 종단(terminate)하여 양단이 독립적으로 협상합니다.컨버터 양쪽 세그먼트를 각각 독립적으로 설정합니다. Copper 측과 Fiber 측의 AN 정책이 달라야 할 수 있습니다.
산업용/임베디드 전용 링크점대점(Point-to-Point) 고정 연결에서 링크 업 시간을 최소화할 때. AN 과정(수백 ms~수 초)을 생략하면 링크 복구 시간이 단축됩니다.10/100M에서만 권장합니다. 1G에서는 master/slave 설정이 추가로 필요합니다.
AN 디버깅 격리AN 관련 문제를 배제하고 물리 계층 문제만 분리 진단할 때. 강제 모드로 링크가 올라오면 AN 구현 문제, 올라오지 않으면 케이블/PHY 하드웨어 문제입니다.진단 후 반드시 AN on으로 원복합니다. 강제 모드를 운영 환경에 그대로 두지 않습니다.
PXE/IPMI 부트 환경일부 BMC(Baseboard Management Controller)의 네트워크 스택이 AN을 제대로 처리하지 못해 BIOS/UEFI 단계에서 강제 설정이 필요한 경우가 있습니다.OS 부팅 후에는 드라이버가 AN on으로 재설정하는지 확인합니다.

강제 모드 적용 전 체크리스트

AN Off 적용 전 반드시 확인해야 할 항목:
  1. 양단 동일 설정: speed, duplex, autoneg off가 양쪽 모두 일치하는지 확인합니다. 한쪽이라도 autoneg on이면 parallel detect로 duplex mismatch가 발생합니다.
  2. 1G 이상이면 master/slave: 1000BASE-T 강제 모드에서는 한쪽을 master, 다른 쪽을 slave로 수동 설정합니다. 미설정 시 링크 자체가 올라오지 않습니다.
  3. pause 대체 수단: 흐름 제어가 필요한 환경이라면 AN 없이는 pause를 협상할 수 없으므로, 양단에서 ethtool -A eth0 rx on tx on으로 수동 활성화하거나 충분한 버퍼를 확보합니다.
  4. EEE 영향 검토: AN을 끄면 EEE도 자동 비활성화됩니다. 전력 절감이 중요한 환경에서는 영향을 평가합니다.
  5. 2.5G 이상 불가: NBASE-T(2.5G/5G) 이상 속도에서는 AN이 필수입니다. BMCR 비트로 해당 속도를 지정할 수 없으며, 링크 트레이닝 없이는 물리적으로 통신이 불가능합니다.
  6. 원복 계획: 강제 모드는 진단이나 특수 상황을 위한 임시 설정입니다. 변경 이유와 원복 절차를 문서화합니다.

강제 모드 설정과 검증 절차

# === 1단계: 현재 상태 기록 (원복용) ===
ethtool eth0 | tee /tmp/eth0-before.txt

# === 2단계: 강제 모드 적용 (양단 동시) ===
# 100M full duplex 강제 (가장 안전한 강제 설정)
ethtool -s eth0 speed 100 duplex full autoneg off

# === 3단계: 링크 상태 확인 ===
ethtool eth0 | grep -E "Speed|Duplex|Auto-negotiation|Link detected"
# 기대 출력:
#   Speed: 100Mb/s
#   Duplex: Full
#   Auto-negotiation: off
#   Link detected: yes

# === 4단계: duplex mismatch 징후 점검 ===
ethtool -S eth0 | grep -iE "collision|error|drop|carrier"
# late_collision이 증가하면 상대편 duplex 설정이 다른 것임

# === 5단계: pause 상태 확인 ===
ethtool -a eth0
# 강제 모드에서는 Autonegotiate: off, RX/TX: off로 표시됨
# 필요하면 수동 활성화:
ethtool -A eth0 rx on tx on

# === 원복 ===
ethtool -s eth0 autoneg on
# AN이 재시작되며 수 초 내에 최적 속도로 링크가 올라옴

커널의 강제 모드 상태 판독 경로

강제 모드에서는 AN이 꺼져 있으므로 ANLPAR(Link Partner Advertisement Register)을 읽을 수 없습니다. 대신 genphy_read_status_fixed()가 BMCR을 직접 읽어 현재 speed/duplex를 결정합니다.

/* drivers/net/phy/phy_device.c — 강제 모드 상태 판독 */
int genphy_read_status_fixed(struct phy_device *phydev)
{
    int bmcr = phy_read(phydev, MII_BMCR);

    if (bmcr < 0)
        return bmcr;

    /* BMCR speed 비트 해석 */
    if (bmcr & BMCR_SPEED1000)
        phydev->speed = SPEED_1000;
    else if (bmcr & BMCR_SPEED100)
        phydev->speed = SPEED_100;
    else
        phydev->speed = SPEED_10;

    /* BMCR duplex 비트 해석 */
    phydev->duplex = (bmcr & BMCR_FULLDPLX) ?
                      DUPLEX_FULL : DUPLEX_HALF;

    /* pause는 AN 결과가 아니므로 0으로 유지 */
    phydev->pause = 0;
    phydev->asym_pause = 0;

    return 0;
}

이 경로에서 pauseasym_pause가 항상 0으로 설정되는 것을 주목하세요. 강제 모드에서는 AN이 수행되지 않았으므로 pause 능력 교환이 없고, 커널은 흐름 제어를 비활성으로 보고합니다. 이것이 ethtool -a에서 "Autonegotiate: off"로 표시되는 이유입니다.

강제 모드 대안 — advertisement 제한: AN을 완전히 끄는 대신, 광고(advertisement)를 원하는 속도 하나로 제한하면 AN의 이점(pause 협상, master/slave 자동 결정 등)을 유지하면서 사실상 속도를 고정할 수 있습니다. 대부분의 환경에서 이 방법이 강제 모드보다 안전합니다.
# 1G full만 광고 — AN 유지, 속도 사실상 고정
ethtool -s eth0 autoneg on advertise 0x020
# 100M full만 광고
ethtool -s eth0 autoneg on advertise 0x008

비대칭 AN 설정: 한쪽 On / 한쪽 Off의 동작 메커니즘

실무에서 가장 빈번하고 진단이 어려운 문제는 한쪽은 AN on, 다른 쪽은 AN off인 비대칭 설정입니다. 이 조합에서 링크가 올라오기는 하지만 duplex mismatch가 거의 확실하게 발생하며, 겉으로는 정상처럼 보이기 때문에 문제를 인지하기까지 수주가 걸리는 경우도 있습니다. IEEE 802.3 Clause 28.2.3.1에 정의된 Parallel Detection 메커니즘이 이 상황의 핵심입니다.

Parallel Detection의 정확한 동작 원리

AN이 활성화된 PHY(이하 "A측")는 FLP(Fast Link Pulse) 버스트를 지속적으로 송신하면서 상대편의 FLP 응답을 기다립니다. 그런데 AN이 꺼진 PHY(이하 "B측")는 FLP를 송신하지 않고, 속도에 따라 다른 종류의 신호만 보냅니다.

B측 강제 속도B측이 보내는 신호A측 PHY의 감지 방식A측의 해석 결과
10BASE-TNLP (16ms 간격 단일 펄스)FLP 대신 NLP만 수신됨을 감지10BASE-T로 판단, duplex는 half로 가정
100BASE-TXIdle 심볼 (4B5B + MLT-3)100MHz 대역 에너지 감지100BASE-TX로 판단, duplex는 half로 가정
1000BASE-T없음 (AN 없이는 훈련 불가)감지 불가 또는 비표준 동작링크 실패 또는 PHY 벤더 특정 동작

핵심은 Parallel Detection이 속도만 감지하고 duplex 정보는 전달받지 못한다는 점입니다. IEEE 802.3은 Parallel Detection 시 duplex를 half duplex로 기본 설정하도록 규정합니다. 그런데 B측은 자신이 full duplex로 강제 설정되어 있으므로, 양단의 duplex가 불일치하게 됩니다.

비대칭 AN: 한쪽 On / 한쪽 Off 시퀀스 (100M 예시) A측: AN on B측: AN off (100M full) FLP burst (Base Page: 100M/1G 광고) 16.8ms 간격으로 반복 송신 4B5B Idle 심볼 (FLP 아님) AN off이므로 FLP를 생성하지 않음 autoneg_wait_timer (1~3초) 경과 FLP 응답 없음 → Parallel Detection 진입 100MHz 에너지 감지 → 속도 = 100M duplex 정보 없음 → half duplex 적용 BMCR 설정대로 full duplex 유지 Link Up (양측 모두 링크 성공으로 보고) Duplex Mismatch 발생 A측(half): 송신 전 캐리어 감지, 충돌 감지 활성 | B측(full): 동시 송수신, 충돌 감지 비활성 증상: 링크는 정상이나 성능 심각한 저하 A측에서 late collision 폭증 | B측에서 FCS error 증가 | 양방향 처리량 이론치의 10~30% 수준
비대칭 AN 설정의 시간 순서입니다. A측(AN on)이 FLP를 보내지만 B측(AN off)은 응답하지 않고, Parallel Detection이 속도만 감지하고 duplex를 half로 잘못 설정하여 mismatch가 발생합니다.
속도별 비대칭 AN 결과 상세

비대칭 설정의 결과는 B측(AN off)의 강제 속도에 따라 크게 달라집니다. 아래 표는 A측이 항상 AN on(전체 속도 광고)일 때의 각 시나리오를 정리합니다.

B측 강제 설정A측 Parallel Detection 결과실제 양단 상태링크 가능 여부세부 증상
10M full 10M half A: 10M half / B: 10M full 링크 성공 저속이라 증상이 경미합니다. 간헐적 collision이 발생하지만 10M 환경에서는 재전송으로 흡수됩니다. 부하가 낮으면 문제를 인지하기 어렵습니다.
10M half 10M half A: 10M half / B: 10M half 링크 성공 유일하게 정상 동작하는 비대칭 조합입니다. Parallel Detection이 half로 가정하고 B측도 half이므로 일치합니다. 단, pause 협상은 여전히 불가합니다.
100M full 100M half A: 100M half / B: 100M full 링크 성공 가장 흔한 duplex mismatch 시나리오입니다. A측이 half duplex로 동작하므로 B측이 동시에 송신하면 A측에서 late collision이 발생합니다. 부하가 낮을 때는 정상처럼 보이다가 트래픽이 증가하면 급격한 처리량 저하가 나타납니다.
100M half 100M half A: 100M half / B: 100M half 링크 성공 duplex는 일치하지만, half duplex 자체의 한계(CSMA/CD 오버헤드)로 실효 처리량이 full duplex의 50~60% 수준입니다. pause 협상도 불가합니다.
1000M full 감지 실패 또는 비표준 불확정 대부분 링크 실패 IEEE 802.3은 1000BASE-T에 대한 Parallel Detection을 표준으로 정의하지 않습니다. 일부 PHY 벤더는 자체 구현으로 감지를 시도하지만, master/slave 역할이 결정되지 않아 링크가 올라오지 않습니다. 링크가 올라와도 극도로 불안정합니다.
1000M half 해당 없음 해당 없음 불가 IEEE 802.3ab는 1000BASE-T half duplex를 정의하지 않습니다. 대부분의 PHY가 이 조합을 거부합니다.
Duplex Mismatch의 구체적 메커니즘

duplex mismatch가 왜 late collision을 유발하는지 이해하려면 half duplex와 full duplex의 근본적인 차이를 알아야 합니다.

동작Half Duplex (A측)Full Duplex (B측)충돌 시나리오
송신 전캐리어 감지(Carrier Sense) 수행 — 매체가 유휴일 때만 송신 시작캐리어 감지 없음 — 언제든 송신 가능A측이 매체를 감시하는 동안 B측은 무관하게 송신합니다.
동시 송수신불가 — 하나의 방향만 사용가능 — 송수신 독립 경로 사용B측이 A측 송신 중에 동시에 보내면, A측은 이를 충돌로 인식합니다.
충돌 감지활성 — 충돌 시 jam 신호 후 백오프(backoff)비활성 — 충돌 개념 자체가 없음A측이 jam 신호를 보내고 재전송을 기다리지만, B측은 jam을 무시하고 계속 송신합니다.
프레임 크기 제한slot time(512비트, 100M 기준 51.2μs) 이내 충돌은 normal collision제한 없음slot time 이후의 충돌은 late collision으로 분류되며, 이는 반드시 비정상입니다.
Late collision이 의미하는 것: 정상적인 half duplex 네트워크에서는 slot time 이내에만 충돌이 발생합니다(케이블 전파 시간 내). Late collision(slot time 이후 충돌)은 100% 비정상이며, duplex mismatch의 가장 확실한 지표입니다. ethtool -S eth0 | grep -i late에서 카운터가 증가하면 즉시 양단 duplex 설정을 비교해야 합니다. Late collision으로 드롭된 프레임은 MAC 계층에서 재전송하지 않으므로 TCP 재전송에 의존하게 되어 지연이 크게 증가합니다.
비대칭 AN의 처리량 영향 분석

duplex mismatch 상태에서의 처리량 저하는 트래픽 패턴에 따라 크게 달라집니다.

트래픽 패턴정상 AN (100M full)duplex mismatch (A: half / B: full)저하 비율원인
단방향 소량 (1 Mbps)~1 Mbps~1 Mbps거의 없음half duplex에서도 충돌 확률이 낮아 정상처럼 동작합니다. 이것이 문제를 늦게 발견하는 원인입니다.
단방향 대량 (A→B, 90 Mbps)~94 Mbps~60~70 Mbps25~35%A측(half)이 송신 중 B측의 동시 송신을 충돌로 인식하여 백오프가 발생합니다.
양방향 대칭 (50 Mbps 씩)~94 Mbps (합산)~15~30 Mbps (합산)70~85%A측(half)이 한 방향만 사용할 수 있어 양방향 동시 전송 시 충돌이 극심합니다. late collision으로 프레임 드롭이 빈발합니다.
양방향 버스트 (간헐적 대량)~94 Mbps~10~25 Mbps75~90%최악의 시나리오입니다. B측의 버스트 송신이 A측의 송신 윈도와 지속적으로 겹쳐 late collision이 폭증합니다.
비대칭 AN 상태의 진단 방법
# === 1단계: 양단 AN 상태 비교 ===
# A측 (의심되는 장비)
ethtool eth0 | grep -E "Speed|Duplex|Auto-negotiation|Link partner"
# 출력 예시:
#   Speed: 100Mb/s
#   Duplex: Half          ← half이면 의심
#   Auto-negotiation: on
#   Link partner advertised link modes: Not reported  ← 상대가 AN off인 증거

# === 2단계: 카운터 확인 (결정적 증거) ===
ethtool -S eth0 | grep -iE "late.?coll|coll|fcs|align|carrier"
# late_collision 카운터가 증가 중이면 duplex mismatch 확정
# 10초 간격으로 두 번 읽어 증가율 확인:
ethtool -S eth0 | grep late; sleep 10; ethtool -S eth0 | grep late

# === 3단계: 상대편 설정 확인 (접근 가능할 때) ===
# B측 (상대편 장비)
ethtool eth0 | grep -E "Speed|Duplex|Auto-negotiation"
# 출력 예시:
#   Speed: 100Mb/s
#   Duplex: Full           ← A측과 불일치!
#   Auto-negotiation: off  ← 원인

# === 4단계: 스위치 포트 확인 (상대가 스위치일 때) ===
# Cisco IOS
# show interface GigabitEthernet0/1 | include duplex|collision
# Juniper JunOS
# show interfaces ge-0/0/1 extensive | match "duplex|collision"
비대칭 AN의 해결 방법 (우선순위 순)
우선순위방법조치 내용장점단점
1 (권장)양단 모두 AN onB측에서 ethtool -s eth0 autoneg onpause, master/slave, EEE 모두 정상 협상됩니다. 가장 안전한 해결책입니다.B측 장비에 접근 권한이 필요합니다.
2AN on + advertisement 제한A측: ethtool -s eth0 autoneg on advertise 0x008 (100M full만 광고)AN을 유지하면서 속도를 사실상 고정합니다. B측이 AN on이면 해당 속도로 정상 협상됩니다.B측이 AN off이면 여전히 Parallel Detection으로 빠집니다(duplex mismatch 미해결).
3양단 모두 AN off양쪽: ethtool -s eth0 speed 100 duplex full autoneg offduplex mismatch를 확실히 해소합니다.pause, EEE 협상 불가. 1G 이상이면 master/slave 수동 설정 필수입니다.
4A측도 AN off로 맞춤A측을 B측과 동일하게 강제 설정B측을 변경할 수 없을 때 유일한 방법입니다.A측의 모든 AN 기능이 상실됩니다. 반드시 B측과 speed/duplex를 정확히 일치시켜야 합니다.
스위치 포트의 비대칭 AN 주의: 관리형 스위치에서 포트를 speed 100 duplex full로 강제하면 해당 포트의 AN이 꺼집니다. 이때 상대편 서버 NIC가 AN on(기본값)이면 Parallel Detection → duplex mismatch가 발생합니다. 스위치 포트를 강제하면 상대편 NIC도 반드시 같은 값으로 강제하거나, 스위치 포트도 AN on 상태에서 advertisement만 제한하는 것이 안전합니다. 대부분의 스위치는 speed auto 100 같은 advertisement 제한 명령을 지원합니다.
커널의 Parallel Detection 처리 경로

커널의 AN 상태 머신이 Parallel Detection을 어떻게 처리하는지, 코드 경로를 추적합니다. A측(AN on)에서 상대편의 FLP 응답이 없을 때의 흐름입니다.

/* genphy_read_status() — AN 완료 후 파트너 정보 판독 */
/* Parallel Detection으로 링크가 올라온 경우의 동작 */

if (phydev->autoneg == AUTONEG_ENABLE && phydev->link) {
    err = genphy_read_lpa(phydev);
    /*
     * genphy_read_lpa()는 ANLPAR(레지스터 5)을 읽음.
     * Parallel Detection 시 상대편이 FLP를 보내지 않았으므로
     * ANLPAR = 0x0000 (파트너 광고 없음).
     *
     * 이 경우 lp_advertising 비트맵이 비어 있고,
     * phy_resolve_aneg_linkmode()가 AND 연산 시
     * 공통 모드를 찾지 못함.
     *
     * PHY 하드웨어가 Parallel Detection으로 이미 결정한
     * 속도를 BMCR/BMSR에서 반영하되, duplex는 half로 설정됨.
     */

    phy_resolve_aneg_linkmode(phydev);
    /*
     * ANLPAR이 비어 있으면:
     * - speed: PHY 하드웨어가 감지한 값 (Parallel Detection 결과)
     * - duplex: HALF (파트너 duplex 정보 없음 → 보수적 기본값)
     * - pause: 0 (파트너 pause 능력 알 수 없음)
     */
}
ANLPAR로 Parallel Detection 여부 확인: ethtool의 출력에서 "Link partner advertised link modes"가 "Not reported"이면 상대편이 AN에 참여하지 않았다는 뜻이며, Parallel Detection으로 링크가 올라온 것입니다. 이 상태에서 duplex가 "Half"이고 상대편이 "Full"로 강제되어 있다면 duplex mismatch가 확정됩니다. 커널 로그(dmesg | grep -i duplex)에서도 mismatch 경고를 확인할 수 있으며, 일부 NIC 드라이버는 netdev_warn()으로 이 상황을 명시적으로 경고합니다.
비대칭 AN에서 흔히 간과되는 부차적 문제
문제원인영향진단 방법
TCP 성능 비대칭한 방향(A→B)은 half duplex 제약을 받고 반대 방향(B→A)은 받지 않습니다.방향에 따라 처리량이 2~5배 차이납니다. 업로드/다운로드 속도 불균형으로 나타납니다.iperf3 -c <peer>iperf3 -c <peer> -R로 양방향 비교
ARP 타임아웃 간헐 발생ARP 요청/응답이 late collision으로 드롭되면 IP 통신 자체가 간헐적으로 실패합니다.ping이 간헐적으로 실패하고, SSH 연결이 끊어집니다. 물리 계층 문제를 네트워크 계층 문제로 오진하기 쉽습니다.ip -s neigh show에서 STALE/FAILED 빈도 확인
VLAN 태그 충돌 증폭802.1Q 태그로 프레임이 4바이트 커지면 slot time 경계에서의 충돌 확률이 높아집니다.VLAN 환경에서 더 심한 성능 저하가 나타납니다.VLAN 인터페이스와 물리 인터페이스의 error 카운터 비교
Jumbo Frame 완전 실패half duplex에서는 slot time 대비 프레임이 너무 커서 late collision이 거의 모든 프레임에서 발생합니다.MTU 9000에서 실효 처리량이 거의 0에 가깝습니다.ping -s 8000 -M do <peer>로 대형 패킷 전달 가능 여부 확인
비대칭 AN 발생 원인별 현실적 시나리오

비대칭 AN은 실무에서 주로 다음과 같은 상황에서 의도치 않게 발생합니다.

시나리오발생 경위진단이 늦어지는 이유예방 조치
스위치 교체 후기존 스위치는 AN on이었는데, 새 스위치가 포트별 기본 설정이 AN off인 경우. 일부 산업용/관리형 스위치의 기본값이 100M full 강제입니다.기존 링크가 정상이었으므로 NIC 측 설정은 확인하지 않습니다. 링크가 올라오니 "정상 교체"로 판단합니다.스위치 교체 후 ethtool eth0에서 duplex와 "Link partner advertised"를 반드시 확인합니다.
NIC 드라이버 업데이트드라이버 업데이트 시 기본 AN 설정이 변경되거나, 이전에 적용된 /etc/network/ 설정이 누락되는 경우.드라이버 업데이트는 "네트워크 설정 변경"으로 인식되지 않아 AN 상태를 확인하지 않습니다.드라이버 업데이트 후 ethtool eth0 결과를 업데이트 전과 비교합니다.
VM 마이그레이션물리 서버 간 마이그레이션 시 목적지 서버의 물리 NIC AN 설정이 출발지와 다른 경우.VM 내부에서는 가상 NIC만 보이므로 물리 계층 문제를 인지하기 어렵습니다.하이퍼바이저 수준에서 물리 NIC의 ethtool 출력을 모니터링합니다.
미디어 컨버터 삽입Copper-Fiber 컨버터가 AN을 종단하여, 컨버터 양쪽이 독립적인 AN 도메인이 됩니다. 컨버터의 Copper 측이 AN off로 기본 설정된 경우.컨버터는 "투명한 장비"로 인식되어 AN 정책을 확인하지 않습니다.컨버터 삽입 시 양쪽 세그먼트에서 각각 ethtool을 확인합니다.
BIOS/UEFI PXE 부팅 후PXE 부팅 중 BIOS가 NIC를 100M full 강제로 설정하고, OS 드라이버가 로드되기 전 스위치와 Parallel Detection으로 링크가 올라옵니다. OS 드라이버가 AN on으로 재설정하지 못하는 경우.부팅은 성공하므로 "정상"으로 판단합니다. 느린 성능을 소프트웨어 문제로 오진합니다.OS 부팅 후 ethtool eth0에서 AN 상태를 확인하는 스크립트를 시작 시 실행합니다.
비대칭 AN을 자동 감지하는 모니터링 방법: 운영 환경에서 비대칭 AN을 조기에 발견하려면 다음 조건을 모니터링 시스템(Prometheus, Zabbix 등)에 등록합니다.
# 1. late_collision 카운터 증가율 감시 (0이어야 정상)
ethtool -S eth0 | awk '/late.?coll/{print $2}'

# 2. duplex가 half인 인터페이스 탐지
ethtool eth0 | grep "Duplex: Half" && echo "WARNING: half duplex detected on eth0"

# 3. AN on인데 Link partner advertised가 비어 있으면 Parallel Detection
ethtool eth0 | grep -A1 "Auto-negotiation: on" | grep "Not reported"

# 4. 전체 인터페이스 일괄 점검 스크립트
for iface in $(ls /sys/class/net/ | grep -v lo); do
  duplex=$(ethtool "$iface" 2>/dev/null | awk '/Duplex:/{print $2}')
  an=$(ethtool "$iface" 2>/dev/null | awk '/Auto-negotiation:/{print $2}')
  [ "$duplex" = "Half" ] && [ "$an" = "on" ] && \
    echo "ALERT: $iface — AN on but half duplex (possible parallel detect)"
done

AN 트리거 조건과 재시작 메커니즘

Auto-Negotiation은 PHY가 특정 이벤트를 감지했을 때 자동으로 시작되거나, 소프트웨어가 명시적으로 재시작할 수 있습니다. AN이 언제, 왜 시작되는지를 정확히 이해하면 링크 flap의 원인 분석이 훨씬 수월해집니다.

트리거메커니즘AN 재시작 여부설명
케이블 삽입PHY 하드웨어 자동예 (전체 AN)링크 파트너 전압 감지 → FLP 송신 시작. PHY의 전원이 켜진 상태에서 케이블이 연결되면 수백 ms 이내에 AN이 시작됩니다.
PHY 리셋BMCR bit 15 (Reset)예 (전체 AN)모든 레지스터가 기본값으로 복원되고, AN Enable이 기본 1이므로 자동으로 AN이 시작됩니다.
AN RestartBMCR bit 9 (Restart_AN)예 (재협상)기존 링크를 끊고 break_link_timer(≥75ms) 후 새로 FLP를 교환합니다. 광고 레지스터 변경 후 반드시 이 비트를 세트해야 합니다.
Power-Down 복귀BMCR bit 11 (Power_Down) 해제예 (전체 AN)PHY가 저전력 모드에서 깨어나면 리셋과 유사하게 AN이 재시작됩니다.
링크 실패 감지PHY 하드웨어 자동PHY 구현별signal loss, excessive CRC 등 감지 시 일부 PHY가 자동 재협상합니다. Downshift 기능이 활성화되면 낮은 속도로 재시도합니다.
ethtool -s 명령커널 → config_aneg()변경 시에만phy_modify_changed()로 광고 비트가 실제로 달라졌을 때만 AN을 재시작하여 불필요한 링크 끊김을 방지합니다.
phy_start() 호출MAC 드라이버네트워크 인터페이스 up 시 MAC 드라이버가 호출합니다. phy_start_aneg()를 통해 AN을 시작합니다.
suspend/resume시스템 전원 관리예 (resume 시)resume 후 phy_init_hw()config_init()config_aneg() 순서로 PHY를 재초기화합니다.
/* AN 재시작의 핵심: phy_start_aneg() — drivers/net/phy/phy.c */
int phy_start_aneg(struct phy_device *phydev)
{
    int err;

    if (!phydev->drv)
        return -EIO;

    mutex_lock(&phydev->lock);

    if (!__phy_is_started(phydev)) {
        WARN(1, "called from state %s\n",
             phy_state_to_str(phydev->state));
        err = -EBUSY;
        goto out;
    }

    /* AN 미지원이면 강제 모드로 전환 */
    if (AUTONEG_DISABLE == phydev->autoneg)
        phy_sanitize_settings(phydev);

    /* PHY 드라이버의 config_aneg 콜백 호출 */
    err = phydev->drv->config_aneg(phydev);
    if (err < 0)
        goto out;

    /* 상태를 AN으로 전환 — 상태 머신이 완료를 감시 */
    if (phydev->autoneg == AUTONEG_ENABLE) {
        err = phy_check_link_status(phydev);
    }
out:
    mutex_unlock(&phydev->lock);
    return err;
}
AN 재시작 시 링크 순간 끊김: BMCR의 AN_RESTART 비트를 세트하면 PHY는 기존 링크를 즉시 끊고 break_link_timer(최소 75ms) 동안 대기한 뒤 FLP 교환을 새로 시작합니다. 따라서 광고 비트를 변경하지 않았는데 불필요하게 AN을 재시작하면 수 초간 링크가 끊어집니다. 커널의 phy_modify_changed()는 이 문제를 방지하기 위해 레지스터 값이 실제로 달라졌을 때만 쓰기를 수행합니다.
Downshift와 AN 재시작의 관계: Downshift 기능이 활성화된 PHY는 AN이 특정 속도로 수렴하지 못하면(예: equalizer 훈련 실패), 자동으로 해당 속도를 광고에서 제외하고 AN을 재시작합니다. 예를 들어 1G 링크 훈련에 3회 연속 실패하면 1G 광고를 내리고 100M으로 재협상합니다. 이 과정은 PHY 하드웨어 내부에서 자율적으로 진행되며, 커널은 최종 결과만 읽습니다. ethtool eth0 | grep -i "downshift"로 현재 설정을 확인하고, ethtool --set-phy-tunable eth0 downshift on count 3으로 재시도 횟수를 설정할 수 있습니다.

linkmode 비트맵과 MDIO 레지스터 매핑

커널의 phydev->advertisingunsigned long 배열로 구현된 linkmode 비트맵이며, 이것이 실제 PHY 레지스터(ANAR, GBCR 등)로 변환되는 과정을 이해하면 AN 동작의 전체 그림이 보입니다. 변환은 양방향으로 이루어집니다: 설정 시에는 linkmode → 레지스터, 판독 시에는 레지스터 → linkmode입니다.

linkmode 비트맵 ↔ MDIO 레지스터 변환 phydev->advertising (linkmode 비트맵, unsigned long 배열) bit 0: 10baseT_Half | bit 1: 10baseT_Full | bit 2: 100baseTX_Half | bit 3: 100baseTX_Full | bit 4: 1000baseT_Half | bit 5: 1000baseT_Full bit 13: Pause | bit 14: Asym_Pause | bit 25: 2500baseT_Full | bit 26: 5000baseT_Full | bit 12: 10000baseT_Full | ... linkmode_adv_to_lcl_adv_t() + ethtool_adv_to_mii_adv_t() ethtool_adv_to_mii_ctrl1000_t() ANAR (레지스터 0x04, MII_ADVERTISE) bit 5 10T HD bit 6 10T FD bit 7 100TX HD bit 8 100TX FD bit 10 Pause bit 11 Asym Pause ← FLP 버스트의 Base Page로 송신됨 → GBCR (레지스터 0x09, MII_CTRL1000) bit 8 1000T HD bit 9 1000T FD bit 11:12 Master/Slave bit 10 1000T Port Type ← FLP + Clause 40 확장으로 송신됨 → 결과 판독 (역방향) ANLPAR (레지스터 0x05) — 상대편 Base Page mii_lpa_to_ethtool_lpa_t() → linkmode으로 변환 GBSR (레지스터 0x0A) — 1G 결과 mii_stat1000_to_ethtool_lpa_t() → linkmode으로 변환 phy_resolve_aneg_linkmode() advertising ∩ lp_advertising → 교집합 계산 → 우선순위 테이블로 최고 모드 선택 → phydev->speed, phydev->duplex, phydev->pause, phydev->asym_pause 갱신
linkmode 비트맵은 커널 내부 표현이고, ANAR/GBCR은 PHY 하드웨어 레지스터입니다. 변환 함수가 양방향으로 매핑하며, 결과 해석은 phy_resolve_aneg_linkmode()가 담당합니다.
/* 변환 함수 체인 — include/linux/mii.h 기반 개념 재현 */

/* 1. linkmode → ANAR (설정 경로) */
static inline u32 linkmode_adv_to_lcl_adv_t(
    const unsigned long *advertising)
{
    u32 lcl_adv = 0;

    if (linkmode_test_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT, advertising))
        lcl_adv |= ADVERTISED_10baseT_Half;
    if (linkmode_test_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT, advertising))
        lcl_adv |= ADVERTISED_10baseT_Full;
    if (linkmode_test_bit(ETHTOOL_LINK_MODE_100baseTX_Half_BIT, advertising))
        lcl_adv |= ADVERTISED_100baseT_Half;
    if (linkmode_test_bit(ETHTOOL_LINK_MODE_100baseTX_Full_BIT, advertising))
        lcl_adv |= ADVERTISED_100baseT_Full;
    /* Pause 비트도 동일 패턴 */
    if (linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT, advertising))
        lcl_adv |= ADVERTISED_Pause;
    if (linkmode_test_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, advertising))
        lcl_adv |= ADVERTISED_Asym_Pause;
    return lcl_adv;
}

static inline u32 ethtool_adv_to_mii_adv_t(u32 ethadv)
{
    u32 result = 0;
    /* ethtool 비트 → MII 레지스터 비트로 위치 변환 */
    if (ethadv & ADVERTISED_10baseT_Half)
        result |= ADVERTISE_10HALF;       /* ANAR bit 5 */
    if (ethadv & ADVERTISED_10baseT_Full)
        result |= ADVERTISE_10FULL;       /* ANAR bit 6 */
    if (ethadv & ADVERTISED_100baseT_Half)
        result |= ADVERTISE_100HALF;      /* ANAR bit 7 */
    if (ethadv & ADVERTISED_100baseT_Full)
        result |= ADVERTISE_100FULL;      /* ANAR bit 8 */
    if (ethadv & ADVERTISED_Pause)
        result |= ADVERTISE_PAUSE_CAP;    /* ANAR bit 10 */
    if (ethadv & ADVERTISED_Asym_Pause)
        result |= ADVERTISE_PAUSE_ASYM;   /* ANAR bit 11 */
    return result;
}

/* 2. ANLPAR → linkmode (판독 경로) — genphy_read_lpa() 내부 */
static inline u32 mii_lpa_to_ethtool_lpa_t(u32 lpa)
{
    u32 result = 0;
    /* MII 레지스터 비트 → ethtool 비트로 역변환 */
    if (lpa & LPA_10HALF)     result |= ADVERTISED_10baseT_Half;
    if (lpa & LPA_10FULL)     result |= ADVERTISED_10baseT_Full;
    if (lpa & LPA_100HALF)    result |= ADVERTISED_100baseT_Half;
    if (lpa & LPA_100FULL)    result |= ADVERTISED_100baseT_Full;
    if (lpa & LPA_PAUSE_CAP)  result |= ADVERTISED_Pause;
    if (lpa & LPA_PAUSE_ASYM) result |= ADVERTISED_Asym_Pause;
    return result;
}

/* 3. 결과 해석 — phy_resolve_aneg_linkmode() */
void phy_resolve_aneg_linkmode(struct phy_device *phydev)
{
    __ETHTOOL_DECLARE_LINK_MODE_MASK(common);

    /* 교집합: 로컬 advertising ∩ 상대편 lp_advertising */
    linkmode_and(common, phydev->advertising, phydev->lp_advertising);

    /* 우선순위에 따라 speed/duplex 결정 */
    if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, common)) {
        phydev->speed = SPEED_1000;
        phydev->duplex = DUPLEX_FULL;
    } else if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT, common)) {
        phydev->speed = SPEED_1000;
        phydev->duplex = DUPLEX_HALF;
    } else if (linkmode_test_bit(ETHTOOL_LINK_MODE_100baseTX_Full_BIT, common)) {
        phydev->speed = SPEED_100;
        phydev->duplex = DUPLEX_FULL;
    } else if (linkmode_test_bit(ETHTOOL_LINK_MODE_100baseTX_Half_BIT, common)) {
        phydev->speed = SPEED_100;
        phydev->duplex = DUPLEX_HALF;
    } else {
        phydev->speed = SPEED_10;
        phydev->duplex = linkmode_test_bit(
            ETHTOOL_LINK_MODE_10baseT_Full_BIT, common)
            ? DUPLEX_FULL : DUPLEX_HALF;
    }

    /* Pause 해석 — Annex 28B 알고리즘 */
    phy_resolve_aneg_pause(phydev);
}
linkmode vs ethtool 비트맵의 역사: 커널 5.x 이전에는 u32 기반의 ethtool_link_ksettings 비트맵을 사용했지만, 2.5G/5G/25G/100G 등 새로운 속도가 추가되면서 32비트로는 부족해졌습니다. 이를 해결하기 위해 __ETHTOOL_LINK_MODE_MASK_NBITS까지 확장 가능한 unsigned long 배열 기반의 linkmode 비트맵이 도입되었습니다. 현재 커널에서는 linkmode_* 계열 함수가 표준이며, 구형 u32 인터페이스는 호환 래퍼를 통해 유지됩니다.

1000BASE-T PHY Training(링크 훈련) 상세

10/100BASE-T에서는 AN이 완료되면 곧바로 데이터 전송이 가능하지만, 1000BASE-T는 AN 완료 후 추가적인 PHY Training 단계가 필요합니다. 이 훈련 과정은 4쌍 양방향 PAM-5 부호화의 복잡성에서 비롯됩니다.

속도사용 쌍 수부호화통신 방향훈련 필요
10BASE-T2쌍 (TX 1, RX 1)맨체스터(Manchester)단방향 (쌍별)불필요
100BASE-TX2쌍 (TX 1, RX 1)MLT-3 + NRZI단방향 (쌍별)불필요
1000BASE-T4쌍 (모두 양방향)4D-PAM5양방향 동시 (쌍별)필수
2.5GBASE-T4쌍 (모두 양방향)PAM-16 (DSQ128)양방향 동시필수 (더 복잡)
5GBASE-T4쌍 (모두 양방향)PAM-16 (DSQ128)양방향 동시필수
10GBASE-T4쌍 (모두 양방향)PAM-16 (DSQ128)양방향 동시필수 (가장 복잡)

1000BASE-T의 핵심 문제는 같은 쌍(pair)으로 동시에 송신과 수신을 한다는 것입니다. 이를 위해 PHY 내부의 DSP(Digital Signal Processor)가 여러 간섭 성분을 제거해야 합니다:

1000BASE-T PHY Training: 4가지 간섭 제거 로컬 PHY TX+RX 동시 Pair 0 (1,2번 핀) Pair 1 (3,6번 핀) Pair 2 (4,5번 핀) Pair 3 (7,8번 핀) 원격 PHY TX+RX 동시 ← 4쌍 모두 양방향 동시 통신 → CAT-5 UTP (비차폐 트위스트 페어) ① Echo Cancellation 자기 송신 신호 제거 같은 쌍에서 TX와 RX가 동시에 동작하므로, 자신이 보낸 신호가 수신단에 반사되어 돌아옴 → 자기 TX 신호의 복사본을 빼서 원격 신호만 추출 ② NEXT 제거 근단 누화(Near-End CrossTalk) 인접 쌍의 TX 신호가 로컬 쪽에서 다른 쌍의 RX에 혼입. 같은 커넥터/케이블 근처에서 발생 → 로컬 TX 3쌍의 신호 모델로 누화분 추정·제거 ③ FEXT 제거 원단 누화(Far-End CrossTalk) 원격 PHY의 TX 신호가 케이블 내에서 인접 쌍에 혼입되어 로컬에 도착. 케이블 길수록 심각 → 원격 TX 3쌍 기준 으로 적응 필터 수렴 ④ ISI 등화 심볼 간 간섭(Inter-Symbol) 케이블의 주파수 특성으로 이전 심볼이 현재 심볼에 중첩. CAT-5에서 125MHz 대역에서 심각 → DFE (Decision Feedback EQ) 적용 PHY Training 타임라인 (1000BASE-T 기준) 0ms AN 완료 Master/Slave 확정 ~50ms Echo/NEXT 필터 계수 수렴 ~300ms FEXT + ISI 등화기 수렴 ~800ms BER 검증 + 안정화 ~1.5s Link Up ✓ 1~3s 케이블이 길거나 품질이 나쁘면 각 단계의 수렴 시간이 증가합니다 CAT-5e 100m 기준: 총 1~3초 | CAT-3 또는 불량 케이블: 3~5초 또는 실패
1000BASE-T PHY Training은 4가지 간섭 성분을 DSP 필터로 제거하는 과정입니다. 각 필터의 계수가 수렴해야 안정적인 데이터 전송이 시작됩니다.
간섭 유형원인제거 기법필터 계수수렴 시간
Echo동일 쌍에서 자기 TX 신호가 RX에 반사Hybrid(하이브리드) 회로 + DSP Echo Canceller~128 탭 FIR50~200ms
NEXT인접 3쌍의 로컬 TX가 RX에 혼입3채널 NEXT Canceller (로컬 TX 신호 참조)3 × ~48 탭 FIR100~300ms
FEXT인접 3쌍의 원격 TX가 케이블 내에서 혼입3채널 FEXT Canceller (수신 심볼 기반 추정)3 × ~16 탭 FIR200~500ms
ISI케이블 주파수 응답으로 심볼 간 간섭FFE(Feed-Forward EQ) + DFE(Decision Feedback EQ)FFE ~24탭 + DFE ~8탭100~300ms
4D-PAM5의 동작 원리: 1000BASE-T는 각 쌍에서 PAM-5(5-level Pulse Amplitude Modulation) 심볼을 125Msym/s로 전송합니다. 5개 레벨(-2, -1, 0, +1, +2) 중 실제 데이터에는 4개만 사용하고 나머지 하나는 트렐리스 코딩(Trellis Coding)의 여유 비트로 활용합니다. 4쌍 × log₂(4) × 125M = 1000Mbps이며, 트렐리스 코딩으로 약 6dB의 코딩 이득을 얻어 CAT-5 케이블의 감쇠와 노이즈를 극복합니다. Master PHY가 TX 클럭의 기준이 되고, Slave PHY는 수신 신호에서 클럭을 복구하여 자신의 TX 타이밍을 맞춥니다.
Training 실패의 일반적 원인: CAT-3(또는 구형 CAT-5) 케이블, 90m 이상 길이, 불량 압착, 쌍 꼬임 풀림, 근처 EMI 소스(형광등 안정기, 전원 케이블 병주 등)는 모두 DSP 필터의 수렴을 방해합니다. Training이 실패하면 PHY는 AN을 재시작하며, Downshift가 활성화되어 있으면 100M으로 강등됩니다. ethtool eth0에서 Speed가 기대보다 낮으면, dmesg | grep -i "downshift\|link"로 강등 이력을 확인하세요.
# PHY Training 관련 진단

# 1. SNR(Signal-to-Noise Ratio) 확인 — 일부 PHY만 지원
# Marvell PHY: 페이지 5, 레지스터 21~24 (쌍별 SNR)
phytool write eth0/0/22 0x0005    # 페이지 5 선택
phytool read eth0/0/21             # Pair 0 SNR (dB 단위)
phytool read eth0/0/22             # Pair 1 SNR
phytool read eth0/0/23             # Pair 2 SNR
phytool read eth0/0/24             # Pair 3 SNR
# SNR > 20dB: 양호 | 15~20dB: 경계 | < 15dB: 불안정

# 2. 케이블 길이 추정 — TDR(Time Domain Reflectometry)
ethtool --cable-test eth0
# 또는 PHY 벤더별 TDR 레지스터 직접 접근

# 3. 심볼 에러 카운터
ethtool -S eth0 | grep -i "symbol\|phy_err\|frame"
# 심볼 에러가 지속 증가하면 Training 수렴이 불완전

# 4. Downshift 발생 여부 확인
ethtool eth0 | grep -i "downshift"
dmesg | grep -i "downshift"

# 5. PHY Training 시간 측정
# AN restart부터 link up까지의 dmesg 타임스탬프 차이
ethtool -r eth0    # AN 재시작
dmesg -w | grep "Link is"
# 1G: 보통 1~3초, 100M: 보통 0.5~1.5초

Clause 37 In-Band Auto-Negotiation 구현 상세

Clause 37은 1000BASE-X(광섬유) 및 SGMII(구리, MAC-PHY 간) 환경에서 사용되는 SerDes 기반 In-Band AN입니다. Clause 28이 별도의 FLP 펄스를 사용하는 것과 달리, Clause 37은 데이터 경로 자체(8B/10B 인코딩의 /C/ ordered set)를 통해 설정 정보를 교환합니다.

구분Clause 28 (구리)Clause 37 (SerDes)
물리적 매체트위스트 페어(UTP)광섬유 또는 SerDes 백보드
신호 방식FLP 전압 펄스8B/10B /C/ ordered set
정보량16비트 Base Page16비트 Config Reg
속도 협상10/100/1000M 선택고정 (1000M 또는 SGMII 확장)
협상 대상speed, duplex, pauseduplex, pause (speed 고정)
상태 머신7개 상태5개 상태 (AN_ENABLE, ABILITY, ACK, IDLE, LINK_OK)
하드웨어 위치PHY 아날로그단PCS(Physical Coding Sublayer)

Clause 37의 Config Register(16비트)는 /C1/ 또는 /C2/ ordered set에 실려 전송됩니다. 이 ordered set는 8B/10B 인코딩에서 데이터와 구분되는 특수 심볼(/K28.5/)로 시작하므로, 데이터 전송 중에도 링크 상태를 교환할 수 있습니다.

Clause 37 Config Register vs SGMII Control Word 1000BASE-X Config Register (표준 Clause 37) bit 0 Rsvd bit 5 Full Dup bit 6 Half Dup bit 7 PS1 (Pause) bit 8 PS2 (Asym) bit 12:9 Rsvd bit 13 Remote Fault bit 14 Ack bit 15 Next Page 속도 고정 (1000M) duplex + pause만 협상 SGMII Control Word (Cisco 변형, 비표준) bit 0 SGMII=1 bit 11:10 Speed (00=10, 01=100, 10=1000) bit 12 Duplex bit 14 Ack bit 15 Link PHY→MAC 방향: speed/duplex/link 전달 MAC→PHY 방향: bit 0=1만 (SGMII 식별) Clause 37 동작 핵심 메커니즘 8B/10B /C/ Ordered Set: /C1/ = /K28.5/D21.5/Config_Reg[7:0]/Config_Reg[15:8]/ (4 code-groups, 40비트) /C2/ = /K28.5/D2.2/Config_Reg[7:0]/Config_Reg[15:8]/ (/C1/과 교대 전송) 전송 조건: 데이터 미전송 시 /C/ ordered set를 연속 전송. 데이터 전송 중에는 IFG(Inter-Frame Gap)에서만 /C/ 삽입. 수렴 조건: Config Reg를 3회 연속 동일하게 수신 + 상대 Ack 확인 → AN 완료 → /I/ (Idle) 전송 → 데이터 전송 가능 SGMII 비대칭: PHY→MAC: speed/duplex/link 정보 포함. MAC→PHY: bit 0=1(SGMII 식별)만 전송. 사실상 단방향 통보. 1000BASE-X 구분: bit 0 = 0이면 1000BASE-X(표준), bit 0 = 1이면 SGMII(Cisco 변형). PCS가 이 비트로 해석 모드를 결정합니다.
1000BASE-X와 SGMII는 같은 Clause 37 프레임워크를 사용하지만 Config Register의 비트 해석이 완전히 다릅니다. bit 0으로 어떤 모드인지 구분합니다.
/* Clause 37 AN 구현 — drivers/net/pcs/pcs-lynx.c 참조 (개념 재현) */

/* PCS에서 Clause 37 AN 설정 */
static int lynx_pcs_config_cl37(struct phylink_pcs *pcs,
                                  const unsigned long *advertising)
{
    u16 config_reg = 0;

    /* 1000BASE-X: speed 협상 없음, duplex + pause만 */
    if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT, advertising))
        config_reg |= ADVERTISE_1000XFULL;    /* bit 5 */
    if (linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT, advertising))
        config_reg |= ADVERTISE_1000XPAUSE;   /* bit 7 */
    if (linkmode_test_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, advertising))
        config_reg |= ADVERTISE_1000XPSE_ASYM; /* bit 8 */

    /* PCS AN Advertisement 레지스터에 기록 (MII_ADVERTISE, reg 4) */
    mdiodev_modify(pcs->mdio, MII_ADVERTISE,
                    ADVERTISE_1000XFULL | ADVERTISE_1000XHALF |
                    ADVERTISE_1000XPAUSE | ADVERTISE_1000XPSE_ASYM,
                    config_reg);

    /* BMCR에서 AN 활성화 + 재시작 */
    mdiodev_modify(pcs->mdio, MII_BMCR,
                    BMCR_ANENABLE | BMCR_ANRESTART,
                    BMCR_ANENABLE | BMCR_ANRESTART);
    return 0;
}

/* SGMII AN 결과 판독 — PHY→MAC 방향 Control Word 해석 */
static void lynx_pcs_get_sgmii_state(struct phylink_pcs *pcs,
                                      struct phylink_link_state *state)
{
    int lpa = mdiodev_read(pcs->mdio, MII_LPA);  /* ANLPAR */

    /* SGMII Control Word 해석 */
    state->link   = !!(lpa & LPA_SGMII_LINK);     /* bit 15 */
    state->duplex = (lpa & LPA_SGMII_DPX_SPD_1000)
                    ? DUPLEX_FULL : DUPLEX_HALF;   /* bit 12 */

    /* Speed: bit 11:10 */
    switch (lpa & LPA_SGMII_SPEED) {
    case LPA_SGMII_10:
        state->speed = SPEED_10;
        break;
    case LPA_SGMII_100:
        state->speed = SPEED_100;
        break;
    case LPA_SGMII_1000:
        state->speed = SPEED_1000;
        break;
    }
}
phylink에서의 Clause 37 처리: phylink 프레임워크에서 Clause 37 AN은 phylink_pcs 드라이버의 pcs_config()pcs_get_state() 콜백을 통해 제어됩니다. pcs_config()은 AN 광고를 설정하고, pcs_get_state()는 AN 결과를 읽어 phylink_link_state에 채웁니다. phylink은 이 정보를 MAC 드라이버의 mac_config()/mac_link_up() 콜백에 전달하여 MAC 속도를 맞춥니다. 이 구조 덕분에 PHY 드라이버(copper AN)와 PCS 드라이버(SerDes AN)가 동시에 동작하는 복잡한 토폴로지도 처리할 수 있습니다.
SGMII AN의 함정 — 비대칭 구조: SGMII AN은 사실상 PHY가 MAC에 통보하는 단방향 프로토콜입니다. PHY 측 Control Word에는 speed/duplex/link 정보가 있지만, MAC 측 응답에는 "나는 SGMII를 사용한다"는 bit 0만 세트되어 있습니다. 따라서 MAC이 PHY의 AN 결과를 거부하거나 다른 속도를 제안할 방법이 없습니다. 만약 MAC PCS가 SGMII 대신 1000BASE-X로 설정되어 있으면 bit 0 해석이 달라져 완전히 잘못된 speed/duplex를 인식합니다. 이것이 phy-mode Device Tree 속성이 정확해야 하는 이유입니다.

AN 시퀀스 타임라인: 실제 교환 시나리오

Auto-Negotiation의 전체 과정을 시간 순서대로 추적하면, 각 단계에서 어떤 신호가 오가고 어떤 조건으로 다음 단계로 진행되는지 명확해집니다. 아래는 1000BASE-T 환경에서 NIC와 스위치가 연결되는 전형적인 시나리오입니다.

1000BASE-T AN 전체 시퀀스 타임라인 NIC PHY (로컬) 스위치 PHY (원격) 0ms 케이블 삽입 → 전압 감지 케이블 삽입 → 전압 감지 ~16ms FLP burst → Base Page: 10T/10T-FD/100TX/100TX-FD/Pause/Asym ~32ms FLP burst → Base Page: 10T/10T-FD/100TX/100TX-FD/Pause ABILITY_DETECT 3회 일관 수신 대기 ~80ms FLP burst → Base Page + Ack (bit 14 = 1) ~96ms FLP burst → Base Page + Ack (bit 14 = 1) ACK_DETECT → COMPLETE_ACK ~128ms GBCR: 1000T-FD 광고 + prefer-master (bit 11=1) ~144ms GBCR: 1000T-FD 광고 + prefer-slave (bit 11=0) NEXT_PAGE_WAIT ~180ms (선택) Next Page: EEE 1000T 광고 (선택) Next Page: EEE 1000T 광고 ~200ms AN 완료: 1000BASE-T FD 선택 | NIC=master, Switch=slave BMSR.AN_COMPLETE = 1 (양단) ~200ms Master(NIC)가 TX 클럭 기준 → Slave(Switch)가 클럭 복구 200~ 800ms Echo Canceller + NEXT Canceller + FEXT Canceller 계수 수렴 4쌍 × PAM-5 심볼 동기화, 트렐리스 디코더 안정화 800ms~ 1.5s BER(Bit Error Rate) 검증: 에러율이 임계값 이하인지 확인 ~2s Link Up ✓ — 1000BASE-T Full Duplex link_fail_inhibit_timer 만료 → BMSR.LINK_STATUS = 1 ~3s 커널 PHY 폴러 감지 phy_state_machine() → read_status() → phy_adjust_link() → MAC 속도 설정 사용자 공간 통보 netlink RTNL → ip monitor: eth0 state UP dmesg: "eth0: Link is Up - 1Gbps/Full"
케이블 삽입부터 "Link is Up" 메시지까지의 전체 과정입니다. 1000BASE-T에서는 AN 자체(~200ms)보다 PHY Training(~1초 이상)이 더 오래 걸립니다.
단계소요 시간하드웨어/소프트웨어실패 시 결과
전압 감지 + AN 시작0~16msPHY 하드웨어케이블 미연결 → BMSR.LINK=0
FLP Base Page 교환16~80msPHY 하드웨어FLP 미수신 → Parallel Detect 대기
Ack + Consistency Check80~130msPHY 하드웨어노이즈로 불일치 → AN 재시작
Next Page (GBCR, EEE)130~200msPHY 하드웨어NP 미지원 → 1G 미광고 → 100M 폴백
Master/Slave 결정AN 완료 직후PHY 하드웨어양쪽 force-master → config fault
Echo/NEXT Canceller 수렴200~500msPHY DSP수렴 실패 → AN 재시작 (downshift)
FEXT/ISI Equalizer 수렴500ms~1.5sPHY DSPBER 초과 → 링크 불안정
link_fail_inhibit_timer~750msPHY 하드웨어(안정화 대기, 실패 아님)
커널 폴링 → MAC 설정~1s (폴링 주기)커널 소프트웨어IRQ 모드면 즉시 감지
AN 시퀀스 추적 실전 팁: 링크 문제를 디버깅할 때 각 단계가 정상적으로 진행되었는지 확인하는 가장 빠른 방법은 다음과 같습니다:
# AN 시퀀스 추적 체크리스트

# 1단계: AN 시작 확인 — BMCR 읽기
phytool read eth0/0/0
# bit 12(AN_ENABLE)=1, bit 9(AN_RESTART) 확인
# AN 시작 직후에는 bit 9가 1이었다가 PHY가 자동으로 0으로 클리어

# 2단계: Base Page 교환 확인 — BMSR, ANAR, ANLPAR 읽기
phytool read eth0/0/1   # BMSR: bit 5(AN_COMPLETE) 확인
phytool read eth0/0/4   # ANAR: 내가 무엇을 광고했는지
phytool read eth0/0/5   # ANLPAR: 상대가 무엇을 광고했는지
# ANLPAR이 0x0000이면 상대가 응답하지 않은 것

# 3단계: 1G 확장 확인 — GBCR, GBSR 읽기
phytool read eth0/0/9   # GBCR: bit 9(1G-FD 광고), bit 12:11(MS 설정)
phytool read eth0/0/10  # GBSR: bit 14(MS config fault), bit 13(master/slave)
# bit 14=1이면 양쪽 모두 같은 역할을 강제한 것

# 4단계: 결과 확인
ethtool eth0
# Speed, Duplex, Autoneg, Link detected, master-slave status 전체 확인

# 5단계: Training 성공 확인 — 에러 카운터
ethtool -S eth0 | grep -E "crc|symbol|error"
# Training 직후 symbol error가 잠깐 나올 수 있으나, 지속되면 문제

# 6단계: 타이밍 측정
ethtool -r eth0 && dmesg -w | grep "Link is"
# AN restart부터 Link Up까지 시간 확인
# 10/100M: 1~2초 | 1000M: 2~4초 | 5초 이상: 케이블/PHY 문제

AN 완료 인터럽트와 폴링 전환 메커니즘

PHY가 AN 완료를 커널에 알리는 방법은 인터럽트(IRQ)폴링(Polling) 두 가지입니다. 어떤 방식이 사용되는지는 하드웨어 설계와 PHY 드라이버에 따라 달라지며, 두 방식의 차이는 링크 감지 지연 시간에 직접 영향을 줍니다.

방식지연조건장점단점
인터럽트 (IRQ)수 µsPHY IRQ 핀이 SoC GPIO에 연결, DT에 interrupts 속성 존재AN 완료 즉시 감지, CPU 부하 없음하드웨어 배선 필요, shared IRQ 시 복잡
폴링 (Timer)최대 1초IRQ 미연결 또는 PHY 드라이버가 PHY_POLL 사용하드웨어 의존성 없음, 항상 동작1초 간격으로 MDIO 읽기, 감지 지연
/* PHY 인터럽트 vs 폴링 — drivers/net/phy/phy.c 참조 */

/* 인터럽트 모드: PHY가 링크 변화를 IRQ로 통보 */
static irqreturn_t phy_interrupt(int irq, void *phy_dat)
{
    struct phy_device *phydev = phy_dat;

    /* PHY 드라이버의 handle_interrupt 콜백 호출 */
    if (phydev->drv->handle_interrupt)
        return phydev->drv->handle_interrupt(phydev);

    /* 레거시: did_interrupt + ack_interrupt */
    if (phydev->drv->did_interrupt &&
        !phydev->drv->did_interrupt(phydev))
        return IRQ_NONE;

    /* threaded IRQ에서 상태 갱신 예약 */
    phy_trigger_machine(phydev);
    return IRQ_HANDLED;
}

/* 폴링 모드: 1초 주기로 상태 확인 */
static void phy_state_machine(struct work_struct *work)
{
    struct phy_device *phydev = ...;

    mutex_lock(&phydev->lock);

    /* BMSR 읽기 → link/speed/duplex 갱신 */
    phy_check_link_status(phydev);

    mutex_unlock(&phydev->lock);

    /* 다음 폴링 예약 (PHY_STATE_TIME = 1초) */
    if (phy_polling_mode(phydev))
        phy_queue_state_machine(phydev, PHY_STATE_TIME);
}

/* phy_polling_mode() 판단 기준 */
static inline bool phy_polling_mode(struct phy_device *phydev)
{
    /* IRQ가 PHY_POLL이면 폴링 모드 */
    if (phydev->irq == PHY_POLL)
        return true;
    /* MAC이 in-band status를 사용하면 PHY 폴링 불필요 */
    if (phydev->drv->flags & PHY_IS_INTERNAL)
        return false;
    return false;
}
인터럽트 모드의 PHY 드라이버 구현 패턴: AN 완료 시 PHY가 발생시키는 인터럽트를 처리하려면, PHY 드라이버가 두 가지를 구현해야 합니다:
(1) config_intr(): PHY의 인터럽트 마스크 레지스터에서 "link change" 이벤트를 활성화합니다.
(2) handle_interrupt(): 인터럽트 발생 시 PHY의 인터럽트 상태 레지스터를 읽어 원인을 확인하고, link change이면 phy_trigger_machine()을 호출합니다. 이 콜백이 없으면 phydev->irq = PHY_POLL로 설정되어 1초 폴링으로 폴백합니다.
/* Realtek RTL8211F의 인터럽트 처리 예시 */
static int rtl8211f_config_intr(struct phy_device *phydev)
{
    int val;

    if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
        /* Link change, AN complete, speed change 인터럽트 활성화 */
        val = phy_read(phydev, RTL8211F_INER);
        val |= RTL8211F_INER_LINK_STATUS;  /* link change */
        return phy_write(phydev, RTL8211F_INER, val);
    } else {
        /* 모든 인터럽트 비활성화 */
        return phy_write(phydev, RTL8211F_INER, 0);
    }
}

static irqreturn_t rtl8211f_handle_interrupt(struct phy_device *phydev)
{
    int val;

    /* 인터럽트 상태 레지스터 읽기 (읽으면 자동 클리어) */
    val = phy_read(phydev, RTL8211F_INSR);
    if (val < 0)
        return IRQ_NONE;

    /* link change 비트가 세트되었으면 상태 머신 트리거 */
    if (val & RTL8211F_INSR_LINK_STATUS) {
        phy_trigger_machine(phydev);
        return IRQ_HANDLED;
    }
    return IRQ_NONE;
}
Device Tree에서의 인터럽트 설정: PHY 노드에 interrupts 또는 interrupt-parent 속성이 있으면 커널이 자동으로 IRQ 모드를 사용합니다. 이 속성이 없으면 PHY_POLL로 폴백됩니다. 인터럽트 배선이 되어 있는데도 폴링 모드로 동작한다면, DT 속성 누락이 원인일 가능성이 높습니다.
/* Device Tree: PHY 인터럽트 설정 예시 */
&mdio {
    phy0: ethernet-phy@0 {
        reg = <0>;
        /* IRQ 모드 — AN 완료를 즉시 감지 */
        interrupt-parent = <&gpio1>;
        interrupts = <25 IRQ_TYPE_LEVEL_LOW>;
    };
};

/* 위 속성이 없으면 → PHY_POLL → 1초 간격 폴링 */

MAC-PHY 인터페이스 모드

phy-mode는 매체 종류가 아니라 MAC과 PHY 사이의 디지털 인터페이스 계약을 뜻합니다. 이 값이 틀리면 링크가 아예 안 올라오거나, 올라와도 CRC와 symbol error가 폭증합니다. 특히 RGMII 계열은 지연 보상 위치를 명확히 해야 하고, SGMII/1000BASE-X/2500BASE-X는 in-band status 처리 여부까지 맞아야 합니다.

모드형태주요 속도핵심 주의점
MII / RMII병렬10/100M저속 임베디드, 핀 수와 기준 클럭 확인
GMII / RGMII병렬1GRGMII는 2ns 지연을 한 곳에서만 추가
SGMII / QSGMII직렬 SerDes1G, 4x1Gin-band status와 PCS 설정 동기화
1000BASE-X / 2500BASE-X직렬 SerDes1G, 2.5G광 모듈, switch port, PCS 모드 일치 필요
USXGMII직렬 SerDes10/5/2.5/1G멀티기가비트 통합, MAC과 PHY 모두 지원 필요
RGMII 모드 해석: rgmii는 내부 지연 없음, rgmii-id는 TX/RX 둘 다 PHY가 지연, rgmii-txidrgmii-rxid는 한 방향만 PHY가 지연한다는 뜻입니다. PCB 지연을 이미 넣은 설계에서 rgmii-id를 추가로 쓰면 이중 지연이 발생합니다.
MAC-PHY 인터페이스 모드 비교: 병렬(MII/RGMII) vs 직렬(SGMII/SerDes) MAC-PHY 인터페이스 모드 비교 병렬 인터페이스 (MII / RMII / GMII / RGMII) MAC TX/RX 병렬 출력 TXD[3:0] + TX_CLK RXD[3:0] + RX_CLK PHY 아날로그 변환 RGMII 지연 보상 옵션 rgmii: 지연 없음 rgmii-id: TX+RX 지연 rgmii-txid/rxid: 단방향 핵심: 지연은 MAC 또는 PHY 중 정확히 한 곳에서만! 직렬 인터페이스 (SGMII / 1000BASE-X / USXGMII) MAC+PCS SerDes 직렬 출력 TX± (차동 1쌍) RX± (차동 1쌍) PHY/SFP 매체 변환 In-Band Status / 프로토콜 차이 SGMII: control word 1000BASE-X: Clause 37 USXGMII: 멀티속도 핵심: 양단 PCS 의미 체계가 반드시 일치해야 함 인터페이스별 핵심 특성 MII / RMII 핀 18개 / 10개 10/100M, 저가 임베디드 GMII / RGMII 핀 24개 / 12개 1G, 지연 보상이 핵심 SGMII / QSGMII 차동 2쌍 / 2쌍×4 1G, in-band status 1000/2500BASE-X 차동 2쌍 광/스위치, Clause 37 AN USXGMII 차동 2쌍 (10G SerDes) 멀티기가비트 통합 phy-mode 선택 기준 보드 회로도에서 MAC-PHY 연결 방식을 확인 → Device Tree의 phy-mode 값과 정확히 일치시키는 것이 첫 번째 원칙
병렬 인터페이스는 핀 수가 많지만 구현이 단순하고, 직렬 인터페이스는 핀 수가 적지만 PCS 프로토콜 일치가 중요합니다.

phy-mode 선택 가이드

phy-mode는 보드 설계에 이미 결정되어 있는 물리적 연결을 소프트웨어에 알려 주는 설정이므로, "무엇이 더 좋은가"가 아니라 "회로도와 일치하는가"가 유일한 기준입니다. 회로도 확인이 어려운 경우에는 다음 순서로 추론할 수 있습니다.

  1. SoC 데이터시트에서 해당 포트의 지원 인터페이스 목록을 확인합니다.
  2. PHY 데이터시트에서 해당 SoC 포트와 연결된 핀이 RGMII인지 SGMII인지 확인합니다.
  3. 클럭 연결을 봅니다. RGMII는 125MHz 참조 클럭이 별도로 있고, SGMII/SerDes는 차동 클럭 또는 CDR 내장입니다.
  4. 기존 벤더 BSP의 Device Tree 설정을 참고하되, 벤더 실수가 있을 수 있으므로 실물 회로도와 교차 확인합니다.
RGMII 지연 이중 적용 주의: RGMII 지연은 MAC 내부, PHY 내부, PCB 트레이스 세 곳에서 각각 추가할 수 있습니다. 정확히 한 곳에서만 2ns 지연을 추가해야 합니다. 지연이 두 곳에 적용되면 4ns 지연이 되어 다음 클럭 에지를 침범하고, 지연이 없으면 setup/hold margin이 부족해집니다. 두 경우 모두 1G에서 CRC 폭증으로 나타납니다.

RGMII DDR 타이밍과 지연 보상 상세

RGMII(Reduced Gigabit Media Independent Interface)는 125MHz 클럭을 사용하여 상승 에지와 하강 에지 모두에서 데이터를 전송하는 DDR(Double Data Rate) 방식입니다. 클럭 주기는 8ns이며, 상승/하강 에지 간격은 4ns입니다. IEEE 802.3 표준과 RGMII 사양은 데이터 유효 윈도우를 클럭 에지 기준으로 setup 시간 약 1.5ns, hold 시간 약 1.5ns로 규정합니다.

문제는 MAC이 클럭과 데이터를 동시에 출력한다는 점입니다. 클럭 에지가 데이터 천이(transition) 순간과 정확히 일치하면 수신 측 PHY는 데이터가 변하는 순간을 샘플링하게 되어 신뢰할 수 없는 결과를 읽습니다. 이를 해결하기 위해 클럭을 2ns 지연시켜 에지가 데이터 유효 구간의 중앙에 오도록 보정합니다.

지연 없음 (잘못된 경우) — 클럭 에지가 데이터 천이 시점과 일치 TX_CLK TXD 에지 = 천이 에지 = 천이 에지 = 천이 에지 = 천이 8ns (클럭 주기) 2ns 지연 적용 (올바른 경우) — 클럭 에지가 데이터 유효 구간 중앙에 위치 TX_CLK TXD 에지 = 중앙 에지 = 중앙 에지 = 중앙 에지 = 중앙 2ns 지연 ≈1.5ns setup ≈1.5ns hold
위: 지연 없는 RGMII — 빨간 영역에서 클럭 에지와 데이터 천이가 겹쳐 샘플링 오류가 발생합니다. 아래: 2ns 지연 적용 — 클럭 에지(녹색 점선)가 데이터 유효 구간 중앙에 위치하여 setup/hold 마진이 확보됩니다.

RGMII 서브모드 상세

Device Tree에서 phy-moderx-internal-delay-ps, tx-internal-delay-ps 속성을 조합하거나, 레거시 방식으로 아래 4가지 서브모드 문자열을 직접 지정합니다. 각 서브모드가 내부 지연을 어느 방향에 적용하는지 정확히 이해해야 이중 적용을 방지할 수 있습니다.

phy-mode 값TX 지연RX 지연의미적합한 상황
rgmii없음없음PHY 내부 지연 없음MAC 또는 PCB가 양방향 지연을 모두 담당하는 경우
rgmii-idPHY 내부PHY 내부PHY가 TX·RX 양방향 모두 2ns 지연 삽입MAC도 PCB도 지연을 제공하지 않는 경우
rgmii-txidPHY 내부없음PHY가 TX 방향만 2ns 지연 삽입RX는 MAC 또는 PCB가 담당하는 경우
rgmii-rxid없음PHY 내부PHY가 RX 방향만 2ns 지연 삽입TX는 MAC 또는 PCB가 담당하는 경우

커널의 RGMII 지연 결정 흐름

커널은 Device Tree 속성을 읽어 PHY 드라이버에 요청할 지연 값을 결정합니다. phy_get_internal_delay() 함수는 rx-internal-delay-ps 또는 tx-internal-delay-ps 속성을 피코초 단위로 읽고, PHY 드라이버가 제공하는 지원 지연 테이블과 가장 가까운 값을 선택합니다.

/* drivers/net/phy/phy.c (개념 흐름 — 실제 함수 시그니처 요약) */

/* Device Tree 속성에서 피코초 단위 지연을 읽는다 */
int phy_get_internal_delay(struct phy_device *phydev,
                           struct device *dev,
                           const int *delay_values, /* PHY 지원 테이블 */
                           int size,
                           bool is_rx)
{
    /* "rx-internal-delay-ps" 또는 "tx-internal-delay-ps" 읽기 */
    u32 delay_ps;
    if (device_property_read_u32(dev,
            is_rx ? "rx-internal-delay-ps"
                  : "tx-internal-delay-ps",
            &delay_ps))
        return -EINVAL;   /* 속성 없음 → 레거시 phy-mode 문자열로 결정 */

    /* PHY 지원 테이블에서 가장 가까운 값 선택 */
    for (int i = 0; i < size - 1; i++) {
        if (delay_ps <= (delay_values[i] + delay_values[i + 1]) / 2)
            return i;
    }
    return size - 1;
}

/* PHY 드라이버 probe 시 호출 예시 */
static int my_phy_config_init(struct phy_device *phydev)
{
    /* rgmii-id / rgmii-txid → TX 내부 지연 활성화 */
    bool tx_delay = phy_interface_is_rgmii(phydev->interface) &&
                    (phydev->interface != PHY_INTERFACE_MODE_RGMII) &&
                    (phydev->interface != PHY_INTERFACE_MODE_RGMII_RXID);

    /* rgmii-id / rgmii-rxid → RX 내부 지연 활성화 */
    bool rx_delay = phy_interface_is_rgmii(phydev->interface) &&
                    (phydev->interface != PHY_INTERFACE_MODE_RGMII) &&
                    (phydev->interface != PHY_INTERFACE_MODE_RGMII_TXID);

    my_phy_set_delay(phydev, tx_delay, rx_delay);
    return 0;
}

지연 삽입 위치와 책임 분리

TX와 RX 각 방향에 대해 2ns 지연을 삽입할 수 있는 위치는 세 군데입니다. 세 위치 중 정확히 한 곳만 지연을 담당해야 합니다. 아래 다이어그램은 TX 방향을 기준으로 각 위치의 역할을 보여 줍니다.

MAC / SoC 내부 지연 회로 (선택적 제공) TX_CLK / TXD PCB 트레이스 길이 차이로 인한 자연 지연 (~2ns) TX_CLK / TXD PHY 칩 내부 지연 레지스터 (rgmii-id/-txid) 핵심 규칙 세 위치 중 정확히 1곳만 2ns 지연 적용 나머지 2곳 = 0 ① MAC 내부 SoC 레지스터 설정 ② PCB 트레이스 하드웨어 설계 단계 결정 ③ PHY 내부 phy-mode 문자열로 설정
TX 방향 기준 지연 삽입 가능 위치 3곳. RX 방향도 동일한 구조이며, TX와 RX 각각 독립적으로 위치를 결정합니다.

흔한 실수 유형

실수 유형총 지연증상원인
이중 적용 (double delay)4ns1G에서 CRC 오류 폭증, 링크 불안정PHY를 rgmii-id로 설정하면서 MAC도 내부 지연 활성화, 또는 PCB가 지연 트레이스를 보유
지연 없음 (no delay)0ns1G에서 CRC 오류, 100M는 정상rgmii로 설정했으나 MAC·PCB 모두 지연 미제공
TX/RX 방향 혼동한쪽 4ns, 한쪽 0nsTX 또는 RX 중 한 방향만 오류 발생rgmii-txidrgmii-rxid를 혼동하거나 PCB 지연 방향 오파악
PCB 지연 미인지2+2=4ns이중 적용과 동일 증상PCB 설계자가 클럭 트레이스 길이 조정으로 이미 지연을 추가했으나 소프트웨어 담당자가 모르고 rgmii-id 사용

PHY 드라이버의 지연 레지스터 설정 예시

Realtek 계열 PHY를 포함한 많은 Gigabit PHY는 내부 지연을 레지스터로 제어합니다. 아래는 개념적인 드라이버 패턴으로, 실제 비트 위치와 레지스터 번호는 각 PHY 데이터시트를 확인해야 합니다.

/* 개념적 Realtek 스타일 RGMII 지연 설정 */

#define PHY_PAGE_ADDR_REG    31
#define PHY_EXT_PAGE         7
#define PHY_RGMII_DELAY_REG  28   /* 확장 레지스터 (페이지 7) */
#define TX_DELAY_BIT         BIT(1)
#define RX_DELAY_BIT         BIT(0)

static int rtl_phy_set_rgmii_delay(struct phy_device *phydev,
                                   bool tx_delay, bool rx_delay)
{
    int ret, val;

    /* 확장 레지스터 페이지 진입 */
    ret = phy_write(phydev, PHY_PAGE_ADDR_REG, PHY_EXT_PAGE);
    if (ret)
        return ret;

    val = phy_read(phydev, PHY_RGMII_DELAY_REG);
    if (val < 0)
        goto restore_page;

    /* TX/RX 지연 비트 설정 */
    if (tx_delay)
        val |= TX_DELAY_BIT;
    else
        val &= ~TX_DELAY_BIT;

    if (rx_delay)
        val |= RX_DELAY_BIT;
    else
        val &= ~RX_DELAY_BIT;

    ret = phy_write(phydev, PHY_RGMII_DELAY_REG, val);

restore_page:
    /* 일반 레지스터 페이지 복귀 */
    phy_write(phydev, PHY_PAGE_ADDR_REG, 0);
    return ret;
}

static int rtl_phy_config_init(struct phy_device *phydev)
{
    bool tx = (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
               phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID);
    bool rx = (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
               phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID);

    return rtl_phy_set_rgmii_delay(phydev, tx, rx);
}
PCB 설계자와 소프트웨어 담당자의 협업 필수: RGMII 지연 위치 결정은 반드시 두 역할이 명시적으로 합의해야 합니다. PCB 설계자가 클럭 라인 트레이스 길이를 조정하여 2ns 지연을 이미 구현했다면, 소프트웨어 담당자는 반드시 rgmii(지연 없음)를 사용해야 합니다. 이 정보가 보드 설계 문서에 명확히 기재되지 않으면 rgmii-id를 잘못 설정하여 4ns 이중 지연이 발생합니다. 보드 수신 후에는 항상 회로도의 클럭 트레이스 길이와 SoC의 내부 지연 레지스터 기본값을 함께 확인하십시오.

SerDes, PCS, SFP 모듈

구리 RJ45 PHY만 생각하면 PHY를 외부 칩 하나로 이해하기 쉽지만, 실제 장비에서는 host PCS, SerDes lane, SFP cage, 모듈 내부 PHY 또는 광 PMD가 나뉘어 있을 수 있습니다. 특히 1000BASE-X와 SGMII는 비슷한 1.25Gbaud 직렬 링크처럼 보여도 control word 의미와 in-band status 해석이 달라, 모드를 잘못 맞추면 링크 LED는 켜져도 실제 프레임 송수신이 안 될 수 있습니다.

모드주요 대상baud / lane협상/상태 전달주의점
SGMIIMAC ↔ 외부 PHY1.25Gbaud, 1 lanein-band status, speed code속도 표시는 control word에 실림
1000BASE-X광 모듈, 스위치 업링크1.25Gbaud, 1 laneClause 37 ANSGMII와 전기적 속도는 비슷해도 의미가 다름
2500BASE-X2.5G SerDes3.125Gbaud, 1 lane장치별 운용 차이 존재양단 PCS 설정 일치 필요
QSGMII4포트 집적 PHY5Gbaud, 1 lane4개 포트 상태 다중화(Multiplexing)포트 분리 해석 필요
USXGMII멀티기가비트10.3125Gbaud, 1 lane10/5/2.5/1G 다중화MAC/PHY 둘 다 지원해야 의미 있음
SerDes와 SFP 모듈 경로 MAC / Host PCS phylink, PCS 설정 SerDes lane SGMII / 1000BASE-X / USXGMII SFP cage LOS / TX_FAULT / MOD_ABS 광 모듈 CDR, laser, DOM EEPROM fiber PMD RJ45 copper 모듈 모듈 내부에 별도 copper PHY 존재 발열과 전력 예산 주의 I2C EEPROM / DOM
광 모듈은 PMD 역할을, RJ45 copper SFP는 모듈 내부에서 다시 copper PHY 역할을 수행할 수 있습니다.

SFP sideband 신호를 같이 봐야 하는 이유

광 모듈 문제는 MDIO만으로 보이지 않는 경우가 많습니다. 모듈이 스스로 올리는 LOS, TX_FAULT, MOD_ABS, TX_DISABLE 같은 sideband는 host PCS와 phylink 정책에 직접 영향을 줍니다. 즉 광량과 DOM은 정상인데도 sideband 한 줄이 반대로 연결되어 전체 링크가 막힐 수 있습니다.

신호의미보통 누가 해석하나문제 시 증상
MOD_ABS모듈 삽입 여부SFP 코어 / phylink모듈이 꽂혀도 미삽입으로 보임
LOS수신 광 신호 손실host 드라이버 / 모듈 상태기광량은 있는데 링크 down 반복
TX_FAULT송신 레이저 faulthost 드라이버송신만 불가, fault latched
TX_DISABLE송신 레이저 비활성화host 제어모듈은 인식되지만 원격 링크 없음
RATE_SELECT속도/이퀄라이저 선택host와 모듈 조합특정 속도에서만 불안정

phylib은 "PHY 칩을 커널이 공통 방식으로 다루기 위한 프레임워크"이고, phylink는 "MAC, PHY, PCS, fixed-link, SFP를 하나의 정책으로 묶는 상위 계층"입니다. 외부 PHY 하나를 단순히 연결하는 임베디드 보드라면 phylib만으로 충분할 수 있지만, SFP 모듈 핫플러그(Hotplug)나 in-band status, 고속 SerDes가 얽히면 phylink가 사실상 표준 선택입니다.

항목phylibphylink
주요 대상외부 PHY 직접 연결MAC + PHY + PCS + SFP 조합
핵심 구조체(Struct)phy_device, phy_driverphylink, phylink_config, phylink_mac_ops
장점단순하고 익숙함fixed-link, SFP, in-band status 통합
주의점복합 토폴로지 대응이 약함MAC 콜백 계약을 정확히 구현해야 함
/* 개념 예시: PHY 드라이버가 구현하는 핵심 콜백 */
static int myphy_config_init(struct phy_device *phydev);
static int myphy_config_aneg(struct phy_device *phydev);
static int myphy_read_status(struct phy_device *phydev);

static struct phy_driver myphy_driver = {
    .phy_id       = 0x001cc916,
    .phy_id_mask  = 0xfffffff0,
    .name         = "Example PHY",
    .config_init  = myphy_config_init,
    .config_aneg  = myphy_config_aneg,
    .read_status  = myphy_read_status,
};
/* 개념 예시: phylink 초기화와 firmware 기반 PHY 연결 */
static const struct phylink_mac_ops my_phylink_ops = {
    .mac_config = my_mac_config,
    .mac_link_up = my_mac_link_up,
    .mac_link_down = my_mac_link_down,
};

static int my_phylink_init(struct my_priv *priv)
{
    struct phylink_config *cfg = &priv->phylink_config;
    struct fwnode_handle *fwnode = dev_fwnode(priv->dev);
    phy_interface_t iface = priv->phy_mode;

    priv->phylink = phylink_create(cfg, fwnode, iface, &my_phylink_ops);
    if (IS_ERR(priv->phylink))
        return PTR_ERR(priv->phylink);

    if (is_of_node(fwnode))
        return phylink_of_phy_connect(priv->phylink, to_of_node(fwnode), 0);
    return phylink_fwnode_phy_connect(priv->phylink, fwnode, 0);
}

phylink를 도입하면 링크 업/다운 알림, SFP 모듈 감지, PCS 선택, fixed-link 해석을 한 계층에서 정리할 수 있습니다. 반대로 mac_config(), mac_link_up(), mac_link_down() 구현이 느슨하면 링크는 오르는데 실제 MAC 레지스터가 이전 속도를 유지하는 문제가 생길 수 있습니다.

phylib vs phylink 소프트웨어 스택과 선택 기준 phylib vs phylink 소프트웨어 스택 phylib만 사용하는 경우 net_device (netdev) MAC 드라이버 (phy_connect 호출) phylib (phy_device + phy_driver) MDIO 버스 → PHY 하드웨어 적합한 경우 외부 PHY 1개, 고정 인터페이스, SFP 없음 phylink을 사용하는 경우 net_device (netdev) phylink (정책 통합 계층) phylink_mac_ops phylink_pcs_ops MAC HW phylib PCS HW 적합한 경우 SFP, in-band status, PCS 분리, fixed-link 혼합 phylink 링크 변화 시 콜백 순서 PHY 링크 변화 mac_config() mac_link_up() 또는 mac_link_down() netif_carrier 갱신
phylib은 단순한 PHY 연결에 적합하고, phylink은 SFP·PCS·in-band status·fixed-link 같은 복합 토폴로지를 하나의 정책으로 관리합니다.

새 MAC 드라이버를 작성할 때 어느 쪽을 선택할지는 보드의 PHY 토폴로지에 따라 결정됩니다. 아래 조건 중 하나라도 해당하면 phylink을 사용하는 것이 맞습니다.

조건phylibphylink
SFP 슬롯(핫플러그) 지원어려움필수 선택
in-band status (SGMII/1000BASE-X)직접 구현 필요프레임워크 지원
PCS가 MAC과 분리된 하드웨어미지원phylink_pcs로 통합
fixed-link (PHY 없는 직결)별도 처리of_phy_is_fixed_link 자동 감지
외부 PHY 1개, RGMII 고정충분가능하지만 불필요한 복잡도
mac_link_up() 구현 주의: mac_link_up()에서 MAC 레지스터의 speed, duplex, flow control을 반드시 갱신해야 합니다. 이 콜백을 빈 함수로 두면 PHY가 1G→100M으로 재협상해도 MAC은 1G 설정을 유지하여 프레임이 깨집니다. 이 오류는 "링크 업 상태에서 CRC 폭증"으로 나타나 RGMII delay 문제와 혼동하기 쉽습니다.

분리형 PCS와 In-Band Status

고속 SerDes 기반 설계에서는 PHY가 모든 계층을 혼자 담당하지 않습니다. host MAC 안에 별도 PCS가 있고, 외부에는 PMA/PMD 성격이 강한 장치만 있거나, 반대로 외부 PHY는 있지만 링크 상태 결정은 host PCS가 수행하는 경우가 있습니다. 이때 커널은 단순한 phy_device만으로는 부족하고, phylink_pcs를 통해 "MAC 옆의 PCS"를 별도 객체로 다뤄야 합니다.

실무적으로는 SGMII, 1000BASE-X, 2500BASE-X, 10GBASE-R에서 이 구분이 중요합니다. 외부 copper PHY가 속도 협상을 끝냈더라도, host PCS가 그 결과를 in-band status로 받아 MAC 속도를 다시 맞추지 않으면 실제 데이터 경로가 잘못된 속도로 남을 수 있습니다.

구성누가 상태를 결정하나phylink 역할실수하기 쉬운 점
RGMII + 외부 PHY외부 PHYPHY 결과를 MAC에 전달phy-mode와 지연 설정 불일치
SGMII + 외부 PHY + host PCS외부 PHY와 host PCS 둘 다PCS와 PHY 결과 동기화in-band status 미적용
1000BASE-X + SFPhost PCS와 모듈 상태모듈 삽입, LOS, Clause 37 AN 조정SGMII와 의미 혼동
fixed-link + host PCSfirmware 고정값MAC 설정만 적용고정 링크인데도 AN 기대
backplane + Clause 73host PCS/AN 블록FEC와 lane 정책 조정FEC 강제/자동 혼선
/* 개념 예시: 분리형 PCS를 phylink에 등록 */
static void my_pcs_get_state(struct phylink_pcs *pcs,
                             struct phylink_link_state *state)
{
    /* host PCS의 block lock, speed code, duplex 상태 해석 */
}

static const struct phylink_pcs_ops my_pcs_ops = {
    .pcs_get_state = my_pcs_get_state,
    .pcs_config = my_pcs_config,
    .pcs_an_restart = my_pcs_restart_an,
    .pcs_link_up = my_pcs_link_up,
};

static int my_mac_select_pcs(struct phylink_config *config,
                                 phy_interface_t interface)
{
    return interface == PHY_INTERFACE_MODE_SGMII ? 0 : 0;
}
/* SFP/SerDes 경로에서 host PCS가 in-band status를 읽는 예시 */
&eth0 {
    phy-mode = "sgmii";
    managed = "in-band-status";
    sfp = <&sfp0>;
    status = "okay";
};
분리형 PCS와 In-Band Status MAC DMA, queue, pause Host PCS Clause 37 / 73, block lock SerDes lane SGMII / 1000BASE-X 외부 copper PHY 구리선 협상 수행 SFP / 광 모듈 LOS, MOD_ABS, optical PMD in-band status phylink MAC, PCS, SFP 상태 통합
분리형 PCS가 있는 설계에서는 PHY만 읽어서는 부족하고, host PCS와 in-band status까지 함께 해석해야 합니다.
중요: fixed-link, managed = "in-band-status", 외부 PHY attach는 같은 포트에서 역할이 다릅니다. 고정 링크인데도 in-band status를 기대하거나, host PCS가 속도를 결정하는데 외부 PHY 결과만 믿으면 MAC 속도가 이전 값에 남아 실제 송수신이 어긋날 수 있습니다.

In-Band vs Out-of-Band 링크 상태 결정

PHY 링크 상태를 MAC에 전달하는 경로는 크게 두 가지입니다. Out-of-Band는 데이터 경로와 물리적으로 분리된 관리 채널을 통해 상태를 읽는 방식이고, In-Band는 데이터가 흐르는 직렬 링크 자체에 상태 정보를 실어 보내는 방식입니다. 두 방식은 배타적이지 않으며, 같은 포트에서 외부 PHY의 MDIO(out-of-band)와 host PCS의 in-band status가 동시에 존재할 수 있습니다. 이 경우 phylink이 양쪽 결과를 통합하여 MAC 속도를 결정합니다.

항목Out-of-BandIn-Band
물리 경로데이터 경로와 별도 (MDIO 2선, SFP I2C)데이터 경로와 동일 (SerDes 직렬 링크)
대표 예MDIO Clause 22/45 BMSR 폴링, SFP MOD_ABS/LOSClause 37 /C/ ordered set, SGMII control word, Clause 73 DME page
속도/duplex 결정소프트웨어가 PHY 레지스터를 읽어 해석PCS 하드웨어가 수신 스트림에서 자동 추출
Device Tree 설정phy-handle = <&phy>managed = "in-band-status"
커널 콜백phy_read_status() → phylibpcs_get_state() → phylink_pcs
대표 인터페이스RGMII, MII, RMIISGMII, 1000BASE-X, 2500BASE-X, 10GBASE-R
대표 실패 증상PHY ID 0xffff, MDIO timeout링크 LED 켜지지만 speed code 불일치로 데이터 미전송
In-Band 경로 Out-of-Band 경로 MAC DMA, queue Host PCS Clause 37/73 SerDes 직렬 링크 데이터 + 상태가 같은 경로 MAC DMA, queue 외부 PHY 구리/광 매체 처리 MDIO bus 별도 관리 채널 phylink in-band + out-of-band 상태 통합 pcs_get_state() phy_read_status() MAC 속도 적용 mac_link_up()
In-Band 경로는 데이터와 상태가 같은 SerDes 링크를 공유하고, Out-of-Band 경로는 MDIO 같은 별도 관리 버스를 사용합니다. phylink은 양쪽을 통합하여 최종 MAC 속도를 결정합니다.

SGMII In-Band Control Word 형식

SGMII는 Cisco가 정의한 인터페이스로, Clause 37과 같은 1.25Gbaud 8B/10B 직렬 링크를 사용하지만 control word의 의미가 완전히 다릅니다. PHY→MAC 방향에서는 링크 상태, 속도, duplex 정보를 담은 16비트 word를 보내고, MAC→PHY 방향에서는 acknowledge만 돌려보냅니다. 이 비대칭 구조가 Clause 37(양쪽 대칭)과의 핵심 차이입니다.

비트PHY→MAC 방향MAC→PHY 방향설명
15LinkLink (echo)1 = link up, 0 = link down
14AcknowledgeAcknowledge상대편 word 수신 확인
13:12[12] DuplexReserved1 = full duplex, 0 = half duplex
11:10SpeedReserved00=10M, 01=100M, 10=1000M, 11=reserved
9:1ReservedReserved향후 확장용
01 (고정)1 (고정)SGMII 식별자 — Clause 37과 구분하는 핵심 비트
bit 0으로 구분합니다: SGMII는 bit 0이 항상 1이고, Clause 37 1000BASE-X는 bit 0이 항상 0입니다. PCS가 이 비트를 검사하여 어떤 프로토콜로 협상할지 자동 판별하는 구현도 있지만, 대부분의 실무 환경에서는 Device Tree의 phy-mode로 명시적으로 지정합니다.
/* 개념 예시: SGMII control word 해석 */
static void sgmii_decode_word(u16 word, struct phylink_link_state *state)
{
    state->link  = !!(word & BIT(15));
    state->duplex = (word & BIT(12)) ? DUPLEX_FULL : DUPLEX_HALF;
    switch ((word >> 10) & 3) {
    case 0:  state->speed = SPEED_10;   break;
    case 1:  state->speed = SPEED_100;  break;
    case 2:  state->speed = SPEED_1000; break;
    default: state->link = 0;          break;
    }
}
/* 참조: drivers/net/pcs/pcs-xpcs.c — xpcs_get_state_sgmii()
 *       drivers/net/pcs/pcs-lynx.c — lynx_pcs_get_state_sgmii() */

1000BASE-X Clause 37 Base Page 형식

Clause 37은 IEEE 802.3에서 정의한 1000BASE-X 전용 Auto-Negotiation입니다. 8B/10B 스트림 안에서 /C/ ordered set을 교환하여 16비트 base page를 주고받습니다. SGMII와 달리 양쪽이 대칭이며, 속도 정보가 없습니다(항상 1000Mbps). 협상의 주된 목적은 pause(흐름 제어) 능력과 duplex를 결정하는 것입니다.

비트이름의미
15Next Page추가 next page 존재 여부
14Acknowledge상대편 base page 수신 확인
13:12Remote Fault00=정상, 01=link failure, 10=offline, 11=AN error
11:9Reserved
8:7Pause01=symmetric, 10=asymmetric, 11=both
6Half Duplexhalf duplex 지원
5Full Duplexfull duplex 지원
4:0Reserved
Clause 37 vs SGMII Control Word 비교 Clause 37 — 1000BASE-X Base Page (16비트) 4:0 Rsvd (0) 5 Full Dup 6 Half Dup 8:7 Pause 11:9 Rsvd 13:12 Rmt Fault 14 Ack 15 NP 속도 고정 (1G) pause/duplex만 협상 SGMII Control Word (16비트) — PHY → MAC 방향 0 Link (1=up) 1 Ack 11:10 Speed 12 Duplex 14:13 Rsvd 15 Link Speed: 10=10M, 01=100M, 00=1G bit 0이 1 → Clause 37과 구분 가능 Clause 37 vs SGMII 핵심 차이 • bit 0: Clause 37에서는 항상 0(Reserved), SGMII에서는 link status — 이 한 비트로 PCS가 프로토콜을 구분합니다. • Clause 37: 대칭(양쪽 동일 포맷), 속도 고정(1G), pause/duplex/remote fault만 교환합니다. • SGMII: 비대칭(PHY→MAC 방향만 speed/link 전달), MAC→PHY는 항상 0x4001(Ack + Link), 10/100/1000M 속도 가변입니다. • PCS 모드 불일치 시: block lock은 되지만 control word 해석이 어긋나 AN이 완료되지 않거나, 속도가 1G로 고정됩니다. • 커널에서 phylink_pcs의 pcs_config()가 PCS 모드를 MLO_AN_INBAND + interface mode에 맞춰 설정합니다.
같은 1.25Gbaud SerDes를 사용하지만, Clause 37(1000BASE-X)과 SGMII는 control word의 비트 의미가 완전히 다릅니다. bit 0의 유무로 프로토콜이 구분됩니다.
SGMII와 혼동 주의: SGMII와 1000BASE-X는 같은 1.25Gbaud SerDes를 사용하지만, control word의 의미가 완전히 다릅니다. PCS 모드를 잘못 맞추면 block lock은 되어도 협상 결과를 서로 다르게 해석합니다. 예를 들어 1000BASE-X 모드에서 SGMII word를 받으면 bit 0이 1이므로 예약된 값으로 취급되어 협상이 끝나지 않습니다.
/* 개념 예시: Clause 37 base page 해석 */
static void clause37_decode_page(u16 lpa, struct phylink_link_state *state)
{
    state->speed  = SPEED_1000;  /* 항상 1000Mbps */
    state->duplex = (lpa & BIT(5)) ? DUPLEX_FULL : DUPLEX_HALF;
    state->pause  = MLO_PAUSE_NONE;
    if (lpa & BIT(7))
        state->pause |= MLO_PAUSE_SYM;
    if (lpa & BIT(8))
        state->pause |= MLO_PAUSE_ASYM;
}
/* 참조: include/uapi/linux/mii.h — ADVERTISE_1000XFULL, ADVERTISE_1000XPAUSE */

Clause 73 백플레인 In-Band AN

10GBASE-KR, 25GBASE-KR, 40GBASE-KR4 등 고속 백플레인(Backplane) 링크에서는 Clause 73이 사용됩니다. Clause 37과 달리 multi-page 구조(base page 48비트 + next page)를 가지며, FEC 능력 광고와 AN 후 link training 시퀀스가 추가됩니다. AN이 완료된 뒤 양단이 SerDes equalizer를 최적화하는 훈련 과정을 거쳐야 비로소 데이터 경로가 열립니다.

base page 필드비트 범위용도
Selector FieldA[4:0]기술 유형 (IEEE 802.3 = 1)
Echoed NonceA[8:5]상대편 nonce 반향 — 자기 자신과 협상 중이 아님을 확인
Pause AbilityA[12:10]symmetric/asymmetric pause 광고
NonceA[17:13]로컬 난수
Technology AbilityA[24:21]10GBase-KR, 40GBase-KR4, 25GBase-KR 등 속도 능력
FEC AbilityA[47:44]BASE-R FEC(Clause 74), RS-FEC(Clause 91/108) 지원 여부
Clause 73 Base Page (48비트) DME(Differential Manchester Encoding) page로 직렬 전송 — 양단이 1:1 교환 A[4:0] Selector (=1) A[8:5] Echo Nonce A[9] Pause A[10] ASM Dir A[12:11] Rmt Fault A[17:13] Nonce A[24:21] Technology Ability (속도 능력) A[47:44] FEC Ability NP Next Page Technology Ability 비트 (대표 속도) A[21]=1000BASE-KX, A[22]=10GBASE-KR, A[23]=40GBASE-KR4, A[24]=40GBASE-CR4 확장: A[25]=100GBASE-CR10, Next Page로 25GBASE-KR/CR, 50GBASE-KR/CR2, 100GBASE-KR4/CR4 등을 추가 광고합니다. Clause 73 AN → Link Training 전체 흐름 1. DME base page 교환 → Nonce/Echo Nonce로 자기 자신과의 협상이 아님을 확인합니다. 2. Next Page 체인으로 확장 능력(25G, 50G, 100G, RS-FEC 등)을 추가 교환합니다. 3. 최고 공통 모드 선택 후 Link Training(Clause 72/93) 진입 — equalizer 계수를 반복 조정합니다. 4. Training 완료 → PCS lock 확인 → 데이터 경로 개방. 전체 과정이 수백 ms~수 초 소요될 수 있습니다.
Clause 73 base page는 48비트로 속도·FEC·pause 능력을 교환하며, Next Page로 확장 속도를 추가 광고합니다. AN 완료 후 Link Training이 필수입니다.
FEC 협상이 핵심입니다: 백플레인에서는 FEC 설정 불일치가 가장 흔한 링크 장애 원인입니다. 한쪽이 RS-FEC를 강제하고 상대편이 BASE-R FEC만 지원하면 AN은 완료되어도 link training 단계에서 실패하여 링크가 올라오지 않습니다. phylink에서는 phylink_pcs_ops.pcs_config()에서 FEC 정책을 PCS에 반영합니다.

phylink은 초기화 시점에 Device Tree 설정과 MAC 드라이버 능력에 따라 링크 상태 결정 모드를 선택합니다. 세 가지 모드 — MLO_AN_PHY, MLO_AN_INBAND, MLO_AN_FIXED — 는 각각 다른 콜백 체인을 활성화하며, 이 선택이 링크 수명주기 전체를 결정합니다.

항목MLO_AN_PHYMLO_AN_INBANDMLO_AN_FIXED
상태 소스외부 PHY (MDIO 레지스터)Host PCS (in-band signaling)Device Tree 고정값
DT 설정phy-handle = <&phy>managed = "in-band-status"fixed-link { speed = 1000; ... }
대표 인터페이스RGMII, MII, RMIISGMII, 1000BASE-X, 2500BASE-Xchip-to-chip 직결, 일부 스위치
MAC 속도 결정 주체PHY가 알려줌PCS가 in-band word에서 추출DT에 명시된 고정값
핵심 콜백phy_read_status()pcs_get_state()초기 설정만
대표 실패 패턴PHY ID 못 읽음, MDIO timeoutspeed code 불일치, AN 무한 반복속도 변경 불가, 상대편 변화 감지 불가
phylink_create() DT 설정에 따라 AN 모드 결정 AN 모드? MLO_AN_PHY phy_start() 외부 PHY 폴링/인터럽트 phy_read_status() → MDIO MLO_AN_INBAND phylink_resolve() PCS 상태 주기적 확인 pcs_get_state() → in-band word MLO_AN_FIXED 고정값 사용 DT fixed-link 속성 speed, duplex 즉시 적용 mac_link_up() MAC 속도/duplex/pause 최종 적용
phylink은 DT 설정에 따라 세 가지 경로 중 하나를 선택하고, 각각 다른 콜백 체인으로 링크 상태를 결정한 뒤 최종적으로 MAC에 적용합니다.
/* 개념 예시: phylink_resolve() 단순화 흐름
 * 참조: drivers/net/phy/phylink.c — phylink_resolve() */
static void phylink_resolve(struct work_struct *w)
{
    struct phylink *pl = container_of(w, struct phylink, resolve);
    struct phylink_link_state link_state;

    switch (pl->cur_link_an_mode) {
    case MLO_AN_PHY:
        /* 외부 PHY가 이미 읽어둔 상태를 복사 */
        link_state = pl->phy_state;
        break;
    case MLO_AN_INBAND:
        /* Host PCS에서 in-band 상태를 직접 읽음 */
        pl->pcs->ops->pcs_get_state(pl->pcs, &link_state);
        break;
    case MLO_AN_FIXED:
        /* DT에서 가져온 고정값 사용 */
        link_state = pl->link_config;
        link_state.link = 1;
        break;
    }

    /* 상태가 변경되었으면 MAC에 반영 */
    if (link_state.link && !pl->old_link_state)
        pl->mac_ops->mac_link_up(pl->config, pl->cur_link_an_mode,
                                   link_state.interface, ...);
}
/* 개념 예시: PCS 드라이버가 SGMII in-band 상태를 보고하는 콜백
 * 참조: drivers/net/pcs/pcs-xpcs.c, drivers/net/pcs/pcs-lynx.c */
static void my_pcs_get_state(struct phylink_pcs *pcs,
                             struct phylink_link_state *state)
{
    u32 status = readl(priv->pcs_base + PCS_STATUS);
    u16 lpa    = readl(priv->pcs_base + PCS_LP_ABILITY);

    state->an_complete = !!(status & PCS_AN_DONE);
    state->link = !!(status & PCS_LINK_UP);

    if (state->link) {
        if (state->interface == PHY_INTERFACE_MODE_SGMII) {
            /* SGMII: control word에서 speed/duplex 추출 */
            sgmii_decode_word(lpa, state);
        } else {
            /* 1000BASE-X: Clause 37 base page에서 pause/duplex 추출 */
            clause37_decode_page(lpa, state);
        }
    }
}
MLO_AN_INBAND와 외부 PHY의 공존: SGMII + 외부 copper PHY 구성에서는 MLO_AN_PHYMLO_AN_INBAND가 동시에 작동할 수 있습니다. 외부 PHY가 구리선에서 협상한 속도를 SGMII control word로 host PCS에 전달하면, phylink은 PCS 상태와 PHY 상태를 모두 확인한 뒤 MAC 속도를 결정합니다. 이 경우 PCS의 pcs_get_state()가 SGMII word를 올바르게 해석하지 못하면, PHY는 1G를 보고하는데 MAC은 100M으로 남는 증상이 발생합니다.

PHY 기능과 튜닝 포인트

PHY는 단순히 링크를 올리는 장치가 아니라, 운영 중 성능과 안정성을 좌우하는 여러 정책을 가집니다. 저전력 중심이면 EEE가 중요하고, 긴 케이블과 구형 배선이면 downshift와 cable diagnostics가 유용하며, 고속 광링크에서는 FEC 선택이 BER과 latency를 좌우합니다. 튜닝은 "항상 켠다/끈다"가 아니라 증상과 환경에 따라 선택해야 합니다.

기능무엇을 하나언제 점검하나대표 명령주의점
EEE유휴 시 LPI로 전력 절감idle 후 첫 패킷 지연, link flapethtool --show-eee eth0저지연 환경에서 불리할 수 있음
Downshift고속 협상 실패 시 낮은 속도로 폴백2.5G/5G가 자주 1G로 떨어짐ethtool --set-phy-tunable eth0 downshift on count 3배선 문제를 가릴 수 있음
Master/Slave1000BASE-T 기준 클럭 소스 결정특정 스위치와 협상 불안정ethtool eth0한쪽만 강제하면 오히려 문제
Cable TestTDR 기반 배선 진단구리선 품질 의심ethtool --cable-test eth0PHY 지원 필요
FEC고속 링크 비트 오류 정정25G+/광 링크 BER 높음ethtool --show-fec eth0약간의 지연 증가 가능
LoopbackMAC/PHY 내부 경로 단독 검사상대편 없이 자체 진단벤더 레지스터 또는 self-test트래픽 경로 일부만 검증
# EEE 상태 조회와 비활성화
ethtool --show-eee eth0
ethtool --set-eee eth0 eee off

# Downshift 정책 설정 (지원 PHY만)
ethtool --set-phy-tunable eth0 downshift on count 3

# 케이블 테스트 (지원 PHY만)
ethtool --cable-test eth0

# FEC 상태 조회
ethtool --show-fec eth0

Master/Slave, downshift, EDPD를 함께 읽기

1000BASE-T 이상 구리 PHY에서는 링크 양단이 기준 클럭 제공자를 정해야 합니다. 이것이 master/slave 결정입니다. 동시에 배선 품질이 좋지 않으면 고속 협상에 반복 실패하고, 그 결과 downshift가 작동해 낮은 속도로만 링크가 붙습니다. 여기서 EDPD(Energy Detect Power Down)가 섞이면 idle 상태에서 PHY가 더 깊게 잠들었다가 복귀하면서 협상이 늦어지는 증상이 나타날 수 있습니다.

항목좋은 경우나쁜 경우주로 보이는 증상
Master/Slave자동 결정 또는 양단 일관 설정한쪽만 강제간헐적 협상 실패, 재협상 반복
Downshift불량 배선에서 폴백 안전장치원인 숨김2.5G 장비가 계속 1G로만 링크 업
EDPD유휴 전력 절감상대편과 복귀 정책 불일치idle 후 첫 링크 업 지연
EEE지속 유휴 구간에서 전력 절감낮은 지연 요구와 충돌burst 트래픽에서 변동 지연
# PHY 튜너블 조회
ethtool --show-phy-tunable eth0

# 에너지 감지 파워다운 설정 (지원 PHY만)
ethtool --set-phy-tunable eth0 energy-detect-power-down on

Loopback과 자가 진단을 계층별로 분리하기

루프백은 "상대편 없이 내 경로만 검증"하는 가장 빠른 방법이지만, 어느 계층에서 되돌리는지에 따라 검증 범위가 달라집니다. MAC loopback은 MAC과 DMA까지만 확인하고, PCS loopback은 host PCS와 SerDes 일부까지, PHY loopback은 PMA/PMD까지 포함할 수 있습니다. 반대로 외부 케이블과 원격 포트는 빠지므로, 루프백이 통과했다고 실제 배선 문제가 사라지는 것은 아닙니다.

Loopback 계층별 검증 범위와 EEE 전력 상태 전환 Loopback 계층별 검증 범위 DMA descriptor ring MAC 프레임 처리 PCS 8B/10B, SerDes PHY (PMA) DSP, CDR 케이블 매체 원격 PHY 상대편 MAC Loopback PCS Loopback PHY Loopback 원격 Loopback (전체 경로) EEE (Energy Efficient Ethernet) 전력 상태 전환 Active 정상 데이터 전송 idle 감지 Sleep (LPI) 저전력, 주기적 refresh 패킷 도착 Wake 복귀 지연 (~16-30µs) 동기 완료 Active 데이터 재전송 시작 Wake 지연(16~30µs)이 저지연 애플리케이션에서 tail latency 변동을 일으킬 수 있음
Loopback은 계층별로 검증 범위가 다르며, EEE는 idle→sleep→wake 전환 시 복귀 지연이 발생합니다.
  1. MAC loopback
    DMA, descriptor, netdev 경로를 빠르게 검증합니다.
  2. PCS loopback
    SerDes encoding과 block lock 경로를 좁혀 봅니다.
  3. PHY 내부 loopback
    외부 케이블 없이 PHY 내부 DSP 경로를 확인합니다.
  4. 원격 loopback
    상대편 장비가 지원할 때 전체 선로까지 포함한 검증이 가능합니다.

PHY 하드웨어 타임스탬프와 시간 민감 네트워크

일부 PHY는 MAC보다 더 선로 가까운 위치에서 송수신 타임스탬프를 찍을 수 있습니다. 이런 PHY는 PTP(IEEE 1588)나 TSN 환경에서 유리할 수 있는데, 패킷이 실제 wire를 통과한 시점에 더 가까운 시각을 얻을 수 있기 때문입니다. 다만 시스템 전체에서는 MAC 쪽 PHC와 PHY 쪽 타임스탬프 경로를 혼동하지 않아야 하며, 사용자 공간(User Space)에서는 결국 SO_TIMESTAMPINGethtool -T로 노출된 능력으로 보게 됩니다.

위치장점주의점대표 점검
MAC 타임스탬프드라이버 통합이 단순선로 기준과 약간 거리 있음ethtool -T eth0
PHY 타임스탬프wire-side 기준에 더 가까움PHY와 MAC의 clock domain 정렬 필요driver timestamp capability
소프트웨어 타임스탬프지원 폭 넓음지연/지터 큼SO_TIMESTAMPING fallback
# 인터페이스의 HW timestamping / PHC 지원 확인
ethtool -T eth0
운영 기준: link flap이나 지연 변동이 먼저 보이면 EEE부터 의심하고, 고속 협상 실패가 보이면 downshift와 케이블 품질을 함께 보고, 25G 이상 광링크의 symbol/BER 문제가 보이면 FEC와 모듈 DOM을 확인하는 순서가 효율적입니다.

PHY 인터럽트, 폴링(Polling), 전원 관리(Power Management)

PHY 상태 머신은 크게 폴링인터럽트 두 방식으로 링크 변화를 감시합니다. 폴링은 단순하고 안정적이지만 변화 감지가 느리고, 인터럽트는 빠르지만 마스크/ack 처리와 공유 IRQ 문제를 정확히 구현해야 합니다. 모바일·임베디드 장치에서는 여기에 suspend/resume, Wake-on-LAN, EEE, EDPD가 더해져 "전원이 꺼져 있을 때 PHY를 어디까지 살릴 것인가"가 추가 과제가 됩니다.

주제핵심 API/개념장점위험 요소
폴링phy_start(), 상태 머신 주기 점검구현 단순, IRQ 불필요감지 지연, 불필요한 MDIO 트래픽
인터럽트handle_interrupt, phy_trigger_machine()링크 변화 즉시 반응ack 누락, IRQ storm, 공유 선 문제
suspend/resumesuspend, resume전력 절감refclk/strap 재초기화 누락
Wake-on-LANmagic packet, PHY 유지 전원저전력 대기에서 원격 기동analog rail 완전 차단 시 실패
EDPD/EEELPI, energy detect유휴 전력 절감복귀 지연, flap 오인
/* 개념 예시: IRQ 기반 PHY 링크 감지 */
static irqreturn_t myphy_handle_interrupt(struct phy_device *phydev)
{
    int irq_stat;

    irq_stat = phy_read(phydev, 0x13);
    if (irq_stat < 0)
        return IRQ_NONE;

    /* 벤더 IRQ status를 읽어 clear 한 뒤 상태 머신 재실행 */
    phy_trigger_machine(phydev);
    return IRQ_HANDLED;
}

static int myphy_suspend(struct phy_device *phydev)
{
    /* WoL, EEE, EDPD 정책에 따라 analog block 일부만 유지 */
    return genphy_suspend(phydev);
}

static int myphy_resume(struct phy_device *phydev)
{
    int ret = genphy_resume(phydev);
    if (ret)
        return ret;

    /* resume 뒤 strap override와 delay bit를 다시 써야 하는 PHY도 있다 */
    return myphy_set_rgmii_delay(phydev);
}
폴링, 인터럽트, 저전력 상태 폴링 상태 머신 주기적 read_status() PHY IRQ link change / AN done phylib state machine phy_trigger_machine() MAC 재설정 clock / speed / pause Suspend EEE / WoL / EDPD 정책 Resume strap override 재적용 링크 재훈련 AN restart / block lock
링크 상태 감시는 폴링과 IRQ 사이의 선택 문제이면서, suspend/resume 정책과도 직접 연결됩니다.

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 = <&reg_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 주소다른 주소로 부팅기대 주소에서 0xffffpull 저항과 reset 타이밍 재검토
RGMII delay 기본값내부 지연 on/off 반전1G CRC 폭증strap 상태와 runtime override 둘 다 확인
fiber / copper 선택잘못된 매체 모드링크 LED 동작이 이상함strap 조합과 보드 옵션 확인
LED 모드LED 핀 동작 혼선링크는 되는데 LED만 오작동strap 겸용 핀 배치 재검토
RGMII 지연 보상 위치 정상 예시: 지연 1곳만 사용 MAC PHY PCB 또는 PHY 한 곳만 +2ns 예: phy-mode = "rgmii-id" PCB 추가 지연은 넣지 않음 문제 예시: 이중 지연 MAC PHY PCB +2ns PHY 내부 +2ns 결과: CRC, 속도 강등, 간헐적 링크 다운 rgmii-id와 보드 지연을 동시에 쓰면 안 됨
RGMII는 TX/RX 클럭에 필요한 2ns 지연을 정확히 한 곳에서만 넣어야 합니다.
항목왜 중요한가실패 시 증상
phy-handle / PHY 주소올바른 PHY 노드와 연결probe 실패, 잘못된 PHY 드라이버 매칭
phy-modeMAC-PHY 인터페이스 계약 일치링크 불안정, CRC, 속도 강등
reset 타이밍strap pin, clock 안정화 보장PHY ID가 0xffff로 읽힘
전원 / regulator아날로그 블록 안정성 확보온도 민감, 링크 flapping
reference clockPMA/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/strapreset 폭, 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 보드 Bring-up 흐름 전원 rail reference clock reset / strap MDIO 스캔 PHY ID / 드라이버 링크 정책 트래픽 / CRC / drops iperf + ethtool -S idle / wake / EEE link flap 여부 열 / 장시간 안정성 온도와 장시간 burn-in
Bring-up은 전원과 클럭부터 시작해, MDIO 스캔과 링크 정책 검증을 거쳐 트래픽/열 안정성으로 올라가는 순서가 효율적입니다.

스위치 CPU 포트나 백플레인처럼 외부 PHY가 없이 PCS/SerDes만 존재하는 경우에는 phy-handle 대신 fixed-link를 사용하기도 합니다. 이 경우 MDIO로 읽을 PHY가 없으므로, 링크 정책과 상태 전달을 MAC/phylink 계층에서 처리해야 합니다.

/* 외부 PHY 없이 고정 링크를 선언하는 예시 */
&eth0 {
    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아날로그 프런트엔드 안정화온도 의존 링크 flapanalog/digital rail 분리와 근접 캐패시터
SFP cage 접지EMI와 모듈 안정성모듈별 편차, hot-plug 불안정cage 접지, 전력 예산, sideband pull-up
보드 레이아웃과 신호 무결성 MAC SoC 내부 PHY refclk, analog rail Magnetics 절연, common-mode RJ45 / SFP cage 외부 매체 접점 RGMII skew / SerDes pair 짧고 연속적인 return path 잡음과 ESD 경계 refclk / decoupling PHY 핀 가까이 배치 return path / cage 접지 고속 차동쌍 단절 방지
PHY 품질 문제는 드라이버 설정뿐 아니라 refclk, 차동쌍, magnetics, cage 접지처럼 보드 물리 배치와 직접 연결됩니다.
숫자에 집착하지 말 것: trace 길이와 skew 허용치는 PHY 모델, PCB 적층, 기준 클럭 구조에 따라 달라집니다. 데이터시트 권장 길이 규칙과 레퍼런스 레이아웃을 우선 기준으로 삼고, 일반론으로 얻은 숫자를 그대로 복사해 적용하지 마세요.

대표 장애 패턴과 원인 분리

PHY 문제는 반복적으로 비슷한 모양으로 나타납니다. 아래 패턴을 외워 두면 "드라이버 버그인지, 보드 타이밍인지, 상대편 정책인지"를 빨리 구분할 수 있습니다.

PHY 장애 증상에서 원인 분리 진단 흐름도 PHY 장애 진단 흐름도 PHY 문제 발생 링크가 올라오는가? No PHY ID 읽히나? No MDIO / 하드웨어 문제 PHY 주소, reset GPIO, refclk, 전원 Yes 협상 / 상대편 문제 AN 정책, advertisement, 케이블, 상대편 포트 Yes CRC/오류 증가? Yes 속도 의존적인가? 1G에서만 RGMII delay phy-mode 재확인 모든 속도 신호 / 매체 문제 케이블, magnetics, 온도, 전원 No Flapping? Yes EEE / 전원 / 열 EEE off 테스트, DOM, 온도 빠른 원인 분리 참고표 RGMII delay 1G CRC, 100M 정상 AN / 정책 링크 안 오름, ID는 정상 EEE / 열 idle 후 flap, 온도 상관 PCS 모드 LED만 반응, 데이터 없음 MDIO / HW ID 0xffff, probe 실패
장애 증상을 "링크 업 여부 → 오류 유형 → 속도 의존성" 순서로 분기하면 원인 계층을 빠르게 좁힐 수 있습니다.

패턴 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 트러블슈팅 실전 점검 순서 실전 점검 순서와 도구 매핑 1. PHY 존재 확인 mdio_bus/devices PHY ID 읽기 2. 링크 정책 확인 ethtool eth0 speed/duplex/AN 3. 물리 오류 확인 ethtool -S eth0 CRC/align/symbol 4. 보드 타이밍 phy-mode, delay reset, clock 5. 상대편 포트/케이블 교체 테스트 단계별 사용 도구와 확인 대상 dmesg probe 로그 확인 PHY 드라이버 바인딩 sysfs /sys/bus/mdio_bus/ devices 목록 readlink /sys/class/net/ eth0/phydev ethtool speed, duplex 확인 supported/advertised ethtool --show-eee EEE 상태 확인 ip monitor link 링크 이벤트 실시간 ethtool -S CRC, align, carrier symbol error 카운터 ethtool -m SFP DOM 광량 TX/RX power, 온도 --cable-test TDR 배선 진단 Device Tree 확인 phy-mode 값 reset-gpios 타이밍 dynamic debug drivers/net/phy/* +p ethtool -d MAC/PHY 레지스터 dump 비교 교체 테스트 케이블 교체 스위치 포트 변경 강제 100M 테스트로 속도 분리
트러블슈팅은 PHY 존재 확인부터 시작해 링크 정책, 물리 오류, 보드 타이밍, 상대편 순서로 좁혀 나갑니다.
증상가능한 원인즉시 확인할 것다음 조치
PHY를 전혀 찾지 못함잘못된 PHY 주소, reset 타이밍 부족, 전원 미인가dmesg, /sys/bus/mdio_bus/devicesPHY 주소와 reset GPIO, regulator 순서 점검
링크 업 안 됨상대편 포트 정책, 케이블, advertisement 불일치ethtool eth0양쪽 autoneg 정책과 케이블 등급 확인
링크 업 후 CRC 증가RGMII skew, 신호 품질, magnetics 문제ethtool -S eth0phy-mode와 보드 지연 위치 재검토
2.5G/5G가 1G로만 붙음케이블 품질, 상대편 광고 제한supported/advertised link modesCat5e/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, 케이블 테스트가장 먼저
dmesgprobe, reset, link flap, SFP 이벤트부팅 직후 실패
dynamic debugphylib 상태 머신과 벤더 드라이버 상세 로그간헐 재현, resume 이슈
ethtool -m광 모듈 DOM, 온도, RX/TX power광 링크 품질 문제
ethtool -d드라이버 제공 레지스터 덤프(Dump)벤더 드라이버 비교

실전 점검 순서

  1. PHY가 존재하는지
    /sys/bus/mdio_bus/devices와 PHY ID부터 봅니다.
  2. 링크 정책이 맞는지
    ethtool eth0에서 speed, duplex, advertisement, pause를 확인합니다.
  3. 오류가 물리 계층인지
    ethtool -S에서 CRC, align, symbol, carrier 카운터를 봅니다.
  4. 보드 타이밍 문제인지
    phy-mode, RGMII delay, reset timing, clock 안정성을 점검합니다.
  5. 상대편 의존 문제인지
    스위치 포트, 모듈, 케이블, 강제 속도 설정을 바꿔 증상이 이동하는지 확인합니다.
오판하기 쉬운 경우: 커널 로그에 드라이버 이름이 보인다고 해서 PHY 설정이 맞다는 뜻은 아닙니다. PHY 드라이버가 바인딩되더라도 RGMII delay, strap pin, EEE, SerDes mode, 모듈 전력 예산까지 모두 맞아야 실제 링크 품질이 확보됩니다.
현장 팁: 증상이 애매하면 먼저 상대편 장비를 바꾸세요. PHY 문제는 로컬 드라이버만 봐서는 절반만 보입니다. 케이블 교체, 스위치 포트 변경, 강제 1G/100M 테스트는 원인을 빠르게 반으로 줄이는 가장 싸고 빠른 방법입니다.

2.5GBASE-T / 5GBASE-T / 10GBASE-T PHY

NBASE-T(IEEE 802.3bz)는 기존 Cat5e/Cat6 배선 위에서 1G 이상 속도를 달성하기 위해 만들어진 규격입니다. 2.5GBASE-T와 5GBASE-T는 각각 Cat5e와 Cat6 케이블에서 동작하도록 설계되었으며, 10GBASE-T(802.3an)는 Cat6a 이상을 요구합니다. 이 PHY들은 기존 1000BASE-T PHY보다 훨씬 복잡한 DSP를 탑재하고 있어 전력 소비, 발열, 협상 시간이 모두 증가합니다.

규격IEEE속도최소 케이블최대 거리부호화전력 특성
2.5GBASE-T802.3bz2.5 GbpsCat5e100mPAM-16 / DSQ128~2.5W (PHY 단독)
5GBASE-T802.3bz5 GbpsCat6100mPAM-16 / DSQ128~3.5W
10GBASE-T802.3an10 GbpsCat6a100mPAM-16 + LDPC~4~7W
1000BASE-T802.3ab1 GbpsCat5e100mPAM-5~0.5W
NBASE-T 탄생 배경: 802.11ac Wave 2 무선 AP가 1G 이상 백홀이 필요해지면서, 기존 건물 배선(Cat5e/Cat6)을 뜯지 않고 속도를 높이려는 수요가 폭발했습니다. NBASE-T Alliance(현 Ethernet Alliance)가 주도해 802.3bz로 표준화되었습니다.

NBASE-T PHY 내부 구조

2.5G/5G/10G 구리 PHY는 1000BASE-T와 마찬가지로 4쌍 동시 전송을 사용하지만, 심볼 레이트와 부호화 복잡도가 크게 다릅니다. 10GBASE-T는 LDPC(Low-Density Parity-Check) FEC를 필수로 적용하고, echo cancellation과 NEXT/FEXT 제거를 위한 DSP 복잡도가 수십 배 증가합니다. 이 때문에 초기 협상에 최대 수 초가 걸릴 수 있으며, PHY 칩의 발열이 상당합니다.

NBASE-T PHY 내부 DSP 파이프라인 NBASE-T PHY 내부 DSP 파이프라인 TX 경로 (2.5G/5G/10G) MAC 데이터 XGMII/USXGMII PCS + LDPC 스크램블, FEC 인코딩 DSP + PAM-16 심볼 매핑, pre-coding 아날로그 프런트엔드 DAC, line driver (4쌍) magnetics RJ45 매체 RX 경로 (10GBASE-T 예시) 4쌍 ADC 500MHz+ 샘플링 Echo/NEXT/FEXT 제거 적응형 필터 (수백 탭) LDPC 디코딩 반복 디코딩 (수 회) PCS 디스크램블 block sync MAC 입력 XGMII 10GBASE-T는 RX에서 LDPC 반복 디코딩과 수백 탭 적응형 필터가 PHY 전력의 대부분을 차지합니다.
NBASE-T PHY는 1000BASE-T 대비 DSP 복잡도가 수십 배 높아 전력과 발열이 크게 증가합니다.

NBASE-T와 downshift 관계

2.5G/5G/10G PHY에서 downshift는 특히 중요합니다. 케이블 품질이 부족하면 10G에서 5G, 5G에서 2.5G, 2.5G에서 1G로 순차 폴백이 일어날 수 있습니다. 문제는 downshift가 자동으로 일어나면 사용자가 "왜 10G 포트인데 1G로만 연결되지?"라는 혼란을 겪는다는 점입니다. downshift 횟수와 현재 협상된 속도를 같이 확인해야 원인을 분리할 수 있습니다.

# NBASE-T 포트의 지원 속도와 현재 속도 확인
ethtool eth0 | grep -E "Speed|Supported|Advertised"

# 2.5G/5G/10G 속도 광고 제한
ethtool -s eth0 advertise 0x1000000000000   # 2.5GBASE-T만 광고

# 케이블 등급과 품질이 NBASE-T에 미치는 영향 확인
ethtool --cable-test eth0
ethtool --cable-test-tdr eth0
속도케이블 요구실패 시 downshift 대상주의 사항
10GBASE-TCat6a / Cat7 (100m)5G, 2.5G, 1GCat6은 55m까지만 공식 지원, 짧은 거리에서만 동작 가능
5GBASE-TCat6 (100m)2.5G, 1GCat5e는 일부 환경에서만 동작, 보장 없음
2.5GBASE-TCat5e (100m)1G오래된 Cat5(비 e)에서는 실패 가능
10GBASE-T PHY 발열: 10GBASE-T PHY는 4~7W의 전력을 소모하며, 적절한 방열판 없이는 열 보호(thermal shutdown)가 작동할 수 있습니다. 서버 환경에서는 NIC 자체 냉각이 충분하지만, 임베디드 보드에서 10GBASE-T를 사용할 때는 반드시 열 설계를 확인하세요.

Clause 45 레지스터 디버깅 실전

10G 이상 PHY를 디버깅할 때는 Clause 22의 32개 레지스터로는 부족하고, Clause 45의 MMD별 레지스터를 직접 읽어야 합니다. 리눅스에서는 mdio-tools 패키지나 devlink, ethtool의 레지스터 덤프 기능을 사용할 수 있습니다. 실전에서는 PCS block lock 상태, PMA signal detect, AN 상태를 체계적으로 읽어 문제를 계층별로 분리하는 것이 핵심입니다.

mdio-tools를 이용한 직접 레지스터 접근

mdio-tools는 사용자 공간에서 MDIO 버스를 통해 PHY 레지스터를 직접 읽고 쓸 수 있는 도구입니다. Clause 22와 Clause 45 모두 지원하며, 벤더 확장 레지스터 접근이나 드라이버가 노출하지 않는 상태 비트를 확인하는 데 유용합니다.

# mdio-tools 설치 (배포판에 따라 다름)
apt install mdio-tools    # Debian/Ubuntu
dnf install mdio-tools    # Fedora/RHEL

# Clause 22: PHY 주소 1의 BMCR(0x00)과 BMSR(0x01) 읽기
mdio eth0 0x00
mdio eth0 0x01

# Clause 45: PCS(MMD 3)의 status 1 레지스터 읽기
mdio eth0 mmd 3 0x0001

# Clause 45: PMA/PMD(MMD 1)의 status 1 읽기
mdio eth0 mmd 1 0x0001

# Clause 45: AN(MMD 7)의 status 읽기
mdio eth0 mmd 7 0x0001

# Clause 45: 벤더 확장(MMD 30/31) 레지스터 접근
mdio eth0 mmd 30 0x8000

# 레지스터 쓰기 (주의: 잘못 쓰면 링크 장애 발생)
mdio eth0 mmd 3 0x0000 0x2040

Clause 45 디버깅 체계적 접근

10G 이상 PHY 장애를 체계적으로 분석하려면 PMA, PCS, AN 순서로 상태를 확인합니다. 각 MMD의 핵심 비트를 읽어 어느 계층에서 문제가 시작되는지 좁히는 것이 목표입니다.

단계MMD레지스터확인 비트의미
1. PMA 링크MMD 1 (PMA/PMD)1.1 Status 1bit 2: Receive Link StatusPMA 수준에서 신호 감지 여부
2. PCS lockMMD 3 (PCS)3.1 Status 1bit 2: PCS Receive Linkblock lock과 alignment 달성 여부
3. PCS 상세MMD 3 (PCS)3.32-33BER counter, errored blocksFEC 전후 오류율
4. AN 상태MMD 7 (AN)7.1 Statusbit 5: AN Complete협상 완료 여부와 결과
5. 벤더 확장MMD 30/31벤더별온도, SNR, cable lengthPHY 내부 진단 정보
Clause 45 체계적 디버깅 흐름 Clause 45 체계적 디버깅 흐름 1. PMA Signal MMD 1, reg 1.1 신호 감지 여부 2. PCS Lock MMD 3, reg 3.1 block lock 달성 3. FEC/BER MMD 3, reg 3.32-33 오류 카운터 4. AN Status MMD 7, reg 7.1 협상 완료/결과 PMA 실패 시 케이블, 광량, magnetics, refclk 문제 PCS 실패 / BER 높음 FEC 모드, SerDes 설정, 신호 품질 AN 실패 시 advertisement 불일치, FEC 정책 차이, master/slave 충돌
Clause 45 디버깅은 PMA signal detect부터 시작해 PCS lock, BER, AN 순서로 진행하면 문제 계층을 빠르게 좁힐 수 있습니다.

최신 커널과 드라이버에서는 devlink를 통해 PHY 진단 정보를 구조화된 형태로 얻을 수 있습니다. devlink health reporter, port parameters, 드라이버별 진단 메시지를 통해 드라이버가 해석한 PHY 상태를 확인할 수 있습니다.

# devlink 포트 목록 확인
devlink port show

# devlink health reporter 목록
devlink health show

# devlink health 덤프 (드라이버 지원 필요)
devlink health dump show pci/0000:01:00.0 reporter fw

# devlink port 파라미터 조회
devlink port param show pci/0000:01:00.0/0

# ethtool 레지스터 덤프와 함께 사용
ethtool -d eth0
실전 팁: Clause 45 레지스터를 직접 읽을 때는 래치(latch) 비트에 주의하세요. PCS status나 fault 비트는 한 번 읽으면 클리어되는 경우가 있어, 첫 읽기와 두 번째 읽기의 값이 다를 수 있습니다. 디버깅 시에는 반드시 두 번 연속 읽어서 현재 상태를 확인하세요.

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");

주요 콜백 상세 설명

콜백호출 시점기본 구현커스텀 필요 상황주의점
probePHY 장치 발견 시phylib 코어특수 초기화 (firmware 로드)슬립(Sleep) 가능 컨텍스트
config_initattach 직후, resume 시genphy_config_initstrap override, delay bit, LED 모드resume에서 재호출 보장 필요
config_aneg광고 능력 변경 시genphy_config_aneg벤더 고유 AN 확장 (EEE, FEC)restart AN 포함 여부 주의
read_status상태 머신 주기 또는 IRQ 후genphy_read_status벤더 레지스터에서 추가 상태 해석phydev 필드에 결과 반영
handle_interruptIRQ 발생 시없음IRQ 지원 PHYIRQ status read-to-clear 순서
suspend/resume시스템 PMgenphy_suspend/resumeWoL, EDPD, strap 재적용analog rail 상태에 따라 분기
get_featuresPHY 능력 감지genphy_read_abilities표준 레지스터에 없는 능력 추가linkmode 비트맵(Bitmap) 정확히 설정
set_tunableethtool --set-phy-tunable없음downshift, EDPD 정책범위 검증 필수
PHY 드라이버 콜백 호출 흐름 PHY 드라이버 콜백 호출 흐름 MDIO 발견 PHY ID 매칭 probe() FW 로드 등 config_init() HW 초기 설정 config_aneg() 광고 설정 phy_start() 상태 머신 시작 read_status() 주기적 호출 또는 IRQ handle_interrupt() 링크 변화 인터럽트 suspend() / resume() 시스템 PM 진입/복귀
PHY 드라이버 콜백은 발견-초기화-협상-상태 감시-전원 관리 순서로 호출되며, resume 시 config_init 재적용이 중요합니다.

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_statusgenphy_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 레지스터 페이지 아키텍처와 커널 API 벤더 PHY 레지스터 페이지 아키텍처와 커널 API 커널 공통 API phy_select_page() / phy_restore_page() / phy_modify_paged() Realtek RTL8211F 페이지 선택: reg 0x1f (31번) Marvell 88E1510 페이지 선택: reg 22 (0x16) Microchip LAN8720A 페이지 없음 (단일 주소 공간) Page 0 (기본) IEEE 표준 레지스터 Page 0xd08 RGMII TX/RX 지연 Page 0xd04 LED 제어 Page 0xd40 EEE 제어 Page 0 (Copper) AN, 링크 상태, downshift Page 1 (Fiber) 1000BASE-X / SGMII Page 2 (MAC) RGMII 지연, MAC IF Page 18 (MISC) Copper/Fiber 선택 단일 레지스터 공간 reg 16: 특수 모드 (XTAL_EN) reg 17: EDPD 제어 reg 30: 인터럽트 소스 phy_write(phydev, 0x1f, page_no) 페이지 전환, 작업 후 0으로 복원 phy_write(phydev, 22, page_no) Marvell 표준, Copper 페이지(0)로 복원 커널 phy_select_page / phy_restore_page 래핑 구조 1. phy_select_page(phydev, page) -> .select_page 콜백 호출 (벤더별 구현) 2. 레지스터 R/W 수행 -> 3. phy_restore_page()로 이전 페이지 복원
Realtek은 레지스터 0x1f, Marvell은 레지스터 22로 페이지를 선택하며, Microchip LAN8720A는 페이지 전환 없이 단일 레지스터 공간을 사용합니다. 커널의 phy_select_page()phy_restore_page()는 벤더별 .select_page 콜백을 통해 이 차이를 추상화합니다.

EEE와 PHY 상호작용

EEE(Energy Efficient Ethernet, IEEE 802.3az)는 유휴 시 PHY를 LPI(Low Power Idle) 모드로 전환해 전력을 절감합니다. 그러나 EEE는 단순한 "켜기/끄기"가 아니라, LPI 진입 타이밍, wake 타이밍, MAC과 PHY 간의 LPI 신호 동기화가 정확히 맞아야 합니다. 이 동기화가 깨지면 유휴 후 첫 패킷의 지연이 크게 늘어나거나, 심한 경우 링크 flap이 발생합니다.

EEE 파라미터의미기본값 범위영향
tx-lpiMAC이 LPI 신호를 보내는지 여부on/offoff이면 EEE 사실상 비활성화
tx-timer유휴 후 LPI 진입까지 대기 시간(Latency)60~255 us짧으면 절전 효과 크나 지연 증가
Tw_sys_txLPI 해제 후 송신 가능까지 시간규격별 다름이 시간 동안 MAC은 전송 불가
Tw_sys_rxLPI 해제 후 수신 가능까지 시간규격별 다름PHY가 완전 깨어나야 수신 시작
EEE LPI 상태 전이와 wake 타이밍 EEE LPI 상태 전이와 Wake 타이밍 Active 정상 전력, 데이터 전송 중 Sleep (LPI 진입) tx-timer 경과 후 Quiet (저전력) 최소 전력 소모 Wake (Refresh) Tw_sys 대기 유휴 감지 LPI assert 패킷 도착 Active 복귀 데이터 전송 재개 주기적 Quiet ↔ Refresh 반복 Tw_sys 동안 MAC은 데이터 전송 불가 -- 저지연 환경에서 EEE 비활성화가 유리
EEE LPI는 Active-Sleep-Quiet-Wake 사이클을 반복하며, wake 시 Tw_sys 동안의 전송 불가 시간이 지연의 원인이 됩니다.
# EEE 상세 상태 확인
ethtool --show-eee eth0

# EEE 비활성화 (저지연 환경)
ethtool --set-eee eth0 eee off

# EEE 활성화 + tx-timer 조정
ethtool --set-eee eth0 eee on tx-timer 200

# LPI 카운터 확인 (드라이버 지원 필요)
ethtool -S eth0 | grep -i "lpi"
EEE와 링크 flap: EEE가 켜진 상태에서 링크 flap이 발생하면 먼저 EEE를 끄고 증상이 사라지는지 확인하세요. 일부 PHY-스위치 조합에서 LPI 진입/복귀 타이밍 불일치로 인한 링크 flap이 알려져 있습니다. 특히 구형 PHY와 최신 스위치, 또는 서로 다른 벤더 PHY 사이에서 발생하기 쉽습니다.

PTP와 PHY 타임스탬프 지원

PTP(IEEE 1588) 정밀 시간 동기화에서 PHY 수준 타임스탬프는 MAC 타임스탬프보다 매체에 더 가까운 위치에서 시각을 찍을 수 있어 정밀도가 높습니다. 일부 PHY(예: Marvell Alaska, Microchip LAN8814, TI DP83869)는 내부에 PHC(PTP Hardware Clock)를 탑재하고 있어, 패킷이 PHY를 통과하는 시점에 타임스탬프를 기록합니다.

PTP 타임스탬프 삽입 위치와 정밀도 비교 PTP 타임스탬프 삽입 위치와 정밀도 애플리케이션 PTP 데몬 커널 스택 소켓 계층 MAC DMA + 프레임 PHY PCS + PMA 매체 (Wire) 구리 / 광 SW 타임스탬프 정밀도: ~1µs, 지터 큼 SO_TIMESTAMPING 폴백 MAC HW 타임스탬프 정밀도: ~10ns i210/i225, stmmac 등 PHY HW 타임스탬프 정밀도: ~ns 수준 LAN8814, DP83869 등 정밀도 비교 (매체에 가까울수록 정밀) SW (~1µs) MAC (~10ns) PHY (~ns, wire에 가장 가까움) 애플리케이션 쪽 매체 쪽 (정밀도 높음)
타임스탬프 삽입 지점이 매체에 가까울수록 정밀도가 높아지지만, PHY 내부 PHC와 시스템 클럭 사이 동기화 복잡도도 증가합니다.
타임스탬프 위치정밀도장점단점대표 PHY
소프트웨어~us 수준모든 인터페이스에서 사용 가능커널 스케줄링 지터 포함해당 없음
MAC 하드웨어~10ns드라이버 통합이 비교적 단순MAC-PHY 사이 지연 포함Intel i210/i225
PHY 하드웨어~ns 수준매체에 가장 가까운 시점PHY와 MAC 시계 동기화 필요LAN8814, DP83869
# 인터페이스의 타임스탬프 능력 확인
ethtool -T eth0

# PHC 장치 목록 확인
ls /dev/ptp*

# PHC 정보 확인
ethtool -T eth0 | grep -i "PTP"

# hwstamp_config 설정 (PTP 데몬이 자동으로 설정하기도 함)
hwstamp_ctl -i eth0 -r 1 -t 1
/* PHY 드라이버에서 PTP 지원 등록 (개념 예시) */
static const struct ptp_clock_info myphy_ptp_caps = {
    .owner       = THIS_MODULE,
    .name        = "myphy_ptp",
    .max_adj     = 50000000,
    .n_alarm     = 0,
    .n_ext_ts    = 0,
    .n_per_out   = 0,
    .adjfine     = myphy_ptp_adjfine,
    .adjtime     = myphy_ptp_adjtime,
    .gettime64   = myphy_ptp_gettime,
    .settime64   = myphy_ptp_settime,
};

/* mii_timestamper 등록으로 PHY 타임스탬프를 네트워크 스택에 연결 */
static const struct mii_timestamper myphy_mii_ts = {
    .rxtstamp = myphy_rxtstamp,
    .txtstamp = myphy_txtstamp,
    .hwtstamp = myphy_hwtstamp,
};
PHY vs MAC 타임스탬프 선택: PHY 타임스탬프가 항상 우월한 것은 아닙니다. PHY 내부 PHC와 시스템 클럭 사이의 동기화가 추가되고, 드라이버 복잡도가 높아집니다. TSN/산업용 환경에서 sub-microsecond 정밀도가 필요한 경우에만 PHY 타임스탬프가 의미 있고, 일반 NTP 수준이면 MAC 타임스탬프로 충분합니다.

Temperature 센서와 PHY 진단 (TDR)

현대 PHY 칩은 내부에 온도 센서를 탑재하고 있어, 런타임에 PHY 다이 온도를 모니터링할 수 있습니다. 커널 6.x부터는 hwmon 서브시스템과 통합되어 sensors 명령이나 sysfs를 통해 PHY 온도를 읽을 수 있는 드라이버가 늘고 있습니다. 또한 TDR(Time Domain Reflectometry) 기반 케이블 테스트는 PHY 내부 DSP를 이용해 케이블 길이, 단선/단락 위치, 쌍별 상태를 진단합니다.

진단 기능PHY 지원 필요커널 API사용자 공간 도구주요 용도
온도 센서벤더 레지스터hwmon 또는 phy_driver 직접sensors, sysfs열 보호, 환경 모니터링
Cable Test (기본)TDR 하드웨어cable_test ethtool opethtool --cable-testopen/short/ok 상태
Cable Test TDRTDR DSPcable_test_tdr ethtool opethtool --cable-test-tdr거리별 반사 파형, 정밀 진단
SNR벤더 레지스터벤더 드라이버벤더 도구 또는 직접 레지스터신호 품질 정량 측정
# PHY 온도 확인 (hwmon 통합 드라이버, 예: Marvell Alaska)
sensors | grep -A2 "phy"
cat /sys/class/hwmon/hwmon*/temp1_input    # 밀리도 단위

# 케이블 테스트 (기본) - 각 쌍의 open/short/ok 상태
ethtool --cable-test eth0

# TDR 케이블 테스트 - 거리별 반사 파형
ethtool --cable-test-tdr eth0

# 벤더 레지스터에서 온도 직접 읽기 (예: Realtek RTL8211F)
mdio eth0 mmd 31 0xa43  # 페이지 전환 후
mdio eth0 0x1a            # 온도 레지스터
/* PHY 드라이버에서 cable test 지원 (개념 예시) */
static int myphy_cable_test_start(struct phy_device *phydev)
{
    /* PHY에게 TDR 테스트 시작 명령 */
    return phy_write(phydev, MYPHY_REG_CABLE_TEST, 0x8000);
}

static int myphy_cable_test_get_status(struct phy_device *phydev,
                                       bool *finished)
{
    int val = phy_read(phydev, MYPHY_REG_CABLE_RESULT);

    if (val < 0)
        return val;

    *finished = !!(val & 0x8000);

    if (*finished) {
        /* 각 쌍 결과를 ethnl_cable_test_result()로 보고 */
        ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A,
                               (val & 0x03) == 0 ?
                                   ETHTOOL_A_CABLE_RESULT_CODE_OK :
                                   ETHTOOL_A_CABLE_RESULT_CODE_OPEN);
    }
    return 0;
}

static struct phy_driver myphy_drivers[] = {
    {
        /* ... 기존 콜백 ... */
        .cable_test_start      = myphy_cable_test_start,
        .cable_test_get_status = myphy_cable_test_get_status,
    },
};
TDR 결과 해석: TDR 결과에서 "open"은 케이블이 끊어졌거나 미연결, "short"는 쌍 내부 단락, "impedance mismatch"는 중간 커넥터나 접점 불량을 의미합니다. 거리 정보는 PHY DSP의 전파 속도 추정에 기반하므로 ±10% 오차가 있을 수 있습니다.
TDR 반사 파형 패턴과 케이블 진단 결과 해석 TDR 반사 파형 패턴과 진단 결과 정상 (OK) 진폭 반사 없음 → 종단 정상 Open (단선) 진폭 양(+) 반사 → 단선 또는 미연결 거리 Short (단락) 진폭 음(-) 반사 → 쌍 내부 단락 Cat5e/6 케이블 쌍별 TDR 진단 구조 PHY TDR DSP 펄스 송신 반사 분석 거리 추정 (전파속도 기반) Pair A (1,2) — OK Pair B (3,6) — Open @ 42m Pair C (4,5) — OK Pair D (7,8) — OK 원격 PHY 결과 해석 가이드 OK 정상 종단, 반사 없음 Open 단선/미연결, 거리 표시 Short 단락, 거리 표시 Mismatch 중간 커넥터/접점 불량
TDR은 펄스를 보내고 반사 파형의 부호와 지연 시간으로 단선(양의 반사), 단락(음의 반사), 임피던스 불일치를 쌍별로 진단합니다.

Copper/Fiber 전환 미디어 컨버터 처리

일부 PHY는 copper(RJ45)와 fiber(SFP) 인터페이스를 모두 지원하는 combo(dual-media) 포트를 제공합니다. 이런 PHY는 strap pin이나 소프트웨어 레지스터로 활성 매체를 선택하며, 일부는 자동 매체 감지(Auto Media Detect)를 지원해 케이블이 꽂힌 쪽을 자동으로 선택합니다.

Combo PHY 내부 경로와 매체 선택 MUX 구조 Combo PHY 내부 경로와 매체 선택 MAC RGMII / SGMII Combo PHY (Dual-Media) PCS/PMA 공통 디지털 MUX 매체 선택 레지스터 Copper PHY 10/100/1000BASE-T Fiber SerDes 1000BASE-X / SGMII Auto Media Detect copper 우선 / fiber 우선 설정 RJ45 Magnetics Cat5e/6 케이블 SFP Cage 광 / Copper SFP MOD_ABS, LOS 외부 미디어 컨버터 대안 Copper PHY 미디어 컨버터 Fiber ← 추가 지연·전원·장애점
Combo PHY는 내부 MUX로 copper/fiber 경로를 전환하며, 외부 미디어 컨버터보다 지연과 장애점이 적습니다.
방식동작장점주의점
수동 선택strap pin 또는 레지스터로 고정예측 가능, 단순양쪽 동시 연결 시 혼란
Auto Media DetectPHY가 copper/fiber 중 활성 매체 자동 선택운영 편의전환 지연, 우선순위(Priority) 정책 필요
외부 미디어 컨버터별도 장치가 copper와 fiber 사이를 변환기존 장비 재사용추가 지연, 전원, 장애점 증가
/* Combo PHY: copper와 fiber를 함께 지원하는 Device Tree 예시 */
&mdio {
    combo_phy: ethernet-phy@1 {
        reg = <1>;
        /* 벤더 고유 속성으로 매체 선택 */
        marvell,preferred-medium = "copper";
        sfp = <&sfp0>;
    };
};
/* 개념 예시: combo PHY 드라이버에서 매체 전환 처리 */
static int combo_phy_config_init(struct phy_device *phydev)
{
    int val;
    bool prefer_fiber = of_property_read_bool(
        phydev->mdio.dev.of_node, "prefer-fiber");

    /* 매체 선택 레지스터 설정 */
    val = prefer_fiber ? 0x0002 : 0x0001;
    return phy_modify_paged(phydev, 0x12, 0x14, 0x0003, val);
}
미디어 컨버터 함정: 외부 미디어 컨버터를 통해 copper와 fiber를 연결할 때는 양단의 속도와 duplex가 모두 일치하는지 확인하세요. 컨버터가 autoneg을 제대로 전달하지 못하면 한쪽은 full-duplex, 다른 쪽은 half-duplex로 동작해 late collision과 처리량 저하가 발생합니다.

LED 제어와 PHY LED 트리거

PHY의 LED 핀은 링크 상태, 활동(activity), 속도, 전이중(full-duplex) 같은 네트워크 상태를 표시합니다. 전통적으로는 PHY 내부 레지스터로 LED 동작을 설정했지만, 커널 6.x부터는 netdev LED 트리거 프레임워크가 도입되어 PHY LED를 커널 LED 서브시스템에 통합할 수 있습니다. 이를 통해 사용자 공간에서 /sys/class/leds/를 통해 PHY LED 동작을 제어할 수 있습니다.

LED 모드의미전통적 설정커널 LED 트리거
Link링크 상태 표시PHY 레지스터 directnetdev trigger: link
ActivityTX/RX 트래픽 표시PHY 레지스터 directnetdev trigger: tx rx
Speed속도별 다른 색상/깜빡임PHY 레지스터 직접 설정커널 6.5+ 속도별 트리거
Force On/Off강제 켜기/끄기PHY 레지스터 overridesysfs brightness
# PHY LED를 커널 LED 서브시스템으로 확인 (드라이버 지원 시)
ls /sys/class/leds/ | grep "phy"

# LED 트리거 목록 확인
cat /sys/class/leds/eth0-led0/trigger

# netdev LED 트리거 설정: 링크 + 활동
echo "netdev" > /sys/class/leds/eth0-led0/trigger
echo "1" > /sys/class/leds/eth0-led0/link
echo "1" > /sys/class/leds/eth0-led0/tx
echo "1" > /sys/class/leds/eth0-led0/rx

# LED 강제 켜기/끄기
echo "255" > /sys/class/leds/eth0-led0/brightness
echo "0" > /sys/class/leds/eth0-led0/brightness
/* PHY 드라이버에서 LED 하드웨어 제어 콜백 등록 (커널 6.x) */
static int myphy_led_hw_is_supported(struct phy_device *phydev,
                                     u8 index, unsigned long rules)
{
    /* LED index와 규칙(link, activity, speed 등)이 HW에서 지원 가능한지 */
    if (index > 1)
        return -EINVAL;
    return 0;
}

static int myphy_led_hw_control_set(struct phy_device *phydev,
                                    u8 index, unsigned long rules)
{
    u16 val = 0;

    if (rules & BIT(TRIGGER_NETDEV_LINK))
        val |= 0x01;
    if (rules & BIT(TRIGGER_NETDEV_TX))
        val |= 0x02;
    if (rules & BIT(TRIGGER_NETDEV_RX))
        val |= 0x04;

    return phy_modify_paged(phydev, 0xd04,
                            MYPHY_REG_LED_CTRL + index,
                            0x07, val);
}

static struct phy_driver myphy_drivers[] = {
    {
        /* ... 기존 콜백 ... */
        .led_hw_is_supported = myphy_led_hw_is_supported,
        .led_hw_control_set  = myphy_led_hw_control_set,
        .led_hw_control_get  = myphy_led_hw_control_get,
        .led_brightness_set  = myphy_led_brightness_set,
    },
};
PHY LED 제어 경로: 전통 vs 커널 LED 서브시스템 PHY LED 제어 경로 전통적 방식 (PHY 레지스터 직접) PHY 드라이버 LED 레지스터 PHY 드라이버 커널 LED 서브시스템 (6.x+) sysfs (사용자) LED 코어 netdev trigger PHY LED ops HW offload 가능 시 PHY가 직접 제어, 불가 시 SW 폴링
커널 6.x의 netdev LED 트리거는 PHY LED를 사용자 공간에서 제어 가능하게 만들며, HW offload를 지원하면 PHY가 직접 LED를 구동합니다.

phylib과 phylink는 커널 버전이 올라갈 때마다 기능이 확장되고 API가 변경됩니다. 드라이버를 작성하거나 기존 드라이버를 다른 커널 버전으로 포팅할 때는 이 변경 이력을 참고해야 합니다.

커널 버전phylib/phylink 주요 변경영향
4.20phylink 프레임워크 병합SFP, in-band status, fixed-link 통합 관리 시작
5.2phy_deviceis_gigabit_capable 추가1G 이상 PHY 감지 간소화
5.5phylink_pcs 인터페이스 도입host PCS를 별도 객체로 분리, MAC 드라이버 구조 변경
5.9phy_modify_mmd(), C45 헬퍼 강화Clause 45 레지스터 접근 편의성 향상
5.14EEE 설정 API 재구조화phy_init_eee() 폐지 방향, phylink EEE 통합
5.17cable_test / cable_test_tdr netlink 인터페이스사용자 공간 케이블 진단 표준화
5.19SFP quirks 프레임워크비표준 SFP 모듈 대응 개선
6.1PHY LED 프레임워크 (led_hw_control_* 콜백)PHY LED를 커널 LED 서브시스템에 통합
6.3phylink_pcs_neg_mode() 도입PCS AN 모드 선택 명확화
6.5linkmode 2.5GBASE-T, 5GBASE-T 비트 추가NBASE-T PHY 속도 광고 표준화
6.6EEE 리워크 (struct eee_config)EEE 설정이 phylink/ethtool에서 통합 관리
6.8phy_package 인프라 강화공유 PHY 패키지 관리 개선
6.9Rate Matching 지원 (phy_rate_matching())MAC과 PHY 사이 속도 불일치 처리
6.11PHY LED netdev 속도별 트리거1G/100M/10M 속도별 LED 색상/패턴 분리
포팅 팁: phylib API를 다른 커널 버전으로 포팅할 때는 phy_driver 구조체의 콜백 시그니처 변경에 특히 주의하세요. 예를 들어 read_statusconfig_aneg의 반환값 의미, phylink_mac_ops의 콜백 목록, pcs_ops의 멤버 변경이 빈번합니다. 커널 drivers/net/phy/ 디렉터리의 Kconfig와 기존 벤더 드라이버 변경 이력을 함께 참고하세요.

PHY 패키지와 공유 PHY (Shared MDIO Bus)

다수의 PHY 벤더는 4포트, 8포트 PHY를 하나의 패키지(single IC)로 제공합니다. 이런 PHY 패키지는 MDIO 버스를 공유하고, 일부 레지스터(global configuration, LED 정책, 온도 센서)는 패키지 전체에 공통으로 적용됩니다. 리눅스 커널은 phy_package 인프라를 통해 이러한 공유 PHY를 관리합니다.

항목개별 PHY 기준PHY 패키지 기준주의점
초기화각 PHY 개별 config_init패키지 글로벌 레지스터는 한 번만init 순서와 경합(Contention) 방지
온도 센서각 PHY마다 별도패키지 공통 1개 (또는 가장 뜨거운 다이)hwmon 등록 중복 방지
LED 정책포트별 독립글로벌 LED 모드 + 포트별 override글로벌 설정이 포트별 설정을 덮어쓸 수 있음
reset포트별 가능패키지 reset은 모든 포트에 영향하나의 포트만 재설정하려면 soft reset 사용
MDIO 주소개별 주소연속 주소 (base + offset)base 주소는 strap pin으로 결정
/* 개념 예시: PHY 패키지 공유 레지스터 접근 */
static int myphy_package_config_init(struct phy_device *phydev)
{
    int ret;

    /* 패키지 내 첫 번째 PHY에서만 글로벌 초기화 수행 */
    ret = phy_package_init_once(phydev);
    if (ret == 0) {
        /* 이 PHY가 첫 번째: 글로벌 레지스터 설정 */
        phy_package_write(phydev, MYPHY_GLOBAL_LED_REG, 0x1234);
        phy_package_write(phydev, MYPHY_GLOBAL_TEMP_REG, 0x0001);
    }

    /* 포트별 개별 설정 */
    return myphy_port_config_init(phydev);
}

/* 패키지 내 PHY 사이에서 공유 잠금 사용 */
static int myphy_shared_reg_read(struct phy_device *phydev,
                                  u16 reg)
{
    int val;

    phy_lock_mdio_bus(phydev);
    val = __phy_package_read(phydev, reg);
    phy_unlock_mdio_bus(phydev);
    return val;
}
/* 4포트 PHY 패키지의 Device Tree 예시 */
&mdio {
    ethernet-phy-package@8 {
        #address-cells = <1>;
        #size-cells = <0>;
        reg = <8>;

        phy0: ethernet-phy@8 { reg = <8>; };
        phy1: ethernet-phy@9 { reg = <9>; };
        phy2: ethernet-phy@a { reg = <0xa>; };
        phy3: ethernet-phy@b { reg = <0xb>; };
    };
};
4포트 PHY 패키지와 공유 MDIO 구조 4포트 PHY 패키지와 공유 MDIO MDIO 컨트롤러 mii_bus MDC/MDIO PHY 패키지 (4포트 IC) 글로벌 레지스터 PHY @8 PHY @9 PHY @A PHY @B 공유 자원: LED 글로벌 모드, 온도 센서, 패키지 reset phy_package_init_once()로 첫 PHY에서만 초기화 RJ45 포트 Port 0 (PHY @8) Port 1 (PHY @9) Port 2 (PHY @A) Port 3 (PHY @B)
4포트 PHY 패키지는 글로벌 레지스터를 공유하며, 커널의 phy_package 인프라가 초기화 순서와 공유 자원 접근을 관리합니다.
PHY 패키지 reset 주의: PHY 패키지의 하드웨어 reset은 모든 포트에 동시 영향을 줍니다. 하나의 포트만 재초기화하려면 소프트웨어 reset(BMCR bit 15)을 사용하세요. 또한 strap pin은 패키지 전체에 적용되므로, 하나의 포트의 strap을 바꾸면 다른 포트에도 영향이 갈 수 있습니다.

Rate Matching과 MAC-PHY 속도 불일치 처리

Rate Matching(속도 정합)은 PHY가 원격 링크 파트너와 협상한 속도와 MAC-PHY 사이 SerDes 링크 속도가 다를 때 그 차이를 흡수하는 메커니즘입니다. 예를 들어 SGMII나 QSGMII 인터페이스에서 SerDes는 항상 1.25 Gbps(1000BASE-X 기준)로 고정 동작하는데, PHY가 원격 파트너와 100 Mbps Auto-Negotiation에 성공하면 속도 차이가 4배에 달합니다. 이 때 Rate Matching 없이 그냥 데이터를 보내면 MAC과 PHY 사이의 흐름 제어가 무너집니다.

Rate Matching이 필요한 대표적인 인터페이스는 SGMII, QSGMII, USXGMII partial 등입니다. 이 인터페이스들은 SerDes 속도를 링크 협상 결과에 따라 바꿀 수 없거나, 바꾸려면 재초기화가 필요하기 때문에 PHY나 중간 PCS 계층에서 속도 차이를 메워야 합니다. 반면 RGMII 같은 병렬 인터페이스는 클럭 자체가 속도에 따라 달라지므로 Rate Matching 개념이 적용되지 않습니다.

Rate Matching 세 가지 모드

리눅스 커널은 enum phy_rate_matching으로 세 가지 Rate Matching 모드를 정의합니다.

모드상수동작 방식장점단점
Pause 기반 PHY_RATE_MATCH_PAUSE PHY가 SerDes와 실제 링크 속도 차이만큼 PAUSE 프레임을 주기적으로 생성해 MAC쪽 흐름을 제어 구현 단순, 추가 버퍼 불필요 PAUSE 프레임이 대역폭을 소비, 레이턴시 증가 가능
CRS 기반 PHY_RATE_MATCH_CRS PHY가 Carrier Sense 신호를 반 듀플렉스(half-duplex) 방식으로 조작해 MAC 송신을 자연스럽게 억제 PAUSE 프레임 오버헤드 없음 반 듀플렉스 환경에서만 적용, 전이중(full-duplex)에는 부적합
MAC측 적응 PHY_RATE_MATCH_NONE(+MAC 지원) MAC 드라이버가 PHY 링크 속도에 따라 SerDes 클럭을 동적으로 변경 가장 깔끔한 해결책 MAC 하드웨어가 SerDes 재설정을 지원해야 함

커널 API

PHY 드라이버는 phy_driver.get_rate_matching 콜백을 구현해 자신이 지원하는 Rate Matching 모드를 알려 줍니다. phylink는 링크 협상 결과와 SerDes 모드를 보고 적절한 모드를 선택합니다.

/* include/linux/phy.h */
enum phy_rate_matching {
    PHY_RATE_MATCH_NONE  = 0, /* Rate Matching 미지원 또는 불필요 */
    PHY_RATE_MATCH_PAUSE = 1, /* PAUSE 프레임으로 속도 불일치 흡수 */
    PHY_RATE_MATCH_CRS   = 2, /* CRS 조작으로 반이중 억제 */
};

/* PHY 드라이버가 구현하는 콜백 */
int (*get_rate_matching)(struct phy_device *phydev,
                         phy_interface_t iface);

phylink 내부에서는 phylink_get_link_timer_ns() 흐름과 연동되어, Rate Matching이 필요한 구간에서 자동으로 PAUSE 협상 활성화나 CRS 모드 전환이 이루어집니다.

MAC SerDes 고정속도 1 Gbps SGMII / QSGMII PHY (Rate Matching) SerDes측: 1 Gbps 수신 ▼ 속도 차이 흡수 PAUSE / CRS 링크측: 100 Mbps 출력 x10 속도 불일치 흡수 원격 장비 협상 완료 100 Mbps 100BASE-TX 1 Gbps 100 Mbps PAUSE / CRS 피드백
PHY의 Rate Matching은 MAC 측 SerDes 고정 속도(1 Gbps)와 원격 협상 속도(100 Mbps) 사이의 차이를 PAUSE 프레임 또는 CRS 조작으로 흡수합니다.

PHY 드라이버 구현 예시

/* PHY 드라이버가 get_rate_matching 콜백을 구현하는 예시 */
static int example_get_rate_matching(struct phy_device *phydev,
                                     phy_interface_t iface)
{
    /* SGMII 또는 QSGMII 인터페이스에서만 Rate Matching 지원 */
    if (iface == PHY_INTERFACE_MODE_SGMII ||
        iface == PHY_INTERFACE_MODE_QSGMII) {
        /*
         * 이 PHY는 PAUSE 기반 Rate Matching을 지원합니다.
         * phylink는 이 정보를 바탕으로 PAUSE 협상을 활성화합니다.
         */
        return PHY_RATE_MATCH_PAUSE;
    }

    /* 그 외 인터페이스(RGMII 등)는 Rate Matching 불필요 */
    return PHY_RATE_MATCH_NONE;
}

static struct phy_driver example_phy_driver = {
    .phy_id       = 0x001CC916,
    .phy_id_mask  = 0x001FFFFF,
    .name         = "Example PHY",
    .features     = PHY_GBIT_FEATURES,
    .get_rate_matching = example_get_rate_matching,
    /* ... */
};
Rate Matching이 필요한 경우:
  • SGMII/QSGMII SerDes를 사용하는 SoC에서 10/100 Mbps 장비와 연결할 때
  • SerDes 재초기화 없이 속도 변경이 불가능한 하드웨어 구조일 때
Rate Matching이 불필요한 경우:
  • RGMII처럼 클럭 속도가 링크 속도에 따라 변하는 인터페이스
  • MAC이 SerDes 클럭을 동적으로 재설정 가능한 경우 (PHY_RATE_MATCH_NONE + MAC 측 처리)
  • Gigabit 전용 링크여서 속도 불일치가 발생하지 않는 환경

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 업데이트 필요

펌웨어 로딩 문제 해결

펌웨어 관련 문제는 크게 세 가지 유형으로 나뉩니다.

펌웨어 라이선스와 linux-firmware: PHY 펌웨어 파일은 대부분 독점(Proprietary) 라이선스를 가집니다. 커널 트리에 직접 포함할 수 없으며, linux-firmware 저장소(git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git)를 통해 별도 배포됩니다. 배포판(Distro)에서는 linux-firmware 패키지로 제공되며, 임베디드 시스템에서는 rootfs 또는 initramfs에 펌웨어 파일을 직접 포함시켜야 합니다. 펌웨어 파일의 재배포 가능 여부는 각 칩 제조사의 EULA를 반드시 확인하시기 바랍니다.

Wake-on-LAN PHY 수준 상세 구현

Wake-on-LAN(WoL)은 시스템이 S3(Suspend-to-RAM) 또는 S5(Soft-off) 상태일 때 네트워크에서 오는 특정 패킷을 PHY가 감지해 시스템을 깨우는 기능입니다. PHY 수준에서는 완전히 전원을 차단하는 대신 아날로그 수신(Rx) 회로만 켜 두고, 디지털 처리 부분과 송신(Tx) 회로는 최소 전력 상태로 유지합니다. Magic Packet을 감지하면 PME(Power Management Event) 핀 또는 인터럽트를 통해 SoC나 PCH에 웨이크업 신호를 보냅니다.

Magic Packet 구조

Magic Packet은 동기화 스트림(6바이트의 0xFF)과 그 뒤에 오는 대상 MAC 주소의 16회 반복으로 구성됩니다. 총 102바이트(6+6×16=102)의 페이로드가 UDP 또는 Raw Ethernet 프레임에 담겨 전송됩니다.

/* Magic Packet 구조 (총 102 bytes payload) */

/* 1단계: 동기화 스트림 (6 bytes) */
FF FF FF FF FF FF

/* 2단계: 대상 MAC 주소 16회 반복 (6 bytes x 16 = 96 bytes) */
/* 대상 MAC이 AA:BB:CC:DD:EE:FF 라면: */
AA BB CC DD EE FF  /* 1번째 */
AA BB CC DD EE FF  /* 2번째 */
...
AA BB CC DD EE FF  /* 16번째 */

/* 선택적: 4~6 bytes 패스워드 (SecureOn) */
/* PHY 하드웨어가 패스워드 검증을 지원하는 경우에만 사용 */

WoL 중 PHY 전원 도메인

WoL 동작 중 PHY 내부의 전원 도메인은 다음과 같이 분리 관리됩니다.

전원 도메인WoL 대기 상태이유
아날로그 Rx (PMA) 활성(Active) 케이블에서 들어오는 신호를 계속 수신해야 함
클럭 복구(CDR) 활성 수신 데이터의 비트 동기를 유지해야 함
MAC 패턴 매처 활성 Magic Packet 패턴 비교 엔진 동작
아날로그 Tx (PMA) 비활성(Off) WoL 대기 중 송신 불필요, 전력 절약
디지털 MAC 인터페이스 비활성 MAC(SoC)이 절전 상태이므로 RGMII/SGMII 클럭 없음
MDIO 슬레이브 최소 활성 일부 칩은 WoL 상태 확인을 위해 MDIO 응답 유지
케이블 Magic Packet 수신 아날로그 Rx (PMA 활성) 신호 비트 복원 POWERED CDR 활성 패턴 매처 (PHY 내부) FF x6 + MAC x16 매치 여부 판단 ACTIVE PME# 핀 웨이크업 신호 Active Low SoC / PCH S3/S5 절전 상태 PME 감지 후 시스템 웨이크업 ACPI wakeup 매치! 아날로그 Tx 비활성 (전력 절약)
WoL 대기 중 PHY는 아날로그 Rx와 패턴 매처만 활성화하고, Tx와 디지털 MAC 인터페이스는 비활성 상태를 유지합니다. Magic Packet 감지 시 PME# 핀으로 시스템을 깨웁니다.

PHY 드라이버 WoL 콜백 구현

#include <linux/ethtool.h>
#include <linux/phy.h>

/* WoL 활성화/비활성화 콜백 */
static int example_set_wol(struct phy_device *phydev,
                            struct ethtool_wolinfo *wol)
{
    int ret;

    if (wol->wolopts & ~(WAKE_MAGIC | WAKE_PHY)) {
        /* 이 PHY가 지원하지 않는 WoL 옵션 */
        return -EOPNOTSUPP;
    }

    if (wol->wolopts & WAKE_MAGIC) {
        /*
         * 1단계: PHY에 자신의 MAC 주소를 프로그래밍합니다.
         *         PHY는 이 주소를 기준으로 Magic Packet을 필터링합니다.
         */
        ret = phy_write_mmd(phydev, MDIO_MMD_VEND1,
                            EXAMPLE_WOL_MAC_HI,
                            (wol->sopass[0] << 8) | wol->sopass[1]);
        if (ret)
            return ret;

        ret = phy_write_mmd(phydev, MDIO_MMD_VEND1,
                            EXAMPLE_WOL_MAC_MID,
                            (wol->sopass[2] << 8) | wol->sopass[3]);
        if (ret)
            return ret;

        ret = phy_write_mmd(phydev, MDIO_MMD_VEND1,
                            EXAMPLE_WOL_MAC_LO,
                            (wol->sopass[4] << 8) | wol->sopass[5]);
        if (ret)
            return ret;

        /* 2단계: WoL 기능 활성화 */
        ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
                               EXAMPLE_WOL_CTRL,
                               EXAMPLE_WOL_MAGIC_EN | EXAMPLE_WOL_PME_EN);
        if (ret)
            return ret;

        /* 3단계: PHY를 WoL 모드로 설정 (저전력 + Rx 유지) */
        ret = phy_set_bits(phydev, MII_BMCR, BMCR_PDOWN);
        /* 주의: Rx 회로는 유지하는 칩별 설정이 필요합니다 */

    } else {
        /* WoL 비활성화: 정상 모드 복귀 */
        ret = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
                                 EXAMPLE_WOL_CTRL,
                                 EXAMPLE_WOL_MAGIC_EN | EXAMPLE_WOL_PME_EN);
    }

    return ret;
}

/* 현재 WoL 설정 조회 콜백 */
static void example_get_wol(struct phy_device *phydev,
                             struct ethtool_wolinfo *wol)
{
    int val;

    /* 이 PHY가 지원하는 WoL 옵션 목록 */
    wol->supported = WAKE_MAGIC | WAKE_PHY;
    wol->wolopts   = 0;

    val = phy_read_mmd(phydev, MDIO_MMD_VEND1, EXAMPLE_WOL_CTRL);
    if (val < 0)
        return;

    if (val & EXAMPLE_WOL_MAGIC_EN)
        wol->wolopts |= WAKE_MAGIC;
}

static struct phy_driver example_phy_driver = {
    /* ... */
    .set_wol = example_set_wol,
    .get_wol = example_get_wol,
};

커널 ethtool WoL API

phylib는 상위 ethtool 인터페이스와 PHY 드라이버의 set_wol/get_wol 콜백을 연결하는 래퍼(Wrapper) 함수를 제공합니다.

/* include/linux/phy.h */

/* ethtool_ops.get_wol에서 호출 */
int phy_ethtool_get_wol(struct phy_device *phydev,
                        struct ethtool_wolinfo *wol);

/* ethtool_ops.set_wol에서 호출 */
int phy_ethtool_set_wol(struct phy_device *phydev,
                        struct ethtool_wolinfo *wol);

/* MAC 드라이버의 ethtool_ops 연결 예시 */
static const struct ethtool_ops example_mac_ethtool_ops = {
    .get_wol = phy_ethtool_get_wol,
    .set_wol = phy_ethtool_set_wol,
    /* ... */
};

Device Tree wakeup-source 속성

&mdio0 {
    phy0: ethernet-phy@0 {
        compatible = "ethernet-phy-id001c.c916";
        reg = <0>;
        /*
         * 이 노드가 시스템 wakeup source임을 선언합니다.
         * ACPI가 없는 임베디드 시스템에서 사용합니다.
         * device_init_wakeup()이 자동으로 호출됩니다.
         */
        wakeup-source;
        /*
         * WoL을 위한 인터럽트 핀 연결.
         * PME# 핀이 GPIO에 연결된 경우 필요합니다.
         */
        interrupts-extended = <&gpio1 5 IRQ_TYPE_LEVEL_LOW>;
    };
};

WoL 모드 비교

모드ethtool 상수동작PHY 하드웨어 지원
Magic Packet WAKE_MAGIC FF x6 + MAC x16 패턴 감지 대부분 지원 (가장 일반적)
PHY 링크 변화 WAKE_PHY 링크 업/다운 이벤트 자체로 웨이크업 대부분 지원
유니캐스트 WAKE_UCAST 자신의 MAC 주소로 오는 임의 프레임 일부 고급 PHY
멀티캐스트 WAKE_MCAST 멀티캐스트 주소 수신 시 웨이크업 드물게 지원
브로드캐스트 WAKE_BCAST 브로드캐스트 프레임 수신 시 웨이크업 일부 PHY
ARP WAKE_ARP ARP 요청 수신 시 웨이크업 드물게 지원, MAC에서 처리하는 경우도 있음
Magic Packet + 패스워드 WAKE_MAGICSECURE Magic Packet에 SecureOn 패스워드 추가 검증 일부 PHY (sopass 필드 활용)

일반적인 WoL 문제와 해결책

WoL 하드웨어 요구 사항:
  • PHY 전원 유지: suspend 중 PHY에 최소 전원이 공급되어야 합니다. 보드 설계 시 절전 상태에서도 PHY 전원 레일이 유지되도록 PowerSEQ나 PMIC 설정을 확인하세요.
  • PME# 라우팅: PHY PME# 신호선이 SoC 웨이크업 핀 또는 PCIe PME# 핀에 보드 레벨에서 물리적으로 연결되어 있어야 합니다. 이 연결이 없으면 소프트웨어로는 해결할 수 없습니다.
  • 케이블 연결 유지: WoL은 링크가 맺힌 상태에서만 동작합니다. 케이블이 뽑혀 있으면 Magic Packet 자체가 도달하지 않습니다. 일부 PHY는 WAKE_PHY 모드로 링크 복구 후 웨이크업을 지원하기도 합니다.
  • 스위치/라우터 지원: 네트워크 스위치나 라우터가 Magic Packet을 올바른 포트로 포워딩해야 합니다. VLAN 환경에서는 Tagged/Untagged 설정도 고려해야 합니다.

ethtool 통계와 PHY 카운터 해석 실전 가이드

ethtool -S <interface> 명령은 드라이버가 노출하는 하드웨어 통계 카운터를 출력합니다. 이 카운터들은 NIC(Network Interface Card) MAC 계층과 PHY 계층에서 독립적으로 집계되므로, 어떤 카운터가 증가하는지 조합해서 보면 문제의 위치를 MAC인지 PHY인지, 더 나아가 케이블인지 상대방 장비인지까지 좁혀 낼 수 있습니다.

카운터 분류 개요

ethtool -S 출력은 드라이버마다 이름이 다르지만, 기능상으로 다음 세 범주로 분류할 수 있습니다.

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 collisions (Full-duplex) symbol_errors rx_dropped / missed RGMII 타이밍 점검 rgmii-rxid / txid 설정 확인 rx-internal-delay-ps 케이블·커넥터 교체 시도 Duplex mismatch ethtool eth0 으로 양단 duplex 확인 강제 설정 또는 AN 재협상 PCS/PMA 물리 계층 SFP: ethtool -m 광 출력 확인 구리: 케이블 길이·품질 SerDes 이퀄라이저 튜닝 소프트웨어 처리 속도 Rx 링 버퍼 크기 확인 ethtool -G 링 확대 IRQ affinity / NAPI 조정 장기 추이 확인 → watch -n 1 ethtool -S eth0 | grep -v ": 0" 근본 원인 식별 후 수정
카운터 종류별 진단 흐름. 점선은 모든 경우에 장기 추이 모니터링이 필요함을 나타냅니다.

지속 모니터링 스크립트

일회성 조회 대신 카운터 변화량을 지속적으로 관찰하면 간헐적 오류도 포착할 수 있습니다.

# 방법 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_statsethtool_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
래치(Latched) 카운터와 런닝(Running) 카운터의 차이: 일부 PHY(특히 구형 Clause 22 PHY)의 오류 카운터는 래치 방식으로 동작합니다. 레지스터를 읽는 순간 값이 초기화되므로, 연속 두 번 읽으면 첫 번째 읽기에서만 값이 나옵니다. 반면 MAC의 RMON 카운터와 최신 Clause 45 PHY 카운터는 런닝 방식으로 누적됩니다. 드라이버가 폴링 주기마다 읽어 누적하는 경우도 있으므로, 짧은 폴링 간격에서 카운터가 0으로 보이더라도 오류가 없다고 단정하지 마세요. PHY 데이터시트(Datasheet)의 레지스터 설명에서 "clear on read" 여부를 반드시 확인하세요.

Device Tree 실전 예제 모음

Device Tree(장치 트리)에서 이더넷 PHY를 올바르게 기술하는 것은 보드 bring-up의 핵심입니다. 이 절은 다양한 토폴로지별 완성된 예제를 제공하며, 각 속성의 용도와 자주 발생하는 실수를 함께 설명합니다.

RGMII + 외부 PHY (STM32MP1 + RTL8211F)

STM32MP1 SoC의 GMAC과 Realtek RTL8211F PHY를 RGMII 모드로 연결하는 가장 일반적인 예제입니다. RGMII 인터페이스는 1 Gbps에서 클럭 스큐가 매우 민감하므로 지연(delay) 속성을 정확히 기술해야 합니다.

&ethernet0 {
    status = "okay";
    pinctrl-0 = <&ethernet0_rgmii_pins_a>;
    pinctrl-1 = <&ethernet0_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>;
        };
    };
};
rgmii-id vs rgmii-rxid vs rgmii: 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를 감지할 수 없으므로 권장합니다.

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-linkphy-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 참조 */
&eth0 {
    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를 개별 참조 */
&eth0 { phy-handle = <&phy_pkg0>; phy-mode = "rgmii-id"; }
&eth1 { phy-handle = <&phy_pkg1>; phy-mode = "rgmii-id"; }
&eth2 { phy-handle = <&phy_pkg2>; phy-mode = "rgmii-id"; }
&eth3 { 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 드라이버 문서를 참고하세요.

이 절의 전제: 여기서 "NIC 드라이버"란 MAC 하드웨어를 제어하는 드라이버(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);
    }
}
mdio_read/write 구현 시 주의: MDIO read/write 콜백은 슬립이 가능한 컨텍스트에서 호출됩니다. 하드웨어 레지스터 접근 시 폴링 타임아웃을 반드시 설정하고, 무한 루프를 방지해야 합니다. 또한 버스 잠금(locking)은 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/MIISFP, 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);
}

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 금지: 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;
}
ndo_open / ndo_stop 호출 순서 ndo_open (시작) ① MAC HW 초기화 (DMA, 링 버퍼) ② IRQ 등록 ③ napi_enable() ④ phy_start() / phylink_start() ⑤ netif_tx_start_all_queues() ndo_stop (정지, 역순) ① netif_tx_stop + carrier_off ② phy_stop() / phylink_stop() ③ napi_disable() ④ IRQ 해제 ⑤ MAC HW 정지 역순 phy_stop() 이후에는 adjust_link가 호출되지 않으므로, 그 이전에 큐 정지가 완료되어야 합니다
ndo_open과 ndo_stop은 서로 역순으로 리소스를 초기화/해제합니다. PHY 시작·정지 위치가 잘못되면 경합 조건이 발생합니다.

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);
}
해제 순서 위반 시 증상: MDIO bus를 PHY disconnect 전에 해제하면, PHY 상태 머신이 이미 해제된 버스에 read/write를 시도하여 커널 oops가 발생합니다. phylink 사용 시에도 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-idPHY가 TX/RX 모두 지연 추가PHY 내부PCB에서 이미 보상했는데 이중 지연
rgmii-txidPHY가 TX만 지연 추가PHY TX + PCB RXTX/RX를 혼동하여 반대로 설정
rgmii-rxidPHY가 RX만 지연 추가PHY RX + PCB TXTX/RX를 혼동하여 반대로 설정
sgmiiSerDes 기반 1.25 Gbps해당 없음1000BASE-X와 혼동, control word 해석 차이
mii / rmii10/100M 전용PHY/MAC/PCB 조합ref_clk 방향(입력/출력) 미설정
RGMII 지연 디버깅 팁: 100M에서는 정상이고 1G에서만 CRC가 증가하면 거의 확실히 RGMII 지연 문제입니다. 보드 회로도에서 PHY와 MAC 사이의 트레이스 지연 보상 여부를 확인하고, 해당 조합에 맞는 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 후 링크 품질 저하: PHY의 resume 콜백이 config_init을 다시 호출하지 않는 드라이버가 간혹 있습니다. 이 경우 RGMII delay bit, LED 모드, downshift 정책이 strap 기본값으로 복귀하여 부팅 직후와 다른 동작을 보입니다. PHY 드라이버의 resume 구현을 확인하고, 필요하면 MAC 드라이버에서 PHY soft reset 후 재연결을 시도하세요.

초보 NIC 드라이버 개발자의 흔한 실수 TOP 10

아래는 커널 메일링 리스트와 실무에서 반복적으로 보고되는 PHY 관련 실수를 정리한 것입니다. 새 드라이버를 작성하거나 기존 드라이버를 수정할 때 체크리스트로 활용하세요.

#실수증상올바른 처리
1adjust_link/mac_link_up을 빈 함수로 둠속도 재협상 후 CRC 폭증, "링크 업인데 통신 불가"speed, duplex, pause를 MAC 레지스터에 반드시 반영
2phy-mode 불일치1G에서만 CRC 에러, 100M에서는 정상보드 회로도 확인, PHY 데이터시트의 지연 보상 위치 맞추기
3MDIO bus 등록 전에 PHY 연결 시도probe 실패, NULL 포인터of_mdiobus_register()of_phy_connect() 순서 보장
4deferred probe 미처리부팅 순서에 따라 간헐적 PHY 연결 실패-EPROBE_DEFER 반환으로 재시도 허용
5phy_disconnect() 전에 MDIO bus 해제커널 oops, use-after-free반드시 PHY disconnect → MDIO unregister → MDIO free 순서
6ethtool PHY ops 미연결ethtool eth0에서 속도/듀플렉스 정보 없음phy_ethtool_* 또는 phylink_ethtool_* 헬퍼 연결
7phy_start() 호출 누락인터페이스 up 해도 링크 상태 머신 미가동, 항상 링크 다운ndo_open에서 phy_start() 반드시 호출
8resume에서 PHY 재초기화 누락suspend/resume 후 링크 불안정 또는 속도 강등resume 경로에서 phy_start() 재호출, PHY 드라이버 resume 콜백 확인
9phy_set_max_speed() 미호출MAC이 100M만 지원하는데 PHY가 1G 광고 → 상대편과 1G 협상 → 프레임 손실MAC 하드웨어 능력에 맞게 최대 속도 제한
10fixed-link인데 phy-handle 기술존재하지 않는 PHY를 탐색하느라 probe 지연 또는 실패PHY 없는 직결 구성은 fixed-link만 사용, 둘을 동시에 기술하지 않기

첫 보드 Bring-up 시 최소 동작 검증 절차

새 MAC 드라이버를 작성한 후, 또는 새 보드에 기존 드라이버를 포팅한 후 PHY 동작을 확인하는 최소 절차입니다.

  1. 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)
  2. 링크 업 확인 — 케이블 연결 후 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: 링크 다운
  3. 속도/듀플렉스 확인ethtool eth0으로 협상된 속도와 듀플렉스가 기대값인지 확인합니다.
    ethtool eth0
    # Speed: 1000Mb/s, Duplex: Full, Auto-negotiation: on
  4. ping 테스트 — 작은 패킷과 큰 패킷 모두 테스트합니다.
    ping -c 10 192.168.1.1
    ping -c 10 -s 1472 192.168.1.1  # MTU 근처 크기
  5. 에러 카운터 확인ethtool -S eth0으로 CRC, carrier, frame 에러가 증가하지 않는지 확인합니다.
    ethtool -S eth0 | grep -iE 'err|crc|drop|miss|carrier'
    # 모든 에러 카운터가 0 또는 증가하지 않아야 정상
  6. 속도 강제 변경 테스트 — 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 # 원복
  7. 링크 플랩 테스트 — 케이블 분리/재연결을 3~5회 반복하면서 dmesg에 링크 up/down 로그가 정상 출력되는지, 복구 후 통신이 되는지 확인합니다.
  8. 대역폭 측정iperf3로 실제 throughput이 링크 속도의 90% 이상인지 확인합니다. 현저히 낮으면 DMA, NAPI, 또는 PHY 설정 문제일 수 있습니다.
    # 서버 측
    iperf3 -s
    
    # 클라이언트 측
    iperf3 -c 192.168.1.1 -t 30
    # 1G 링크에서 ~940 Mbps 이상이면 정상
디버깅 순서 요약: PHY ID 확인 → 링크 업 여부 → 속도/듀플렉스 → CRC 카운터 → ping → iperf 순서로 점검하세요. 각 단계에서 문제가 발견되면 이후 단계를 건너뛰고 해당 계층을 먼저 해결하는 것이 효율적입니다. PHY ID가 안 읽히면 드라이버를 아무리 바꿔도 소용없고, CRC가 증가하면 iperf 결과도 의미가 없습니다.

참고자료

다음 학습: