bcm57xx: only report a link down when a readable PHY confirms it

A link-down verdict is not a status update on this system, it is
destructive: the stack stops being able to route for the sockets it
already has and never re-resolves them, so a connection with data in
flight is over for good. This driver handed one out on the strength of a
single sample: check_link sent a failed PHY read to .fallback and, if the
MI status word did not have its link bit set there - it is the
auto-poller's view of the PHY, so it is stale exactly when the MI
interface is what broke - declared the carrier gone. A send stall was
then read as a link event, the reclaim threw the ring away and the box
lost every connection it had, which is part of why this took so long to
pin down.

An unreadable PHY is now treated as what it is, an absence of evidence:
the state is left alone and the next attention decides. MI_STS may keep a
link up, never declare one down, the way bge_link_upd uses it. A down
verdict that does come from a readable PHY is sampled once more after
LINK_CONFIRM_US and dropped if the carrier is back; the number of
withdrawn verdicts is kept. Every branch logs the evidence it decided on
- both BMSR reads, MI_STS, MAC_STS, the transmit MAC's own status and the
confirmation reads - so a verdict can be explained afterwards.

Assisted-by: ZCode:deepseek-flash
This commit is contained in:
Igor Shutrov committed 2026-09-20 20:20:27 +05:00
1 parent 801f175550
commit 9fb27f5bc7
2 files changed
+163 -13

No files matched your search

+155 -13
View File
@@ -45,6 +45,23 @@ entry START
TX_RING_SIZE = 512
RX_STD_SIZE = 512
; How long to keep sampling the PHY before a "the carrier is gone" verdict
; is passed on to the stack. Milliseconds matter here: the link status bit
; latches low, so a single sample can be stale news, and on this system a
; link-down verdict is final for every connection the box has open - it
; never recovers one. A real outage lasts far longer than this; a port
; flap on a switch does not.
LINK_CONFIRM_US = 10000
; Evidence bits recorded by check_link; its log lines print them.
LKEV_BMSR1_FAIL = 1
LKEV_BMSR_FAIL = 2
LKEV_BMSR_NOLINK = 4
LKEV_AUX_FAIL = 8
LKEV_AUX_UNKNOWN = 16
LKEV_FALLBACK = 32
LKEV_BMSR_LINK = 64
; The receive return ring is host resident and its length does come from
; the ring control block, but the chip has a per-family maximum for it
; (tg3's TG3_RX_RET_MAX_SIZE_5705 / _5700) and both reference drivers
@@ -162,6 +179,19 @@ struct device ETH_DEVICE
txfull_count dd ?
oom_count dd ?
; What the last link check saw. A link verdict that cannot be explained
; afterwards is worse than useless.
lk_ev dd ? ; LKEV_* bits
lk_bmsr1 dd ? ; first BMSR read (latched bits)
lk_bmsr dd ? ; second BMSR read (current state)
lk_mists dd ? ; MI_STS at the moment of the verdict
lk_macsts dd ? ; MAC_STS at the moment of the verdict
lk_txsts dd ? ; MAC_TX_STS, the transmit MAC's own view
lk_conf1 dd ? ; confirm reads, after LINK_CONFIRM_US
lk_conf2 dd ?
link_downs dd ? ; verdicts passed on to the stack
link_withdrawn dd ? ; verdicts dropped again as stale
tx_buffs rd TX_RING_SIZE
rx_buffs rd RX_STD_SIZE
@@ -951,6 +981,16 @@ reset:
mov [ebx + device.irq_count], 0
mov [ebx + device.txfull_count], 0
mov [ebx + device.oom_count], 0
mov [ebx + device.lk_ev], 0
mov [ebx + device.lk_bmsr1], 0
mov [ebx + device.lk_bmsr], 0
mov [ebx + device.lk_mists], 0
mov [ebx + device.lk_macsts], 0
mov [ebx + device.lk_txsts], 0
mov [ebx + device.lk_conf1], 0
mov [ebx + device.lk_conf2], 0
mov [ebx + device.link_downs], 0
mov [ebx + device.link_withdrawn], 0
mov edi, [ebx + device.dma_virt]
mov ecx, DMA_ALLOC / 4
@@ -2160,6 +2200,20 @@ int_handler:
;***************************************************************************
; check_link - read the negotiated speed and tell the stack
;
; A "link down" verdict is not cheap to hand out. The stack takes it as
; the carrier being gone for good: routing for the sockets it already
; has stops working, and a connection with data in flight never comes
; back from it, however brief the outage was. So nothing here reports a
; link down unless a readable PHY still says so a moment later. A PHY
; that cannot be read at all says nothing about the carrier, and the MI
; status word is only allowed to keep a link up, never to declare one
; down: it is the auto-poller's view of the PHY, so it is stale exactly
; when the MI interface is what broke. The reference driver splits it
; the same way - bge_link_upd asks the PHY and reads MI_STS only to
; decide whether the PHY is worth asking.
;;
; What was seen is kept in the device structure.
;
; All registers preserved.
;***************************************************************************
@@ -2168,6 +2222,13 @@ check_link:
push eax ebx ecx edx esi edi
mov esi, [ebx + device.mmio_addr]
mov dword [ebx + device.lk_ev], 0
mov dword [ebx + device.lk_bmsr1], 0
mov dword [ebx + device.lk_bmsr], 0
mov dword [ebx + device.lk_mists], 0
mov dword [ebx + device.lk_macsts], 0
mov dword [ebx + device.lk_conf1], 0
mov dword [ebx + device.lk_conf2], 0
; Ask the PHY directly rather than looking at MI_STS. MI_STS only ever
; changes when the MI auto-poller has actually run, so straight after
@@ -2175,17 +2236,31 @@ check_link:
; check comes out wrong.
mov eax, PHY_BMSR
call phy_read
mov [ebx + device.lk_bmsr1], eax
cmp eax, -1
je .fallback
je .first_failed
mov edi, eax
jmp .second
.first_failed:
or [ebx + device.lk_ev], LKEV_BMSR1_FAIL
mov edi, -1
.second:
mov eax, PHY_BMSR ; latching bits, read twice
call phy_read
mov [ebx + device.lk_bmsr], eax
cmp eax, -1
je .fallback
jne @f
or [ebx + device.lk_ev], LKEV_BMSR_FAIL
jmp .fallback
@@:
DEBUGF 1,"check_link: BMSR 0x%x/0x%x MI_STS 0x%x\n",\
edi, eax, [esi + MI_STS]
test eax, PHY_BMSR_LINK
jz .down
jnz @f
or [ebx + device.lk_ev], LKEV_BMSR_NOLINK
jmp .down
@@:
or [ebx + device.lk_ev], LKEV_BMSR_LINK
; The PHY reports carrier before auto-negotiation finishes, and the
; auxiliary status register holds nonsense until it does. Reading it too
@@ -2199,7 +2274,10 @@ check_link:
mov eax, PHY_AUX_STAT
call phy_read
cmp eax, -1
je .fallback
jne @f
or [ebx + device.lk_ev], LKEV_AUX_FAIL
jmp .fallback
@@:
DEBUGF 1,"check_link: AUX_STAT 0x%x\n", eax
and eax, AUX_SPEED_MASK
@@ -2218,6 +2296,7 @@ check_link:
cmp eax, AUX_SPEED_1000FULL
je .s1000f
DEBUGF 2,"Unknown speed code 0x%x in AUX_STAT\n", eax
or [ebx + device.lk_ev], LKEV_AUX_UNKNOWN
jmp .fallback
.s10h:
@@ -2245,19 +2324,26 @@ check_link:
mov edx, MAC_MODE_PORT_MODE_GMII
jmp .apply
; The PHY did not answer. The link is up, so guess from the only other
; hint the MAC gives us.
; The PHY did not answer, or gave a speed code we do not know. MI_STS is
; the only other hint the MAC gives us, but it is a hint about the last
; poll of the PHY rather than about the carrier now, so it is used to
; keep the link up and for nothing else.
.fallback:
or [ebx + device.lk_ev], LKEV_FALLBACK
mov eax, [esi + MI_STS]
mov [ebx + device.lk_mists], eax
test [ebx + device.lk_ev], LKEV_BMSR_LINK
jnz .assume ; PHY saw carrier, speed unknown
test eax, MISTS_LINK
jz .down
jz .undecided
.assume:
mov ecx, ETH_LINK_SPEED_100M or ETH_LINK_FULL_DUPLEX
test eax, MISTS_10MBPS
jz @f
mov ecx, ETH_LINK_SPEED_10M or ETH_LINK_FULL_DUPLEX
@@:
mov edx, MAC_MODE_PORT_MODE_MII
DEBUGF 2,"PHY unreadable, assuming state 0x%x\n", ecx
DEBUGF 2,"Link speed unknown, assuming state 0x%x, MI_STS %x\n", ecx, eax
.apply:
mov eax, [esi + MAC_MODE]
@@ -2272,17 +2358,74 @@ check_link:
cmp [ebx + device.state], ecx
je .leave
mov [ebx + device.state], ecx
DEBUGF 2,"Link up, state 0x%x, MAC_MODE 0x%x\n", ecx, eax
DEBUGF 2,"Link up, state 0x%x, MAC_MODE 0x%x, ev %u BMSR %x/%x MI_STS %x\n",\
ecx, eax, [ebx + device.lk_ev], [ebx + device.lk_bmsr1],\
[ebx + device.lk_bmsr], [ebx + device.lk_mists]
invoke NetLinkChanged
.leave:
pop edi esi edx ecx ebx eax
ret
; Neither the PHY nor the MI status word says the link is up, and the PHY
; could not be read. That is an absence of evidence rather than evidence
; the carrier is gone, so the state is left alone and the next link
; attention decides. The chip goes on transmitting either way.
.undecided:
mov eax, [esi + MAC_STS]
mov [ebx + device.lk_macsts], eax
mov edx, [esi + MAC_TX_STS]
mov [ebx + device.lk_txsts], edx
DEBUGF 2,"Link state undecided: ev %u BMSR %x/%x MI_STS %x MAC_STS %x TX_STS %x\n",\
[ebx + device.lk_ev], [ebx + device.lk_bmsr1],\
[ebx + device.lk_bmsr], [ebx + device.lk_mists], eax, edx
jmp .leave
; The PHY itself says the carrier is gone. Sample it once more before
; telling the stack: the link status bit latches low, so a single reading
; can already be old news, and there is no taking this verdict back
; afterwards.
.down:
mov eax, [esi + MI_STS]
mov [ebx + device.lk_mists], eax
mov eax, [esi + MAC_STS]
mov [ebx + device.lk_macsts], eax
mov edx, [esi + MAC_TX_STS]
mov [ebx + device.lk_txsts], edx
cmp [ebx + device.state], ETH_LINK_DOWN
je .leave
je .leave ; already reported, nothing to add
mov ecx, LINK_CONFIRM_US
call delay_us
mov eax, PHY_BMSR
call phy_read
mov [ebx + device.lk_conf1], eax
mov eax, PHY_BMSR
call phy_read
mov [ebx + device.lk_conf2], eax
cmp eax, -1
je .withdrawn
test eax, PHY_BMSR_LINK
jz .report_down
.withdrawn:
inc [ebx + device.link_withdrawn]
DEBUGF 2,"Link down withdrawn: ev %u BMSR %x/%x MI_STS %x MAC_STS %x TX_STS %x\n",\
[ebx + device.lk_ev], [ebx + device.lk_bmsr1], [ebx + device.lk_bmsr],\
[ebx + device.lk_mists], [ebx + device.lk_macsts], [ebx + device.lk_txsts]
DEBUGF 2,"Link down withdrew, confirm %x/%x\n",\
[ebx + device.lk_conf1], [ebx + device.lk_conf2]
jmp .leave
.report_down:
inc [ebx + device.link_downs]
mov [ebx + device.state], ETH_LINK_DOWN
DEBUGF 2,"Link down, prod %u cons %u\n",\
DEBUGF 2,"Link down: ev %u BMSR %x/%x MI_STS %x MAC_STS %x TX_STS %x\n",\
[ebx + device.lk_ev], [ebx + device.lk_bmsr1], [ebx + device.lk_bmsr],\
[ebx + device.lk_mists], [ebx + device.lk_macsts], [ebx + device.lk_txsts]
DEBUGF 2,"Link down confirmed %x/%x, prod %u cons %u\n",\
[ebx + device.lk_conf1], [ebx + device.lk_conf2],\
[ebx + device.tx_prod], [ebx + device.tx_cons]
; Anything still queued for transmission is never going to complete now
@@ -2292,8 +2435,7 @@ check_link:
call tx_reclaim_all
invoke NetLinkChanged
pop edi esi edx ecx ebx eax
ret
jmp .leave
;***************************************************************************
+8
View File
@@ -88,6 +88,7 @@ MI_COMM = 0x044c
MI_STS = 0x0450
MI_MODE = 0x0454
MAC_TX_MODE = 0x045c
MAC_TX_STS = 0x0460
MAC_TX_LENGTHS = 0x0464
MAC_RX_MODE = 0x0468
MAC_RX_STS = 0x046c
@@ -132,6 +133,13 @@ TX_MODE_ENABLE = 0x00000002
TX_MODE_FLOW_CTRL_ENABLE = 0x00000010
TXMODE_MBUF_LOCKUP_FIX = 0x00000100
TXSTAT_RX_XOFFED = 0x00000001
TXSTAT_SENT_XOFF = 0x00000002
TXSTAT_SENT_XON = 0x00000004
TXSTAT_LINK_UP = 0x00000008
TXSTAT_ODI_UFLOW = 0x00000010
TXSTAT_ODI_OFLOW = 0x00000020
TX_LENGTHS_DEFAULT = 0x00002620 ; inter packet gap etc.
TX_BACKOFF_SEED_MASK = 0x000003ff