Skip to content

Instantly share code, notes, and snippets.

@dv1
Created May 6, 2022 08:36
Show Gist options
  • Save dv1/7c40329d1cf83529eb989344f046d899 to your computer and use it in GitHub Desktop.
Save dv1/7c40329d1cf83529eb989344f046d899 to your computer and use it in GitHub Desktop.
#include <stdio.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <inttypes.h>
#include <time.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/ion.h>
#include <linux/dma-buf.h>
#include <linux/dma-heap.h>
#include <linux/version.h>
typedef struct
{
int fd;
int size;
void *mapped_virtual_address;
int mmap_prot;
}
dmabuf_info;
typedef struct _allocator
{
int fd;
int (*allocate_dmabuf)(dmabuf_info *dmabuf, struct _allocator *alloc, int size);
int (*sync_start)(dmabuf_info *dmabuf);
int (*sync_stop)(dmabuf_info *dmabuf);
}
allocator;
static unsigned int get_ion_heap_id_mask(int ion_fd, int *error)
{
unsigned int heap_id_mask = 0;
/* Starting with kernel 4.14.34, we can iterate over the
* ION heaps and find those with type ION_HEAP_TYPE_DMA. */
int i;
int heap_count;
struct ion_heap_query query = { 0 };
struct ion_heap_data *heap_data = NULL;
if ((ioctl(ion_fd, ION_IOC_HEAP_QUERY, &query) < 0) || (query.cnt == 0))
{
if (error != NULL)
*error = errno;
return 0;
}
heap_count = query.cnt;
heap_data = calloc(heap_count, sizeof(struct ion_heap_data));
query.cnt = heap_count;
query.heaps = (__u64)((uintptr_t)heap_data);
if (ioctl(ion_fd, ION_IOC_HEAP_QUERY, &query) < 0)
{
if (error != NULL)
*error = errno;
free(heap_data);
return 0;
}
for (i = 0; i < heap_count; ++i)
{
int is_dma_heap = (heap_data[i].type == ION_HEAP_TYPE_DMA);
if (is_dma_heap)
heap_id_mask |= 1u << heap_data[i].heap_id;
}
free(heap_data);
return heap_id_mask;
}
int ion_allocate_dmabuf(dmabuf_info *dmabuf, allocator *alloc, int size)
{
int err = 0;
unsigned int ion_heap_id_mask = get_ion_heap_id_mask(alloc->fd, &err);
dmabuf->fd = -1;
if (err != 0)
{
fprintf(stderr, "Could not get ION heap ID mask: %s (%d)\n", strerror(err), err);
return err;
}
struct ion_allocation_data allocation_data =
{
.len = size,
.heap_id_mask = ion_heap_id_mask,
.flags = 0
};
if (ioctl(alloc->fd, ION_IOC_ALLOC, &allocation_data) < 0)
{
fprintf(stderr, "Could not allocate DMA-BUF memory with ION: %s (%d)\n", strerror(errno), errno);
return errno;
}
dmabuf->fd = allocation_data.fd;
dmabuf->size = size;
dmabuf->mapped_virtual_address = NULL;
dmabuf->mmap_prot = 0;
return 0;
}
int ion_sync_start(dmabuf_info *dmabuf)
{
/* no-op */
return 0;
}
int ion_sync_stop(dmabuf_info *dmabuf)
{
/* no-op */
return 0;
}
allocator* create_ion_allocator(void)
{
int ion_fd;
allocator *alloc;
ion_fd = open("/dev/ion", O_RDONLY);
if (ion_fd < 0)
{
fprintf(stderr, "Could not create ION allocator: %s (%d)\n", strerror(errno), errno);
return NULL;
}
alloc = malloc(sizeof(allocator));
alloc->fd = ion_fd;
alloc->allocate_dmabuf = ion_allocate_dmabuf;
alloc->sync_start = ion_sync_start;
alloc->sync_stop = ion_sync_stop;
return alloc;
}
int dma_heap_allocate_dmabuf(dmabuf_info *dmabuf, allocator *alloc, int size)
{
struct dma_heap_allocation_data heap_alloc_data;
dmabuf->fd = -1;
memset(&heap_alloc_data, 0, sizeof(heap_alloc_data));
heap_alloc_data.len = size;
heap_alloc_data.heap_flags = DMA_HEAP_VALID_HEAP_FLAGS;
heap_alloc_data.fd_flags = (O_RDWR | O_CLOEXEC);
if (ioctl(alloc->fd, DMA_HEAP_IOCTL_ALLOC, &heap_alloc_data) < 0)
{
fprintf(stderr, "Could not allocate DMA-BUF memory with ION: %s (%d)\n", strerror(errno), errno);
return errno;
}
dmabuf->fd = heap_alloc_data.fd;
dmabuf->size = size;
dmabuf->mapped_virtual_address = NULL;
dmabuf->mmap_prot = 0;
return 0;
}
int dma_heap_phys_ioctl(int dmabuf_fd)
{
struct dma_buf_phys dma_phys = { 0 };
if (ioctl(dmabuf_fd, DMA_BUF_IOCTL_PHYS, &dma_phys) < 0)
return errno;
else
return 0;
}
int dma_heap_sync_start(dmabuf_info *dmabuf)
{
if (dmabuf->mmap_prot & PROT_READ)
return dma_heap_phys_ioctl(dmabuf->fd);
else
return 0;
}
int dma_heap_sync_stop(dmabuf_info *dmabuf)
{
if (dmabuf->mmap_prot & PROT_WRITE)
return dma_heap_phys_ioctl(dmabuf->fd);
else
return 0;
}
allocator* create_dma_heap_allocator(void)
{
int dma_heap_fd = open("/dev/dma_heap/linux,cma", O_RDWR);
allocator *alloc = malloc(sizeof(allocator));
alloc->fd = dma_heap_fd;
alloc->allocate_dmabuf = dma_heap_allocate_dmabuf;
alloc->sync_start = dma_heap_sync_start;
alloc->sync_stop = dma_heap_sync_stop;
return alloc;
}
void destroy_allocator(allocator *alloc)
{
close(alloc->fd);
free(alloc);
}
uint8_t* map_dmabuf(dmabuf_info *dmabuf, int mmap_prot)
{
dmabuf->mmap_prot = mmap_prot;
dmabuf->mapped_virtual_address = mmap(0, dmabuf->size, dmabuf->mmap_prot, MAP_SHARED, dmabuf->fd, 0);
if (dmabuf->mapped_virtual_address == MAP_FAILED)
return NULL;
else
return dmabuf->mapped_virtual_address;
}
void unmap_dmabuf(dmabuf_info *dmabuf)
{
munmap(dmabuf->mapped_virtual_address, dmabuf->size);
}
void deallocate_dmabuf(dmabuf_info *dmabuf)
{
close(dmabuf->fd);
dmabuf->fd = -1;
}
uint64_t get_current_time(void)
{
struct timespec tp;
clock_gettime(CLOCK_MONOTONIC, &tp);
return ((uint64_t)(tp.tv_sec) * 1000000000ULL) + ((uint64_t)(tp.tv_nsec));
}
void do_test_write(uint8_t *dest, int num_bytes)
{
int i;
for (i = 0; i < num_bytes; ++i)
dest[i] = (uint8_t)(i & 255);
}
void do_test_readwrite(uint8_t *dest, int num_bytes)
{
int i;
for (i = 0; i < num_bytes; ++i)
dest[i] ^= 255;
}
void do_test(allocator *alloc, char const *allocator_name)
{
dmabuf_info dmabuf = { .fd = -1 };
int const blocksize = 16 * 1048576;
uint64_t ts1, ts2, ts3, ts4, ts5;
int err;
if (alloc == NULL)
return;
fprintf(stderr, "Running test with %s allocator\n", allocator_name);
alloc->allocate_dmabuf(&dmabuf, alloc, blocksize);
if (dmabuf.fd < 0)
goto finish;
fprintf(stderr, "Allocated DMA-BUF memory, FD %d\n", dmabuf.fd);
fprintf(stderr, "\n");
if (map_dmabuf(&dmabuf, PROT_READ|PROT_WRITE) == NULL)
{
fprintf(stderr, "Could not mmap DMA-BUF memory: %s (%d)\n", strerror(errno), errno);
goto finish;
}
ts1 = get_current_time();
if ((err = alloc->sync_start(&dmabuf)) != 0)
{
fprintf(stderr, "sync-start failed: %s (%d)\n", strerror(errno), errno);
goto finish;
}
ts2 = get_current_time();
do_test_write(dmabuf.mapped_virtual_address, blocksize);
ts3 = get_current_time();
do_test_readwrite(dmabuf.mapped_virtual_address, blocksize);
ts4 = get_current_time();
if ((err = alloc->sync_stop(&dmabuf)) != 0)
{
fprintf(stderr, "sync-stop failed: %s (%d)\n", strerror(errno), errno);
goto finish;
}
ts5 = get_current_time();
unmap_dmabuf(&dmabuf);
printf("sync_start: %" PRIu64 " us\n", (ts2 - ts1) / 1000);
printf("test write: %" PRIu64 " us\n", (ts3 - ts2) / 1000);
printf("test readwrite: %" PRIu64 " us\n", (ts4 - ts3) / 1000);
printf("sync_stop: %" PRIu64 " us\n", (ts5 - ts4) / 1000);
finish:
if (dmabuf.fd > 0)
deallocate_dmabuf(&dmabuf);
destroy_allocator(alloc);
}
int main(void)
{
do_test(create_ion_allocator(), "ION");
printf("\n");
do_test(create_dma_heap_allocator(), "dma-heap");
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment