Skip site navigation (1)Skip section navigation (2)
Date:      Fri, 24 Apr 2015 15:13:05 +0200
From:      Svatopluk Kraus <onwahe@gmail.com>
To:        FreeBSD Arch <freebsd-arch@freebsd.org>
Subject:   bus_dmamap_sync() for bounced client buffers from user address space
Message-ID:  <CAFHCsPXMjge84AR2cR8KXMXWP4kH2YvuV_uqtPKUvn5C3ygknw@mail.gmail.com>

next in thread | raw e-mail | index | archive | help
DMA can be done on client buffer from user address space. For example,
thru bus_dmamap_load_uio() when uio->uio_segflg is UIO_USERSPACE. Such
client buffer can bounce and then, it must be copied to and from
bounce buffer in bus_dmamap_sync().

Current implementations (in all archs) do not take into account that
bus_dmamap_sync() is asynchronous for POSTWRITE and POSTREAD in
general. It can be asynchronous for PREWRITE and PREREAD too. For
example, in some driver implementations where DMA client buffers
operations are buffered. In those cases, simple bcopy() is not
correct.

Demonstration of current implementation (x86) is the following:

-----------------------------
struct bounce_page {
    vm_offset_t vaddr;      /* kva of bounce buffer */
    bus_addr_t  busaddr;    /* Physical address */
    vm_offset_t datavaddr;  /* kva of client data */
    bus_addr_t  dataaddr;   /* client physical address */
    bus_size_t  datacount;  /* client data count */
    STAILQ_ENTRY(bounce_page) links;
};


if ((op & BUS_DMASYNC_PREWRITE) != 0) {
    while (bpage != NULL) {
        if (bpage->datavaddr != 0) {
            bcopy((void *)bpage->datavaddr,
                (void *)bpage->vaddr,
                bpage->datacount);
        } else {
            physcopyout(bpage->dataaddr,
                (void *)bpage->vaddr,
                bpage->datacount);
        }
        bpage = STAILQ_NEXT(bpage, links);
    }
    dmat->bounce_zone->total_bounced++;
}
-----------------------------

There are two things:

(1) datavaddr is not always kva of client data, but sometimes it can
be uva of client data.
(2) bcopy() can be used only if datavaddr is kva or when map->pmap is
current pmap.

Note that there is an implication for bus_dmamap_load_uio() with
uio->uio_segflg set to UIO_USERSPACE that used physical pages are
in-core and wired. See "man bus_dma".

There is not public interface to check that map->pmap is current pmap.
So one solution is the following:

if (bpage->datavaddr >= VM_MIN_KERNEL_ADDRESS) {
	bcopy();
} else {
	physcopy();
}

If there will be public pmap_is_current() then another solution is the
following:

if (bpage->datavaddr != 0) && pmap_is_current(map->pmap)) {
	bcopy();
} else {
	physcopy();
}

The second solution implies that context switch must not happen during
bus_dmamap_sync() called from an interrupt routine. However, IMO, it's
granted.

Note that map->pmap should be always kernel_pmap for datavaddr >=
VM_MIN_KERNEL_ADDRESS.

Comments, different solutions, or objections?

Svatopluk Kraus



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?CAFHCsPXMjge84AR2cR8KXMXWP4kH2YvuV_uqtPKUvn5C3ygknw>