Index: uspace/lib/usb/src/dma_buffer.c
===================================================================
--- uspace/lib/usb/src/dma_buffer.c	(revision cc63815c45bec69f85b3455ff1f4c5dcc15c228c)
+++ uspace/lib/usb/src/dma_buffer.c	(revision 1d758fc6d12dfedeea03efca816537794a056af2)
@@ -39,9 +39,45 @@
 #include "usb/dma_buffer.h"
 
+dma_policy_t dma_policy_create(unsigned flags, size_t chunk_size)
+{
+	assert((chunk_size & (chunk_size - 1)) == 0); /* Check if power of 2 */
+	assert(chunk_size >= PAGE_SIZE || chunk_size == 0);
+
+	return ((chunk_size - 1) & DMA_POLICY_CHUNK_SIZE_MASK)
+		| (flags & DMA_POLICY_FLAGS_MASK);
+}
+
+/**
+ * As the driver is typically using only a few buffers at once, we cache the
+ * physical mapping to avoid calling the kernel unnecessarily often. This cache
+ * is global for a task.
+ *
+ * TODO: "few" is currently limited to one.
+ */
+static struct {
+	const void *last;
+	uintptr_t phys;
+} phys_mapping_cache = { 0 };
+
+static void cache_insert(const void *v, uintptr_t p)
+{
+	phys_mapping_cache.last = v;
+	phys_mapping_cache.phys = p;
+}
+
+static void cache_evict(const void *v)
+{
+	if (phys_mapping_cache.last == v)
+		phys_mapping_cache.last = NULL;
+}
+
+static bool cache_find(const void *v, uintptr_t *p)
+{
+	*p = phys_mapping_cache.phys;
+	return phys_mapping_cache.last == v;
+}
+
 /**
  * Allocate a DMA buffer.
- *
- * XXX: Currently cannot make much use of missing constraints, as it always
- * allocates page-aligned contiguous buffer. We rely on it in dma_buffer_phys.
  *
  * @param[in] db dma_buffer_t structure to fill
@@ -62,18 +98,20 @@
 	void *address = AS_AREA_ANY;
 
-	const int ret = dmamem_map_anonymous(real_size,
+	const int err = dmamem_map_anonymous(real_size,
 	    flags, AS_AREA_READ | AS_AREA_WRITE, 0,
 	    &phys, &address);
-
-	if (ret == EOK) {
-		/* Access the pages to force mapping */
-		volatile char *buf = address;
-		for (size_t i = 0; i < size; i += PAGE_SIZE)
-			buf[i] = 0xff;
-
-		db->virt = address;
-		db->phys = phys;
-	}
-	return ret;
+	if (err)
+		return err;
+
+	/* Access the pages to force mapping */
+	volatile char *buf = address;
+	for (size_t i = 0; i < size; i += PAGE_SIZE)
+		buf[i] = 0xff;
+
+	db->virt = address;
+	db->policy = dma_policy_create(policy, 0);
+	cache_insert(db->virt, phys);
+
+	return EOK;
 }
 
@@ -102,5 +140,5 @@
 		dmamem_unmap_anonymous(db->virt);
 		db->virt = NULL;
-		db->phys = 0;
+		db->policy = 0;
 	}
 }
@@ -112,73 +150,38 @@
  * @param[in] virt Pointer somewhere inside db
  */
-uintptr_t dma_buffer_phys(const dma_buffer_t *db, void *virt)
-{
-	return db->phys + (virt - db->virt);
-}
-
-/**
- * Check whether a memory area is compatible with a policy.
- *
- * Useful to skip copying when the buffer is already ready to be given to
- * hardware as is.
- *
- * Note that the "as_get_physical_mapping" fails when the page is not mapped
- * yet, and that the caller is responsible for forcing the mapping.
- */
-bool dma_buffer_check_policy(const void *buffer, size_t size, const dma_policy_t policy)
-{
-	uintptr_t addr = (uintptr_t) buffer;
-
-	const bool check_4gib       = !!(policy & DMA_POLICY_4GiB);
-	const bool check_crossing   = !!(policy & DMA_POLICY_NOT_CROSSING);
-	const bool check_alignment  = !!(policy & DMA_POLICY_PAGE_ALIGNED);
-	const bool check_contiguous = !!(policy & DMA_POLICY_CONTIGUOUS);
-
-	/* Check the two conditions that are easy */
-	if (check_crossing && (addr + size - 1) / PAGE_SIZE != addr / PAGE_SIZE)
-		goto violated;
-
-	if (check_alignment && ((uintptr_t) buffer) % PAGE_SIZE)
-		goto violated;
-
-	/*
-	 * For these conditions, we need to walk through pages and check
-	 * physical address of each one
-	 */
-	if (check_contiguous || check_4gib) {
-		const void *virt = buffer;
-		uintptr_t phys;
-
-		/* Get the mapping of the first page */
-		if (as_get_physical_mapping(virt, &phys))
-			goto error;
-
-		/* First page can already break 4GiB condition */
-		if (check_4gib && (phys & DMAMEM_4GiB) != 0)
-			goto violated;
-
-		while (size >= PAGE_SIZE) {
-			/* Move to the next page */
-			virt += PAGE_SIZE;
-			size -= PAGE_SIZE;
-
-			uintptr_t last_phys = phys;
-			if (as_get_physical_mapping(virt, &phys))
-				goto error;
-
-			if (check_contiguous && (phys - last_phys) != PAGE_SIZE)
-				goto violated;
-
-			if (check_4gib && (phys & DMAMEM_4GiB) != 0)
-				goto violated;
-		}
-	}
-
-	/* All checks passed */
+uintptr_t dma_buffer_phys(const dma_buffer_t *db, const void *virt)
+{
+	const size_t chunk_mask = dma_policy_chunk_mask(db->policy);
+	const uintptr_t offset = (virt - db->virt) & chunk_mask;
+	const void *chunk_base = virt - offset;
+
+	uintptr_t phys;
+
+	if (!cache_find(chunk_base, &phys)) {
+		if (as_get_physical_mapping(chunk_base, &phys))
+			return 0;
+		cache_insert(chunk_base, phys);
+	}
+
+	return phys + offset;
+}
+
+static bool dma_buffer_is_4gib(dma_buffer_t *db, size_t size)
+{
+	if (sizeof(uintptr_t) <= 32)
+		return true;
+
+	const size_t chunk_size = dma_policy_chunk_mask(db->policy) + 1;
+	const size_t chunks = chunk_size ? 1 : size / chunk_size;
+
+	for (size_t c = 0; c < chunks; c++) {
+		const void *addr = db->virt + (c * chunk_size);
+		const uintptr_t phys = dma_buffer_phys(db, addr);
+	
+		if ((phys & DMAMEM_4GiB) != 0)
+			return false;
+	}
+
 	return true;
-
-violated:
-error:
-	return false;
 }
 
@@ -192,6 +195,22 @@
 errno_t dma_buffer_lock(dma_buffer_t *db, void *virt, size_t size)
 {
+	assert(virt);
+
+	uintptr_t phys;
+
+	const errno_t err = dmamem_map(virt, size, 0, 0, &phys);
+	if (err)
+		return err;
+
 	db->virt = virt;
-	return dmamem_map(db->virt, size, 0, 0, &db->phys);
+	db->policy = dma_policy_create(0, PAGE_SIZE);
+	cache_insert(virt, phys);
+
+	unsigned flags = -1U;
+	if (!dma_buffer_is_4gib(db, size))
+		flags &= ~DMA_POLICY_4GiB;
+	db->policy = dma_policy_create(flags, PAGE_SIZE);
+
+	return EOK;
 }
 
@@ -204,6 +223,23 @@
 		dmamem_unmap(db->virt, size);
 		db->virt = NULL;
-		db->phys = 0;
-	}
+		db->policy = 0;
+	}
+}
+
+/**
+ * Must be called when the buffer is received over IPC. Clears potentially
+ * leftover value from different buffer mapped to the same virtual address.
+ */
+void dma_buffer_acquire(dma_buffer_t *db)
+{
+	cache_evict(db->virt);
+}
+
+/**
+ * Counterpart of acquire.
+ */
+void dma_buffer_release(dma_buffer_t *db)
+{
+	cache_evict(db->virt);
 }
 
