From 469dcdda611ee19ee3956a2ad8ce945aae28c38b Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 25 Oct 2026 14:29:12 -0700 Subject: [PATCH] Add DMA-BUF support --- README.md | 7 +++ SConscript | 3 + lefthook.yml | 2 + msgq/visionipc/visionbuf_dmabuf.cc | 94 ++++++++++++++++++++++++++++++ msgq/visionipc/visionbuf_tests.cc | 46 +++++++++++++++ 5 files changed, 152 insertions(+) create mode 100644 msgq/visionipc/visionbuf_dmabuf.cc create mode 100644 msgq/visionipc/visionbuf_tests.cc diff --git a/README.md b/README.md index 1609f32a8..739b3b94e 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,13 @@ msgq.MultiplePublishersError # Publisher conflict; subclass Issues and pull requests are welcome on [GitHub](https://github.com/commaai/msgq). Run `./test.sh` to build, lint, and test the package. +VisionIPC selects its buffer backend at build time: ION when `/dev/ion` exists, +DMA-BUF heaps when `/dev/dma_heap/system` exists, and shared memory otherwise. +The selected device must be accessible at runtime. With DMA-BUF buffers, bracket +CPU access with `sync(VISIONBUF_SYNC_FROM_DEVICE)` before access and +`sync(VISIONBUF_SYNC_TO_DEVICE)` afterwards; device completion must be handled by +the caller. + ## License MSGQ is available under the [MIT License](https://github.com/commaai/msgq/blob/master/LICENSE). diff --git a/SConscript b/SConscript index 5a8d52041..f62a64c6b 100644 --- a/SConscript +++ b/SConscript @@ -16,6 +16,8 @@ msgq_python = envCython.Program('msgq/ipc_pyx.so', 'msgq/ipc_pyx.pyx', LIBS=envC vipc_files = ['visionipc.cc', 'visionipc_server.cc', 'visionipc_client.cc'] if File('/dev/ion').exists(): vipc_files += ['visionbuf_ion.cc'] +elif File('/dev/dma_heap/system').exists(): + vipc_files += ['visionbuf_dmabuf.cc'] else: vipc_files += ['visionbuf.cc'] vipc_sources = [f'{visionipc_dir.abspath}/{f}' for f in vipc_files] @@ -30,5 +32,6 @@ envCython.Program(f'{visionipc_dir.abspath}/visionipc_pyx.so', f'{visionipc_dir. if GetOption('extras'): env.Program('msgq/test_runner', ['msgq/msgq_tests.cc'], LIBS=[msgq]+common) + env.Program('msgq/visionipc/test_runner', ['msgq/visionipc/visionbuf_tests.cc'], LIBS=[visionipc]+common) Export('visionipc', 'msgq', 'msgq_python') diff --git a/lefthook.yml b/lefthook.yml index 91b18aa52..2af864578 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -28,5 +28,7 @@ test: # *** tests *** test_runner: run: msgq/test_runner + visionbuf_test_runner: + run: msgq/visionipc/test_runner unittest: run: python -m unittest discover -t . -s msgq diff --git a/msgq/visionipc/visionbuf_dmabuf.cc b/msgq/visionipc/visionbuf_dmabuf.cc new file mode 100644 index 000000000..d2eb3d36f --- /dev/null +++ b/msgq/visionipc/visionbuf_dmabuf.cc @@ -0,0 +1,94 @@ +#include "msgq/visionipc/visionbuf.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +int dma_ioctl(int fd, unsigned long request, void *data) { + int ret; + do { + ret = ioctl(fd, request, data); + } while (ret == -1 && (errno == EINTR || errno == EAGAIN)); + return ret; +} + +struct DmaHeapFileHandle { + DmaHeapFileHandle() { + fd = open("/dev/dma_heap/system", O_RDWR | O_CLOEXEC); + assert(fd >= 0); + } + ~DmaHeapFileHandle() { + close(fd); + } + int fd = -1; +}; + +} // namespace + +void VisionBuf::allocate(size_t length) { + static DmaHeapFileHandle heap; + struct dma_heap_allocation_data alloc = {}; + alloc.len = length + sizeof(uint64_t); + alloc.fd_flags = O_RDWR | O_CLOEXEC; + + int err = dma_ioctl(heap.fd, DMA_HEAP_IOCTL_ALLOC, &alloc); + assert(err == 0); + + this->len = length; + this->mmap_len = alloc.len; + this->fd = alloc.fd; + this->import(); + + err = sync(VISIONBUF_SYNC_FROM_DEVICE); + assert(err == 0); + memset(this->addr, 0, this->mmap_len); + err = sync(VISIONBUF_SYNC_TO_DEVICE); + assert(err == 0); +} + +void VisionBuf::import() { + assert(this->fd >= 0); + this->addr = mmap(NULL, this->mmap_len, PROT_READ | PROT_WRITE, MAP_SHARED, this->fd, 0); + assert(this->addr != MAP_FAILED); + this->frame_id = (uint64_t*)((uint8_t*)this->addr + this->len); +} + +void VisionBuf::init_yuv(size_t init_width, size_t init_height, size_t init_stride, size_t init_uv_offset) { + this->width = init_width; + this->height = init_height; + this->stride = init_stride; + this->uv_offset = init_uv_offset; + + this->y = (uint8_t *)this->addr; + this->uv = this->y + this->uv_offset; +} + +int VisionBuf::sync(int dir) { + assert(dir == VISIONBUF_SYNC_FROM_DEVICE || dir == VISIONBUF_SYNC_TO_DEVICE); + // Bracket CPU access: invalidate before reading, flush after writing. + struct dma_buf_sync sync_data = {}; + sync_data.flags = DMA_BUF_SYNC_RW | ((dir == VISIONBUF_SYNC_FROM_DEVICE) ? DMA_BUF_SYNC_START : DMA_BUF_SYNC_END); + return dma_ioctl(this->fd, DMA_BUF_IOCTL_SYNC, &sync_data); +} + +int VisionBuf::free() { + int err = munmap(this->addr, this->mmap_len); + if (err != 0) return err; + return close(this->fd); +} + +uint64_t VisionBuf::get_frame_id() { + return *frame_id; +} + +void VisionBuf::set_frame_id(uint64_t id) { + *frame_id = id; +} diff --git a/msgq/visionipc/visionbuf_tests.cc b/msgq/visionipc/visionbuf_tests.cc new file mode 100644 index 000000000..6e5195894 --- /dev/null +++ b/msgq/visionipc/visionbuf_tests.cc @@ -0,0 +1,46 @@ +#include +#include +#include + +#include "msgq/test_runner.h" +#include "msgq/visionipc/visionbuf.h" + +TEST_CASE("VisionBuf allocation and fd import") { + // Include a frame id beyond a page boundary to exercise the full mapping. + constexpr size_t length = 4096; + VisionBuf producer; + producer.allocate(length); + REQUIRE(producer.len == length); + REQUIRE(producer.mmap_len >= length + sizeof(uint64_t)); + + REQUIRE(producer.sync(VISIONBUF_SYNC_FROM_DEVICE) == 0); + REQUIRE(producer.get_frame_id() == 0); + for (size_t i = 0; i < length; ++i) { + REQUIRE(static_cast(producer.addr)[i] == 0); + } + memset(producer.addr, 0xa5, length); + producer.set_frame_id(1234); + REQUIRE(producer.sync(VISIONBUF_SYNC_TO_DEVICE) == 0); + + VisionBuf consumer; + consumer.fd = dup(producer.fd); + REQUIRE(consumer.fd >= 0); + consumer.len = producer.len; + consumer.mmap_len = producer.mmap_len; + consumer.import(); + consumer.init_yuv(64, 32, 64, 2048); + REQUIRE(consumer.y == consumer.addr); + REQUIRE(consumer.uv == consumer.y + 2048); + + // The imported fd must keep the allocation alive after the producer exits. + REQUIRE(producer.free() == 0); + REQUIRE(consumer.sync(VISIONBUF_SYNC_FROM_DEVICE) == 0); + REQUIRE(consumer.get_frame_id() == 1234); + for (size_t i = 0; i < length; ++i) { + REQUIRE(static_cast(consumer.addr)[i] == 0xa5); + } + REQUIRE(consumer.sync(VISIONBUF_SYNC_TO_DEVICE) == 0); + int fd = consumer.fd; + REQUIRE(consumer.free() == 0); + REQUIRE(fcntl(fd, F_GETFD) == -1); +}