// Copyright 2019 Fuzhou Rockchip Electronics Co., Ltd. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "buffer.h" #include #include #include #include #include #include #include "key_string.h" #include "utils.h" namespace easymedia { MediaBuffer::MemType StringToMemType(const char* s) { if (s) { #ifdef LIBION if (!strcmp(s, KEY_MEM_ION) || !strcmp(s, KEY_MEM_HARDWARE)) { return MediaBuffer::MemType::MEM_HARD_WARE; } #endif #ifdef LIBDRM if (!strcmp(s, KEY_MEM_DRM) || !strcmp(s, KEY_MEM_HARDWARE)) { return MediaBuffer::MemType::MEM_HARD_WARE; } #endif LOG("warning: %s is not supported or not integrated, fallback to common\n", s); } return MediaBuffer::MemType::MEM_COMMON; } static int free_common_memory(void* buffer) { if (buffer) { free(buffer); } return 0; } static MediaBuffer alloc_common_memory(size_t size) { void* buffer = malloc(size); if (!buffer) { return MediaBuffer(); } return MediaBuffer(buffer, size, -1, buffer, free_common_memory); } static MediaGroupBuffer* alloc_common_memory_group(size_t size) { void* buffer = malloc(size); if (!buffer) { return nullptr; } MediaGroupBuffer* mgb = new MediaGroupBuffer(buffer, size, -1, buffer, free_common_memory); return mgb; } #ifdef LIBION #include class IonBuffer { public: IonBuffer(int param_client, ion_user_handle_t param_handle, int param_fd, void* param_map_ptr, size_t param_len) : client(param_client), handle(param_handle), fd(param_fd), map_ptr(param_map_ptr), len(param_len) { } ~IonBuffer(); private: int client; ion_user_handle_t handle; int fd; void* map_ptr; size_t len; }; IonBuffer::~IonBuffer() { if (map_ptr) { munmap(map_ptr, len); } if (fd >= 0) { close(fd); } if (client < 0) { return; } if (handle) { int ret = ion_free(client, handle); if (ret) { LOG("ion_free() failed : %m!\n", handle); } } ion_close(client); } static int free_ion_memory(void* buffer) { assert(buffer); IonBuffer* ion_buffer = static_cast(buffer); delete ion_buffer; return 0; } static MediaBuffer alloc_ion_memory(size_t size) { ion_user_handle_t handle; int ret; int fd; void* ptr; IonBuffer* buffer; int client = ion_open(); if (client < 0) { LOG("ion_open() failed: %m\n"); goto err; } ret = ion_alloc(client, size, 0, ION_HEAP_TYPE_DMA_MASK, 0, &handle); if (ret) { LOG("ion_alloc() failed: %m\n"); ion_close(client); goto err; } ret = ion_share(client, handle, &fd); if (ret < 0) { LOG("ion_share() failed: %m\n"); ion_free(client, handle); ion_close(client); goto err; } ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED, fd, 0); if (!ptr) { LOG("ion mmap() failed: %m\n"); ion_free(client, handle); ion_close(client); close(fd); goto err; } buffer = new IonBuffer(client, handle, fd, ptr, size); if (!buffer) { ion_free(client, handle); ion_close(client); close(fd); munmap(ptr, size); goto err; } return MediaBuffer(ptr, size, fd, buffer, free_ion_memory); err: return MediaBuffer(); } #endif #ifdef LIBDRM #include #include /** * Copy from libdrm_macros.h while is not exposed by libdrm, * be replaced by #include "libdrm_macros.h" someday. */ /** * Static (compile-time) assertion. * Basically, use COND to dimension an array. If COND is false/zero the * array size will be -1 and we'll get a compilation error. */ #define STATIC_ASSERT(COND) \ do { \ (void)sizeof(char[1 - 2 * !(COND)]); \ } while (0) #if defined(ANDROID) && !defined(__LP64__) #include /* for EINVAL */ extern void* __mmap2(void*, size_t, int, int, int, size_t); static inline void* drm_mmap(void* addr, size_t length, int prot, int flags, int fd, loff_t offset) { /* offset must be aligned to 4096 (not necessarily the page size) */ if (offset & 4095) { errno = EINVAL; return MAP_FAILED; } return __mmap2(addr, length, prot, flags, fd, (size_t)(offset >> 12)); } #define drm_munmap(addr, length) munmap(addr, length) #else /* assume large file support exists */ #define drm_mmap(addr, length, prot, flags, fd, offset) mmap(addr, length, prot, flags, fd, offset) static inline int drm_munmap(void* addr, size_t length) { /* Copied from configure code generated by AC_SYS_LARGEFILE */ #define LARGE_OFF_T ((((off_t)1 << 31) << 31) - 1 + (((off_t)1 << 31) << 31)) STATIC_ASSERT(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1); #undef LARGE_OFF_T return munmap(addr, length); } #endif // default static int card_index = 0; static int drm_device_open(const char* device = nullptr) { drmVersionPtr version; char drm_dev[] = "/dev/dri/card0000"; uint64_t has_dumb; if (!device) { snprintf(drm_dev, sizeof(drm_dev), DRM_DEV_NAME, DRM_DIR_NAME, card_index); device = drm_dev; } int fd = open(device, O_RDWR); if (fd < 0) { return fd; } version = drmGetVersion(fd); if (!version) { LOG("Failed to get version information " "from %s: probably not a DRM device?\n", device); close(fd); return -1; } LOG("Opened DRM device %s: driver %s " "version %d.%d.%d.\n", device, version->name, version->version_major, version->version_minor, version->version_patchlevel); drmFreeVersion(version); if (drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &has_dumb) < 0 || !has_dumb) { LOG("drm device '%s' " "does not support dumb buffers\n", device); close(fd); return -1; } return fd; } class DrmDevice { public: DrmDevice() { fd = drm_device_open(); } ~DrmDevice() { if (fd >= 0) { close(fd); } } bool Valid() { return fd >= 0; } const static std::shared_ptr& GetInstance() { const static std::shared_ptr mDrmDevice = std::make_shared(); return mDrmDevice; } int fd; }; class DrmBuffer { public: DrmBuffer(std::shared_ptr dev, size_t s, __u32 flags = 0) : device(dev), handle(0), len(UPALIGNTO(s, PAGE_SIZE)), fd(-1), map_ptr(nullptr) { struct drm_mode_create_dumb dmcb; memset(&dmcb, 0, sizeof(struct drm_mode_create_dumb)); dmcb.bpp = 8; dmcb.width = len; dmcb.height = 1; dmcb.flags = flags; int ret = drmIoctl(dev->fd, DRM_IOCTL_MODE_CREATE_DUMB, &dmcb); if (ret < 0) { LOG("Failed to create dumb: %m\n", dmcb.width, dmcb.height, dmcb.bpp); return; } assert(dmcb.handle > 0); assert(dmcb.size >= dmcb.width * dmcb.height * dmcb.bpp / 8); handle = dmcb.handle; len = dmcb.size; ret = drmPrimeHandleToFD(dev->fd, dmcb.handle, DRM_CLOEXEC, &fd); if (ret) { LOG("Failed to convert drm handle to fd: %m\n"); return; } assert(fd >= 0); } ~DrmBuffer() { if (map_ptr) { drm_munmap(map_ptr, len); } int ret; if (handle > 0) { struct drm_mode_destroy_dumb data = { .handle = handle, }; ret = drmIoctl(device->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &data); if (ret) { LOG("Failed to free drm handle <%d>: %m\n", handle); } } if (fd >= 0) { ret = close(fd); if (ret) { LOG("Failed to close drm buffer fd <%d>: %m\n", fd); } } } bool MapToVirtual() { struct drm_mode_map_dumb dmmd; memset(&dmmd, 0, sizeof(dmmd)); dmmd.handle = handle; int ret = drmIoctl(device->fd, DRM_IOCTL_MODE_MAP_DUMB, &dmmd); if (ret) { LOG("Failed to map dumb: %m\n"); return false; } // default read and write void* ptr = drm_mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, device->fd, dmmd.offset); if (ptr == MAP_FAILED) { LOG("Failed to drm_mmap: %m\n"); return false; } assert(ptr); map_ptr = ptr; return true; } bool Valid() { return fd >= 0; } std::shared_ptr device; __u32 handle; size_t len; int fd; void* map_ptr; }; static int free_drm_memory(void* buffer) { assert(buffer); delete static_cast(buffer); return 0; } /* memory type definitions. */ enum drm_rockchip_gem_mem_type { /* Physically Continuous memory. */ ROCKCHIP_BO_CONTIG = 1 << 0, /* cachable mapping. */ ROCKCHIP_BO_CACHABLE = 1 << 1, /* write-combine mapping. */ ROCKCHIP_BO_WC = 1 << 2, ROCKCHIP_BO_SECURE = 1 << 3, ROCKCHIP_BO_MASK = ROCKCHIP_BO_CONTIG | ROCKCHIP_BO_CACHABLE | ROCKCHIP_BO_WC }; static MediaBuffer alloc_drm_memory(size_t size, bool map = true) { const static std::shared_ptr& drm_dev = DrmDevice::GetInstance(); DrmBuffer* db = nullptr; do { if (!drm_dev || !drm_dev->Valid()) { break; } db = new DrmBuffer(drm_dev, size, ROCKCHIP_BO_CACHABLE); if (!db || !db->Valid()) { break; } if (map && !db->MapToVirtual()) { break; } return MediaBuffer(db->map_ptr, db->len, db->fd, db, free_drm_memory); } while (false); if (db) { delete db; } return MediaBuffer(); } static MediaGroupBuffer* alloc_drm_memory_group(size_t size, bool map = true) { const static std::shared_ptr& drm_dev = DrmDevice::GetInstance(); DrmBuffer* db = nullptr; do { if (!drm_dev || !drm_dev->Valid()) { break; } db = new DrmBuffer(drm_dev, size, ROCKCHIP_BO_CACHABLE); if (!db || !db->Valid()) { break; } if (map && !db->MapToVirtual()) { break; } MediaGroupBuffer* mgb = nullptr; mgb = new MediaGroupBuffer(db->map_ptr, db->len, db->fd, db, free_drm_memory); return mgb; } while (false); if (db) { delete db; } return nullptr; } #endif std::shared_ptr MediaBuffer::Alloc(size_t size, MemType type) { MediaBuffer&& mb = Alloc2(size, type); if (mb.GetSize() == 0) { return nullptr; } return std::make_shared(mb); } MediaBuffer MediaBuffer::Alloc2(size_t size, MemType type) { switch (type) { case MemType::MEM_COMMON: return alloc_common_memory(size); #ifdef LIBION case MemType::MEM_HARD_WARE: return alloc_ion_memory(size); #endif #ifdef LIBDRM case MemType::MEM_HARD_WARE: return alloc_drm_memory(size); #endif default: LOG("unknown memtype\n"); return MediaBuffer(); } } std::shared_ptr MediaBuffer::Clone(MediaBuffer& src, MemType dst_type) { size_t size = src.GetValidSize(); if (!size) { return nullptr; } auto new_buffer = Alloc(size, dst_type); if (!new_buffer) { LOG_NO_MEMORY(); return nullptr; } if (src.IsHwBuffer() && new_buffer->IsHwBuffer()) { LOG_TODO(); // TODO: fd -> fd by RGA } memcpy(new_buffer->GetPtr(), src.GetPtr(), size); new_buffer->SetValidSize(size); new_buffer->CopyAttribute(src); return new_buffer; } void MediaBuffer::CopyAttribute(MediaBuffer& src_attr) { type = src_attr.GetType(); user_flag = src_attr.GetUserFlag(); ustimestamp = src_attr.GetUSTimeStamp(); eof = src_attr.IsEOF(); } struct dma_buf_sync { uint64_t flags; }; #define DMA_BUF_SYNC_READ (1 << 0) #define DMA_BUF_SYNC_WRITE (2 << 0) #define DMA_BUF_SYNC_RW (DMA_BUF_SYNC_READ | DMA_BUF_SYNC_WRITE) #define DMA_BUF_SYNC_START (0 << 2) #define DMA_BUF_SYNC_END (1 << 2) #define DMA_BUF_SYNC_VALID_FLAGS_MASK (DMA_BUF_SYNC_RW | DMA_BUF_SYNC_END) #define DMA_BUF_BASE 'b' #define DMA_BUF_IOCTL_SYNC _IOW(DMA_BUF_BASE, 0, struct dma_buf_sync) void MediaBuffer::BeginCPUAccess(bool readonly) { struct dma_buf_sync sync = {0}; if (fd < 0) { return; } if (readonly) { sync.flags = DMA_BUF_SYNC_READ | DMA_BUF_SYNC_START; } else { sync.flags = DMA_BUF_SYNC_RW | DMA_BUF_SYNC_START; } int ret = ioctl(fd, DMA_BUF_IOCTL_SYNC, &sync); if (ret < 0) { LOG("%s: %s\n", __func__, strerror(errno)); } } void MediaBuffer::EndCPUAccess(bool readonly) { struct dma_buf_sync sync = {0}; if (fd < 0) { return; } if (readonly) { sync.flags = DMA_BUF_SYNC_READ | DMA_BUF_SYNC_END; } else { sync.flags = DMA_BUF_SYNC_RW | DMA_BUF_SYNC_END; } int ret = ioctl(fd, DMA_BUF_IOCTL_SYNC, &sync); if (ret < 0) { LOG("%s: %s\n", __func__, strerror(errno)); } } MediaGroupBuffer* MediaGroupBuffer::Alloc(size_t size, MediaBuffer::MemType type) { switch (type) { case MediaBuffer::MemType::MEM_COMMON: return alloc_common_memory_group(size); #ifdef LIBDRM case MediaBuffer::MemType::MEM_HARD_WARE: return alloc_drm_memory_group(size); #endif default: LOG("unknown memtype\n"); return nullptr; } } BufferPool::BufferPool(int cnt, int size, MediaBuffer::MemType type) { bool sucess = true; if (cnt <= 0) { LOG("ERROR: BufferPool: cnt:%d is invalid!\n", cnt); return; } for (int i = 0; i < cnt; i++) { auto mgb = MediaGroupBuffer::Alloc(size, type); if (!mgb) { sucess = false; break; } mgb->SetBufferPool(this); LOGD("Create: pool:%p, mgb:%p, ptr:%p, fd:%d, size:%zu\n", this, mgb, mgb->GetPtr(), mgb->GetFD(), mgb->GetSize()); ready_buffers.push_back(mgb); } if (!sucess) { while (ready_buffers.size() > 0) { ready_buffers.pop_front(); } LOG("ERROR: BufferPool: Create buffer pool failed! Please check space is " "enough!\n"); return; } buf_cnt = cnt; buf_size = size; LOGD("BufferPool: Create buffer pool:%p, size:%d, cnt:%d\n", this, size, cnt); } BufferPool::~BufferPool() { int cnt = 0; int wait_times = 30; while (busy_buffers.size() > 0) { if (wait_times-- <= 0) { LOG("ERROR: BufferPool: waiting bufferpool free for 900ms, TimeOut!\n"); break; } easymedia::usleep(30000); // wait 30ms } MediaGroupBuffer* mgb = NULL; while (ready_buffers.size() > 0) { mgb = ready_buffers.front(); ready_buffers.pop_front(); LOGD("BufferPool: #%02d Destroy buffer pool(ready):[%p,%p]\n", cnt, this, mgb); delete mgb; cnt++; } while (busy_buffers.size() > 0) { mgb = busy_buffers.front(); busy_buffers.pop_front(); LOG("WARN: BufferPool: #%02d Destroy buffer pool(busy):[%p,%p]\n", cnt, this, mgb); delete mgb; cnt++; } } static int __groupe_buffer_free(void* data) { assert(data); if (data == NULL) { LOG("ERROR: BufferPool: free ptr is null!\n"); return 0; } MediaGroupBuffer* mgb = (MediaGroupBuffer*)data; if (mgb->pool == NULL) { LOG("ERROR: BufferPool: free pool ptr is null!\n"); return 0; } BufferPool* bp = (BufferPool*)mgb->pool; return bp->PutBuffer(mgb); } std::shared_ptr BufferPool::GetBuffer(bool block) { AutoLockMutex _alm(mtx); while (1) { if (!ready_buffers.size()) { if (block) { mtx.wait(); } else { return nullptr; } } // mtx.notify wake up all mtx.wait. if (ready_buffers.size() > 0) { break; } } auto mgb = ready_buffers.front(); ready_buffers.pop_front(); busy_buffers.push_back(mgb); auto&& mb = std::make_shared(mgb->GetPtr(), mgb->GetSize(), mgb->GetFD(), mgb, __groupe_buffer_free); return mb; } int BufferPool::PutBuffer(MediaGroupBuffer* mgb) { std::list::iterator it; bool sucess = false; AutoLockMutex _alm(mtx); for (it = busy_buffers.begin(); it != busy_buffers.end();) { if (*it == mgb) { sucess = true; it = busy_buffers.erase(it); ready_buffers.push_back(mgb); mtx.notify(); break; } it++; } if (!sucess) { LOG("ERROR: BufferPool: Unknow media group buffer:%p\n", mgb); } return sucess ? 0 : -1; } void BufferPool::DumpInfo() { int id = 0; LOG("##BufferPool DumpInfo:%p\n", this); LOG("\tcnt:%d\n", buf_cnt); LOG("\tsize:%zu\n", buf_size); LOG("\tready buffers(%d):\n", ready_buffers.size()); for (auto dev : ready_buffers) LOG("\t #%02d Pool:%p, mgb:%p, ptr:%p\n", id++, dev->pool, dev, dev->GetPtr()); LOG("\tbusy buffers(%d):\n", busy_buffers.size()); id = 0; for (auto dev : busy_buffers) LOG("\t #%02d Pool:%p, mgb:%p, ptr:%p\n", id++, dev->pool, dev, dev->GetPtr()); } } // namespace easymedia