/** * @file drm.c * */ /********************* * INCLUDES *********************/ #include "drm.h" #if USE_DRM #include #include #include #include #include #include #include #include #include #include #include #define DBG_TAG "drm" #define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) #define print(msg, ...) fprintf(stderr, msg, ##__VA_ARGS__); #define err(msg, ...) print("error: " msg "\n", ##__VA_ARGS__) #define info(msg, ...) print(msg "\n", ##__VA_ARGS__) #define dbg(msg, ...) {} //print(DBG_TAG ": " msg "\n", ##__VA_ARGS__) struct drm_buffer { uint32_t handle; uint32_t pitch; uint32_t offset; unsigned long int size; uint8_t * map; uint32_t fb_handle; }; struct drm_dev { int fd; uint32_t conn_id, enc_id, crtc_id, plane_id, crtc_idx; uint32_t width, height; uint32_t mmWidth, mmHeight; uint32_t fourcc; drmModeModeInfo mode; uint32_t blob_id; drmModeCrtc *saved_crtc; drmModeAtomicReq *req; drmEventContext drm_event_ctx; drmModePlane *plane; drmModeCrtc *crtc; drmModeConnector *conn; uint32_t count_plane_props; uint32_t count_crtc_props; uint32_t count_conn_props; drmModePropertyPtr plane_props[128]; drmModePropertyPtr crtc_props[128]; drmModePropertyPtr conn_props[128]; struct drm_buffer drm_bufs[2]; /*DUMB buffers*/ uint8_t active_drm_buf_idx; /*Double buffering handling*/ lv_disp_draw_buf_t draw_buf; } drm_dev; static uint32_t get_plane_property_id(const char *name) { uint32_t i; dbg("Find plane property: %s", name); for (i = 0; i < drm_dev.count_plane_props; ++i) if (!strcmp(drm_dev.plane_props[i]->name, name)) return drm_dev.plane_props[i]->prop_id; dbg("Unknown plane property: %s", name); return 0; } static uint32_t get_crtc_property_id(const char *name) { uint32_t i; dbg("Find crtc property: %s", name); for (i = 0; i < drm_dev.count_crtc_props; ++i) if (!strcmp(drm_dev.crtc_props[i]->name, name)) return drm_dev.crtc_props[i]->prop_id; dbg("Unknown crtc property: %s", name); return 0; } static uint32_t get_conn_property_id(const char *name) { uint32_t i; dbg("Find conn property: %s", name); for (i = 0; i < drm_dev.count_conn_props; ++i) if (!strcmp(drm_dev.conn_props[i]->name, name)) return drm_dev.conn_props[i]->prop_id; dbg("Unknown conn property: %s", name); return 0; } static void page_flip_handler(int fd, unsigned int sequence, unsigned int tv_sec, unsigned int tv_usec, void *user_data) { LV_UNUSED(fd); LV_UNUSED(sequence); LV_UNUSED(tv_sec); LV_UNUSED(tv_usec); LV_UNUSED(user_data); dbg("flip"); if(drm_dev.req) { drmModeAtomicFree(drm_dev.req); drm_dev.req = NULL; } } static int drm_get_plane_props(void) { uint32_t i; drmModeObjectPropertiesPtr props = drmModeObjectGetProperties(drm_dev.fd, drm_dev.plane_id, DRM_MODE_OBJECT_PLANE); if (!props) { err("drmModeObjectGetProperties failed"); return -1; } dbg("Found %u plane props", props->count_props); drm_dev.count_plane_props = props->count_props; for (i = 0; i < props->count_props; i++) { drm_dev.plane_props[i] = drmModeGetProperty(drm_dev.fd, props->props[i]); dbg("Added plane prop %u:%s", drm_dev.plane_props[i]->prop_id, drm_dev.plane_props[i]->name); } drmModeFreeObjectProperties(props); return 0; } static int drm_get_crtc_props(void) { uint32_t i; drmModeObjectPropertiesPtr props = drmModeObjectGetProperties(drm_dev.fd, drm_dev.crtc_id, DRM_MODE_OBJECT_CRTC); if (!props) { err("drmModeObjectGetProperties failed"); return -1; } dbg("Found %u crtc props", props->count_props); drm_dev.count_crtc_props = props->count_props; for (i = 0; i < props->count_props; i++) { drm_dev.crtc_props[i] = drmModeGetProperty(drm_dev.fd, props->props[i]); dbg("Added crtc prop %u:%s", drm_dev.crtc_props[i]->prop_id, drm_dev.crtc_props[i]->name); } drmModeFreeObjectProperties(props); return 0; } static int drm_get_conn_props(void) { uint32_t i; drmModeObjectPropertiesPtr props = drmModeObjectGetProperties(drm_dev.fd, drm_dev.conn_id, DRM_MODE_OBJECT_CONNECTOR); if (!props) { err("drmModeObjectGetProperties failed"); return -1; } dbg("Found %u connector props", props->count_props); drm_dev.count_conn_props = props->count_props; for (i = 0; i < props->count_props; i++) { drm_dev.conn_props[i] = drmModeGetProperty(drm_dev.fd, props->props[i]); dbg("Added connector prop %u:%s", drm_dev.conn_props[i]->prop_id, drm_dev.conn_props[i]->name); } drmModeFreeObjectProperties(props); return 0; } static int drm_add_plane_property(const char *name, uint64_t value) { int ret; uint32_t prop_id = get_plane_property_id(name); if (!prop_id) { err("Couldn't find plane prop %s", name); return -1; } ret = drmModeAtomicAddProperty(drm_dev.req, drm_dev.plane_id, get_plane_property_id(name), value); if (ret < 0) { err("drmModeAtomicAddProperty (%s:%" PRIu64 ") failed: %d", name, value, ret); return ret; } return 0; } static int drm_add_crtc_property(const char *name, uint64_t value) { int ret; uint32_t prop_id = get_crtc_property_id(name); if (!prop_id) { err("Couldn't find crtc prop %s", name); return -1; } ret = drmModeAtomicAddProperty(drm_dev.req, drm_dev.crtc_id, get_crtc_property_id(name), value); if (ret < 0) { err("drmModeAtomicAddProperty (%s:%" PRIu64 ") failed: %d", name, value, ret); return ret; } return 0; } static int drm_add_conn_property(const char *name, uint64_t value) { int ret; uint32_t prop_id = get_conn_property_id(name); if (!prop_id) { err("Couldn't find conn prop %s", name); return -1; } ret = drmModeAtomicAddProperty(drm_dev.req, drm_dev.conn_id, get_conn_property_id(name), value); if (ret < 0) { err("drmModeAtomicAddProperty (%s:%" PRIu64 ") failed: %d", name, value, ret); return ret; } return 0; } static int drm_dmabuf_set_plane(struct drm_buffer *buf) { int ret; static int first = 1; uint32_t flags = DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK; drm_dev.req = drmModeAtomicAlloc(); /* On first Atomic commit, do a modeset */ if (first) { drm_add_conn_property("CRTC_ID", drm_dev.crtc_id); drm_add_crtc_property("MODE_ID", drm_dev.blob_id); drm_add_crtc_property("ACTIVE", 1); flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; first = 0; } drm_add_plane_property("FB_ID", buf->fb_handle); drm_add_plane_property("CRTC_ID", drm_dev.crtc_id); drm_add_plane_property("SRC_X", 0); drm_add_plane_property("SRC_Y", 0); drm_add_plane_property("SRC_W", drm_dev.width << 16); drm_add_plane_property("SRC_H", drm_dev.height << 16); drm_add_plane_property("CRTC_X", 0); drm_add_plane_property("CRTC_Y", 0); drm_add_plane_property("CRTC_W", drm_dev.width); drm_add_plane_property("CRTC_H", drm_dev.height); ret = drmModeAtomicCommit(drm_dev.fd, drm_dev.req, flags, NULL); if (ret) { err("drmModeAtomicCommit failed: %s", strerror(errno)); drmModeAtomicFree(drm_dev.req); return ret; } return 0; } static int find_plane(unsigned int fourcc, uint32_t *plane_id, uint32_t crtc_id, uint32_t crtc_idx) { LV_UNUSED(crtc_id); drmModePlaneResPtr planes; drmModePlanePtr plane; unsigned int i; unsigned int j; int ret = 0; unsigned int format = fourcc; planes = drmModeGetPlaneResources(drm_dev.fd); if (!planes) { err("drmModeGetPlaneResources failed"); return -1; } dbg("drm: found planes %u", planes->count_planes); for (i = 0; i < planes->count_planes; ++i) { plane = drmModeGetPlane(drm_dev.fd, planes->planes[i]); if (!plane) { err("drmModeGetPlane failed: %s", strerror(errno)); break; } if (!(plane->possible_crtcs & (1 << crtc_idx))) { drmModeFreePlane(plane); continue; } for (j = 0; j < plane->count_formats; ++j) { if (plane->formats[j] == format) break; } if (j == plane->count_formats) { drmModeFreePlane(plane); continue; } *plane_id = plane->plane_id; drmModeFreePlane(plane); dbg("found plane %d", *plane_id); break; } if (i == planes->count_planes) ret = -1; drmModeFreePlaneResources(planes); return ret; } static int drm_find_connector(void) { drmModeConnector *conn = NULL; drmModeEncoder *enc = NULL; drmModeRes *res; int i; if ((res = drmModeGetResources(drm_dev.fd)) == NULL) { err("drmModeGetResources() failed"); return -1; } if (res->count_crtcs <= 0) { err("no Crtcs"); goto free_res; } /* find all available connectors */ for (i = 0; i < res->count_connectors; i++) { conn = drmModeGetConnector(drm_dev.fd, res->connectors[i]); if (!conn) continue; #if DRM_CONNECTOR_ID >= 0 if (conn->connector_id != DRM_CONNECTOR_ID) { drmModeFreeConnector(conn); continue; } #endif if (conn->connection == DRM_MODE_CONNECTED) { dbg("drm: connector %d: connected", conn->connector_id); } else if (conn->connection == DRM_MODE_DISCONNECTED) { dbg("drm: connector %d: disconnected", conn->connector_id); } else if (conn->connection == DRM_MODE_UNKNOWNCONNECTION) { dbg("drm: connector %d: unknownconnection", conn->connector_id); } else { dbg("drm: connector %d: unknown", conn->connector_id); } if (conn->connection == DRM_MODE_CONNECTED && conn->count_modes > 0) break; drmModeFreeConnector(conn); conn = NULL; }; if (!conn) { err("suitable connector not found"); goto free_res; } drm_dev.conn_id = conn->connector_id; dbg("conn_id: %d", drm_dev.conn_id); drm_dev.mmWidth = conn->mmWidth; drm_dev.mmHeight = conn->mmHeight; lv_memcpy(&drm_dev.mode, &conn->modes[0], sizeof(drmModeModeInfo)); if (drmModeCreatePropertyBlob(drm_dev.fd, &drm_dev.mode, sizeof(drm_dev.mode), &drm_dev.blob_id)) { err("error creating mode blob"); goto free_res; } drm_dev.width = conn->modes[0].hdisplay; drm_dev.height = conn->modes[0].vdisplay; for (i = 0 ; i < res->count_encoders; i++) { enc = drmModeGetEncoder(drm_dev.fd, res->encoders[i]); if (!enc) continue; dbg("enc%d enc_id %d conn enc_id %d", i, enc->encoder_id, conn->encoder_id); if (enc->encoder_id == conn->encoder_id) break; drmModeFreeEncoder(enc); enc = NULL; } if (enc) { drm_dev.enc_id = enc->encoder_id; dbg("enc_id: %d", drm_dev.enc_id); drm_dev.crtc_id = enc->crtc_id; dbg("crtc_id: %d", drm_dev.crtc_id); drmModeFreeEncoder(enc); } else { /* Encoder hasn't been associated yet, look it up */ for (i = 0; i < conn->count_encoders; i++) { int crtc, crtc_id = -1; enc = drmModeGetEncoder(drm_dev.fd, conn->encoders[i]); if (!enc) continue; for (crtc = 0 ; crtc < res->count_crtcs; crtc++) { uint32_t crtc_mask = 1 << crtc; crtc_id = res->crtcs[crtc]; dbg("enc_id %d crtc%d id %d mask %x possible %x", enc->encoder_id, crtc, crtc_id, crtc_mask, enc->possible_crtcs); if (enc->possible_crtcs & crtc_mask) break; } if (crtc_id > 0) { drm_dev.enc_id = enc->encoder_id; dbg("enc_id: %d", drm_dev.enc_id); drm_dev.crtc_id = crtc_id; dbg("crtc_id: %d", drm_dev.crtc_id); break; } drmModeFreeEncoder(enc); enc = NULL; } if (!enc) { err("suitable encoder not found"); goto free_res; } drmModeFreeEncoder(enc); } drm_dev.crtc_idx = UINT32_MAX; for (i = 0; i < res->count_crtcs; ++i) { if (drm_dev.crtc_id == res->crtcs[i]) { drm_dev.crtc_idx = i; break; } } if (drm_dev.crtc_idx == UINT32_MAX) { err("drm: CRTC not found"); goto free_res; } dbg("crtc_idx: %d", drm_dev.crtc_idx); return 0; free_res: drmModeFreeResources(res); return -1; } static int drm_open(const char *path) { int fd, flags; uint64_t has_dumb; int ret; fd = open(path, O_RDWR); if (fd < 0) { err("cannot open \"%s\"", path); return -1; } /* set FD_CLOEXEC flag */ if ((flags = fcntl(fd, F_GETFD)) < 0 || fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0) { err("fcntl FD_CLOEXEC failed"); goto err; } /* check capability */ ret = drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &has_dumb); if (ret < 0 || has_dumb == 0) { err("drmGetCap DRM_CAP_DUMB_BUFFER failed or \"%s\" doesn't have dumb " "buffer", path); goto err; } return fd; err: close(fd); return -1; } static int drm_setup(unsigned int fourcc) { int ret; const char *device_path = NULL; device_path = getenv("DRM_CARD"); if (!device_path) device_path = DRM_CARD; drm_dev.fd = drm_open(device_path); if (drm_dev.fd < 0) return -1; ret = drmSetClientCap(drm_dev.fd, DRM_CLIENT_CAP_ATOMIC, 1); if (ret) { err("No atomic modesetting support: %s", strerror(errno)); goto err; } ret = drm_find_connector(); if (ret) { err("available drm devices not found"); goto err; } ret = find_plane(fourcc, &drm_dev.plane_id, drm_dev.crtc_id, drm_dev.crtc_idx); if (ret) { err("Cannot find plane"); goto err; } drm_dev.plane = drmModeGetPlane(drm_dev.fd, drm_dev.plane_id); if (!drm_dev.plane) { err("Cannot get plane"); goto err; } drm_dev.crtc = drmModeGetCrtc(drm_dev.fd, drm_dev.crtc_id); if (!drm_dev.crtc) { err("Cannot get crtc"); goto err; } drm_dev.conn = drmModeGetConnector(drm_dev.fd, drm_dev.conn_id); if (!drm_dev.conn) { err("Cannot get connector"); goto err; } ret = drm_get_plane_props(); if (ret) { err("Cannot get plane props"); goto err; } ret = drm_get_crtc_props(); if (ret) { err("Cannot get crtc props"); goto err; } ret = drm_get_conn_props(); if (ret) { err("Cannot get connector props"); goto err; } drm_dev.drm_event_ctx.version = DRM_EVENT_CONTEXT_VERSION; drm_dev.drm_event_ctx.page_flip_handler = page_flip_handler; drm_dev.fourcc = fourcc; info("drm: Found plane_id: %u connector_id: %d crtc_id: %d", drm_dev.plane_id, drm_dev.conn_id, drm_dev.crtc_id); info("drm: %dx%d (%dmm X% dmm) pixel format %c%c%c%c", drm_dev.width, drm_dev.height, drm_dev.mmWidth, drm_dev.mmHeight, (fourcc>>0)&0xff, (fourcc>>8)&0xff, (fourcc>>16)&0xff, (fourcc>>24)&0xff); return 0; err: close(drm_dev.fd); return -1; } static int drm_allocate_dumb(struct drm_buffer *buf) { struct drm_mode_create_dumb creq; struct drm_mode_map_dumb mreq; uint32_t handles[4] = {0}, pitches[4] = {0}, offsets[4] = {0}; int ret; /* create dumb buffer */ lv_memset(&creq, 0, sizeof(creq)); creq.width = drm_dev.width; creq.height = drm_dev.height; creq.bpp = LV_COLOR_DEPTH; ret = drmIoctl(drm_dev.fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq); if (ret < 0) { err("DRM_IOCTL_MODE_CREATE_DUMB fail"); return -1; } buf->handle = creq.handle; buf->pitch = creq.pitch; buf->size = creq.size; /* prepare buffer for memory mapping */ lv_memset(&mreq, 0, sizeof(mreq)); mreq.handle = creq.handle; ret = drmIoctl(drm_dev.fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq); if (ret) { err("DRM_IOCTL_MODE_MAP_DUMB fail"); return -1; } buf->offset = mreq.offset; info("size %lu pitch %u offset %u", buf->size, buf->pitch, buf->offset); /* perform actual memory mapping */ buf->map = mmap(0, creq.size, PROT_READ | PROT_WRITE, MAP_SHARED, drm_dev.fd, mreq.offset); if (buf->map == MAP_FAILED) { err("mmap fail"); return -1; } /* clear the framebuffer to 0 (= full transparency in ARGB8888) */ lv_memset(buf->map, 0, creq.size); /* create framebuffer object for the dumb-buffer */ handles[0] = creq.handle; pitches[0] = creq.pitch; offsets[0] = 0; ret = drmModeAddFB2(drm_dev.fd, drm_dev.width, drm_dev.height, drm_dev.fourcc, handles, pitches, offsets, &buf->fb_handle, 0); if (ret) { err("drmModeAddFB fail"); return -1; } return 0; } static int drm_setup_buffers(void) { int ret; /*Allocate DUMB buffers*/ ret = drm_allocate_dumb(&drm_dev.drm_bufs[0]); if (ret) return ret; ret = drm_allocate_dumb(&drm_dev.drm_bufs[1]); if (ret) return ret; return 0; } void drm_wait_vsync(lv_disp_drv_t * disp_drv) { while(drm_dev.req) { struct pollfd pfd; pfd.fd = drm_dev.fd; pfd.events = POLLIN; int ret; do { ret = poll(&pfd, 1, -1); } while (ret == -1 && errno == EINTR); if(ret > 0) drmHandleEvent(drm_dev.fd, &drm_dev.drm_event_ctx); else { err("poll failed: %s", strerror(errno)); return; } } lv_disp_flush_ready(disp_drv); } void drm_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) { struct drm_buffer *fbuf = &drm_dev.drm_bufs[drm_dev.active_drm_buf_idx ^ 1]; if(!disp_drv->direct_mode) { /*Backwards compatibility: Non-direct flush */ uint32_t w = (area->x2 - area->x1) + 1; for (int y = 0, i = area->y1; i <= area->y2 ; ++i, ++y) { lv_memcpy(fbuf->map + (area->x1 * (LV_COLOR_SIZE / 8)) + (fbuf->pitch * i), (uint8_t *)color_p + (w * (LV_COLOR_SIZE / 8) * y), w * (LV_COLOR_SIZE / 8)); } } if(lv_disp_flush_is_last(disp_drv)) { /*Request buffer swap*/ if(drm_dmabuf_set_plane(fbuf)) { err("Flush fail"); return; } else dbg("Flush done"); drm_dev.active_drm_buf_idx ^= 1; } } #if LV_COLOR_DEPTH == 32 #define DRM_FOURCC DRM_FORMAT_XRGB8888 #elif LV_COLOR_DEPTH == 16 #define DRM_FOURCC DRM_FORMAT_RGB565 #else #error LV_COLOR_DEPTH not supported #endif void drm_get_sizes(lv_coord_t *width, lv_coord_t *height, uint32_t *dpi) { if (width) *width = drm_dev.width; if (height) *height = drm_dev.height; if (dpi && drm_dev.mmWidth) *dpi = DIV_ROUND_UP(drm_dev.width * 25400, drm_dev.mmWidth * 1000); } int drm_init(void) { int ret; ret = drm_setup(DRM_FOURCC); if (ret) { close(drm_dev.fd); drm_dev.fd = -1; return -1; } ret = drm_setup_buffers(); if (ret) { err("DRM buffer allocation failed"); close(drm_dev.fd); drm_dev.fd = -1; return -1; } info("DRM subsystem and buffer mapped successfully"); return 0; } int drm_disp_drv_init(lv_disp_drv_t * disp_drv) { lv_disp_drv_init(disp_drv); int ret = drm_init(); if(ret) return ret; lv_disp_draw_buf_init(&drm_dev.draw_buf, drm_dev.drm_bufs[1].map, drm_dev.drm_bufs[0].map, drm_dev.width * drm_dev.height); disp_drv->draw_buf = &drm_dev.draw_buf; disp_drv->direct_mode = true; disp_drv->hor_res = drm_dev.width; disp_drv->ver_res = drm_dev.height; disp_drv->flush_cb = drm_flush; disp_drv->wait_cb = drm_wait_vsync; return 0; } void drm_exit(void) { close(drm_dev.fd); drm_dev.fd = -1; } #endif