Adding support for WC (write-combining) memory to bus_dma



I have a need to allocate static DMA memory via bus_dmamem_alloc() that is
also WC (for a PCI-e device so it can use "nosnoop" transactions). This is
similar to what the nvidia driver needs, but in my case it is much cleaner to
allocate the memory via bus dma since the existing code I am extending all
uses busdma.

I have a patch to implement this on 8.x for amd64 that I can port to HEAD if
folks don't object. What I would really like to do is add a new paramter to
bus_dmamem_alloc() to specify the memory attribute to use, but I am hesitant
to break that API. Instead, I added a new flag similar to the existing
BUS_DMA_NOCACHE used to allocate UC memory.

While doing this, I ran into an old bug, which is that if you were to call
bus_dmamem_alloc() with BUS_DMA_NOCACHE but a tag that otherwise fell through
to using malloc() instead of contigmalloc(), bus_dmamem_alloc() would actually
change the state of the entire page. This seems wrong. Instead, I think that
any request for a non-default memory attribute should always use
contigmalloc(). In fact, even better is to call kmem_alloc_contig() directly
rather than using contigmalloc(). However, if you change this, then
bus_dmamem_free() won't always DTRT as it doesn't have enough state to know if
a small allocation should be free'd via free() or contigfree() (the latter
would be required if it used a non-default memory attribute). The fix I used
for this was to create a new dummy dmamap that is returned by bus_dmamem_alloc
if it uses contigmalloc(). bus_dmamem_free() then checks the passed in map
pointer to decide which type of free to perform. Once this is fixed, the
actual WC support is rather trivial as it merely consists of passing a
different argument to kmem_alloc_contig().

Oh, and using kmem_alloc_contig() instead of the pmap_change_attr() hack is
required if you want to be able to export the same pages to userland via
mmap (e.g. using an OBJT_SG object). :)

Peter, this is somewhat orthognal (but related) to your bus_dma patch which is
what prompted me to post this.

Patch for 8 is below. Porting it to HEAD should be fairly trivial and direct.

Index: amd64/amd64/busdma_machdep.c
===================================================================
--- amd64/amd64/busdma_machdep.c (revision 225604)
+++ amd64/amd64/busdma_machdep.c (working copy)
@@ -42,6 +42,8 @@
#include <sys/sysctl.h>

#include <vm/vm.h>
+#include <vm/vm_extern.h>
+#include <vm/vm_kern.h>
#include <vm/vm_page.h>
#include <vm/vm_map.h>

@@ -125,7 +127,7 @@

static STAILQ_HEAD(, bus_dmamap) bounce_map_waitinglist;
static STAILQ_HEAD(, bus_dmamap) bounce_map_callbacklist;
-static struct bus_dmamap nobounce_dmamap;
+static struct bus_dmamap nobounce_dmamap, contig_dmamap;

static void init_bounce_pages(void *dummy);
static int alloc_bounce_zone(bus_dma_tag_t dmat);
@@ -455,7 +457,7 @@
int
bus_dmamap_destroy(bus_dma_tag_t dmat, bus_dmamap_t map)
{
- if (map != NULL && map != &nobounce_dmamap) {
+ if (map != NULL && map != &nobounce_dmamap && map != &contig_dmamap) {
if (STAILQ_FIRST(&map->bpages) != NULL) {
CTR3(KTR_BUSDMA, "%s: tag %p error %d",
__func__, dmat, EBUSY);
@@ -480,6 +482,7 @@
bus_dmamem_alloc(bus_dma_tag_t dmat, void** vaddr, int flags,
bus_dmamap_t *mapp)
{
+ vm_memattr_t attr;
int mflags;

if (flags & BUS_DMA_NOWAIT)
@@ -502,6 +505,12 @@
}
if (flags & BUS_DMA_ZERO)
mflags |= M_ZERO;
+ if (flags & BUS_DMA_WRITE_COMBINING)
+ attr = VM_MEMATTR_WRITE_COMBINING;
+ else if (flags & BUS_DMA_NOCACHE)
+ attr = VM_MEMATTR_UNCACHEABLE;
+ else
+ attr = VM_MEMATTR_DEFAULT;

/*
* XXX:
@@ -513,7 +522,8 @@
*/
if ((dmat->maxsize <= PAGE_SIZE) &&
(dmat->alignment < dmat->maxsize) &&
- dmat->lowaddr >= ptoa((vm_paddr_t)Maxmem)) {
+ dmat->lowaddr >= ptoa((vm_paddr_t)Maxmem) &&
+ attr == VM_MEMATTR_DEFAULT) {
*vaddr = malloc(dmat->maxsize, M_DEVBUF, mflags);
} else {
/*
@@ -522,9 +532,10 @@
* multi-seg allocations yet though.
* XXX Certain AGP hardware does.
*/
- *vaddr = contigmalloc(dmat->maxsize, M_DEVBUF, mflags,
- 0ul, dmat->lowaddr, dmat->alignment? dmat->alignment : 1ul,
- dmat->boundary);
+ *vaddr = (void *)kmem_alloc_contig(kernel_map, dmat->maxsize,
+ mflags, 0ul, dmat->lowaddr, dmat->alignment ?
+ dmat->alignment : 1ul, dmat->boundary, attr);
+ *mapp = &contig_dmamap;
}
if (*vaddr == NULL) {
CTR4(KTR_BUSDMA, "%s: tag %p tag flags 0x%x error %d",
@@ -533,9 +544,6 @@
} else if ((uintptr_t)*vaddr & (dmat->alignment - 1)) {
printf("bus_dmamem_alloc failed to align memory properly.\n");
}
- if (flags & BUS_DMA_NOCACHE)
- pmap_change_attr((vm_offset_t)*vaddr, dmat->maxsize,
- PAT_UNCACHEABLE);
CTR4(KTR_BUSDMA, "%s: tag %p tag flags 0x%x error %d",
__func__, dmat, dmat->flags, 0);
return (0);
@@ -550,18 +558,15 @@
{
/*
* dmamem does not need to be bounced, so the map should be
- * NULL
+ * NULL if malloc() was used and contig_dmamap if
+ * contigmalloc() was used.
*/
- if (map != NULL)
+ if (!(map == NULL || map == &contig_dmamap))
panic("bus_dmamem_free: Invalid map freed\n");
- pmap_change_attr((vm_offset_t)vaddr, dmat->maxsize, PAT_WRITE_BACK);
- if ((dmat->maxsize <= PAGE_SIZE) &&
- (dmat->alignment < dmat->maxsize) &&
- dmat->lowaddr >= ptoa((vm_paddr_t)Maxmem))
+ if (map == NULL)
free(vaddr, M_DEVBUF);
- else {
- contigfree(vaddr, dmat->maxsize, M_DEVBUF);
- }
+ else
+ kmem_free(kernel_map, (vm_offset_t)vaddr, dmat->maxsize);
CTR3(KTR_BUSDMA, "%s: tag %p flags 0x%x", __func__, dmat, dmat->flags);
}

@@ -588,7 +593,7 @@
bus_addr_t paddr;
int seg;

- if (map == NULL)
+ if (map == NULL || map == &contig_dmamap)
map = &nobounce_dmamap;

if ((map != &nobounce_dmamap && map->pagesneeded == 0)
@@ -1119,7 +1124,7 @@
struct bounce_page *bpage;

KASSERT(dmat->bounce_zone != NULL, ("no bounce zone in dma tag"));
- KASSERT(map != NULL && map != &nobounce_dmamap,
+ KASSERT(map != NULL && map != &nobounce_dmamap && map != &contig_dmamap,
("add_bounce_page: bad map %p", map));

bz = dmat->bounce_zone;
Index: sys/bus_dma.h
===================================================================
--- sys/bus_dma.h (revision 225604)
+++ sys/bus_dma.h (working copy)
@@ -101,6 +101,7 @@
*/
#define BUS_DMA_NOWRITE 0x100
#define BUS_DMA_NOCACHE 0x200
+#define BUS_DMA_WRITE_COMBINING 0x400

/*
* The following flag is a DMA tag hint that the page offset of the

--
John Baldwin
_______________________________________________
freebsd-current@xxxxxxxxxxx mailing list
http://lists.freebsd.org/mailman/listinfo/freebsd-current
To unsubscribe, send any mail to "freebsd-current-unsubscribe@xxxxxxxxxxx"



Relevant Pages

  • Re: NdisAllocateMemoryWithTag(Priority) -> Does it ensure Congtiguous physical memory by default
    ... I believe in W2K8 if your device does 64 bit DMA, when you call NdisMAllocateSharedMemory you will get high physical addresses before getting low addresses. ... NDIS does not provide an API to allocate physically contiguous memory outside what HAL DMA APIs provide. ...
    (microsoft.public.development.device.drivers)
  • Re: Adding support for WC (write-combining) memory to bus_dma
    ... bus_dmamem_allocto specify the memory attribute to use, ... BUS_DMA_NOCACHE used to allocate UC memory. ... to using mallocinstead of contigmalloc(), ... this is somewhat orthognal to your bus_dma patch which is ...
    (freebsd-current)
  • Re: 1G DMA buffer under XP - how?
    ... system memory, and what is address 0 to the controller could be something ... so only the HAL's DMA routines can allocate ... common buffer correctly in all situations. ... My card is happy with any physical address> below 4G, and I can tell this call to only allocate below the given> HighestAcceptableAddress. ...
    (microsoft.public.development.device.drivers)
  • Re: Adding support for WC (write-combining) memory to bus_dma
    ... I have a need to allocate static DMA memory via bus_dmamem_allocthat is ... with API design and interface compatibility. ...
    (freebsd-current)
  • How to handle a specific DMA configuration ?
    ... My physical memory mapping is a bit special: ... last 128MB are usable for DMA. ... My problem is the allocation of skbuff. ... even if a I allow Linux to allocate memory between ...
    (Linux-Kernel)