← Hardware

Hardware 04 · Ethernet · Linux v6.18.37

Ethernet DMA Descriptor에서 NAPI와 Socket까지

wire의 Ethernet frame이 MAC filter, RX descriptor, DMA ownership, interrupt moderation, NAPI budget, skb/GRO, IP와 socket receive queue로 이동하는 전 과정을 검증합니다.

검증 깊이
board → Linux 8 layers
그림
4 boundary diagrams
원본 코드
5 excerpts · 335 lines
기준
Linux v6.18.37

01 · QUESTION

무엇을 증명할 것인가

PHY link와 MAC RX counter는 정상인데 application이 packet을 받지 못할 때 frame은 정확히 어느 queue와 context에서 멈췄는가?

wire의 Ethernet frame이 MAC filter, RX descriptor, DMA ownership, interrupt moderation, NAPI budget, skb/GRO, IP와 socket receive queue로 이동하는 전 과정을 검증합니다. 이 글은 Linux 로그를 출발점으로 삼지 않습니다. 같은 사건이 보드, controller, firmware, kernel에서 어떤 이름과 번호로 바뀌는지 아래에서 위로 연결합니다.

02 · CONTRACT

입력과 완료 조건

구분확인할 내용
입력정상 PHY/MAC link, DMA 가능한 memory, RX/TX ring, interrupt vector와 열린 net_device
출력RX packet이 socket queue까지 전달되고 TX completion이 descriptor와 BQL에 반영되는 상태
소유권descriptor ownership bit가 CPU/device 소유를 나누고 skb reference가 Linux stack의 packet lifetime을 소유
완료 조건wire/MAC/ring/NAPI/netstack/socket의 packet count가 동일한 test flow에서 설명 가능

03 · BOTTOM-UP

가장 아래 계층부터 확인하는 8단계

그림 1. 아래에서 Linux까지 올라오는 8개 검증 경계위 계층의 로그보다 아래 계층의 물리 증거를 먼저 확정합니다.
01
0. Frame · Preamble, MAC header, payload와 FCS

MAC은 preamble/SFD로 수신을 동기화하고 destination/source MAC, EtherType/length, payload와 FCS를 처리한다. VLAN tag, pause frame, jumbo MTU와 minimum frame padding이 descriptor length와 offload metadata에 영향을 준다.

증거: external capture, MAC good/bad frame counter, frame length histogram

START
02
1. Filter · MAC address, multicast hash와 VLAN filter

promiscuous, unicast perfect filter, multicast hash와 VLAN filter가 frame을 DMA로 보낼지 결정한다. hardware drop counter와 Linux `rx_dropped`는 같은 위치의 counter가 아니다. filter 설정 직후 readback이 필요하다.

증거: MAC filter register, multicast list, VLAN table, missed/filtered frame counter

CHECKPOINT
03
2. Ring · Descriptor base, length, producer/consumer와 ownership

driver는 coherent descriptor ring과 mapped data buffer를 준비한다. CPU가 buffer address와 length를 쓰고 ownership을 device로 넘긴다. device는 frame을 DMA로 옮긴 뒤 status/length를 쓰고 ownership을 CPU로 돌린다. wrap bit와 tail pointer 단위가 틀리면 한 바퀴 뒤에 멈춘다.

증거: ring base/size, descriptor raw dump, head/tail, ownership bit와 DMA address

CHECKPOINT
04
3. IRQ · completion interrupt와 moderation

packet마다 IRQ를 내면 고속 traffic에서 CPU가 interrupt 처리에 묶인다. controller는 packet count/timer 기반 coalescing으로 interrupt를 줄인다. handler는 status를 ack하고 RX IRQ를 mask한 뒤 NAPI를 schedule하는 최소 작업만 수행한다.

증거: IRQ status/mask, coalescing parameter, `/proc/interrupts`, interrupt-to-packet ratio

CHECKPOINT
05
4. NAPI · poll budget과 interrupt 재활성화

NET_RX_SOFTIRQ가 NAPI poll callback을 호출해 budget만큼 descriptor를 회수한다. work가 budget보다 작고 ring이 비었을 때 `napi_complete_done()` 뒤 IRQ를 다시 연다. complete와 새 packet race를 driver가 올바르게 처리해야 lost interrupt를 막는다.

증거: napi poll/complete trace, budget/work, ring pending과 IRQ mask timestamp

CHECKPOINT
06
5. skb/GRO · descriptor metadata를 sk_buff로 변환

driver가 length, checksum, VLAN, RSS hash와 timestamp를 skb metadata로 옮긴다. page-pool과 build_skb 경로에서는 DMA buffer page lifetime이 skb와 연결된다. GRO는 같은 flow packet을 합쳐 protocol processing 비용을 줄인다.

증거: skb tracepoint, checksum/VLAN/RSS flag, page_pool stats, GRO count

CHECKPOINT
07
6. Stack · EtherType → IP → transport → socket

`__netif_receive_skb_core()`가 packet_type과 rx_handler를 통해 protocol을 선택한다. IPv4/IPv6 routing, netfilter와 TCP/UDP lookup을 거쳐 socket receive queue에 skb가 들어간다. 이 구간의 drop은 NIC statistics보다 protocol SNMP와 tracepoint에서 보인다.

증거: netif_receive trace, drop reason, IP/TCP/UDP counter, socket queue length

CHECKPOINT
08
7. Userspace · poll/epoll readiness와 read

socket queue가 비어 있지 않으면 wait queue를 깨우고 epoll readiness가 생성된다. application buffer보다 datagram이 크거나 TCP framing을 잘못하면 수신은 성공해도 application protocol이 깨질 수 있다. hardware 검증은 packet sequence와 timestamp를 application까지 유지해야 끝난다.

증거: strace/ss, socket drop, packet sequence, application timestamp

LINUX / RESULT
01

0. Frame · Preamble, MAC header, payload와 FCS

MAC은 preamble/SFD로 수신을 동기화하고 destination/source MAC, EtherType/length, payload와 FCS를 처리한다. VLAN tag, pause frame, jumbo MTU와 minimum frame padding이 descriptor length와 offload metadata에 영향을 준다.

남길 증거: external capture, MAC good/bad frame counter, frame length histogram

PHY symbol은 정상이어도 FCS/align error면 DMA 전에 MAC에서 drop될 수 있다.

02

1. Filter · MAC address, multicast hash와 VLAN filter

promiscuous, unicast perfect filter, multicast hash와 VLAN filter가 frame을 DMA로 보낼지 결정한다. hardware drop counter와 Linux `rx_dropped`는 같은 위치의 counter가 아니다. filter 설정 직후 readback이 필요하다.

남길 증거: MAC filter register, multicast list, VLAN table, missed/filtered frame counter

ring을 보기 전에 frame이 MAC filter를 통과했는지 확인한다.

03

2. Ring · Descriptor base, length, producer/consumer와 ownership

driver는 coherent descriptor ring과 mapped data buffer를 준비한다. CPU가 buffer address와 length를 쓰고 ownership을 device로 넘긴다. device는 frame을 DMA로 옮긴 뒤 status/length를 쓰고 ownership을 CPU로 돌린다. wrap bit와 tail pointer 단위가 틀리면 한 바퀴 뒤에 멈춘다.

남길 증거: ring base/size, descriptor raw dump, head/tail, ownership bit와 DMA address

descriptor와 buffer의 DMA address를 CPU virtual address와 혼동하지 않는다.

04

3. IRQ · completion interrupt와 moderation

packet마다 IRQ를 내면 고속 traffic에서 CPU가 interrupt 처리에 묶인다. controller는 packet count/timer 기반 coalescing으로 interrupt를 줄인다. handler는 status를 ack하고 RX IRQ를 mask한 뒤 NAPI를 schedule하는 최소 작업만 수행한다.

남길 증거: IRQ status/mask, coalescing parameter, `/proc/interrupts`, interrupt-to-packet ratio

IRQ가 없다고 DMA가 없는 것은 아니며 polling/interrupt mask 상태를 함께 본다.

05

4. NAPI · poll budget과 interrupt 재활성화

NET_RX_SOFTIRQ가 NAPI poll callback을 호출해 budget만큼 descriptor를 회수한다. work가 budget보다 작고 ring이 비었을 때 `napi_complete_done()` 뒤 IRQ를 다시 연다. complete와 새 packet race를 driver가 올바르게 처리해야 lost interrupt를 막는다.

남길 증거: napi poll/complete trace, budget/work, ring pending과 IRQ mask timestamp

budget을 모두 썼다면 NAPI는 계속 scheduled 상태여야 하며 IRQ를 섣불리 열지 않는다.

06

5. skb/GRO · descriptor metadata를 sk_buff로 변환

driver가 length, checksum, VLAN, RSS hash와 timestamp를 skb metadata로 옮긴다. page-pool과 build_skb 경로에서는 DMA buffer page lifetime이 skb와 연결된다. GRO는 같은 flow packet을 합쳐 protocol processing 비용을 줄인다.

남길 증거: skb tracepoint, checksum/VLAN/RSS flag, page_pool stats, GRO count

checksum status를 잘못 번역하면 정상 packet을 stack이 drop하거나 손상 packet을 신뢰한다.

07

6. Stack · EtherType → IP → transport → socket

`__netif_receive_skb_core()`가 packet_type과 rx_handler를 통해 protocol을 선택한다. IPv4/IPv6 routing, netfilter와 TCP/UDP lookup을 거쳐 socket receive queue에 skb가 들어간다. 이 구간의 drop은 NIC statistics보다 protocol SNMP와 tracepoint에서 보인다.

남길 증거: netif_receive trace, drop reason, IP/TCP/UDP counter, socket queue length

NIC `rx_packets`와 application read count 사이의 모든 software drop point를 구분한다.

08

7. Userspace · poll/epoll readiness와 read

socket queue가 비어 있지 않으면 wait queue를 깨우고 epoll readiness가 생성된다. application buffer보다 datagram이 크거나 TCP framing을 잘못하면 수신은 성공해도 application protocol이 깨질 수 있다. hardware 검증은 packet sequence와 timestamp를 application까지 유지해야 끝난다.

남길 증거: strace/ss, socket drop, packet sequence, application timestamp

ping 성공만으로 RX/TX ring wrap, jumbo, checksum, multicast와 sustained load를 검증했다고 보지 않는다.

04 · DIAGRAMS

주소와 상태가 이동하는 모습

그림 2. Ethernet 실행 경로왼쪽부터 신호나 데이터를 처리하는 순서를 나타냅니다. 각 단계에서 확인할 항목을 함께 표시했습니다.
01 Wire frame
02 MAC filter/FCS
03 DMA descriptor
04 IRQ mask+NAPI
05 skb/GRO
06 IP/TCP/UDP
07 socket
그림 3. firmware와 Linux 번호 공간의 연결왼쪽부터 신호나 데이터를 처리하는 순서를 나타냅니다. 각 단계에서 확인할 항목을 함께 표시했습니다.
01 skb/qdisc
02 ndo_start_xmit
03 TX descriptor
04 dma_wmb+doorbell
05 MAC wire
06 TX completion
07 BQL wake
그림 4. 입력, 소유권, 완료 조건의 경계완료 조건을 충족하기 전에는 다음 계층이 해당 자원을 재사용하면 안 됩니다.
INPUT

정상 PHY/MAC link, DMA 가능한 memory, RX/TX ring, interrupt vector와 열린 net_device

OWNER

descriptor ownership bit가 CPU/device 소유를 나누고 skb reference가 Linux stack의 packet lifetime을 소유

OUTPUT

RX packet이 socket queue까지 전달되고 TX completion이 descriptor와 BQL에 반영되는 상태

DONE

wire/MAC/ring/NAPI/netstack/socket의 packet count가 동일한 test flow에서 설명 가능

05 · DETAILED MANUAL

DWMAC descriptor에서 NAPI와 socket까지 이어 읽는 상세 매뉴얼

Synopsys GMAC/GMAC4/XGMAC 계열을 기준으로 HW feature register, DMA channel, descriptor ownership, tail pointer, interrupt status, NAPI budget, skb와 page_pool lifetime을 이어서 봅니다. IP release와 합성 옵션에 따라 descriptor와 register가 달라지므로 먼저 capability를 읽고 해당 경로만 해석합니다.

공개적으로 다시 확인할 수 있는 자료

공개되지 않은 제조사 레지스터나 제품별 오프셋은 추측하지 않았습니다. 아래의 제조사 자료, 아키텍처 문서, Linux v6.18.37 원본으로 확인할 수 있는 내용만 다뤘습니다.

Linux stmmac driver

Synopsys GMAC/GMAC4/XGMAC, descriptor, NAPI, PTP와 offload 공개 설명

Linux NAPI

schedule, poll budget, completion과 IRQ 재활성화 계약

Linux DMA API HOWTO

CPU와 장치 사이의 디스크립터·패킷 버퍼 소유권, 주소 매핑, 메모리 배리어

06 · REGISTERS

읽어야 할 상태와 해석

같은 동작에 대한 활성화 상태, 처리 상태, 자원 소유권, 완료 레지스터 값을 함께 기록합니다. 읽으면 지워지는 비트나 1을 써야 지워지는 비트가 있는지는 먼저 데이터시트에서 확인합니다.

레지스터/상태소유 블록검증할 의미
MAC RX/TX controlMAC blockenable, duplex, speed, checksum/filter
DMA descriptor baseDMA blockphysical/IOVA base와 alignment
Ring length/tailDMA blockdescriptor count 단위와 wrap
DMA status/maskDMA blockRX/TX completion, error, stopped state
Coalescing timer/countDMA blocklatency와 IRQ rate tradeoff
MTL/FIFO statusMAC/DMA 사이underflow, overflow와 queue backpressure

07 · LINUX SOURCE

Linux v6.18.37 원본 코드와 줄별 해설

라인 번호를 고정해 외우는 대신 함수 선언을 기준으로 Linux v6.18.37 tree에서 다시 찾았습니다. 로컬 tree에 있는 파일은 실제 코드를 직접 싣고, 나머지는 원본 파일과 읽을 함수 좌표를 연결했습니다.

Linux v6.18.37 · 실제 원본 코드

skb를 queue와 descriptor에 매핑하고 DWMAC TX DMA에 ownership을 넘기는 경로

drivers/net/ethernet/stmicro/stmmac/stmmac_main.c:4452-4542

4452/**
4453 *  stmmac_xmit - Tx entry point of the driver
4454 *  @skb : the socket buffer
4455 *  @dev : device pointer
4456 *  Description : this is the tx entry point of the driver.
4457 *  It programs the chain or the ring and supports oversized frames
4458 *  and SG feature.
4459 */
4460static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev)
4461{
4462	unsigned int first_entry, tx_packets, enh_desc;
4463	struct stmmac_priv *priv = netdev_priv(dev);
4464	unsigned int nopaged_len = skb_headlen(skb);
4465	int i, csum_insertion = 0, is_jumbo = 0;
4466	u32 queue = skb_get_queue_mapping(skb);
4467	int nfrags = skb_shinfo(skb)->nr_frags;
4468	int gso = skb_shinfo(skb)->gso_type;
4469	struct stmmac_txq_stats *txq_stats;
4470	struct dma_edesc *tbs_desc = NULL;
4471	struct dma_desc *desc, *first;
4472	struct stmmac_tx_queue *tx_q;
4473	bool has_vlan, set_ic;
4474	int entry, first_tx;
4475	dma_addr_t des;
4476	u32 sdu_len;
4477 
4478	tx_q = &priv->dma_conf.tx_queue[queue];
4479	txq_stats = &priv->xstats.txq_stats[queue];
4480	first_tx = tx_q->cur_tx;
4481 
4482	if (priv->tx_path_in_lpi_mode && priv->eee_sw_timer_en)
4483		stmmac_stop_sw_lpi(priv);
4484 
4485	/* Manage oversized TCP frames for GMAC4 device */
4486	if (skb_is_gso(skb) && priv->tso) {
4487		if (gso & (SKB_GSO_TCPV4 | SKB_GSO_TCPV6))
4488			return stmmac_tso_xmit(skb, dev);
4489		if (priv->plat->core_type == DWMAC_CORE_GMAC4 &&
4490		    (gso & SKB_GSO_UDP_L4))
4491			return stmmac_tso_xmit(skb, dev);
4492	}
4493 
4494	if (priv->est && priv->est->enable &&
4495	    priv->est->max_sdu[queue]) {
4496		sdu_len = skb->len;
4497		/* Add VLAN tag length if VLAN tag insertion offload is requested */
4498		if (priv->dma_cap.vlins && skb_vlan_tag_present(skb))
4499			sdu_len += VLAN_HLEN;
4500		if (sdu_len > priv->est->max_sdu[queue]) {
4501			priv->xstats.max_sdu_txq_drop[queue]++;
4502			goto max_sdu_err;
4503		}
4504	}
4505 
4506	if (unlikely(stmmac_tx_avail(priv, queue) < nfrags + 1)) {
4507		if (!netif_tx_queue_stopped(netdev_get_tx_queue(dev, queue))) {
4508			netif_tx_stop_queue(netdev_get_tx_queue(priv->dev,
4509								queue));
4510			/* This is a hard error, log it. */
4511			netdev_err(priv->dev,
4512				   "%s: Tx Ring full when queue awake\n",
4513				   __func__);
4514		}
4515		return NETDEV_TX_BUSY;
4516	}
4517 
4518	/* Check if VLAN can be inserted by HW */
4519	has_vlan = stmmac_vlan_insert(priv, skb, tx_q);
4520 
4521	entry = tx_q->cur_tx;
4522	first_entry = entry;
4523	WARN_ON(tx_q->tx_skbuff[first_entry]);
4524 
4525	csum_insertion = (skb->ip_summed == CHECKSUM_PARTIAL);
4526	/* DWMAC IPs can be synthesized to support tx coe only for a few tx
4527	 * queues. In that case, checksum offloading for those queues that don't
4528	 * support tx coe needs to fallback to software checksum calculation.
4529	 *
4530	 * Packets that won't trigger the COE e.g. most DSA-tagged packets will
4531	 * also have to be checksummed in software.
4532	 */
4533	if (csum_insertion &&
4534	    (priv->plat->tx_queues_cfg[queue].coe_unsupported ||
4535	     !stmmac_has_ip_ethertype(skb))) {
4536		if (unlikely(skb_checksum_help(skb)))
4537			goto dma_map_err;
4538		csum_insertion = !csum_insertion;
4539	}
4540 
4541	if (likely(priv->extend_desc))
4542		desc = (struct dma_desc *)(tx_q->dma_etx + entry);

Linux v6.18.37 · 실제 원본 코드

RX channel을 budget만큼 처리하고 ring이 비면 NAPI 완료와 DMA IRQ 재활성화를 수행하는 poll

drivers/net/ethernet/stmicro/stmmac/stmmac_main.c:5685-5737

5685	u64_stats_update_end(&rxq_stats->napi_syncp);
5686 
5687	priv->xstats.rx_dropped += rx_dropped;
5688	priv->xstats.rx_errors += rx_errors;
5689 
5690	return count;
5691}
5692 
5693static int stmmac_napi_poll_rx(struct napi_struct *napi, int budget)
5694{
5695	struct stmmac_channel *ch =
5696		container_of(napi, struct stmmac_channel, rx_napi);
5697	struct stmmac_priv *priv = ch->priv_data;
5698	struct stmmac_rxq_stats *rxq_stats;
5699	u32 chan = ch->index;
5700	int work_done;
5701 
5702	rxq_stats = &priv->xstats.rxq_stats[chan];
5703	u64_stats_update_begin(&rxq_stats->napi_syncp);
5704	u64_stats_inc(&rxq_stats->napi.poll);
5705	u64_stats_update_end(&rxq_stats->napi_syncp);
5706 
5707	work_done = stmmac_rx(priv, budget, chan);
5708	if (work_done < budget && napi_complete_done(napi, work_done)) {
5709		unsigned long flags;
5710 
5711		spin_lock_irqsave(&ch->lock, flags);
5712		stmmac_enable_dma_irq(priv, priv->ioaddr, chan, 1, 0);
5713		spin_unlock_irqrestore(&ch->lock, flags);
5714	}
5715 
5716	return work_done;
5717}
5718 
5719static int stmmac_napi_poll_tx(struct napi_struct *napi, int budget)
5720{
5721	struct stmmac_channel *ch =
5722		container_of(napi, struct stmmac_channel, tx_napi);
5723	struct stmmac_priv *priv = ch->priv_data;
5724	struct stmmac_txq_stats *txq_stats;
5725	bool pending_packets = false;
5726	u32 chan = ch->index;
5727	int work_done;
5728 
5729	txq_stats = &priv->xstats.txq_stats[chan];
5730	u64_stats_update_begin(&txq_stats->napi_syncp);
5731	u64_stats_inc(&txq_stats->napi.poll);
5732	u64_stats_update_end(&txq_stats->napi_syncp);
5733 
5734	work_done = stmmac_tx_clean(priv, budget, chan, &pending_packets);
5735	work_done = min(work_done, budget);
5736 
5737	if (work_done < budget && napi_complete_done(napi, work_done)) {

Linux v6.18.37 · 실제 원본 코드

MAC common status와 DMA channel interrupt를 분류하는 최상위 ISR

drivers/net/ethernet/stmicro/stmmac/stmmac_main.c:121-173

121 
122/* By default the driver will use the ring mode to manage tx and rx descriptors,
123 * but allow user to force to use the chain instead of the ring
124 */
125static unsigned int chain_mode;
126module_param(chain_mode, int, 0444);
127MODULE_PARM_DESC(chain_mode, "To use chain instead of ring mode");
128 
129static irqreturn_t stmmac_interrupt(int irq, void *dev_id);
130/* For MSI interrupts handling */
131static irqreturn_t stmmac_mac_interrupt(int irq, void *dev_id);
132static irqreturn_t stmmac_safety_interrupt(int irq, void *dev_id);
133static irqreturn_t stmmac_msi_intr_tx(int irq, void *data);
134static irqreturn_t stmmac_msi_intr_rx(int irq, void *data);
135static void stmmac_reset_rx_queue(struct stmmac_priv *priv, u32 queue);
136static void stmmac_reset_tx_queue(struct stmmac_priv *priv, u32 queue);
137static void stmmac_reset_queues_param(struct stmmac_priv *priv);
138static void stmmac_tx_timer_arm(struct stmmac_priv *priv, u32 queue);
139static void stmmac_flush_tx_descriptors(struct stmmac_priv *priv, int queue);
140static void stmmac_set_dma_operation_mode(struct stmmac_priv *priv, u32 txmode,
141					  u32 rxmode, u32 chan);
142static void stmmac_vlan_restore(struct stmmac_priv *priv);
143 
144#ifdef CONFIG_DEBUG_FS
145static const struct net_device_ops stmmac_netdev_ops;
146static void stmmac_init_fs(struct net_device *dev);
147static void stmmac_exit_fs(struct net_device *dev);
148#endif
149 
150#define STMMAC_COAL_TIMER(x) (ns_to_ktime((x) * NSEC_PER_USEC))
151 
152/**
153 * stmmac_set_clk_tx_rate() - set the clock rate for the MAC transmit clock
154 * @bsp_priv: BSP private data structure (unused)
155 * @clk_tx_i: the transmit clock
156 * @interface: the selected interface mode
157 * @speed: the speed that the MAC will be operating at
158 *
159 * Set the transmit clock rate for the MAC, normally 2.5MHz for 10Mbps,
160 * 25MHz for 100Mbps and 125MHz for 1Gbps. This is suitable for at least
161 * MII, GMII, RGMII and RMII interface modes. Platforms can hook this into
162 * the plat_data->set_clk_tx_rate method directly, call it via their own
163 * implementation, or implement their own method should they have more
164 * complex requirements. It is intended to only be used in this method.
165 *
166 * plat_data->clk_tx_i must be filled in.
167 */
168int stmmac_set_clk_tx_rate(void *bsp_priv, struct clk *clk_tx_i,
169			   phy_interface_t interface, int speed)
170{
171	long rate = rgmii_clock(speed);
172 
173	/* Silently ignore unsupported speeds as rgmii_clock() only

Linux v6.18.37 · 실제 원본 코드

CPU별 NAPI poll list를 NET_RX_SOFTIRQ context에서 실행하는 networking core

net/core/dev.c:7779-7843

7779	struct napi_struct *napi = data;
7780 
7781	while (!napi_thread_wait(napi))
7782		napi_threaded_poll_loop(napi);
7783 
7784	return 0;
7785}
7786 
7787static __latent_entropy void net_rx_action(void)
7788{
7789	struct softnet_data *sd = this_cpu_ptr(&softnet_data);
7790	unsigned long time_limit = jiffies +
7791		usecs_to_jiffies(READ_ONCE(net_hotdata.netdev_budget_usecs));
7792	struct bpf_net_context __bpf_net_ctx, *bpf_net_ctx;
7793	int budget = READ_ONCE(net_hotdata.netdev_budget);
7794	LIST_HEAD(list);
7795	LIST_HEAD(repoll);
7796 
7797	bpf_net_ctx = bpf_net_ctx_set(&__bpf_net_ctx);
7798start:
7799	sd->in_net_rx_action = true;
7800	local_irq_disable();
7801	list_splice_init(&sd->poll_list, &list);
7802	local_irq_enable();
7803 
7804	for (;;) {
7805		struct napi_struct *n;
7806 
7807		skb_defer_free_flush();
7808 
7809		if (list_empty(&list)) {
7810			if (list_empty(&repoll)) {
7811				sd->in_net_rx_action = false;
7812				barrier();
7813				/* We need to check if ____napi_schedule()
7814				 * had refilled poll_list while
7815				 * sd->in_net_rx_action was true.
7816				 */
7817				if (!list_empty(&sd->poll_list))
7818					goto start;
7819				if (!sd_has_rps_ipi_waiting(sd))
7820					goto end;
7821			}
7822			break;
7823		}
7824 
7825		n = list_first_entry(&list, struct napi_struct, poll_list);
7826		budget -= napi_poll(n, &repoll);
7827 
7828		/* If softirq window is exhausted then punt.
7829		 * Allow this to run for 2 jiffies since which will allow
7830		 * an average latency of 1.5/HZ.
7831		 */
7832		if (unlikely(budget <= 0 ||
7833			     time_after_eq(jiffies, time_limit))) {
7834			/* Pairs with READ_ONCE() in softnet_seq_show() */
7835			WRITE_ONCE(sd->time_squeeze, sd->time_squeeze + 1);
7836			break;
7837		}
7838	}
7839 
7840	local_irq_disable();
7841 
7842	list_splice_tail_init(&sd->poll_list, &list);
7843	list_splice_tail(&repoll, &list);

Linux v6.18.37 · 실제 원본 코드

driver가 만든 skb를 protocol handler와 rx_handler로 전달하는 receive core

net/core/dev.c:5880-5952

5880		rcu_read_lock();
5881		ingress_retval = nf_hook_ingress(skb);
5882		rcu_read_unlock();
5883		return ingress_retval;
5884	}
5885	return 0;
5886}
5887 
5888static int __netif_receive_skb_core(struct sk_buff **pskb, bool pfmemalloc,
5889				    struct packet_type **ppt_prev)
5890{
5891	enum skb_drop_reason drop_reason = SKB_DROP_REASON_UNHANDLED_PROTO;
5892	struct packet_type *ptype, *pt_prev;
5893	rx_handler_func_t *rx_handler;
5894	struct sk_buff *skb = *pskb;
5895	struct net_device *orig_dev;
5896	bool deliver_exact = false;
5897	int ret = NET_RX_DROP;
5898	__be16 type;
5899 
5900	net_timestamp_check(!READ_ONCE(net_hotdata.tstamp_prequeue), skb);
5901 
5902	trace_netif_receive_skb(skb);
5903 
5904	orig_dev = skb->dev;
5905 
5906	skb_reset_network_header(skb);
5907#if !defined(CONFIG_DEBUG_NET)
5908	/* We plan to no longer reset the transport header here.
5909	 * Give some time to fuzzers and dev build to catch bugs
5910	 * in network stacks.
5911	 */
5912	if (!skb_transport_header_was_set(skb))
5913		skb_reset_transport_header(skb);
5914#endif
5915	skb_reset_mac_len(skb);
5916 
5917	pt_prev = NULL;
5918 
5919another_round:
5920	skb->skb_iif = skb->dev->ifindex;
5921 
5922	__this_cpu_inc(softnet_data.processed);
5923 
5924	if (static_branch_unlikely(&generic_xdp_needed_key)) {
5925		int ret2;
5926 
5927		migrate_disable();
5928		ret2 = do_xdp_generic(rcu_dereference(skb->dev->xdp_prog),
5929				      &skb);
5930		migrate_enable();
5931 
5932		if (ret2 != XDP_PASS) {
5933			ret = NET_RX_DROP;
5934			goto out;
5935		}
5936	}
5937 
5938	if (eth_type_vlan(skb->protocol)) {
5939		skb = skb_vlan_untag(skb);
5940		if (unlikely(!skb))
5941			goto out;
5942	}
5943 
5944	if (skb_skip_tc_classify(skb))
5945		goto skip_classify;
5946 
5947	if (pfmemalloc)
5948		goto skip_taps;
5949 
5950	list_for_each_entry_rcu(ptype, &dev_net_rcu(skb->dev)->ptype_all,
5951				list) {
5952		if (pt_prev)

Linux v6.18.37 · 원본 좌표

include/linux/netdevice.h

driver와 networking core 사이의 object 및 callback 계약

읽을 함수
struct napi_struct
읽을 함수
struct net_device_ops

Linux v6.18.37 원본 파일 열기 →

08 · VERIFICATION

실제 장비에서 확인하는 순서

  1. Counter ladder

    실행 외부 송신 packet 수와 PHY/MAC/DMA/netdev/socket 수를 한 표에 기록한다.

    통과 기준 처음 감소하는 두 checkpoint 사이가 실제 drop 위치다.

  2. Ring

    실행 traffic 정지 상태에서 descriptor, head/tail과 DMA address를 dump한다.

    통과 기준 ownership과 producer/consumer가 모순 없이 한 방향으로 진행해야 한다.

  3. NAPI

    실행 irq와 napi tracepoint를 함께 수집한다.

    통과 기준 IRQ mask → poll → complete → unmask 순서가 보여야 한다.

  4. Offload

    실행 RX checksum/GRO/TSO를 하나씩 끄고 결과를 비교한다.

    통과 기준 문제가 사라지는 feature가 metadata 또는 segmentation 경계를 좁힌다.

  5. Load

    실행 64B, MTU, jumbo와 burst traffic으로 ring wrap을 반복한다.

    통과 기준 drop/error 증가가 queue depth와 coalescing 설정으로 설명돼야 한다.

09 · FAILURE MATRIX

처음 끊긴 경계로 원인을 좁히기

관측 결과우선 의심할 경계다음 증거
MAC counter 0filter, MAC enable, PHY-MAC interface 문제wire capture와 MAC status
MAC 증가, descriptor 불변DMA enable/base/tail/IOMMU 문제ring register와 IOMMU fault
descriptor 완료, NAPI 없음IRQ mask/routing 또는 schedule 문제status, `/proc/interrupts`, napi state
NAPI 증가, rx_packets 없음descriptor parse, length/checksum/drop 문제driver error와 skb trace
rx_packets 증가, socket 없음protocol/netfilter/routing/socket lookup 문제drop reason과 protocol counter

핵심은 마지막 오류 메시지가 아니라 처음 기대값과 달라진 checkpoint입니다. 그보다 아래의 통과 증거와 그 지점의 실패 증거를 한 묶음으로 남겨야 수정의 효과도 검증할 수 있습니다.