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:
1 parent
801f175550
commit
9fb27f5bc7
2 files changed
+163
-13
No files matched your search
+155
-13
@@ -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
|
||||
|
||||
|
||||
;***************************************************************************
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in new issue
Block a user