Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
3 changes: 3 additions & 0 deletions SConscript
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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')
2 changes: 2 additions & 0 deletions lefthook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
94 changes: 94 additions & 0 deletions msgq/visionipc/visionbuf_dmabuf.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#include "msgq/visionipc/visionbuf.h"

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/dma-buf.h>
#include <linux/dma-heap.h>

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;
}
46 changes: 46 additions & 0 deletions msgq/visionipc/visionbuf_tests.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#include <fcntl.h>
#include <string.h>
#include <unistd.h>

#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<uint8_t *>(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<uint8_t *>(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);
}
Loading