ioat: support running in a process's address space
Whenever a device is assigned to a process, it's DMA *allocations* come
from the process's dma_arena. These addresses are user addresses: both
driver and device. So when we operate in the driver and touch those
bits, we need to be in the user's address space.
Note that dma_map_page() will not return a user-address. We could try
to wire that up, but it's a bit of a pull. The issue is that fresh
allocations are easy, but taking an existing kernel page and mapping it
into the user is a little tougher.
Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
diff --git a/kern/drivers/dma/ioat/init.c b/kern/drivers/dma/ioat/init.c
index 5c6622c..cb9ae76 100644
--- a/kern/drivers/dma/ioat/init.c
+++ b/kern/drivers/dma/ioat/init.c
@@ -574,6 +574,19 @@
if (err)
goto err_setup_interrupts;
+ /* We need to skip the dma_self_test when we are in a user's address
+ * space. The test calls dma_map_page(), which maps an arbitrary kernel
+ * address in the driver. We could try to hook up something where we
+ * remap that page in userspace, but it doesn't seem worth it.
+ *
+ * We are called via pci_ops->init, with the pdev qlock held, so
+ * proc_owner will not change */
+ if (pdev->proc_owner) {
+ dev_info(dev, "skipping self-test: assigned to %d\n",
+ pdev->proc_owner->pid);
+ return 0;
+ }
+
err = ioat3_dma_self_test(ioat_dma);
if (err)
goto err_self_test;
@@ -816,6 +829,32 @@
return -EFAULT;
}
+/* These wrappers switch the driver into the process's address space. Any time
+ * the driver wants to access DMA mapped memory, it needs to be in the user's
+ * address space. That memory came from the user's DMA arena, and the 'driver'
+ * address for that memory is also a user virtual address, not a kernel one. */
+static void ioat_timer_event_wrapper(struct timer_list *t)
+{
+ struct ioatdma_chan *ioat_chan = from_timer(ioat_chan, t, timer);
+ struct pci_device *pdev = to_pdev(ioat_chan);
+ uintptr_t prev = switch_to(pdev->proc_owner);
+
+ ioat_timer_event(t);
+
+ switch_back(pdev->proc_owner, prev);
+}
+
+static void ioat_cleanup_event_wrapper(unsigned long data)
+{
+ struct ioatdma_chan *ioat_chan = to_ioat_chan((void *)data);
+ struct pci_device *pdev = to_pdev(ioat_chan);
+ uintptr_t prev = switch_to(pdev->proc_owner);
+
+ ioat_cleanup_event(data);
+
+ switch_back(pdev->proc_owner, prev);
+}
+
/* common channel initialization */
static void
ioat_init_channel(struct ioatdma_device *ioat_dma,
@@ -832,8 +871,9 @@
dma_cookie_init(&ioat_chan->dma_chan);
list_add_tail(&ioat_chan->dma_chan.device_node, &dma->channels);
ioat_dma->idx[idx] = ioat_chan;
- timer_setup(&ioat_chan->timer, ioat_timer_event, 0);
- tasklet_init(&ioat_chan->cleanup_task, ioat_cleanup_event, data);
+ timer_setup(&ioat_chan->timer, ioat_timer_event_wrapper, 0);
+ tasklet_init(&ioat_chan->cleanup_task, ioat_cleanup_event_wrapper,
+ data);
}
#define IOAT_NUM_SRC_TEST 6 /* must be <= 8 */