RTL8169 transmission
Posted: Wed Jun 23, 2010 11:41 pm
This is ridiculously specific but I'll bet some of you have been through the exact same thing. I'm writing a driver for RealTek RTL8169 NICs (my card has an RTL8169SB) and it's been mostly smooth sailing, except that in my tests, transmitted packets sometimes don't seem to get sent. Actually it likes what happens is, the receiving end times out (for testing I'm using a file transfer program that uses a simple proprietary stop-and-wait protocol) and asks for a retry, and at THAT point the missing packet suddenly gets sent, along with the retransmitted packet. I assumed it was a ring or interrupt problem (so the incoming packet was waking up the chip after I dropped the ball) but more testing shows that interrupts are working, and the TX packet's OWN bit is clearing fairly immediately (not a second later when the NAK packet comes in).
Sooooo ... I'm starting to get the impression that I've pissed off the PHYceiver in some way (maybe something to do with flow control???????), and that stuff is all greek to me. The RTL8169 wiki entry leaves the MII/GMII stuff as an exercise to the reader. I wrote code which mimics the OS/2 driver (whose logic is easier to follow than the Linux driver, and the commenting is no worse), which just tells the PHY to start auto-negotiation and polls it (with a timeout) until it reports that all is well (which IS happening). Any idea if I'm on the right track?
What blows my mind is, *reception* is the hard part of writing an Ethernet driver, and that stuff has worked perfectly almost since the beginning. Transmission should be a piece of cake -- it's always my idea (vs. RX which could happen at any time), and in this particular case the protocol is so simple that there's never more than one TX packet outstanding at a time (so there can't be a problem with my attempt to be cute by not telling the RTL8169 to poll the TX queue if the preceding descriptor's OWN bit is still set since I know help must be on the way).
I doubt anyone wants to see 1900 lines of 386 code (especially since it's full of macros and weird event queues) and I'm not sure what to post in the way of highlights. The init sequence is pretty similar to what's in the wiki. The routine to send packets is straightforward (constants should be mostly obvious, and XSER/SSER are routines which execute MFENCE etc. if this CPU has them):
As I say though, the symptoms suggest that the problem is happening after the packet has already gone into the FIFO and the TX descriptor has been given back. But please tell me how wrong I am!!! I've been bashing my head against this for days and days with no useful progress.
Thanks,
John Wilson
D Bit
Sooooo ... I'm starting to get the impression that I've pissed off the PHYceiver in some way (maybe something to do with flow control???????), and that stuff is all greek to me. The RTL8169 wiki entry leaves the MII/GMII stuff as an exercise to the reader. I wrote code which mimics the OS/2 driver (whose logic is easier to follow than the Linux driver, and the commenting is no worse), which just tells the PHY to start auto-negotiation and polls it (with a timeout) until it reports that all is well (which IS happening). Any idea if I'm on the right track?
What blows my mind is, *reception* is the hard part of writing an Ethernet driver, and that stuff has worked perfectly almost since the beginning. Transmission should be a piece of cake -- it's always my idea (vs. RX which could happen at any time), and in this particular case the protocol is so simple that there's never more than one TX packet outstanding at a time (so there can't be a problem with my attempt to be cute by not telling the RTL8169 to poll the TX queue if the preceding descriptor's OWN bit is still set since I know help must be on the way).
I doubt anyone wants to see 1900 lines of 386 code (especially since it's full of macros and weird event queues) and I'm not sure what to post in the way of highlights. The init sequence is pretty similar to what's in the wiki. The routine to send packets is straightforward (constants should be mostly obvious, and XSER/SSER are routines which execute MFENCE etc. if this CPU has them):
Code: Select all
;+
;
; Send packet.
;
; edi packet buffer from NETXBF -- DS:PK$LEN[EDI], DS:PK$CRT[EDI] set
; ebp IF blk (preserved)
;
; On completion, a call is made at fork level to the routine pointed to by
; DS:PK$CRT[EDI] with EDI (which must be preserved) still set up to point at
; the packet buffer, and EBP (which also must be preserved) pointing at the
; RTL8169 IF block.
;
;-
r69spk: xor eax,eax ;load 0
mov ds:pk$flg[edi],al ;init flags
mov ds:pk$nxt[edi],eax ;end of queue
mov ebx,ss:ethxlq[ebp] ;get existing end of queue
mov ss:ethxlq[ebp],edi ;now it's us
test ebx,ebx ;was there anything?
jnz short @@1 ;yes
mov ss:ethxcq[ebp],edi ;no, we're at head too
jmp short @@2
@@1: mov ds:pk$nxt[ebx],edi ;link us to previous tail
@@2: ; TX descriptor ring is big enough for all our bufs at once
; (so we can always add another immediately -- no queue to get in)
.assume <xring ge r69$xb*dsize>
; fill in descriptor
mov ebx,ss:rtltxf[ebp] ;get "fill" pointer
mov ecx,ds:pk$len[edi] ;get length
lea esi,[ebx+dsize] ;get addr that follows
or ecx,d$own+d$fs+d$ls ;owned by NIC, first/last seg
cmp esi,xring ;final descriptor before wrap?
jne short @@3
or ecx,d$eor ;end of ring (wrap back to beginning)
xor esi,esi ;wrap offset back to beginning too
@@3: mov ss:rtltxf[ebp],esi ;update "fill" pointer
add ebx,ss:rtlxva[ebp] ;add base VA of TX descriptor ring
xor edx,edx ;load 0
mov eax,ds:pk$pa[edi] ;get physical address for this buf
mov ds:dbuf+4[ebx],edx ;zero-extend addr
mov ds:dbuf[ebx],eax ;save addr itself
mov dword ptr ds:dvlan[ebx],edx ;VLAN stuff = 0
.assume <dflg2 eq dvlan+2>
call sser ;serialize stores
mov ds:dflen[ebx],ecx ;set D$OWN last
;(in case RTL8169 was about to check it anyway)
; see if NIC has eaten preceding frame yet
; (if not, no need to tell it to poll, since it'll get to us next)
sub esi,dsize*2 ;back to descriptor before ours
and esi,xring-1 ;(wrap back to end if needed)
.assume <(xring and (xring-1)) eq 0> ;(must be power of two)
add esi,ss:rtlxva[ebp] ;add base VA of TX descriptor ring
call xser ;(serialize loads and stores)
test byte ptr ds:dflen+3[esi],d$own shr 24d ;owned by NIC?
.assume <(d$own and (not 0FF000000h)) eq 0> ;(D$OWN is in the high byte)
jnz short @@4 ;yes, no need to poke it
mov ebx,ss:rtlreg[ebp] ;get pointer to reg window
mov byte ptr ds:tppoll[ebx],tp$npq ;poll normal priority queue
@@4: ret
Thanks,
John Wilson
D Bit