CPU Cache and busdma usage in USB

Piotr Zięcik kosmo at semihalf.com
Mon Jun 29 09:11:23 UTC 2009


Sunday 28 June 2009 11:54:40 Hans Petter Selasky napisał(a):
> Hi Piotr and Rafal,
>
> Your patch is not fully correct. It will break support for x86 and more
> when bounce pages are uses.
>
> Let's get the definitions right:
>
> man busdma
> (...)
>
> My view:
>
> XXX_PREXXX functions should be used prior to read/write device access.
>
> In other words, PRE has to be a flush operation.
>
> XXX_POSTXXX functions should be used after read/write device access.
>
> In other words, POST has to be an invalidate operation.
>
> Reading:
>
> src/sys/arm/arm/busdma_machdep.c
>
> I find bus_dmamap_sync_buf() to be coherent with this view.

If everything is OK, then why USB does not work on ARM and MIPS ?
Let's look into busdma for these platforms:

usb_pc_cpu_invalidate(): [BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE]
	i386:		NOP
	arm:		Invalidate + hacks for VIPT cache.
	mips:		NOP

usb_pc_cpu_flush(): [BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE]
	i386:		NOP
	arm:		Writeback invalidate
	mips:		Writeback invalidate.

I do not see direct coherency between flags passed to bus_dma and cache 
operations, which could be source of problem.

Let's also look at usb_pc_cpu_invalidate() usage in 
sys/dev/usb/controller/ehci.c:
<cite>
while (1) {
		usb_pc_cpu_invalidate(td->page_cache);
                status = hc32toh(sc, td->qtd_status);

		(...)

		if (status & EHCI_QTD_HALTED) {
			break;
		}

		(...)

		td = td->obj_next;
</cite>

In my oppinion usb_pc_cpu_invalidate() used here suggests that it is doing 
cache invalidation not "Perform any synchronization required after 					     
an update of host memory by the device and prior to CPU access to host 
memory".

As this function is implemented as bus_dmamap_sync() all busdma rules should 
be applied:
<cite>
If read and write operations are not preceded and followed by the
appropriate synchronization operations, behavior is undefined.
</cite>

In code shown above (and many more places in USB stack) there is no following
synchronization operation, which also could be source of problem.

My major question here is why bus_dma is used for flushing and invalidation
CPU caches instead of cpu_*() functions wich was written for this purpuose.

> Can you check if the COHERENT bit is set for your allocation?
>
>         if (map->flags & DMAMAP_COHERENT)
>                 return;
>

No. This bit is not set.

> Summed up:
>
> The existing code is doing correct. What is known is a problem with the
> memory mapping, so that the same memory page can get mapped with different
> attributes, which makes the problem appear.

I don't think so:
$ man busdma
<cite>
BUS_DMA_COHERENT
	Attempt to map this memory such that cache sync operations are as cheap as
	possible.  This flag is typically set on memory that will be accessed by both
	a CPU and a DMA engine, frequently.  Use of this flag does not remove the 
	requirement of using bus_dmamap_sync, but it may reduce the cost of 	
	performing these operations. 
</cite>

This means that BUS_DMA_COHERENT does not guarantee no-cache mapping - this is
only a hint for busdma subsystem that region will be synced frequently. Please 
look at discussion at 
http://kerneltrap.org/mailarchive/freebsd-current/2009/5/27/5817243

-- 
Best regards.
Piotr Ziecik


More information about the freebsd-usb mailing list