Re: bus_dma question

From: Scott Long <scottl_at_freebsd.org>
Date: Tue, 14 Dec 2004 18:44:08 -0700
Julian Elischer wrote:
> 
> The bus_dma man page is good. but it there documentation of how to 
> extract the physical page addresses from a map? In other words, if I 
> defien a tag for a device and then create a map using bus_dmamap_create, 
> and then load it using bus_dmamap_load, what is the recommended method 
> of extracting the list of addresses to feed into a DMA scatter_gather 
> list? Looking at various drivers I'm getting more and more confused.. 
> There is  a lot of home-grown s/g list
> generation code out there.. is there a "right" way to do this?
> 

The function pointer that you provide to bus_dmamap_load() is called
when the S/G map is ready (it might be deferred due to needing bounce
pages, so you cannot assume that it will be called before 
bus_dmamap_load() returns).  The S/G list and length is provided in
the arguments to the callback function.  So:

static void
my_start(my_softc, my_data_ptr, my_data_len)
{
	/* Prepare resources for sending data to the hardware */
	[...]

	/*
	 * Hand off to busdma to generate the S/G list.
	 */
	error = bus_dmamap_load(my_softc->dmat, my_softc->dmamap,
	    my_data_ptr, my_data_len, my_callback_func, my_softc, 0);
	if (error == EINPROGRESS) {
		/*
		 * The callback will be called later.  Don't queue up
		 * any more commands until it has so that we don't spin
		 * on busdma.
		 */
		my_softc->flags |= FROZEN;
	}

	return;
}

static void
my_callack_func(void *arg, bus_dma_segment_t *segs, int nseg, int error)
{
	my_softc_t *my_softc = arg;

	if (error) {
		/* Handle error */
		[...]
		return;

	for (i = 0; i < nsegs; i++) {
		my_sg[i].phys = segs[i].ds_addr;
		mg_sg[i].len = segs[i].ds_len;
	}

	if (dma is from host memory to the hardware)
	bus_dmamap_sync(my_dmat, my_dmamap, BUS_DMASYNC_PREWRITE);

	if (dma is from the hardware to host memory)
	bus_dmamap_sync(my_dmat, my_dmamap, BUS_DMASYNC_PREREAD);

	/*
	 * Send S/G list to the hardware and tell the hardware to start
	 * the transaction.
	 */
	[...]

	return;
}

Note that you cannot pass an error value or anything else from the 
callback function to the caller since you have no control over when
the callback will actually be called.  Well, if you use the 
BUS_DMA_NOWAIT flag for bus_dmamap_load(), you'll be guaranteed to get
an error back instead of a deferral, but that should only be used with
great care.

Also, don't forget to call bus_dmamap_sync() with the POSTREAD and 
POSTWRITE ops once the transaction is complete.  This is very important
now that >4GB of memory is easy to aquire; devices like UHCI and ATA can
still only do 32bit S/G lists, so bouncing is required on i386 and
amd64.  You also need to do a bus_dmamap_unload() call to free whatever
busdma resources were used for the transaction, so the sequence should
be something like:

static void
my_intr(void *arg)
{
	/* Check and handle interrupt */
	[...]

	/* return busdma resources */
	if (dma is from host memory to the hardware)
	bus_dmamap_sync(my_dmat, my_dmamap, BUS_DMASYNC_POSTWRITE);

	if (dma is from the hardware to host memory)
	bus_dmamap_sync(my_dmat, my_dmamap, BUS_DMASYNC_POSTREAD);

	bus_dmamap_unload(my_dmat, my_dmamap);

	/* Process the data that was DMA'd */
	[...]

	return;
}

There will likely be changes to the busdma API for 6.0 that will
simplify this a bit, make it faster, and make it resemble NetBSD a
little more, but I'm still working on them.

Scott
Received on Wed Dec 15 2004 - 00:44:35 UTC

This archive was generated by hypermail 2.4.0 : Wed May 19 2021 - 11:38:24 UTC