| /* |
| * Copyright © 2012 ARM Limited |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a |
| * copy of this software and associated documentation files (the "Software"), |
| * to deal in the Software without restriction, including without limitation |
| * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
| * and/or sell copies of the Software, and to permit persons to whom the |
| * Software is furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice (including the next |
| * paragraph) shall be included in all copies or substantial portions of the |
| * Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| * SOFTWARE. |
| * |
| */ |
| #include <stdlib.h> |
| #include <sys/mman.h> |
| #include <assert.h> |
| #include <errno.h> |
| #include <unistd.h> |
| |
| #include <xf86.h> |
| #include <xf86drm.h> |
| #include <xf86drmMode.h> |
| |
| #include "armsoc_dumb.h" |
| #include "drmmode_driver.h" |
| |
| #define ALIGN(val, align) (((val) + (align) - 1) & ~((align) - 1)) |
| |
| struct armsoc_device { |
| int fd; |
| int (*create_custom_gem)(int fd, struct armsoc_create_gem *create_gem); |
| }; |
| |
| struct armsoc_bo { |
| struct armsoc_device *dev; |
| uint32_t handle; |
| uint32_t size; |
| void *map_addr; |
| uint32_t fb_id; |
| uint32_t width; |
| uint32_t height; |
| uint8_t depth; |
| uint8_t bpp; |
| uint32_t pitch; |
| int refcnt; |
| int dmabuf; |
| /* initial size of backing memory. Used on resize to |
| * check if the new size will fit |
| */ |
| uint32_t original_size; |
| uint32_t name; |
| }; |
| |
| /* device related functions: |
| */ |
| |
| struct armsoc_device *armsoc_device_new(int fd, |
| int (*create_custom_gem)(int fd, |
| struct armsoc_create_gem *create_gem)) |
| { |
| struct armsoc_device *new_dev = malloc(sizeof(*new_dev)); |
| if (!new_dev) |
| return NULL; |
| |
| new_dev->fd = fd; |
| new_dev->create_custom_gem = create_custom_gem; |
| return new_dev; |
| } |
| |
| void armsoc_device_del(struct armsoc_device *dev) |
| { |
| free(dev); |
| } |
| |
| /* buffer-object related functions: |
| */ |
| |
| int armsoc_bo_set_dmabuf(struct armsoc_bo *bo) |
| { |
| int res; |
| struct drm_prime_handle prime_handle; |
| |
| assert(bo->refcnt > 0); |
| assert(!armsoc_bo_has_dmabuf(bo)); |
| |
| /* Try to get dma_buf fd */ |
| prime_handle.handle = bo->handle; |
| prime_handle.flags = 0; |
| res = drmIoctl(bo->dev->fd, DRM_IOCTL_PRIME_HANDLE_TO_FD, |
| &prime_handle); |
| if (res) |
| res = errno; |
| else |
| bo->dmabuf = prime_handle.fd; |
| |
| return res; |
| } |
| |
| void armsoc_bo_clear_dmabuf(struct armsoc_bo *bo) |
| { |
| assert(bo->refcnt > 0); |
| assert(armsoc_bo_has_dmabuf(bo)); |
| |
| close(bo->dmabuf); |
| bo->dmabuf = -1; |
| } |
| |
| int armsoc_bo_has_dmabuf(struct armsoc_bo *bo) |
| { |
| assert(bo->refcnt > 0); |
| return bo->dmabuf >= 0; |
| } |
| |
| struct armsoc_bo *armsoc_bo_new_with_dim(struct armsoc_device *dev, |
| uint32_t width, uint32_t height, uint8_t depth, |
| uint8_t bpp, enum armsoc_buf_type buf_type) |
| { |
| struct armsoc_create_gem create_gem; |
| struct armsoc_bo *new_buf; |
| int res; |
| |
| new_buf = malloc(sizeof(*new_buf)); |
| if (!new_buf) |
| return NULL; |
| |
| create_gem.buf_type = buf_type; |
| create_gem.height = height; |
| create_gem.width = width; |
| create_gem.bpp = bpp; |
| res = dev->create_custom_gem(dev->fd, &create_gem); |
| if (res) { |
| free(new_buf); |
| xf86DrvMsg(-1, X_ERROR, |
| "_CREATE_GEM({height: %d, width: %d, bpp: %d buf_type: 0x%X}) failed. errno: %d - %s\n", |
| height, width, bpp, buf_type, |
| errno, strerror(errno)); |
| return NULL; |
| } |
| |
| new_buf->dev = dev; |
| new_buf->handle = create_gem.handle; |
| new_buf->size = create_gem.size; |
| new_buf->map_addr = NULL; |
| new_buf->fb_id = 0; |
| new_buf->pitch = create_gem.pitch; |
| new_buf->width = create_gem.width; |
| new_buf->height = create_gem.height; |
| new_buf->original_size = create_gem.size; |
| new_buf->depth = depth; |
| new_buf->bpp = create_gem.bpp; |
| new_buf->refcnt = 1; |
| new_buf->dmabuf = -1; |
| new_buf->name = 0; |
| |
| return new_buf; |
| } |
| |
| static void armsoc_bo_del(struct armsoc_bo *bo) |
| { |
| int res; |
| struct drm_mode_destroy_dumb destroy_dumb; |
| |
| if (!bo) |
| return; |
| |
| /* NB: name doesn't need cleanup */ |
| |
| assert(bo->refcnt == 0); |
| assert(bo->dmabuf < 0); |
| |
| if (bo->map_addr) { |
| /* always map/unmap the full buffer for consistency */ |
| munmap(bo->map_addr, bo->original_size); |
| } |
| |
| if (bo->fb_id) { |
| res = drmModeRmFB(bo->dev->fd, bo->fb_id); |
| if (res) |
| xf86DrvMsg(-1, X_ERROR, "drmModeRmFb failed %d : %s\n", |
| res, strerror(errno)); |
| } |
| destroy_dumb.handle = bo->handle; |
| res = drmIoctl(bo->dev->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_dumb); |
| if (res) |
| xf86DrvMsg(-1, X_ERROR, "destroy dumb failed %d : %s\n", |
| res, strerror(errno)); |
| |
| free(bo); |
| } |
| |
| void armsoc_bo_unreference(struct armsoc_bo *bo) |
| { |
| if (!bo) |
| return; |
| |
| assert(bo->refcnt > 0); |
| if (--bo->refcnt == 0) |
| armsoc_bo_del(bo); |
| } |
| |
| void armsoc_bo_reference(struct armsoc_bo *bo) |
| { |
| assert(bo->refcnt > 0); |
| bo->refcnt++; |
| } |
| |
| int armsoc_bo_get_name(struct armsoc_bo *bo, uint32_t *name) |
| { |
| if (bo->name == 0) { |
| int ret; |
| struct drm_gem_flink flink; |
| |
| assert(bo->refcnt > 0); |
| flink.handle = bo->handle; |
| |
| ret = drmIoctl(bo->dev->fd, DRM_IOCTL_GEM_FLINK, &flink); |
| if (ret) { |
| xf86DrvMsg(-1, X_ERROR, |
| "_GEM_FLINK(handle:0x%X)failed. errno:0x%X\n", |
| flink.handle, errno); |
| return ret; |
| } |
| |
| bo->name = flink.name; |
| } |
| |
| *name = bo->name; |
| |
| return 0; |
| } |
| |
| uint32_t armsoc_bo_handle(struct armsoc_bo *bo) |
| { |
| assert(bo->refcnt > 0); |
| return bo->handle; |
| } |
| |
| uint32_t armsoc_bo_size(struct armsoc_bo *bo) |
| { |
| assert(bo->refcnt > 0); |
| return bo->size; |
| } |
| |
| uint32_t armsoc_bo_width(struct armsoc_bo *bo) |
| { |
| assert(bo->refcnt > 0); |
| return bo->width; |
| } |
| |
| uint32_t armsoc_bo_height(struct armsoc_bo *bo) |
| { |
| assert(bo->refcnt > 0); |
| return bo->height; |
| } |
| |
| uint32_t armsoc_bo_bpp(struct armsoc_bo *bo) |
| { |
| assert(bo->refcnt > 0); |
| return bo->bpp; |
| } |
| |
| uint32_t armsoc_bo_pitch(struct armsoc_bo *bo) |
| { |
| assert(bo->refcnt > 0); |
| return bo->pitch; |
| } |
| |
| void *armsoc_bo_map(struct armsoc_bo *bo) |
| { |
| assert(bo->refcnt > 0); |
| if (!bo->map_addr) { |
| struct drm_mode_map_dumb map_dumb; |
| int res; |
| |
| map_dumb.handle = bo->handle; |
| |
| res = drmIoctl(bo->dev->fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb); |
| if (res) |
| return NULL; |
| |
| /* always map/unmap the full buffer for consistency */ |
| bo->map_addr = mmap(NULL, bo->original_size, |
| PROT_READ | PROT_WRITE, MAP_SHARED, |
| bo->dev->fd, map_dumb.offset); |
| |
| if (bo->map_addr == MAP_FAILED) |
| bo->map_addr = NULL; |
| } |
| |
| return bo->map_addr; |
| } |
| |
| int armsoc_bo_cpu_prep(struct armsoc_bo *bo, enum armsoc_gem_op op) |
| { |
| int ret = 0; |
| |
| assert(bo->refcnt > 0); |
| if (armsoc_bo_has_dmabuf(bo)) { |
| fd_set fds; |
| /* 10s before printing a msg */ |
| const struct timeval timeout = {10, 0}; |
| struct timeval t; |
| |
| FD_ZERO(&fds); |
| FD_SET(bo->dmabuf, &fds); |
| |
| do { |
| t = timeout; |
| ret = select(bo->dmabuf+1, &fds, NULL, NULL, &t); |
| if (ret == 0) |
| xf86DrvMsg(-1, X_ERROR, |
| "select() on dma_buf fd has timed-out\n"); |
| } while ((ret == -1 && errno == EINTR) || ret == 0); |
| |
| if (ret > 0) |
| ret = 0; |
| } |
| return ret; |
| } |
| |
| int armsoc_bo_cpu_fini(struct armsoc_bo *bo, enum armsoc_gem_op op) |
| { |
| assert(bo->refcnt > 0); |
| return msync(bo->map_addr, bo->size, MS_SYNC | MS_INVALIDATE); |
| } |
| |
| int armsoc_bo_add_fb(struct armsoc_bo *bo) |
| { |
| int ret; |
| |
| assert(bo->refcnt > 0); |
| assert(bo->fb_id == 0); |
| |
| ret = drmModeAddFB(bo->dev->fd, bo->width, bo->height, bo->depth, |
| bo->bpp, bo->pitch, bo->handle, &bo->fb_id); |
| if (ret < 0) { |
| bo->fb_id = 0; |
| return ret; |
| } |
| return 0; |
| } |
| |
| int armsoc_bo_rm_fb(struct armsoc_bo *bo) |
| { |
| int ret; |
| |
| assert(bo->refcnt > 0); |
| assert(bo->fb_id != 0); |
| ret = drmModeRmFB(bo->dev->fd, bo->fb_id); |
| if (ret < 0) { |
| xf86DrvMsg(-1, X_ERROR, |
| "Could not remove fb from bo %d\n", ret); |
| return ret; |
| } |
| bo->fb_id = 0; |
| return 0; |
| } |
| |
| uint32_t armsoc_bo_get_fb(struct armsoc_bo *bo) |
| { |
| assert(bo->refcnt > 0); |
| return bo->fb_id; |
| } |
| |
| int armsoc_bo_clear(struct armsoc_bo *bo) |
| { |
| unsigned char *dst; |
| |
| assert(bo->refcnt > 0); |
| dst = armsoc_bo_map(bo); |
| if (!dst) { |
| xf86DrvMsg(-1, X_ERROR, |
| "Couldn't map scanout bo\n"); |
| return -1; |
| } |
| if (armsoc_bo_cpu_prep(bo, ARMSOC_GEM_WRITE)) { |
| xf86DrvMsg(-1, X_ERROR, |
| " %s: armsoc_bo_cpu_prep failed - unable to synchronise access.\n", |
| __func__); |
| return -1; |
| } |
| memset(dst, 0x0, bo->size); |
| (void)armsoc_bo_cpu_fini(bo, ARMSOC_GEM_WRITE); |
| return 0; |
| } |
| |
| int armsoc_bo_resize(struct armsoc_bo *bo, uint32_t new_width, |
| uint32_t new_height) |
| { |
| uint32_t new_size; |
| uint32_t new_pitch; |
| |
| assert(bo != NULL); |
| assert(new_width > 0); |
| assert(new_height > 0); |
| /* The caller must remove the fb object before |
| * attempting to resize. |
| */ |
| assert(bo->fb_id == 0); |
| assert(bo->refcnt > 0); |
| |
| xf86DrvMsg(-1, X_INFO, "Resizing bo from %dx%d to %dx%d\n", |
| bo->width, bo->height, new_width, new_height); |
| |
| /* TODO: MIDEGL-1563: Get pitch from DRM as |
| * only DRM knows the ideal pitch and alignment |
| * requirements |
| * */ |
| new_pitch = new_width * ((armsoc_bo_bpp(bo)+7)/8); |
| /* Align pitch to 64 byte */ |
| new_pitch = ALIGN(new_pitch, 64); |
| new_size = (((new_height-1) * new_pitch) + |
| (new_width * ((armsoc_bo_bpp(bo)+7)/8))); |
| |
| if (new_size <= bo->original_size) { |
| bo->width = new_width; |
| bo->height = new_height; |
| bo->pitch = new_pitch; |
| bo->size = new_size; |
| return 0; |
| } |
| xf86DrvMsg(-1, X_ERROR, "Failed to resize buffer\n"); |
| return -1; |
| } |