Test application for DMABUF sharing between V4L2 and DRM

Tomasz Stanislawski t.stanislaws at samsung.com
Tue Apr 10 09:13:10 PDT 2012


Hi Everyone,
This email contains a test application showing DMABUF sharing
between DRM/KMS display and capture node including VIVI.
It shows a simple preview on LCD display. The similar application
was posted in thread:
http://thread.gmane.org/gmane.comp.video.dri.devel/65997

This version makes use of single-plane API for V4L2 capture instead
of multiplanar. This change allows VIVI driver to be tested as
DMABUF importer.

The program is written in C99 and it was tested using Exynos/DRM
and VIVI capture.

This application shows how buffer sharing between V4L2/DRM may look like.
Please let me know if/where I use DRM/V4L2 incorrectly.

The application was tested against 3.4-rc1 kernel with patches:

- Integration of videobuf2 with dmabuf
http://thread.gmane.org/gmane.linux.drivers.video-input-infrastructure/46586

- Integration of vb2-vmalloc and VIVI with dmabuf
http://thread.gmane.org/gmane.linux.drivers.video-input-infrastructure/46713

- Support for DRM prime for Exynos by Inki Dae
http://git.infradead.org/users/kmpark/linux-2.6-samsung/shortlog/refs/heads/exynos-drm-prime

- patch fixing kmap/vmap support for EXYNOS-DRM prime.
http://git.infradead.org/users/kmpark/linux-2.6-samsung/commit/3c483f24e418f342eac40dc5fb3991e058deb270


The branch containing all mentioned patches (without platform code)
rebased on 3.4-rc1 is available at link:

http://git.infradead.org/users/kmpark/linux-2.6-samsung/shortlog/refs/heads/v3.4-rc1-v4l-drm-dmabuf-for-test

Regards,
Tomasz Stanislawski

---


#include <errno.h>
#include <fcntl.h>
#include <linux/videodev2.h>
#include <math.h>
#include <poll.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include <xf86drm.h>
#include <xf86drmMode.h>
#include <exynos_drm.h>

#define ERRSTR strerror(errno)

#define BYE_ON(cond, ...) \
do { \
	if (cond) { \
		int errsv = errno; \
		fprintf(stderr, "ERROR(%s:%d) : ", \
			__FILE__, __LINE__); \
		errno = errsv; \
		fprintf(stderr,  __VA_ARGS__); \
		abort(); \
	} \
} while(0)

static inline int warn(const char *file, int line, const char *fmt, ...)
{
	int errsv = errno;
	va_list va;
	va_start(va, fmt);
	fprintf(stderr, "WARN(%s:%d): ", file, line);
	vfprintf(stderr, fmt, va);
	va_end(va);
	errno = errsv;
	return 1;
}

#define WARN_ON(cond, ...) \
	((cond) ? warn(__FILE__, __LINE__, __VA_ARGS__) : 0)

struct setup {
	char module[32];
	uint32_t conId;
	uint32_t crtId;
	char modestr[32];
	char video[32];
	unsigned int w, h;
	unsigned int use_wh : 1;
	unsigned int in_fourcc;
	unsigned int out_fourcc;
	unsigned int buffer_count;
	unsigned int use_crop : 1;
	unsigned int use_compose : 1;
	struct v4l2_rect crop;
	struct v4l2_rect compose;
};

struct buffer {
	unsigned int bo_handle;
	unsigned int fb_handle;
	int dbuf_fd;
};

struct stream {
	int v4lfd;
	int current_buffer;
	int buffer_count;
	struct buffer *buffer;
} stream;

static void usage(char *name)
{
	fprintf(stderr, "usage: %s [-Moisth]\n", name);
	fprintf(stderr, "\t-M <drm-module>\tset DRM module\n");
	fprintf(stderr, "\t-o <connector_id>:<crtc_id>:<mode>\tset a mode\n");
	fprintf(stderr, "\t-i <video-node>\tset video node like /dev/video*\n");
	fprintf(stderr, "\t-S <width,height>\tset input resolution\n");
	fprintf(stderr, "\t-f <fourcc>\tset input format using 4cc\n");
	fprintf(stderr, "\t-F <fourcc>\tset output format using 4cc\n");
	fprintf(stderr, "\t-s <width,height>@<left,top>\tset crop area\n");
	fprintf(stderr, "\t-t <width,height>@<left,top>\tset compose area\n");
	fprintf(stderr, "\t-b buffer_count\tset number of buffers\n");
	fprintf(stderr, "\t-h\tshow this help\n");
	fprintf(stderr, "\n\tDefault is to dump all info.\n");
}

static inline int parse_rect(char *s, struct v4l2_rect *r)
{
	return sscanf(s, "%d,%d@%d,%d", &r->width, &r->height,
		&r->top, &r->left) != 4;
}

static int parse_args(int argc, char *argv[], struct setup *s)
{
	if (argc <= 1)
		usage(argv[0]);

	int c, ret;
	memset(s, 0, sizeof(*s));

	while ((c = getopt(argc, argv, "M:o:i:S:f:F:s:t:b:h")) != -1) {
		switch (c) {
		case 'M':
			strncpy(s->module, optarg, 31);
			break;
		case 'o':
			ret = sscanf(optarg, "%u:%u:%31s", &s->conId, &s->crtId,
				s->modestr);
			if (WARN_ON(ret != 3, "incorrect mode description\n"))
				return -1;
			break;
		case 'i':
			strncpy(s->video, optarg, 31);
			break;
		case 'S':
			ret = sscanf(optarg, "%u,%u", &s->w, &s->h);
			if (WARN_ON(ret != 2, "incorrect input size\n"))
				return -1;
			s->use_wh = 1;
			break;
		case 'f':
			if (WARN_ON(strlen(optarg) != 4, "invalid fourcc\n"))
				return -1;
			s->in_fourcc = ((unsigned)optarg[0] << 0) |
				((unsigned)optarg[1] << 8) |
				((unsigned)optarg[2] << 16) |
				((unsigned)optarg[3] << 24);
			break;
		case 'F':
			if (WARN_ON(strlen(optarg) != 4, "invalid fourcc\n"))
				return -1;
			s->out_fourcc = ((unsigned)optarg[0] << 0) |
				((unsigned)optarg[1] << 8) |
				((unsigned)optarg[2] << 16) |
				((unsigned)optarg[3] << 24);
			break;
		case 's':
			ret = parse_rect(optarg, &s->crop);
			if (WARN_ON(ret, "incorrect crop area\n"))
				return -1;
			s->use_crop = 1;
			break;
		case 't':
			ret = parse_rect(optarg, &s->compose);
			if (WARN_ON(ret, "incorrect compose area\n"))
				return -1;
			s->use_compose = 1;
			break;
		case 'b':
			ret = sscanf(optarg, "%u", &s->buffer_count);
			if (WARN_ON(ret != 1, "incorrect buffer count\n"))
				return -1;
			break;
		case '?':
		case 'h':
			usage(argv[0]);
			return -1;
		}
	}

	return 0;
}

static int buffer_create(struct buffer *b, int drmfd, struct setup *s,
	uint64_t size, uint32_t pitch)
{
	int ret = strncmp(s->module, "exynos", 6);
	if (WARN_ON(ret, "drm: only exynos GEM is supported\n"))
		return -1;

	struct drm_exynos_gem_create gem;
	struct drm_gem_close gem_close;

	memset(&gem, 0, sizeof gem);
	gem.size = size;
	ret = ioctl(drmfd, DRM_IOCTL_EXYNOS_GEM_CREATE, &gem);
	if (WARN_ON(ret, "EXYNOS_GEM_CREATE failed: %s\n", ERRSTR))
		return -1;
	b->bo_handle = gem.handle;

	struct drm_prime_handle prime;
	memset(&prime, 0, sizeof prime);
	prime.handle = b->bo_handle;

	ret = ioctl(drmfd, DRM_IOCTL_PRIME_HANDLE_TO_FD, &prime);
	if (WARN_ON(ret, "PRIME_HANDLE_TO_FD failed: %s\n", ERRSTR))
		goto fail_gem;
	printf("dbuf_fd = %d\n", prime.fd);
	b->dbuf_fd = prime.fd;

	uint32_t offsets[4] = { 0 };
	uint32_t pitches[4] = { pitch };
	uint32_t bo_handles[4] = { b->bo_handle };
	unsigned int fourcc = s->out_fourcc;
	if (!fourcc)
		fourcc = s->in_fourcc;
	ret = drmModeAddFB2(drmfd, s->w, s->h, fourcc, bo_handles,
		pitches, offsets, &b->fb_handle, 0);
	if (WARN_ON(ret, "drmModeAddFB2 failed: %s\n", ERRSTR))
		goto fail_prime;

	return 0;

fail_prime:
	close(b->dbuf_fd);

fail_gem:
	memset(&gem_close, 0, sizeof gem_close);
	gem_close.handle = b->bo_handle,
	ret = ioctl(drmfd, DRM_IOCTL_GEM_CLOSE, gem_close);
	WARN_ON(ret, "GEM_CLOSE failed: %s\n", ERRSTR);

	return -1;
}

static int find_mode(drmModeModeInfo *m, int drmfd, struct setup *s,
	uint32_t *con)
{
	int ret = -1;
	drmModeRes *res = drmModeGetResources(drmfd);
	if (WARN_ON(!res, "drmModeGetResources failed: %s\n", ERRSTR))
		return -1;

	if (WARN_ON(res->count_crtcs <= 0, "drm: no crts\n"))
		goto fail_res;

	if (WARN_ON(res->count_connectors <= 0, "drm: no connectors\n"))
		goto fail_res;

	if (WARN_ON(s->conId >= res->count_connectors, "connector %d "
		"is not supported\n", s->conId))
		goto fail_res;

	drmModeConnector *c;
	c = drmModeGetConnector(drmfd, res->connectors[s->conId]);

	if (WARN_ON(!c, "drmModeGetConnector failed: %s\n", ERRSTR))
		goto fail_res;

	if (WARN_ON(!c->count_modes, "connector supports no mode\n"))
		goto fail_conn;

	drmModeModeInfo *found = NULL;
	for (int i = 0; i < c->count_modes; ++i)
		if (strcmp(c->modes[i].name, s->modestr) == 0)
			found = &c->modes[i];

	if (WARN_ON(!found, "mode %s not supported\n", s->modestr)) {
		fprintf(stderr, "Valid modes:");
		for (int i = 0; i < c->count_modes; ++i)
			fprintf(stderr, " %s", c->modes[i].name);
		fprintf(stderr, "\n");
		goto fail_conn;
	}

	memcpy(m, found, sizeof *found);
	if (con)
		*con = c->connector_id;
	ret = 0;

fail_conn:
	drmModeFreeConnector(c);

fail_res:
	drmModeFreeResources(res);

	return ret;
}

static void page_flip_handler(int fd, unsigned int frame,
	unsigned int sec, unsigned int usec, void *data)
{
	int index = stream.current_buffer;
	struct v4l2_buffer buf;
	int ret;

	stream.current_buffer = (int)data;
	if (index < 0)
		return;

	memset(&buf, 0, sizeof buf);
	buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	buf.memory = V4L2_MEMORY_DMABUF;
	buf.index = index;
	buf.m.fd = stream.buffer[index].dbuf_fd;

	ret = ioctl(stream.v4lfd, VIDIOC_QBUF, &buf);
	BYE_ON(ret, "VIDIOC_QBUF(index = %d) failed: %s\n", index, ERRSTR);
}

int main(int argc, char *argv[])
{
	int ret;
	struct setup s;

	ret = parse_args(argc, argv, &s);
	BYE_ON(ret, "failed to parse arguments\n");
	BYE_ON(s.module[0] == 0, "DRM module is missing\n");
	BYE_ON(s.video[0] == 0, "video node is missing\n");

	int drmfd = drmOpen(s.module, NULL);
	BYE_ON(drmfd < 0, "drmOpen(%s) failed: %s\n", s.module, ERRSTR);

	int v4lfd = open(s.video, O_RDWR);
	BYE_ON(v4lfd < 0, "failed to open %s: %s\n", s.video, ERRSTR);

	struct v4l2_capability caps;
	memset(&caps, 0, sizeof caps);

	ret = ioctl(v4lfd, VIDIOC_QUERYCAP, &caps);
	BYE_ON(ret, "VIDIOC_QUERYCAP failed: %s\n", ERRSTR);

	/* TODO: add single plane support */
	BYE_ON(~caps.capabilities & V4L2_CAP_VIDEO_CAPTURE,
		"video: singleplanar capture is not supported\n");

	struct v4l2_format fmt;
	memset(&fmt, 0, sizeof fmt);
	fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

	ret = ioctl(v4lfd, VIDIOC_G_FMT, &fmt);
	BYE_ON(ret < 0, "VIDIOC_G_FMT failed: %s\n", ERRSTR);
	printf("G_FMT(start): width = %u, height = %u, 4cc = %.4s\n",
		fmt.fmt.pix.width, fmt.fmt.pix.height,
		(char*)&fmt.fmt.pix.pixelformat);

	if (s.use_wh) {
		fmt.fmt.pix.width = s.w;
		fmt.fmt.pix.height = s.h;
	}
	if (s.in_fourcc)
		fmt.fmt.pix.pixelformat = s.in_fourcc;

	ret = ioctl(v4lfd, VIDIOC_S_FMT, &fmt);
	BYE_ON(ret < 0, "VIDIOC_S_FMT failed: %s\n", ERRSTR);

	ret = ioctl(v4lfd, VIDIOC_G_FMT, &fmt);
	BYE_ON(ret < 0, "VIDIOC_G_FMT failed: %s\n", ERRSTR);
	printf("G_FMT(final): width = %u, height = %u, 4cc = %.4s\n",
		fmt.fmt.pix.width, fmt.fmt.pix.height,
		(char*)&fmt.fmt.pix.pixelformat);

	struct v4l2_requestbuffers rqbufs;
	memset(&rqbufs, 0, sizeof(rqbufs));
	rqbufs.count = s.buffer_count;
	rqbufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	rqbufs.memory = V4L2_MEMORY_DMABUF;

	ret = ioctl(v4lfd, VIDIOC_REQBUFS, &rqbufs);
	BYE_ON(ret < 0, "VIDIOC_REQBUFS failed: %s\n", ERRSTR);
	BYE_ON(rqbufs.count < s.buffer_count, "video node allocated only "
		"%u of %u buffers\n", rqbufs.count, s.buffer_count);

	s.in_fourcc = fmt.fmt.pix.pixelformat;
	s.w = fmt.fmt.pix.width;
	s.h = fmt.fmt.pix.height;

	/* TODO: add support for multiplanar formats */
	struct buffer buffer[s.buffer_count];
	uint64_t size = fmt.fmt.pix.sizeimage;
	uint32_t pitch = fmt.fmt.pix.bytesperline;
	printf("size = %llu pitch = %u\n", size, pitch);
	for (int i = 0; i < s.buffer_count; ++i) {
		ret = buffer_create(&buffer[i], drmfd, &s, size, pitch);
		BYE_ON(ret, "failed to create buffer%d\n", i);
	}
	printf("buffers ready\n");

	drmModeModeInfo drmmode;
	uint32_t con;
	ret = find_mode(&drmmode, drmfd, &s, &con);
	BYE_ON(ret, "failed to find valid mode\n");

	ret = drmModeSetCrtc(drmfd, s.crtId, buffer[0].fb_handle, 0, 0,
		&con, 1, &drmmode);
	BYE_ON(ret, "drmModeSetCrtc failed: %s\n", ERRSTR);

	/* enqueueing first buffer to DRM */
	ret = drmModePageFlip(drmfd, s.crtId, buffer[0].fb_handle,
		DRM_MODE_PAGE_FLIP_EVENT, 0);
	BYE_ON(ret, "drmModePageFlip failed: %s\n", ERRSTR);

	for (int i = 1; i < s.buffer_count; ++i) {
		struct v4l2_buffer buf;
		memset(&buf, 0, sizeof buf);

		buf.index = i;
		buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		buf.memory = V4L2_MEMORY_DMABUF;
		buf.m.fd = buffer[i].dbuf_fd;
		ret = ioctl(v4lfd, VIDIOC_QBUF, &buf);
		BYE_ON(ret < 0, "VIDIOC_QBUF for buffer %d failed: %s\n",
			buf.index, ERRSTR);
	}

	int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	ret = ioctl(v4lfd, VIDIOC_STREAMON, &type);
	BYE_ON(ret < 0, "STREAMON failed: %s\n", ERRSTR);

	struct pollfd fds[] = {
		{ .fd = v4lfd, .events = POLLIN },
		{ .fd = drmfd, .events = POLLIN },
	};

	/* buffer currently used by drm */
	stream.v4lfd = v4lfd;
	stream.current_buffer = -1;
	stream.buffer = buffer;

	while ((ret = poll(fds, 2, 5000)) > 0) {
		if (fds[0].revents & POLLIN) {
			struct v4l2_buffer buf;
			memset(&buf, 0, sizeof buf);
			/* dequeue buffer */
			buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
			buf.memory = V4L2_MEMORY_DMABUF;
			ret = ioctl(v4lfd, VIDIOC_DQBUF, &buf);
			BYE_ON(ret, "VIDIOC_DQBUF failed: %s\n", ERRSTR);

			ret = drmModePageFlip(drmfd, s.crtId, buffer[buf.index].fb_handle,
				DRM_MODE_PAGE_FLIP_EVENT, (void*)buf.index);
			BYE_ON(ret, "drmModePageFlip failed: %s\n", ERRSTR);

		}
		if (fds[1].revents & POLLIN) {
			drmEventContext evctx;
			memset(&evctx, 0, sizeof evctx);
			evctx.version = DRM_EVENT_CONTEXT_VERSION;
			evctx.page_flip_handler = page_flip_handler;

			ret = drmHandleEvent(drmfd, &evctx);
			BYE_ON(ret, "drmHandleEvent failed: %s\n", ERRSTR);
		}
	}

	return 0;
}












More information about the dri-devel mailing list