Michael Decker: Driver Development
Week 4
16 & 17 June
I added dynamic allocation of RFDs today.
The struct ifec_rfd rfa[RFD_COUNT]
member of struct ifec_private
was replaced with a struct io_buffer *rx_iobs[RFD_COUNT]
member. Thus, the NIC private data holds an array of pointers to io_buffers.
The io_buffers are allocated in ifec_net_open()
, and initialized as well. Each io_buffer holds a struct ifec_rfd
, which contains both the RFD header and the packet data following contiguously. The immediately following data is a characteristic of “simple mode” and is how the Linux drivers accomplish things. Perhaps experimenting with undocumented methods of separating the packet from the RFD can be an exercise left for a rainy day.
Checking the status of our RFDs in ifec_net_poll()
now traverses the rx_iobs
array. When a packet has been received, iob_reserve()
advances the data pointer past the embedded RFD header data, and iob_put()
marks the length of the received data. The io_buffer is then handed directly to netdev_rx()
. No packet data is copied; the original buffer is sent up the network stack.
Since the driver gave up ownership to that io_buffer, a replacement is allocated for the rx_iobs
array. It is initialized, and linked into the existing chain of RFDs. All the RFDs are linked to allow the hardware to know where to store each following packet. This involves writing a pointer, and modifying the RFD command such that the hardware suspends if it reaches the end of the RFD chain.
By suspending, we preserve the received packets waiting to be processed, rather than overwriting them with new data. This is standard to allow forward progress and recovery of lost packets. I ensured the link address to the last RFD was appended first, before updating the command data so only the last RFD suspends the hardware. This prevents race conditions where the device doesn't suspend and tries to access the next linked RFD with an invalid address. That would be a tough one to debug
In ifec_net_poll()
, after handing a received packet to the network subsystem, a new io_buffer is allocated. At first, if this allocation failed, it simply returned. However, I decided it may be best to allocate the new io_buffer just before handing the current one to the network subsystem. If that allocation fails, we return leaving the received packet intact to be handled at the next ifec_net_poll()
when memory may be available.
Also, since RFDs are initialized both in ifec_net_open()
as well as ifec_net_poll()
, I moved the initialization code for RFDs into ifec_rfd_init()
. And of course I added code in ifec_net_close()
to free the io_buffers in the rx_iobs
array.
18 June
I spent today implementing multiple TCB support. It involved setting the final TCB to enter suspend mode, and each transmit request would issue a start command. Unfortunately, I didn't make a commit at this point, which I should have.
19 June
I spent today attempting to rewrite the tx support such that the card never entered suspend mode. I hoped to keep the card in a loop processing NOP commands while nothing was ready to transmit. After many attempts I found it appears that the card will enter the idle state if it receives a command with the OK and C bits set. These bits are set after a command is processed. Thus, it won't remain active while processing NOP commands unless I clear certain bits after it processes them the first time.
So it would seem either suspending the card or setting it idle is necessary, unless you have enough TCBs to keep the card busy between calls to ifec_net_poll()
. I'm not sure what the difference between suspend mode and idle mode is, aside from a resume command affecting them differently.
I'll have to work on this some more when I get back from my cousin's wedding. It seems that both Linux drivers place the card into suspend mode when the end of the transmit ring is reached, so that seems the feasible way to go.