// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2021 StarFive Technology Co., Ltd. */ #include #include #include "stf_drm.h" #include "stf_log.h" static int drm_open(const char *path, int need_dumb, int need_prime) { int fd, flags; uint64_t has_it; LOG(STF_LEVEL_TRACE, "Enter\n"); if ((fd = open(path, O_RDWR)) < 0) { LOG(STF_LEVEL_ERR, "cannot open \"%s\"\n", path); error("open"); } /* set FD_CLOEXEC flag */ if ((flags = fcntl(fd, F_GETFD)) < 0 || fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0) fatal("fcntl FD_CLOEXEC failed"); if (need_dumb) { if (drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &has_it) < 0) error("drmGetCap DRM_CAP_DUMB_BUFFER failed!"); if (has_it == 0) fatal("can't give us dumb buffers"); } if (need_prime) { /* check prime */ if (drmGetCap(fd, DRM_CAP_PRIME, &has_it) < 0) error("drmGetCap DRM_CAP_PRIME failed!"); if (!(has_it & DRM_PRIME_CAP_EXPORT)) fatal("can't export dmabuf"); } LOG(STF_LEVEL_TRACE, "Exit\n"); return fd; } static struct drm_dev_t *drm_find_dev(int fd, uint32_t width, uint32_t height) { int i, m; struct drm_dev_t *dev = NULL, *dev_head = NULL; drmModeRes *res; drmModeConnector *conn; drmModeEncoder *enc; drmModeModeInfo *mode = NULL, *preferred = NULL; LOG(STF_LEVEL_TRACE, "Enter\n"); if ((res = drmModeGetResources(fd)) == NULL) { fatal("drmModeGetResources() failed"); } LOG(STF_LEVEL_DEBUG, "count_connectors=%d\n", res->count_connectors); /* find all available connectors */ for (i = 0; i < res->count_connectors; i++) { conn = drmModeGetConnector(fd, res->connectors[i]); if (conn != NULL && conn->connection == DRM_MODE_CONNECTED && conn->count_modes > 0) { dev = (struct drm_dev_t *) malloc(sizeof(struct drm_dev_t)); memset(dev, 0, sizeof(struct drm_dev_t)); /* find preferred mode */ for (m = 0; m < conn->count_modes; m++) { mode = &conn->modes[m]; if (mode->hdisplay == width && mode->vdisplay == height) { preferred = mode; LOG(STF_LEVEL_INFO, "find the matched mode, modes index=%d, %dx%d\n", m, width, height); break; } if (mode->type & DRM_MODE_TYPE_PREFERRED) { preferred = mode; LOG(STF_LEVEL_INFO, "find perferred mode, modes index=%d\n", m); } LOG(STF_LEVEL_DEBUG, "mode: %dx%d %s\n", mode->hdisplay, mode->vdisplay, mode->type & DRM_MODE_TYPE_PREFERRED ? "*" : ""); } if (!preferred) preferred = &conn->modes[0]; dev->conn_id = conn->connector_id; // dev->next = NULL; if (conn->count_encoders) { if (conn->encoder_id) { dev->enc_id = conn->encoder_id; } else { dev->enc_id = conn->encoders[0]; } } else { fatal("count_encoders is 0"); } memcpy(&dev->mode, preferred, sizeof(drmModeModeInfo)); dev->width = preferred->hdisplay; dev->height = preferred->vdisplay; /* FIXME: use default encoder/crtc pair */ if ((enc = drmModeGetEncoder(fd, dev->enc_id)) == NULL) fatal("drmModeGetEncoder() faild"); if (enc->crtc_id) { dev->crtc_id = enc->crtc_id; } else { dev->crtc_id = res->crtcs[0]; } drmModeFreeEncoder(enc); dev->saved_crtc = NULL; /* create dev list */ dev->next = dev_head; dev_head = dev; } drmModeFreeConnector(conn); } drmModeFreeResources(res); LOG(STF_LEVEL_INFO, "selected connector(s)\n"); for (dev = dev_head; dev != NULL; dev = dev->next) { LOG(STF_LEVEL_INFO, "connector id:%d\n", dev->conn_id); LOG(STF_LEVEL_INFO, "\tencoder id:%d crtc id:%d\n", dev->enc_id, dev->crtc_id); LOG(STF_LEVEL_INFO, "\twidth:%d height:%d\n", dev->width, dev->height); } LOG(STF_LEVEL_TRACE, "Exit\n"); return dev_head; } static void drm_setup_buffer(int fd, struct drm_dev_t *dev, int width, int height, struct drm_buffer_t *buffer, int map, int export) { struct drm_mode_create_dumb create_req; struct drm_mode_map_dumb map_req; LOG(STF_LEVEL_TRACE, "Enter\n"); buffer->dmabuf_fd = -1; memset(&create_req, 0, sizeof(struct drm_mode_create_dumb)); create_req.width = width; create_req.height = height; create_req.bpp = 24; if (drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_req) < 0) { fatal("drmIoctl DRM_IOCTL_MODE_CREATE_DUMB failed"); } buffer->pitch = create_req.pitch; buffer->size = create_req.size; /* GEM buffer handle */ buffer->bo_handle = create_req.handle; if (export) { int ret; ret = drmPrimeHandleToFD(fd, buffer->bo_handle, DRM_CLOEXEC | DRM_RDWR, &buffer->dmabuf_fd); if (ret < 0) { fatal("could not export the dump buffer"); } LOG(STF_LEVEL_INFO, "drm fd=%d, bo_handle=%d, dmabuf_fd=%d\n", fd, buffer->bo_handle, buffer->dmabuf_fd); } if (map) { memset(&map_req, 0, sizeof(struct drm_mode_map_dumb)); map_req.handle = buffer->bo_handle; if (drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map_req)) { fatal("drmIoctl DRM_IOCTL_MODE_MAP_DUMB failed"); } buffer->buf = (uint8_t *)mmap(0, buffer->size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, map_req.offset); if (buffer->buf == MAP_FAILED) { error("mmap"); } } LOG(STF_LEVEL_TRACE, "Exit\n"); } static uint32_t drm_get_bpp_from_drm(uint32_t drmfmt) { uint32_t bpp; switch (drmfmt) { case DRM_FORMAT_YUV420: case DRM_FORMAT_YVU420: case DRM_FORMAT_YUV422: case DRM_FORMAT_NV12: case DRM_FORMAT_NV21: case DRM_FORMAT_NV16: bpp = 8; break; case DRM_FORMAT_P010: bpp = 10; break; case DRM_FORMAT_UYVY: case DRM_FORMAT_YUYV: case DRM_FORMAT_YVYU: case DRM_FORMAT_P016: bpp = 16; break; case DRM_FORMAT_BGR888: case DRM_FORMAT_RGB888: bpp = 24; break; default: bpp = 32; break; } return bpp; } static uint32_t drm_get_height_from_drm (uint32_t drmfmt, uint32_t height) { uint32_t ret; switch (drmfmt) { case DRM_FORMAT_YUV420: case DRM_FORMAT_YVU420: case DRM_FORMAT_YUV422: case DRM_FORMAT_NV12: case DRM_FORMAT_NV21: case DRM_FORMAT_P010: case DRM_FORMAT_P016: ret = height * 3 / 2; break; case DRM_FORMAT_NV16: ret = height * 2; break; default: ret = height; break; } return ret; } static int drm_setup_buffer2(int fd, struct drm_dev_t *dev, int width, int height, struct drm_buffer_t *buffer, int map, int export) { struct drm_mode_create_dumb create_req; struct drm_mode_map_dumb map_req; int ret = 0; LOG(STF_LEVEL_TRACE, "Enter\n"); buffer->dmabuf_fd = -1; memset(&create_req, 0, sizeof(struct drm_mode_create_dumb)); // For NV12 or NV21, bpp is 8, height is height * 3 / 2 create_req.width = width; create_req.height = drm_get_height_from_drm(dev->drm_format, height); create_req.bpp = drm_get_bpp_from_drm(dev->drm_format); if (drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_req) < 0) { fatal("drmIoctl DRM_IOCTL_MODE_CREATE_DUMB failed"); } buffer->pitch = create_req.pitch; buffer->size = create_req.size; buffer->bo_handle = create_req.handle; /* GEM buffer handle */ if (export) { #if 0 memset(&map_req, 0, sizeof(struct drm_mode_map_dumb)); map_req.handle = buffer->bo_handle; ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map_req); if (ret) { fatal("drmIoctl DRM_IOCTL_MODE_MAP_DUMB failed"); } buffer->buf = (uint8_t *) mmap(0, buffer->size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, map_req.offset); if (buffer->buf == MAP_FAILED) { error("drm mmap"); } LOG(STF_LEVEL_INFO, "buffer->buf= %p\n", buffer->buf); #endif ret = drmPrimeHandleToFD(fd, buffer->bo_handle, DRM_CLOEXEC | DRM_RDWR, &buffer->dmabuf_fd); if (ret < 0) { fatal("could not export the dump buffer"); } LOG(STF_LEVEL_DEBUG, "drm fd=%d, bo_handle=%d, dmabuf_fd=%d\n", fd, buffer->bo_handle, buffer->dmabuf_fd); } if (map) { memset(&map_req, 0, sizeof(struct drm_mode_map_dumb)); map_req.handle = buffer->bo_handle; if (drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map_req)) { fatal("drmIoctl DRM_IOCTL_MODE_MAP_DUMB failed"); } buffer->buf = (uint8_t *)mmap(0, buffer->size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, map_req.offset); if (buffer->buf == MAP_FAILED) { error("mmap"); } } uint32_t handles[4] = {0}, pitches[4] = {0}, offsets[4] = {0}; handles[0] = buffer->bo_handle; pitches[0] = buffer->pitch; offsets[0] = 0; handles[1] = buffer->bo_handle; pitches[1] = buffer->pitch; offsets[1] = buffer->pitch * height; // planes[0] = virtual; // planes[1] = virtual + offsets[1]; LOG(STF_LEVEL_INFO, "map=%d, export=%d. width=%d, height=%d, " "pitch=%d, size=%d, bo_handle=%d, dmabuf_fd=%d\n", map, export, width, height, buffer->pitch, buffer->size, buffer->bo_handle, buffer->dmabuf_fd); //DRM_FORMAT_NV12 ret = drmModeAddFB2(fd, dev->width, dev->height, dev->drm_format, handles, pitches, offsets, &(buffer->fb_id), 0); LOG(STF_LEVEL_TRACE, "Exit\n"); return ret; } static void drm_setup_dummy(int fd, struct drm_dev_t *dev, int map, int export) { int i; LOG(STF_LEVEL_TRACE, "Enter\n"); for (i = 0; i < BUFCOUNT; i++) { drm_setup_buffer(fd, dev, dev->width, dev->height, &dev->bufs[i], map, export); } /* Assume all buffers have the same pitch */ dev->pitch = dev->bufs[0].pitch; LOG(STF_LEVEL_INFO, "DRM: buffer pitch = %d bytes\n", dev->pitch); LOG(STF_LEVEL_TRACE, "Exit\n"); } static void drm_setup_fb(int fd, struct drm_dev_t *dev, int map, int export) { int i; LOG(STF_LEVEL_TRACE, "Enter\n"); #if 0 for (i = 0; i < BUFCOUNT; i++) { int ret; drm_setup_buffer(fd, dev, dev->width, dev->height, &dev->bufs[i], map, export); ret = drmModeAddFB(fd, dev->width, dev->height, DEPTH, BPP, dev->bufs[i].pitch, dev->bufs[i].bo_handle, &dev->bufs[i].fb_id); if (ret) fatal("drmModeAddFB failed"); LOG(STF_LEVEL_INFO, "width=%d,height=%d,pitch=%d,bo_handle=%d,fb_id=%d\n", dev->width, dev->height,dev->bufs[i].pitch,dev->bufs[i].bo_handle, dev->bufs[i].fb_id); } #else for (i = 0; i < BUFCOUNT; i++) { int ret = -1; ret = drm_setup_buffer2(fd, dev, dev->width, dev->height, &dev->bufs[i], map, export); if (ret) { fatal("drmModeAddFB failed"); } } #endif /* Assume all buffers have the same pitch */ dev->pitch = dev->bufs[0].pitch; LOG(STF_LEVEL_DEBUG, "DRM: buffer pitch %d bytes\n", dev->pitch); dev->saved_crtc = drmModeGetCrtc(fd, dev->crtc_id); /* must store crtc data */ /* First buffer to DRM */ if (drmModeSetCrtc(fd, dev->crtc_id, dev->bufs[0].fb_id, 0, 0, &dev->conn_id, 1, &dev->mode)) { fatal("drmModeSetCrtc() failed"); } /* First flip */ drmModePageFlip(fd, dev->crtc_id, dev->bufs[0].fb_id, DRM_MODE_PAGE_FLIP_EVENT, dev); LOG(STF_LEVEL_TRACE, "Exit\n"); } static void drm_destroy(int fd, struct drm_dev_t *dev_head) { struct drm_dev_t *devp, *devp_tmp; int i; LOG(STF_LEVEL_TRACE, "Enter\n"); for (devp = dev_head; devp != NULL;) { if (devp->saved_crtc) { drmModeSetCrtc(fd, devp->saved_crtc->crtc_id, devp->saved_crtc->buffer_id, devp->saved_crtc->x, devp->saved_crtc->y, &devp->conn_id, 1, &devp->saved_crtc->mode); drmModeFreeCrtc(devp->saved_crtc); } for (i = 0; i < BUFCOUNT; i++) { struct drm_mode_destroy_dumb dreq = { .handle = devp->bufs[i].bo_handle }; if (devp->bufs[i].buf) { munmap(devp->bufs[i].buf, devp->bufs[i].size); } if (devp->bufs[i].dmabuf_fd >= 0) { close(devp->bufs[i].dmabuf_fd); } drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); drmModeRmFB(fd, devp->bufs[i].fb_id); } devp_tmp = devp; devp = devp->next; free(devp_tmp); } drmClose(fd); LOG(STF_LEVEL_TRACE, "Exit\n"); } void stf_drm_open(DRMParam_t *param, char *device_name, int iomthd) { int drm_fd; LOG(STF_LEVEL_TRACE, "Enter\n"); if (IO_METHOD_MMAP == iomthd) { drm_fd = drm_open(device_name, 1, 0); } else if (IO_METHOD_DMABUF == iomthd) { drm_fd = drm_open(device_name, 1, 1); } else { LOG(STF_LEVEL_ERR, "drm: not support io_method %d\n", iomthd); exit (EXIT_FAILURE); } param->fd = drm_fd; LOG(STF_LEVEL_TRACE, "Exit\n"); } void stf_drm_init(DRMParam_t *param, uint32_t width, uint32_t height, uint32_t input_fmt, int iomthd, int *dmabufs, int nsize) { struct drm_dev_t *dev_head = NULL; int i = 0; LOG(STF_LEVEL_TRACE, "Enter\n"); dev_head = drm_find_dev(param->fd, width, height); if (dev_head == NULL) { LOG(STF_LEVEL_ERR, "available drm_dev not found\n"); exit (EXIT_FAILURE); } dev_head->drm_format = ffmpegfmt_to_drmfmt(input_fmt); if (IO_METHOD_MMAP == iomthd) { drm_setup_fb(param->fd, dev_head, 1, 0); } else if (IO_METHOD_DMABUF == iomthd) { drm_setup_fb(param->fd, dev_head, 0, 1); assert(nsize == BUFCOUNT); for (i = 0; i < nsize; i++) { dmabufs[i] = dev_head->bufs[i].dmabuf_fd; } } else { LOG(STF_LEVEL_ERR, "drm: not support io_method %d\n", iomthd); exit (EXIT_FAILURE); } param->dev_head = dev_head; LOG(STF_LEVEL_TRACE, "Exit\n"); } void stf_drm_close(DRMParam_t *param) { drm_destroy(param->fd, param->dev_head); }