Subject: Re: Problems with "bus_dmamap_sync()" on sparc64
To: Hans Petter Selasky <hselasky@c2i.net>
From: Eduardo Horvath <eeh@NetBSD.org>
List: tech-kern
Date: 01/24/2007 17:18:28
On Tue, 23 Jan 2007, Hans Petter Selasky wrote:

> When I initialize the EHCI chip I do like this:
> 
> 1) Allocate memory using "usbd_mem_alloc_sub()".
> 
> 2) Call "usbd_page_dma_exit(&(sc->sc_hw_page))"
> 
> 3) Initialize hardware memory
> 
> 4) Call "usbd_page_dma_enter(&(sc->sc_hw_page))"
> 
> 5) Start the EHCI controller.
> 
> But the EHCI PCI controller immediately halts. If I insert a "tsleep(1*hz)" 
> before 5) then at least the EHCI PCI controller starts without halting.
> 
> I suspect that the following function does not do what it promises in the 
> manpage:
>                 bus_dmamap_sync(page->tag, page->map, 0, page->length,
>                                 BUS_DMASYNC_PREWRITE|BUS_DMASYNC_PREREAD);

[...]

> void
> psycho_dmamap_sync(t, map, offset, len, ops)
>         bus_dma_tag_t t;
>         bus_dmamap_t map;
>         bus_addr_t offset;
>         bus_size_t len;
>         int ops;
> {
>         struct psycho_pbm *pp = (struct psycho_pbm *)t->_cookie;
> 
>         if (ops & (BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE)) {
>                 /* Flush the CPU then the IOMMU */
>                 bus_dmamap_sync(t->_parent, map, offset, len, ops);
>                 iommu_dvmamap_sync(t, &pp->pp_sb, map, offset, len, ops);
>         }
>         if (ops & (BUS_DMASYNC_POSTREAD|BUS_DMASYNC_POSTWRITE)) {
>                 /* Flush the IOMMU then the CPU */
>                 iommu_dvmamap_sync(t, &pp->pp_sb, map, offset, len, ops);
>                 bus_dmamap_sync(t->_parent, map, offset, len, ops);
>         }
> 
> }
> 
> void
> iommu_dvmamap_sync(t, sb, map, offset, len, ops)
>         bus_dma_tag_t t;
>         struct strbuf_ctl *sb;
>         bus_dmamap_t map;
>         bus_addr_t offset;
>         bus_size_t len;
>         int ops;
> {
>         bus_size_t count;
>         int i, needsflush = 0;
> 
>         if (!sb->sb_flush)
>                 return;
> 
>         for (i = 0; i < map->dm_nsegs; i++) {
>                 if (offset < map->dm_segs[i].ds_len)
>                         break;
>                 offset -= map->dm_segs[i].ds_len;
>         }
> 
>         if (i == map->dm_nsegs)
>                 panic("iommu_dvmamap_sync: segment too short %llu", 
>                     (unsigned long long)offset);
> 
>         if (ops & (BUS_DMASYNC_PREREAD | BUS_DMASYNC_POSTWRITE)) {
>                 /* Nothing to do */;
>         }
> 
>         if (ops & (BUS_DMASYNC_POSTREAD | BUS_DMASYNC_PREWRITE)) {
> 
>                 for (; len > 0 && i < map->dm_nsegs; i++) {
>                         count = MIN(map->dm_segs[i].ds_len - offset, len);
>                         if (count > 0 && 
>                             iommu_dvmamap_sync_range(sb,
>                                 map->dm_segs[i].ds_addr + offset, count))
>                                 needsflush = 1;
>                         offset = 0;
>                         len -= count;
>                 }
> #ifdef DIAGNOSTIC
>                 if (i == map->dm_nsegs && len > 0)
>                         panic("iommu_dvmamap_sync: leftover %llu",
>                             (unsigned long long)len);
> #endif
> 
>                 if (needsflush)
>                         iommu_strbuf_flush_done(sb);
>         }
> }
> 
> iommu_dvmamap_sync_range() calls iommu_strbuf_flush()
> 
> #define iommu_strbuf_flush(i, v) do {                                   \
>         if ((i)->sb_flush)                                              \
>                 bus_space_write_8((i)->sb_is->is_bustag, (i)->sb_sb,    \
>                         STRBUFREG(strbuf_pgflush), (v));                \
>         } while (0)
> 
> 
> What I see is that "bus_dmamap_sync()" ends up calling "bus_space_write_8()" 
> to synchronize the memory. But nowhere I see that the kernel is waiting for 
> the sync to complete? And also there are no "membars" along the sync path.
> 
> Is bus_dmamap_sync() an asynchronous function then?

Some Sun I/O controllers have streaming buffers.  These buffers are 
basically an I/O cache.  They buffer one cache line at a time.  If an I/O
device does sub-cache line operations then some of the data may remain
in the I/O cache and may need to be flushed to memory.

The iommu_strbuf_flush() macro initiates the flush of one IOMMU page.
iommu_strbuf_flush_done() waits for the I/O controller's flush operation
to complete.

Streaming buffers exist on the SBus and real psycho controllers but not
the controllers built in to USIIi and USIIe processors.

Streaming buffers are great for large sequential I/O operations such as 
disk sectors or full ethernet buffers but lousy for small random I/O 
operations such as ethernet controller ring buffers, and probably USB
control structures.

Memory barriers should be part of the top level bus_dma and bus_space
macros, however, since the kernel uses the TOS memory model such 
operations are in almost all cases redundant.

I would recommend reviewing how DMA segments for one of the network 
driver's ring buffers are handled and do something similar.  Also, 
remember that SPARCs have IOMMUs so DMA addresses are virtual addresses, 
not physical addresses.  Confusion in this area has been a huge pain
in the past.

Eduardo