Subject: Re: m_defrag() addition
To: None <tech-kern@netbsd.org>
From: Jaromir Dolecek <jdolecek@NetBSD.org>
List: tech-kern
Date: 03/05/2005 11:23:48
On Mar 1, 2005, at 7:12 PM, Bill Studenmund wrote:

> I think it'd be better to just return the chain. While you're right 
> that
> chances are we will just drop the packet, I think there are times we 
> may
> want to do something different with it. Yes, that would be using this
> routine for something other than fixing up DMA segments... If we always
> free the chain on error, it'd be very hard to support doing something 
> with
> the chain.

Actually, I've re-examined how driver *_start() routines typically work
and they do need to do 'something different' that free mbuf chain
right away.

Typical pattern is:

	for(;;) {
		IFQ_POLL(..., &m0);
		if (m0 == NULL)
			break;

		/* check if there is enough of free DMA descriptors */
		...

		error = bus_dmamap_load_mbuf();
		if (error) {
			if (error == EFBIG) {
				/* unable to load, too many DMA segments */
				/* DROP MBUF CHAIN */
				IFQ_DEQUEUE(..., m0);
				m_freem(m0);
				break;
			}

			/* Short on resources, just stop for now. */
			break;
		}

		IFQ_DEQUEUE(&ifp->if_snd, m0);

		/*
		 * WE ARE NOW COMMITTED TO TRANSMITTING THE PACKET.
		 */

		/* modify device structures to send the packet */
		...
	}

If m_defrag() were to be called at at the 'DROP MBUF CHAIN' place and free
the old chain, later IFQ_DEQUEUE() would use stale pointer.

I also misread the semantics of the current FreeBSD m_defrag(), thought
it frees the old mbuf chain :)

Given this, I propose following for NetBSD m_defrag():
* signature struct mbuf *m_defrag(struct mbuf *m0, int flags, int nseg_goal)
* function returns defragmented mbuf chain on success, or NULL on failure 
* function fails if there is not enough memory to allocate new mbuf chain
  memory (iff M_NOWAIT) or if it would not be able to meet the nseg_goal
  fragmentation goal
* success means the function has defragmented the mbuf chain
  into <= nseg_goal continuous virtual memory segments, and the defragmented
  virtual memory segments cross the PAGE_SIZE boundary maximum
  (nseg_goal - 1) times; this is to guarantee the result consists
  of nseg_goal physical pages maximum
* old mbuf chain kept intact in either success or error case

Usage pattern would be:

	error = bus_dmamap_load_mbuf(...);
	if (error) {
		if (error == EFBIG) {
			m_new = m_defrag(m0, M_NOWAIT, STGE_MAXSEGS);
			if (m_new == NULL
			    || (error = bus_dmamap_load_mbuf(...,m_new)) ) {
				/* too many DMA segments and cannot defrag */
				if (m_new)
					m_freem(m_new);
				IFQ_DEQUEUE(..., m0);
				m_freem(m0);
				break;
			}

			/* do something do use m_new instead m0 */
		}
	}

Does this sound right?

Jaromir
-- 
Jaromir Dolecek <jdolecek@NetBSD.org>            http://www.NetBSD.cz/
-=- We can walk our road together if our goals are all the same;     -=-
-=- We can run alone and free if we pursue a different aim.          -=-