staging: gasket: support mapping of dma-bufs.
This allows userspace to pass in dma-buf file descriptors for
mapping in addition to userspace virtual memory pointers,
leaving cache maintenance to the dma-buf exporter and userspace
application.
Bug: 149764192
Change-Id: Iee8b581db0a461fe13ee8712d47d66f8fb16e7c9
Signed-off-by: Jonas Larsson <ljonas@google.com>
diff --git a/drivers/staging/gasket/gasket.h b/drivers/staging/gasket/gasket.h
index 93e7af1..bdf468f 100644
--- a/drivers/staging/gasket/gasket.h
+++ b/drivers/staging/gasket/gasket.h
@@ -74,6 +74,21 @@
u64 dma_address;
};
+/*
+ * Common structure for ioctls mapping and unmapping dma-bufs when using the
+ * Gasket page_table module.
+ * map: boolean, non-zero to map, 0 to unmap.
+ * flags: see gasket_page_table_ioctl_flags.flags.
+ */
+struct gasket_page_table_ioctl_dmabuf {
+ u64 page_table_index;
+ u64 device_address;
+ int dmabuf_fd;
+ u32 num_pages;
+ u32 map;
+ u32 flags;
+};
+
/* Base number for all Gasket-common IOCTLs */
#define GASKET_IOCTL_BASE 0xDC
@@ -152,4 +167,11 @@
#define GASKET_IOCTL_MAP_BUFFER_FLAGS \
_IOW(GASKET_IOCTL_BASE, 12, struct gasket_page_table_ioctl_flags)
+/*
+ * Tells the kernel to map/unmap dma-buf with fd to device_address in
+ * page_table_index page table.
+ */
+#define GASKET_IOCTL_MAP_DMABUF \
+ _IOW(GASKET_IOCTL_BASE, 13, struct gasket_page_table_ioctl_dmabuf)
+
#endif /* __GASKET_H__ */
diff --git a/drivers/staging/gasket/gasket_ioctl.c b/drivers/staging/gasket/gasket_ioctl.c
index d36861d..a709598 100644
--- a/drivers/staging/gasket/gasket_ioctl.c
+++ b/drivers/staging/gasket/gasket_ioctl.c
@@ -212,6 +212,38 @@
return 0;
}
+/* Map/unmap dma-buf to/from a device virtual address. */
+static int gasket_map_dmabuf(struct gasket_dev *gasket_dev,
+ struct gasket_page_table_ioctl_dmabuf __user *argp)
+{
+ struct gasket_page_table_ioctl_dmabuf dbuf;
+ struct gasket_page_table *pg_tbl;
+
+ if (copy_from_user(&dbuf, argp, sizeof(dbuf)))
+ return -EFAULT;
+
+ if (dbuf.page_table_index >= gasket_dev->num_page_tables)
+ return -EFAULT;
+
+ pg_tbl = gasket_dev->page_table[dbuf.page_table_index];
+ if (gasket_page_table_is_dev_addr_bad(pg_tbl,
+ dbuf.device_address,
+ dbuf.num_pages * PAGE_SIZE))
+ return -EINVAL;
+
+ if (dbuf.map)
+ return gasket_page_table_map_dmabuf(pg_tbl,
+ dbuf.dmabuf_fd,
+ dbuf.device_address,
+ dbuf.num_pages,
+ dbuf.flags);
+ else
+ return gasket_page_table_unmap_dmabuf(pg_tbl,
+ dbuf.dmabuf_fd,
+ dbuf.device_address,
+ dbuf.num_pages);
+}
+
/*
* Reserve structures for coherent allocation, and allocate or free the
* corresponding memory.
@@ -291,6 +323,7 @@
case GASKET_IOCTL_MAP_BUFFER:
case GASKET_IOCTL_MAP_BUFFER_FLAGS:
case GASKET_IOCTL_UNMAP_BUFFER:
+ case GASKET_IOCTL_MAP_DMABUF:
return alive && write;
case GASKET_IOCTL_CLEAR_EVENTFD:
@@ -388,6 +421,9 @@
trace_gasket_ioctl_integer_data(0);
retval = gasket_interrupt_reset_counts(gasket_dev);
break;
+ case GASKET_IOCTL_MAP_DMABUF:
+ retval = gasket_map_dmabuf(gasket_dev, argp);
+ break;
default:
/* If we don't understand the ioctl, the best we can do is trace
* the arg.
@@ -424,6 +460,7 @@
case GASKET_IOCTL_MAP_BUFFER:
case GASKET_IOCTL_MAP_BUFFER_FLAGS:
case GASKET_IOCTL_UNMAP_BUFFER:
+ case GASKET_IOCTL_MAP_DMABUF:
case GASKET_IOCTL_CLEAR_INTERRUPT_COUNTS:
case GASKET_IOCTL_CONFIG_COHERENT_ALLOCATOR:
return 1;
diff --git a/drivers/staging/gasket/gasket_page_table.c b/drivers/staging/gasket/gasket_page_table.c
index b5438da..5ae2024 100644
--- a/drivers/staging/gasket/gasket_page_table.c
+++ b/drivers/staging/gasket/gasket_page_table.c
@@ -42,9 +42,11 @@
#include "gasket_page_table.h"
#include <linux/device.h>
+#include <linux/dma-buf.h>
#include <linux/file.h>
#include <linux/init.h>
#include <linux/kernel.h>
+#include <linux/list.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/pagemap.h>
@@ -177,6 +179,15 @@
u32 in_use;
};
+/* Storage for dmabuf mapping information. */
+struct gasket_dmabuf_mapping {
+ struct dma_buf *dmabuf;
+ struct dma_buf_attachment *attachment;
+ struct sg_table *sgt;
+ enum dma_data_direction direction;
+ struct list_head list;
+};
+
/*
* [Host-side] page table descriptor.
*
@@ -237,6 +248,9 @@
* gasket_mmap function, so user_virt belongs in the driver anyhow.
*/
struct gasket_coherent_page_entry *coherent_pages;
+
+ /* List of dmabufs currently attached and mapped. */
+ struct list_head dmabufs;
};
/* See gasket_page_table.h for description. */
@@ -306,6 +320,7 @@
(u64 __iomem *)&bar_data->virt_base[page_table_config->extended_reg];
pg_tbl->device = get_device(device);
pg_tbl->pci_dev = pci_dev;
+ INIT_LIST_HEAD(&pg_tbl->dmabufs);
dev_dbg(device, "Page table initialized successfully\n");
@@ -481,7 +496,9 @@
*/
static int gasket_perform_mapping(struct gasket_page_table *pg_tbl,
struct gasket_page_table_entry *ptes,
- u64 __iomem *slots, ulong host_addr,
+ u64 __iomem *slots,
+ struct sg_page_iter *sg_iter,
+ ulong host_addr,
uint num_pages, u32 flags,
int is_simple_mapping)
{
@@ -492,6 +509,12 @@
ulong page_addr;
int i;
+ /* Must have a virtual host address or a sg iterator, but not both. */
+ if(!((uintptr_t)host_addr ^ (uintptr_t)sg_iter)) {
+ dev_err(pg_tbl->device, "need sg_iter or host_addr\n");
+ return -EINVAL;
+ }
+
if (GET(FLAGS_DMA_DIRECTION, flags) == DMA_NONE) {
dev_err(pg_tbl->device, "invalid DMA direction flags=0x%lx\n",
(unsigned long)flags);
@@ -502,7 +525,15 @@
page_addr = host_addr + i * PAGE_SIZE;
offset = page_addr & (PAGE_SIZE - 1);
dev_dbg(pg_tbl->device, "%s i %d\n", __func__, i);
- if (is_coherent(pg_tbl, host_addr)) {
+ if (sg_iter) {
+ if (!__sg_page_iter_next(sg_iter))
+ return -EINVAL;
+
+ /* Page already mapped for DMA. */
+ ptes[i].dma_addr = sg_page_iter_dma_address(sg_iter);
+ ptes[i].page = NULL;
+ offset = 0;
+ } else if (is_coherent(pg_tbl, host_addr)) {
u64 off =
(u64)host_addr -
(u64)pg_tbl->coherent_pages[0].user_virt;
@@ -853,6 +884,7 @@
* If there is an error, no pages are mapped.
*/
static int gasket_map_simple_pages(struct gasket_page_table *pg_tbl,
+ struct sg_page_iter *sg_iter,
ulong host_addr, u64 dev_addr,
uint num_pages, u32 flags)
{
@@ -869,8 +901,8 @@
}
ret = gasket_perform_mapping(pg_tbl, pg_tbl->entries + slot_idx,
- pg_tbl->base_slot + slot_idx, host_addr,
- num_pages, flags, 1);
+ pg_tbl->base_slot + slot_idx, sg_iter,
+ host_addr, num_pages, flags, 1);
if (ret) {
gasket_page_table_unmap_nolock(pg_tbl, dev_addr, num_pages);
@@ -1000,6 +1032,7 @@
* If there is an error, no pages are mapped.
*/
static int gasket_map_extended_pages(struct gasket_page_table *pg_tbl,
+ struct sg_page_iter *sg_iter,
ulong host_addr, u64 dev_addr,
uint num_pages, u32 flags)
{
@@ -1034,8 +1067,8 @@
slot_base =
(u64 __iomem *)(page_address(pte->page) + pte->offset);
ret = gasket_perform_mapping(pg_tbl, pte->sublevel + slot_idx,
- slot_base + slot_idx, host_addr,
- len, flags, 0);
+ slot_base + slot_idx, sg_iter,
+ host_addr, len, flags, 0);
if (ret) {
gasket_page_table_unmap_nolock(pg_tbl, dev_addr,
num_pages);
@@ -1053,7 +1086,8 @@
remain -= len;
slot_idx = 0;
pte++;
- host_addr += len * PAGE_SIZE;
+ if (host_addr)
+ host_addr += len * PAGE_SIZE;
}
return 0;
@@ -1078,10 +1112,10 @@
mutex_lock(&pg_tbl->mutex);
if (gasket_addr_is_simple(pg_tbl, dev_addr)) {
- ret = gasket_map_simple_pages(pg_tbl, host_addr, dev_addr,
+ ret = gasket_map_simple_pages(pg_tbl, NULL, host_addr, dev_addr,
num_pages, flags);
} else {
- ret = gasket_map_extended_pages(pg_tbl, host_addr, dev_addr,
+ ret = gasket_map_extended_pages(pg_tbl, NULL, host_addr, dev_addr,
num_pages, flags);
}
@@ -1116,8 +1150,144 @@
}
EXPORT_SYMBOL(gasket_page_table_unmap);
+int gasket_page_table_map_dmabuf(struct gasket_page_table *pg_tbl, int fd,
+ u64 dev_addr, uint num_pages, u32 flags)
+{
+ int ret, locked = 0;
+ struct dma_buf *dmabuf = NULL;
+ struct dma_buf_attachment *attachment = NULL;
+ struct sg_table *sgt = NULL;
+ struct sg_page_iter sg_iter;
+ struct gasket_dmabuf_mapping *mapping = NULL;
+ enum dma_data_direction direction = GET(FLAGS_DMA_DIRECTION, flags);
+
+ if (direction == DMA_NONE) {
+ dev_err(pg_tbl->device,
+ "invalid DMA direction flags=0x%x\n", flags);
+ return -EINVAL;
+ }
+
+ if (!num_pages)
+ return 0;
+
+ dmabuf = dma_buf_get(fd);
+ if (IS_ERR(dmabuf))
+ return PTR_ERR(dmabuf);
+
+ if (PAGE_ALIGN(dmabuf->size) / PAGE_SIZE < num_pages)
+ return -EINVAL;
+
+ mapping = kzalloc(sizeof(*mapping), GFP_KERNEL);
+ if (!mapping) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ attachment = dma_buf_attach(dmabuf, pg_tbl->device);
+ if (IS_ERR(attachment)) {
+ ret = PTR_ERR(attachment);
+ goto out;
+ }
+
+ sgt = dma_buf_map_attachment(attachment, direction);
+ if (IS_ERR(sgt)) {
+ ret = PTR_ERR(sgt);
+ goto out;
+ }
+
+ mutex_lock(&pg_tbl->mutex);
+ locked = 1;
+
+ __sg_page_iter_start(&sg_iter, sgt->sgl, sgt->nents, 0);
+ if (gasket_addr_is_simple(pg_tbl, dev_addr)) {
+ ret = gasket_map_simple_pages(pg_tbl, &sg_iter, 0, dev_addr,
+ num_pages, flags);
+ } else {
+ ret = gasket_map_extended_pages(pg_tbl, &sg_iter, 0, dev_addr,
+ num_pages, flags);
+ }
+
+ if (!ret) {
+ INIT_LIST_HEAD(&mapping->list);
+ get_dma_buf(dmabuf);
+ mapping->dmabuf = dmabuf;
+ mapping->attachment = attachment;
+ mapping->sgt = sgt;
+ mapping->direction = direction;
+ list_add(&mapping->list, &pg_tbl->dmabufs);
+ sgt = NULL;
+ attachment = NULL;
+ mapping = NULL;
+ }
+
+out:
+ if (locked)
+ mutex_unlock(&pg_tbl->mutex);
+
+ if (!IS_ERR_OR_NULL(sgt))
+ dma_buf_unmap_attachment(attachment, sgt, direction);
+
+ if (!IS_ERR_OR_NULL(attachment))
+ dma_buf_detach(dmabuf, attachment);
+
+ kfree(mapping);
+ dma_buf_put(dmabuf);
+
+ return ret;
+}
+EXPORT_SYMBOL(gasket_page_table_map_dmabuf);
+
+/* Detach dmabuf from our device if attached, NULL to detach all. */
+static void gasket_page_table_detach_dmabuf_nolock(struct gasket_page_table *pg_tbl,
+ struct dma_buf *dmabuf)
+{
+ struct gasket_dmabuf_mapping *mapping, *tmp;
+
+ list_for_each_entry_safe(mapping, tmp, &pg_tbl->dmabufs, list) {
+ if (!dmabuf || mapping->dmabuf == dmabuf) {
+ dma_buf_unmap_attachment(mapping->attachment,
+ mapping->sgt,
+ mapping->direction);
+ dma_buf_detach(mapping->dmabuf, mapping->attachment);
+ dma_buf_put(mapping->dmabuf);
+ list_del(&mapping->list);
+ kfree(mapping);
+ }
+ }
+}
+
+int gasket_page_table_unmap_dmabuf(struct gasket_page_table *pg_tbl, int fd,
+ u64 dev_addr, uint num_pages)
+{
+ struct dma_buf *dmabuf;
+ struct gasket_dmabuf_mapping *mapping, *tmp;
+
+ dmabuf = dma_buf_get(fd);
+ if (IS_ERR(dmabuf))
+ return PTR_ERR(dmabuf);
+
+ if (PAGE_ALIGN(dmabuf->size) / PAGE_SIZE < num_pages) {
+ dma_buf_put(dmabuf);
+ return -EINVAL;
+ }
+
+ mutex_lock(&pg_tbl->mutex);
+
+ gasket_page_table_unmap_nolock(pg_tbl, dev_addr, num_pages);
+ gasket_page_table_detach_dmabuf_nolock(pg_tbl, dmabuf);
+
+ mutex_unlock(&pg_tbl->mutex);
+
+ dma_buf_put(dmabuf);
+
+ return 0;
+}
+EXPORT_SYMBOL(gasket_page_table_unmap_dmabuf);
+
static void gasket_page_table_unmap_all_nolock(struct gasket_page_table *pg_tbl)
{
+ gasket_page_table_detach_dmabuf_nolock(pg_tbl, NULL);
+
gasket_unmap_simple_pages(pg_tbl,
gasket_components_to_dev_address(pg_tbl, 1, 0,
0),
diff --git a/drivers/staging/gasket/gasket_page_table.h b/drivers/staging/gasket/gasket_page_table.h
index 609e1d9..c203c57 100644
--- a/drivers/staging/gasket/gasket_page_table.h
+++ b/drivers/staging/gasket/gasket_page_table.h
@@ -100,6 +100,38 @@
u64 dev_addr, uint num_pages, u32 flags);
/*
+ * Map dma-buf pages into device memory.
+ * @page_table: Gasket page table pointer.
+ * @fd: Dma-buf file descriptor.
+ * @dev_addr: Starting device address of the pages.
+ * @num_pages: Number of [4kB] pages to map.
+ * @flags: Specifies attributes to apply to the pages.
+ * Internal structure matches gasket_page_table_ioctl_flags.flags.
+ *
+ * Description: Maps "num_pages" pages of dma-buf pointed to by
+ * fd to the address "dev_addr" in device memory.
+ *
+ * The caller is responsible for checking the dev_addr range.
+ *
+ * Returns 0 if successful or a non-zero error number otherwise.
+ * If there is an error, no pages are mapped.
+ */
+int gasket_page_table_map_dmabuf(struct gasket_page_table *page_table, int fd,
+ u64 dev_addr, uint num_pages, u32 flags);
+
+/*
+ * Unmap dma-buf pages from device memory.
+ * @page_table: Gasket page table pointer.
+ * @fd: Dma-buf file descriptor.
+ * @dev_addr: Starting device address of the pages.
+ * @num_pages: Number of [4kB] pages to map.
+ *
+ * Description: The inverse of gasket_page_table_map_dmabuf.
+ */
+int gasket_page_table_unmap_dmabuf(struct gasket_page_table *page_table, int fd,
+ u64 dev_addr, uint num_pages);
+
+/*
* Un-map host pages from device memory.
* @page_table: Gasket page table pointer.
* @dev_addr: Starting device address of the pages to unmap.