v4p: dma support

This commit is contained in:
Maxim Devaev 2024-03-08 23:43:24 +00:00
parent e1ef86146f
commit 897ad4951b
5 changed files with 372 additions and 329 deletions

View File

@ -36,11 +36,3 @@
JANUS_LOG(LOG_ERR, "[%s/%-9s] " x_msg ": %s\n", US_PLUGIN_NAME, x_prefix, ##__VA_ARGS__, m_perror_str); \
free(m_perror_str); \
}
#define US_ONCE(...) { \
const int m_reported = __LINE__; \
if (m_reported != once) { \
__VA_ARGS__; \
once = m_reported; \
} \
}

View File

@ -72,6 +72,14 @@
(m_a > m_b ? m_a : m_b); \
})
#define US_ONCE(...) { \
const int m_reported = __LINE__; \
if (m_reported != once) { \
__VA_ARGS__; \
once = m_reported; \
} \
}
INLINE char *us_strdup(const char *str) {
char *const new = strdup(str);

View File

@ -44,14 +44,10 @@
#include "../libs/frametext.h"
static void _drm_vsync_callback(int fd, uint n_frame, uint sec, uint usec, void *v_run);
static int _drm_expose_raw(us_drm_s *drm, const us_frame_s *frame);
static void _drm_cleanup(us_drm_s *drm);
static int _drm_ensure(us_drm_s *drm, const us_frame_s *frame, float hz);
static void _drm_vsync_callback(int fd, uint n_frame, uint sec, uint usec, void *v_buf);
static int _drm_check_status(us_drm_s *drm);
static int _drm_find_sink(us_drm_s *drm, uint width, uint height, float hz);
static int _drm_init_buffers(us_drm_s *drm);
static int _drm_start_video(us_drm_s *drm);
static int _drm_init_buffers(us_drm_s *drm, const us_device_s *dev);
static u32 _find_crtc(int fd, drmModeRes *res, drmModeConnector *conn, u32 *taken_crtcs);
static const char *_connector_type_to_string(u32 type);
@ -70,33 +66,173 @@ us_drm_s *us_drm_init(void) {
US_CALLOC(run, 1);
run->fd = -1;
run->status_fd = -1;
run->has_vsync = true;
run->ft = us_frametext_init();
run->state = US_DRM_STATE_CLOSED;
us_drm_s *drm;
US_CALLOC(drm, 1);
// drm->path = "/dev/dri/card0";
drm->path = "/dev/dri/by-path/platform-gpu-card";
drm->port = "HDMI-A-1";
drm->n_bufs = 4;
drm->timeout = 5;
drm->run = run;
return drm;
}
void us_drm_destroy(us_drm_s *drm) {
_drm_cleanup(drm);
us_frametext_destroy(drm->run->ft);
US_DELETE(drm->run, free);
US_DELETE(drm, free); // cppcheck-suppress uselessAssignmentPtrArg
}
int us_drm_open(us_drm_s *drm, const us_device_s *dev) {
us_drm_runtime_s *const run = drm->run;
assert(run->fd < 0);
switch (_drm_check_status(drm)) {
case 0: break;
case -2: goto unplugged;
default: goto error;
}
_D_LOG_INFO("Configuring DRM device for %s ...", (dev == NULL ? "STUB" : "DMA"));
if ((run->fd = open(drm->path, O_RDWR | O_CLOEXEC | O_NONBLOCK)) < 0) {
_D_LOG_PERROR("Can't open DRM device");
goto error;
}
_D_LOG_DEBUG("DRM device fd=%d opened", run->fd);
int stub = 0; // Open the real device with DMA
if (dev == NULL) {
stub = US_DRM_STUB_USER;
} else if (dev->run->format != V4L2_PIX_FMT_RGB24) {
stub = US_DRM_STUB_BAD_FORMAT;
char fourcc_str[8];
us_fourcc_to_string(dev->run->format, fourcc_str, 8);
_D_LOG_ERROR("Input format %s is not supported, forcing to STUB ...", fourcc_str);
}
# define CHECK_CAP(x_cap) { \
_D_LOG_DEBUG("Checking %s ...", #x_cap); \
u64 m_check; \
if (drmGetCap(run->fd, x_cap, &m_check) < 0) { \
_D_LOG_PERROR("Can't check " #x_cap); \
goto error; \
} \
if (!m_check) { \
_D_LOG_ERROR(#x_cap " is not supported"); \
goto error; \
} \
}
CHECK_CAP(DRM_CAP_DUMB_BUFFER);
if (stub == 0) {
CHECK_CAP(DRM_CAP_PRIME);
}
# undef CHECK_CAP
const uint width = (stub > 0 ? 0 : dev->run->width);
const uint height = (stub > 0 ? 0 : dev->run->height);
const uint hz = (stub > 0 ? 0 : dev->run->hz);
switch (_drm_find_sink(drm, width, height, hz)) {
case 0: break;
case -2: goto unplugged;
default: goto error;
}
if ((stub == 0) && (width != run->mode.hdisplay || height < run->mode.vdisplay)) {
// We'll try to show something instead of nothing if height != vdisplay
stub = US_DRM_STUB_BAD_RESOLUTION;
_D_LOG_ERROR("There is no appropriate modes for the capture, forcing to STUB ...");
}
if (_drm_init_buffers(drm, (stub > 0 ? NULL : dev)) < 0) {
goto error;
}
run->saved_crtc = drmModeGetCrtc(run->fd, run->crtc_id);
_D_LOG_DEBUG("Setting up CRTC ...");
if (drmModeSetCrtc(run->fd, run->crtc_id, run->bufs[0].id, 0, 0, &run->conn_id, 1, &run->mode) < 0) {
_D_LOG_PERROR("Can't set CRTC");
goto error;
}
run->unplugged_reported = false;
_D_LOG_INFO("Opened for %s ...", (stub == 0 ? "DMA" : "STUB"));
return stub;
error:
us_drm_close(drm);
return -1;
unplugged:
if (!run->unplugged_reported) {
_D_LOG_INFO("Display is not plugged");
run->unplugged_reported = true;
}
us_drm_close(drm);
return -2;
}
void us_drm_close(us_drm_s *drm) {
us_drm_runtime_s *const run = drm->run;
if (run->saved_crtc != NULL) {
_D_LOG_DEBUG("Restoring CRTC ...");
if (drmModeSetCrtc(run->fd,
run->saved_crtc->crtc_id, run->saved_crtc->buffer_id,
run->saved_crtc->x, run->saved_crtc->y,
&run->conn_id, 1, &run->saved_crtc->mode
) < 0 && errno != ENOENT) {
_D_LOG_PERROR("Can't restore CRTC");
}
drmModeFreeCrtc(run->saved_crtc);
run->saved_crtc = NULL;
}
if (run->bufs != NULL) {
_D_LOG_DEBUG("Releasing buffers ...");
for (uint n_buf = 0; n_buf < run->n_bufs; ++n_buf) {
us_drm_buffer_s *const buf = &run->bufs[n_buf];
if (buf->fb_added && drmModeRmFB(run->fd, buf->id) < 0) {
_D_LOG_PERROR("Can't remove buffer=%u", n_buf);
}
if (buf->dumb_created) {
struct drm_mode_destroy_dumb destroy = {.handle = buf->handle};
if (drmIoctl(run->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy) < 0) {
_D_LOG_PERROR("Can't destroy dumb buffer=%u", n_buf);
}
}
if (buf->data != NULL && munmap(buf->data, buf->allocated)) {
_D_LOG_PERROR("Can't unmap buffer=%u", n_buf);
}
}
US_DELETE(run->bufs, free);
run->n_bufs = 0;
}
const bool say = (run->fd >= 0);
US_CLOSE_FD(run->status_fd);
US_CLOSE_FD(run->fd);
run->crtc_id = 0;
run->has_vsync = true;
run->stub_n_buf = 0;
if (say) {
_D_LOG_INFO("Closed");
}
}
int us_drm_wait_for_vsync(us_drm_s *drm) {
us_drm_runtime_s *const run = drm->run;
if (_drm_ensure(drm, NULL, 0) < 0) {
return -1;
switch (_drm_check_status(drm)) {
case 0: break;
case -2: return -2;
default: return -1;
}
if (run->has_vsync) {
return 0;
}
@ -110,10 +246,10 @@ int us_drm_wait_for_vsync(us_drm_s *drm) {
const int result = select(run->fd + 1, &fds, NULL, NULL, &timeout);
if (result < 0) {
_D_LOG_PERROR("Can't select(%d) device for VSync", run->fd);
goto error;
return -1;
} else if (result == 0) {
_D_LOG_ERROR("Device timeout while waiting VSync");
goto error;
return -1;
}
drmEventContext ctx = {
@ -123,274 +259,138 @@ int us_drm_wait_for_vsync(us_drm_s *drm) {
_D_LOG_DEBUG("Handling DRM event (maybe VSync) ...");
if (drmHandleEvent(run->fd, &ctx) < 0) {
_D_LOG_PERROR("Can't handle DRM event");
goto error;
}
return 0;
error:
_drm_cleanup(drm);
_D_LOG_ERROR("Device destroyed due an error (vsync)");
return -1;
}
int us_drm_expose(us_drm_s *drm, us_drm_expose_e ex, const us_frame_s *frame, float hz) {
us_drm_runtime_s *const run = drm->run;
if (_drm_ensure(drm, frame, hz) < 0) {
return -1;
}
const drmModeModeInfo *const mode = &run->mode;
bool msg_drawn = false;
# define DRAW_MSG(x_msg) { \
us_frametext_draw(run->ft, (x_msg), mode->hdisplay, mode->vdisplay); \
frame = run->ft->frame; \
msg_drawn = true; \
}
if (frame == NULL) {
switch (ex) {
case US_DRM_EXPOSE_NO_SIGNAL:
DRAW_MSG("=== PiKVM ===\n \n< NO SIGNAL >");
break;
case US_DRM_EXPOSE_BUSY:
DRAW_MSG("=== PiKVM ===\n \n< ONLINE IS ACTIVE >");
break;
default:
DRAW_MSG("=== PiKVM ===\n \n< ??? >");
}
} else if (mode->hdisplay != frame->width/* || mode->vdisplay != frame->height*/) {
// XXX: At least we'll try to show something instead of nothing ^^^
char msg[1024];
US_SNPRINTF(msg, 1023,
"=== PiKVM ==="
"\n \n< UNSUPPORTED RESOLUTION >"
"\n \n< %ux%up%.02f >"
"\n \nby this display",
frame->width, frame->height, hz);
DRAW_MSG(msg);
} else if (frame->format != V4L2_PIX_FMT_RGB24) {
DRAW_MSG(
"=== PiKVM ==="
"\n \n< UNSUPPORTED CAPTURE FORMAT >"
"\n \nIt shouldn't happen ever."
"\n \nPlease check the logs and report a bug:"
"\n \n- https://github.com/pikvm/pikvm -");
}
# undef DRAW_MSG
if (_drm_expose_raw(drm, frame) < 0) {
_drm_cleanup(drm);
_D_LOG_ERROR("Device destroyed due an error (expose)");
}
return (msg_drawn ? -1 : 0);
return 0;
}
static void _drm_vsync_callback(int fd, uint n_frame, uint sec, uint usec, void *v_run) {
static void _drm_vsync_callback(int fd, uint n_frame, uint sec, uint usec, void *v_buf) {
(void)fd;
(void)n_frame;
(void)sec;
(void)usec;
us_drm_runtime_s *const run = v_run;
run->has_vsync = true;
us_drm_buffer_s *const buf = v_buf;
*buf->ctx.has_vsync = true;
_D_LOG_DEBUG("Got VSync signal");
}
static int _drm_expose_raw(us_drm_s *drm, const us_frame_s *frame) {
us_drm_runtime_s *const run = drm->run;
us_drm_buffer_s *const buf = &run->bufs[run->next_n_buf];
_D_LOG_DEBUG("Exposing%s framebuffer n_buf=%u, vsync=%d ...",
(frame == NULL ? " EMPTY" : ""), run->next_n_buf, run->has_vsync);
if (frame == NULL) {
memset(buf->data, 0, buf->allocated);
} else {
memcpy(buf->data, frame->data, US_MIN(frame->used, buf->allocated));
}
run->has_vsync = false;
const int retval = drmModePageFlip(
run->fd, run->crtc_id, buf->id,
DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_PAGE_FLIP_ASYNC,
run);
run->next_n_buf = (run->next_n_buf + 1) % run->n_bufs;
return retval;
}
static void _drm_cleanup(us_drm_s *drm) {
int us_drm_expose_stub(us_drm_s *drm, us_drm_stub_e stub, const us_device_s *dev) {
us_drm_runtime_s *const run = drm->run;
_D_LOG_DEBUG("Cleaning up ...");
if (run->saved_crtc != NULL) {
if (drmModeSetCrtc(run->fd,
run->saved_crtc->crtc_id, run->saved_crtc->buffer_id,
run->saved_crtc->x, run->saved_crtc->y,
&run->conn_id, 1, &run->saved_crtc->mode
) < 0 && errno != ENOENT) {
_D_LOG_PERROR("Can't restore CRTC");
}
drmModeFreeCrtc(run->saved_crtc);
run->saved_crtc = NULL;
}
if (run->bufs != NULL) {
for (uint n_buf = 0; n_buf < run->n_bufs; ++n_buf) {
us_drm_buffer_s *const buf = &run->bufs[n_buf];
if (buf->data != NULL && munmap(buf->data, buf->allocated)) {
_D_LOG_PERROR("Can't unmap buffer=%u", n_buf);
}
if (buf->fb_added && drmModeRmFB(run->fd, buf->id) < 0) {
_D_LOG_PERROR("Can't remove buffer=%u", n_buf);
}
if (buf->dumb_created) {
struct drm_mode_destroy_dumb destroy = {.handle = buf->handle};
if (drmIoctl(run->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy) < 0) {
_D_LOG_PERROR("Can't destroy dumb buffer=%u", n_buf);
}
}
}
US_DELETE(run->bufs, free);
run->n_bufs = 0;
}
US_CLOSE_FD(run->status_fd);
US_CLOSE_FD(run->fd);
run->crtc_id = 0;
run->next_n_buf = 0;
run->has_vsync = false;
if (run->state == US_DRM_STATE_OK) {
_D_LOG_INFO("Stopped");
}
run->state = US_DRM_STATE_CLOSED;
}
static int _drm_ensure(us_drm_s *drm, const us_frame_s *frame, float hz) {
us_drm_runtime_s *const run = drm->run;
assert(run->stub_n_buf >= 0);
switch (_drm_check_status(drm)) {
case 0: break;
case -2: goto unplugged;
default: goto error;
case -2: return -2;
default: return -1;
}
if (frame == NULL && run->state == US_DRM_STATE_OK) {
return 0;
} else if (
frame != NULL
&& run->p_width == frame->width
&& run->p_height == frame->height
&& run->p_hz == hz
&& run->state <= US_DRM_STATE_CLOSED
) {
return (run->state == US_DRM_STATE_OK ? 0 : -1);
# define DRAW_MSG(x_msg) us_frametext_draw(run->ft, (x_msg), run->mode.hdisplay, run->mode.vdisplay)
switch (stub) {
case US_DRM_STUB_BAD_RESOLUTION: {
assert(dev != NULL);
char msg[1024];
US_SNPRINTF(msg, 1023,
"=== PiKVM ==="
"\n \n< UNSUPPORTED RESOLUTION >"
"\n \n< %ux%up%.02f >"
"\n \nby this display",
dev->run->width, dev->run->height, dev->run->hz);
DRAW_MSG(msg);
break;
};
case US_DRM_STUB_BAD_FORMAT:
DRAW_MSG(
"=== PiKVM ==="
"\n \n< UNSUPPORTED CAPTURE FORMAT >"
"\n \nIt shouldn't happen ever."
"\n \nPlease check the logs and report a bug:"
"\n \n- https://github.com/pikvm/pikvm -");
break;
case US_DRM_STUB_NO_SIGNAL:
DRAW_MSG("=== PiKVM ===\n \n< NO SIGNAL >");
break;
case US_DRM_STUB_BUSY:
DRAW_MSG("=== PiKVM ===\n \n< ONLINE IS ACTIVE >");
break;
default:
DRAW_MSG("=== PiKVM ===\n \n< ??? >");
break;
}
# undef DRAW_MSG
const us_drm_state_e saved_state = run->state;
_drm_cleanup(drm);
if (saved_state > US_DRM_STATE_CLOSED) {
run->state = saved_state;
us_drm_buffer_s *const buf = &run->bufs[run->stub_n_buf];
run->has_vsync = false;
_D_LOG_DEBUG("Copying STUB frame ...")
memcpy(buf->data, run->ft->frame->data, US_MIN(run->ft->frame->used, buf->allocated));
_D_LOG_DEBUG("Exposing STUB framebuffer n_buf=%u ...", run->stub_n_buf);
const int retval = drmModePageFlip(
run->fd, run->crtc_id, buf->id,
DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_PAGE_FLIP_ASYNC,
buf);
if (retval < 0) {
_D_LOG_PERROR("Can't expose STUB framebuffer n_buf=%u ...", run->stub_n_buf);
}
_D_LOG_DEBUG("Exposed STUB framebuffer n_buf=%u", run->stub_n_buf);
run->p_width = (frame != NULL ? frame->width : 0); // 0 for find the native resolution
run->p_height = (frame != NULL ? frame->height : 0);
run->p_hz = hz;
run->stub_n_buf = (run->stub_n_buf + 1) % run->n_bufs;
return retval;
}
_D_LOG_INFO("Configuring DRM device ...");
int us_drm_expose_dma(us_drm_s *drm, const us_hw_buffer_s *hw) {
us_drm_runtime_s *const run = drm->run;
us_drm_buffer_s *const buf = &run->bufs[hw->buf.index];
if ((run->fd = open(drm->path, O_RDWR | O_CLOEXEC | O_NONBLOCK)) < 0) {
_D_LOG_PERROR("Can't open DRM device");
goto error;
run->has_vsync = false;
_D_LOG_DEBUG("Exposing DMA framebuffer n_buf=%u ...", hw->buf.index);
const int retval = drmModePageFlip(
run->fd, run->crtc_id, buf->id,
DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_PAGE_FLIP_ASYNC,
buf);
if (retval < 0) {
_D_LOG_PERROR("Can't expose DMA framebuffer n_buf=%u ...", run->stub_n_buf);
}
# define CHECK_CAP(x_cap) { \
u64 m_check; \
if (drmGetCap(run->fd, x_cap, &m_check) < 0) { \
_D_LOG_PERROR("Can't check " #x_cap); \
goto error; \
} \
if (!m_check) { \
_D_LOG_ERROR(#x_cap " is not supported"); \
goto error; \
} \
}
CHECK_CAP(DRM_CAP_DUMB_BUFFER);
// CHECK_CAP(DRM_CAP_PRIME);
# undef CHECK_CAP
switch (_drm_find_sink(drm, run->p_width, run->p_height, run->p_hz)) {
case 0: break;
case -2: goto unplugged;
default: goto error;
}
const float mode_hz = _get_refresh_rate(&run->mode);
if (frame == NULL) {
run->p_width = run->mode.hdisplay;
run->p_height = run->mode.vdisplay;
run->p_hz = mode_hz;
}
_D_LOG_INFO("Using %s mode: %ux%up%.02f",
drm->port, run->mode.hdisplay, run->mode.vdisplay, mode_hz);
if (_drm_init_buffers(drm) < 0) {
goto error;
}
if (_drm_start_video(drm) < 0) {
goto error;
}
_D_LOG_INFO("Showing ...");
run->state = US_DRM_STATE_OK;
return 0;
error:
_drm_cleanup(drm);
_D_LOG_ERROR("Device destroyed due an error (ensure)");
return -1;
unplugged:
if (run->state != US_DRM_STATE_NO_DISPLAY) {
_D_LOG_INFO("Display %s is not plugged", drm->port);
}
_drm_cleanup(drm);
run->state = US_DRM_STATE_NO_DISPLAY;
return -2;
_D_LOG_DEBUG("Exposed DMA framebuffer n_buf=%u", run->stub_n_buf);
return retval;
}
static int _drm_check_status(us_drm_s *drm) {
us_drm_runtime_s *run = drm->run;
if (run->status_fd < 0) {
_D_LOG_DEBUG("Trying to find status file ...");
struct stat st;
if (stat(drm->path, &st) < 0) {
_D_LOG_PERROR("Can't stat() DRM device");
goto error;
}
const uint mi = minor(st.st_rdev);
_D_LOG_DEBUG("DRM device minor(st_rdev)=%u", mi);
char path[128];
US_SNPRINTF(path, 127, "/sys/class/drm/card%u-%s/status", mi, drm->port);
_D_LOG_DEBUG("Opening status file %s ...", path);
if ((run->status_fd = open(path, O_RDONLY | O_CLOEXEC)) < 0) {
_D_LOG_PERROR("Can't open DRM device status file: %s", path);
_D_LOG_PERROR("Can't open status file: %s", path);
goto error;
}
_D_LOG_DEBUG("Status file fd=%d opened", run->status_fd);
}
char status_ch;
if (read(run->status_fd, &status_ch, 1) != 1) {
_D_LOG_PERROR("Can't read connector status");
_D_LOG_PERROR("Can't read status file");
goto error;
}
if (lseek(run->status_fd, 0, SEEK_SET) != 0) {
_D_LOG_PERROR("Can't rewind connector status");
_D_LOG_PERROR("Can't rewind status file");
goto error;
}
_D_LOG_DEBUG("Current display status: %c", status_ch);
return (status_ch == 'd' ? -2 : 0);
error:
@ -434,7 +434,7 @@ static int _drm_find_sink(us_drm_s *drm, uint width, uint height, float hz) {
drm->port, conn->connector_type, conn->connector_type_id);
if (conn->connection != DRM_MODE_CONNECTED) {
_D_LOG_DEBUG("Display is not connected");
_D_LOG_ERROR("Connector for port %s has !DRM_MODE_CONNECTED", drm->port);
drmModeFreeConnector(conn);
goto done;
}
@ -467,13 +467,16 @@ static int _drm_find_sink(us_drm_s *drm, uint width, uint height, float hz) {
if (best == NULL) { best = pref; }
if (best == NULL) { best = (conn->count_modes > 0 ? &conn->modes[0] : NULL); }
if (best == NULL) {
_D_LOG_ERROR("Can't find any appropriate resolutions");
_D_LOG_ERROR("Can't find any appropriate display modes");
drmModeFreeConnector(conn);
goto unplugged;
}
assert(best->hdisplay > 0);
assert(best->vdisplay > 0);
_D_LOG_INFO("The best display mode is: %ux%up%.02f",
best->hdisplay, best->vdisplay, _get_refresh_rate(best));
u32 taken_crtcs = 0; // Unused here
if ((run->crtc_id = _find_crtc(run->fd, res, conn, &taken_crtcs)) == 0) {
_D_LOG_ERROR("Can't find CRTC");
@ -496,31 +499,66 @@ unplugged:
return -2;
}
static int _drm_init_buffers(us_drm_s *drm) {
static int _drm_init_buffers(us_drm_s *drm, const us_device_s *dev) {
us_drm_runtime_s *const run = drm->run;
_D_LOG_DEBUG("Initializing %u buffers ...", drm->n_bufs);
const uint n_bufs = (dev == NULL ? 4 : dev->run->n_bufs);
const char *name = (dev == NULL ? "STUB" : "DMA");
US_CALLOC(run->bufs, drm->n_bufs);
for (run->n_bufs = 0; run->n_bufs < drm->n_bufs; ++run->n_bufs) {
_D_LOG_DEBUG("Initializing %u %s buffers ...", n_bufs, name);
US_CALLOC(run->bufs, n_bufs);
for (run->n_bufs = 0; run->n_bufs < n_bufs; ++run->n_bufs) {
const uint n_buf = run->n_bufs;
us_drm_buffer_s *const buf = &run->bufs[n_buf];
struct drm_mode_create_dumb create = {
.width = run->mode.hdisplay,
.height = run->mode.vdisplay,
.bpp = 24,
};
if (drmIoctl(run->fd, DRM_IOCTL_MODE_CREATE_DUMB, &create) < 0) {
_D_LOG_PERROR("Can't create dumb buffer=%u", n_buf);
return -1;
}
buf->handle = create.handle;
buf->dumb_created = true;
buf->ctx.has_vsync = &run->has_vsync;
u32 handles[4] = {create.handle};
u32 strides[4] = {create.pitch};
u32 handles[4] = {0};
u32 strides[4] = {0};
u32 offsets[4] = {0};
if (dev == NULL) {
struct drm_mode_create_dumb create = {
.width = run->mode.hdisplay,
.height = run->mode.vdisplay,
.bpp = 24,
};
if (drmIoctl(run->fd, DRM_IOCTL_MODE_CREATE_DUMB, &create) < 0) {
_D_LOG_PERROR("Can't create %s buffer=%u", name, n_buf);
return -1;
}
buf->handle = create.handle;
buf->dumb_created = true;
struct drm_mode_map_dumb map = {.handle = create.handle};
if (drmIoctl(run->fd, DRM_IOCTL_MODE_MAP_DUMB, &map) < 0) {
_D_LOG_PERROR("Can't prepare dumb buffer=%u to mapping", n_buf);
return -1;
}
if ((buf->data = mmap(
NULL, create.size,
PROT_READ | PROT_WRITE, MAP_SHARED,
run->fd, map.offset
)) == MAP_FAILED) {
_D_LOG_PERROR("Can't map buffer=%u", n_buf);
return -1;
}
memset(buf->data, 0, create.size);
buf->allocated = create.size;
handles[0] = create.handle;
strides[0] = create.pitch;
} else {
if (drmPrimeFDToHandle(run->fd, dev->run->hw_bufs[n_buf].dma_fd, &buf->handle) < 0) {
_D_LOG_PERROR("Can't import DMA buffer=%u from capture device", n_buf);
return -1;
}
handles[0] = buf->handle;
strides[0] = dev->run->stride;
}
if (drmModeAddFB2(
run->fd,
run->mode.hdisplay, run->mode.vdisplay, DRM_FORMAT_RGB888,
@ -530,38 +568,6 @@ static int _drm_init_buffers(us_drm_s *drm) {
return -1;
}
buf->fb_added = true;
struct drm_mode_map_dumb map = {.handle = create.handle};
if (drmIoctl(run->fd, DRM_IOCTL_MODE_MAP_DUMB, &map) < 0) {
_D_LOG_PERROR("Can't prepare dumb buffer=%u to mapping", n_buf);
return -1;
}
if ((buf->data = mmap(
NULL, create.size,
PROT_READ | PROT_WRITE, MAP_SHARED,
run->fd, map.offset
)) == MAP_FAILED) {
_D_LOG_PERROR("Can't map buffer=%u", n_buf);
return -1;
}
memset(buf->data, 0, create.size);
buf->allocated = create.size;
}
return 0;
}
static int _drm_start_video(us_drm_s *drm) {
us_drm_runtime_s *const run = drm->run;
run->saved_crtc = drmModeGetCrtc(run->fd, run->crtc_id);
_D_LOG_DEBUG("Setting up CRTC ...");
if (drmModeSetCrtc(run->fd, run->crtc_id, run->bufs[0].id, 0, 0, &run->conn_id, 1, &run->mode) < 0) {
_D_LOG_PERROR("Can't set CRTC");
return -1;
}
if (_drm_expose_raw(drm, NULL) < 0) {
_D_LOG_PERROR("Can't flip the first page");
return -1;
}
return 0;
}

View File

@ -28,19 +28,16 @@
#include "../libs/types.h"
#include "../libs/frame.h"
#include "../libs/frametext.h"
#include "../libs/device.h"
typedef enum {
US_DRM_EXPOSE_FRAME = 0,
US_DRM_EXPOSE_NO_SIGNAL,
US_DRM_EXPOSE_BUSY,
} us_drm_expose_e;
typedef enum {
US_DRM_STATE_OK = 0,
US_DRM_STATE_CLOSED,
US_DRM_STATE_NO_DISPLAY,
} us_drm_state_e;
US_DRM_STUB_USER = 1,
US_DRM_STUB_BAD_RESOLUTION,
US_DRM_STUB_BAD_FORMAT,
US_DRM_STUB_NO_SIGNAL,
US_DRM_STUB_BUSY,
} us_drm_stub_e;
typedef struct {
u32 id;
@ -49,11 +46,13 @@ typedef struct {
uz allocated;
bool dumb_created;
bool fb_added;
struct {
bool *has_vsync;
} ctx;
} us_drm_buffer_s;
typedef struct {
int status_fd;
int fd;
u32 crtc_id;
u32 conn_id;
@ -61,22 +60,15 @@ typedef struct {
us_drm_buffer_s *bufs;
uint n_bufs;
drmModeCrtc *saved_crtc;
uint next_n_buf;
bool has_vsync;
int stub_n_buf;
us_frametext_s *ft;
uint p_width;
uint p_height;
float p_hz;
us_drm_state_e state;
bool unplugged_reported;
} us_drm_runtime_s;
typedef struct {
char *path;
char *port;
uint n_bufs;
uint timeout;
us_drm_runtime_s *run;
@ -86,5 +78,9 @@ typedef struct {
us_drm_s *us_drm_init(void);
void us_drm_destroy(us_drm_s *drm);
int us_drm_open(us_drm_s *drm, const us_device_s *dev);
void us_drm_close(us_drm_s *drm);
int us_drm_expose_stub(us_drm_s *drm, us_drm_stub_e stub, const us_device_s *dev);
int us_drm_expose_dma(us_drm_s *drm, const us_hw_buffer_s *hw);
int us_drm_wait_for_vsync(us_drm_s *drm);
int us_drm_expose(us_drm_s *drm, us_drm_expose_e ex, const us_frame_s *frame, float hz);

View File

@ -164,61 +164,102 @@ static void _main_loop(void) {
us_device_s *dev = us_device_init();
dev->path = "/dev/kvmd-video";
dev->n_bufs = drm->n_bufs;
dev->n_bufs = 6;
dev->format = V4L2_PIX_FMT_RGB24;
dev->dv_timings = true;
dev->persistent = true;
dev->dma_export = true;
dev->dma_required = true;
int once = 0;
int drm_opened = -1;
while (!atomic_load(&_g_stop)) {
if (atomic_load(&_g_ustreamer_online)) {
if (us_drm_wait_for_vsync(drm) == 0) {
us_drm_expose(drm, US_DRM_EXPOSE_BUSY, NULL, 0);
}
if (dev->run->fd >= 0) {
if (drm_opened <= 0) {
if ((drm_opened = us_drm_open(drm, NULL)) < 0) {
goto close;
} else {
_slowdown();
continue;
}
}
assert(drm_opened > 0);
if (atomic_load(&_g_ustreamer_online)) {
US_ONCE({ US_LOG_INFO("DRM: Online stream is active"); });
if (us_drm_wait_for_vsync(drm) < 0) {
goto close;
}
if (us_drm_expose_stub(drm, US_DRM_STUB_BUSY, NULL) < 0) {
goto close;
}
_slowdown();
continue;
}
if (us_device_open(dev) < 0) {
if (us_drm_wait_for_vsync(drm) == 0) {
us_drm_expose(drm, US_DRM_EXPOSE_NO_SIGNAL, NULL, 0);
if (us_drm_wait_for_vsync(drm) < 0) {
goto close;
}
if (us_drm_expose_stub(drm, US_DRM_STUB_NO_SIGNAL, NULL) < 0) {
goto close;
}
_slowdown();
continue;
}
us_drm_close(drm);
if ((drm_opened = us_drm_open(drm, dev)) < 0) {
goto close;
}
once = 0;
us_hw_buffer_s *prev_hw = NULL;
while (!atomic_load(&_g_stop)) {
if (atomic_load(&_g_ustreamer_online)) {
goto close;
}
if (us_drm_wait_for_vsync(drm) < 0) {
_slowdown();
continue;
goto close;
}
if (prev_hw != NULL) {
if (us_device_release_buffer(dev, prev_hw) < 0) {
goto close;
}
prev_hw = NULL;
}
us_hw_buffer_s *hw;
const int buf_index = us_device_grab_buffer(dev, &hw);
switch (buf_index) {
const int n_buf = us_device_grab_buffer(dev, &hw);
switch (n_buf) {
case -2: continue; // Broken frame
case -1: goto close; // Any error
}
assert(buf_index >= 0);
assert(n_buf >= 0);
const int exposed = us_drm_expose(drm, US_DRM_EXPOSE_FRAME, &hw->raw, dev->run->hz);
if (us_device_release_buffer(dev, hw) < 0) {
goto close;
int exposed;
if (drm_opened == 0) {
exposed = us_drm_expose_dma(drm, hw);
prev_hw = hw;
} else {
exposed = us_drm_expose_stub(drm, drm_opened, dev);
if (us_device_release_buffer(dev, hw) < 0) {
goto close;
}
}
if (exposed < 0) {
goto close;
} else if (drm_opened > 0) {
_slowdown();
continue;
}
}
close:
us_drm_close(drm);
drm_opened = -1;
us_device_close(dev);
_slowdown();
}