| /* |
| * Copyright © 2011 Red Hat |
| * |
| * 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 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. |
| * |
| * Authors: |
| * Prathyush K <prathyush.k@samsung.com> |
| */ |
| |
| /* |
| * This test program is similar to the modetest program. It tests the exynos |
| * specific drm gem ioctls and mmaps. It also tests the exynos drm prime |
| * module for exporting gem handle to fd and importing fd to gem handle. |
| * |
| * MMAP: |
| * There are three ways to map a exynos gem object to user space. |
| * 1> mmap using ioctl DRM_IOCTL_EXYNOS_GEM_MMAP |
| * 2> mmap of offset. |
| * This is done by first mapping the offset using ioctl |
| * DRM_IOCTL_EXYNOS_GEM_MAP_OFFSET and then calling mmap with this offset |
| * 3> mmap of fd. |
| * This is done by first exporting the gem object to dmabuf FD and |
| * then calling mmap on this fd. |
| * |
| * DMABUF: |
| * A gem object can be exported to a FD by caling ioctl |
| * DRM_IOCTL_PRIME_HANDLE_TO_FD. This will return a FD. |
| * A dmabuf FD can be imported to create a new gem object. This is done using |
| * the ioctl DRM_IOCTL_PRIME_FD_TO_HANDLE. |
| * |
| * These 3 different mmaps are tested in this program as well as the two |
| * ioctls which test handle to fd and fd to handle. The new gem object |
| * created from FD is also mapped to user space using ioctl and mmap. |
| */ |
| |
| #include "config.h" |
| #include <assert.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <stdint.h> |
| #include <inttypes.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <sys/poll.h> |
| #include <sys/time.h> |
| #include "xf86drm.h" |
| #include "xf86drmMode.h" |
| #include "drm_fourcc.h" |
| #include "libkms.h" |
| #include <sys/mman.h> |
| #include "drm.h" |
| #include "exynos_drm.h" |
| |
| drmModeRes *resources; |
| int fd; |
| static char optstr[] = "ecpmfs:P:v"; |
| |
| /* |
| * Mode setting with the kernel interfaces is a bit of a chore. |
| * First you have to find the connector in question and make sure the |
| * requested mode is available. |
| * Then you need to find the encoder attached to that connector so you |
| * can bind it with a free crtc. |
| */ |
| struct connector { |
| uint32_t id; |
| char mode_str[64]; |
| drmModeModeInfo *mode; |
| drmModeEncoder *encoder; |
| int crtc; |
| int pipe; |
| unsigned int fb_id[2], current_fb_id; |
| struct timeval start; |
| |
| int swap_count; |
| }; |
| |
| static void |
| connector_find_mode(struct connector *c) |
| { |
| drmModeConnector *connector; |
| int i, j; |
| |
| /* First, find the connector & mode */ |
| c->mode = NULL; |
| for (i = 0; i < resources->count_connectors; i++) { |
| connector = drmModeGetConnector(fd, resources->connectors[i]); |
| |
| if (!connector) { |
| fprintf(stderr, "could not get connector %i: %s\n", |
| resources->connectors[i], strerror(errno)); |
| drmModeFreeConnector(connector); |
| continue; |
| } |
| |
| if (!connector->count_modes) { |
| drmModeFreeConnector(connector); |
| continue; |
| } |
| |
| if (connector->connector_id != c->id) { |
| drmModeFreeConnector(connector); |
| continue; |
| } |
| |
| for (j = 0; j < connector->count_modes; j++) { |
| c->mode = &connector->modes[j]; |
| if (!strcmp(c->mode->name, c->mode_str)) |
| break; |
| } |
| |
| /* Found it, break out */ |
| if (c->mode) |
| break; |
| |
| drmModeFreeConnector(connector); |
| } |
| |
| if (!c->mode) { |
| fprintf(stderr, "failed to find mode \"%s\"\n", c->mode_str); |
| return; |
| } |
| |
| /* Now get the encoder */ |
| for (i = 0; i < resources->count_encoders; i++) { |
| c->encoder = drmModeGetEncoder(fd, resources->encoders[i]); |
| |
| if (!c->encoder) { |
| fprintf(stderr, "could not get encoder %i: %s\n", |
| resources->encoders[i], strerror(errno)); |
| drmModeFreeEncoder(c->encoder); |
| continue; |
| } |
| |
| if (c->encoder->encoder_id == connector->encoder_id) |
| break; |
| |
| drmModeFreeEncoder(c->encoder); |
| } |
| |
| if (c->crtc == -1) |
| c->crtc = c->encoder->crtc_id; |
| |
| /* and figure out which crtc index it is: */ |
| for (i = 0; i < resources->count_crtcs; i++) { |
| if (c->crtc == resources->crtcs[i]) { |
| c->pipe = i; |
| break; |
| } |
| } |
| |
| } |
| |
| static void |
| set_mode(struct connector *c, int count) |
| { |
| unsigned int fb_id; |
| int ret, size, width, height, i; |
| unsigned handle; |
| void *usr_addr; |
| |
| struct drm_exynos_gem_create gem; |
| struct drm_exynos_gem_map_off map_off; |
| struct drm_exynos_gem_mmap mmapbuf; |
| struct drm_prime_handle prime; |
| |
| width = 0; |
| height = 0; |
| for (i = 0; i < count; i++) { |
| connector_find_mode(&c[i]); |
| if (c[i].mode == NULL) |
| continue; |
| width += c[i].mode->hdisplay; |
| if (height < c[i].mode->vdisplay) |
| height = c[i].mode->vdisplay; |
| } |
| |
| size = width*height*4; |
| |
| /* Allocate GEM object */ |
| gem.size = size; |
| gem.flags = EXYNOS_BO_NONCONTIG; |
| ret = ioctl(fd, DRM_IOCTL_EXYNOS_GEM_CREATE, &gem); |
| if (ret < 0) { |
| printf("Failed to create gem object\n"); |
| return; |
| } |
| |
| /* The allocated buffer is split into 5 parts and each part |
| * is memset using a different mapping to user space. |
| * 1> mmap using ioctl |
| * 2> mmap using mapped offset |
| * 3> mmap using dmabuf fd |
| * 4> mmap using ioctl on gem object created from fd |
| * 5> mmap using mapped offset of gem object created from fd |
| */ |
| |
| /* Map gem object to user space: mmap ioctl*/ |
| mmapbuf.handle = gem.handle; |
| mmapbuf.size = gem.size; |
| ret = ioctl(fd, DRM_IOCTL_EXYNOS_GEM_MMAP, &mmapbuf); |
| if (ret < 0) { |
| printf("Failed to mmap gem object\n"); |
| return; |
| } |
| usr_addr = (void *)(unsigned long)mmapbuf.mapped; |
| |
| printf("vAddr of gem(%x) using mmap ioctl is: %x\n", |
| gem.handle, usr_addr); |
| |
| /* Memset first 1/5th of the buffer*/ |
| if (usr_addr) |
| memset(usr_addr, 0xFF, size/5); |
| |
| /* Map gem object to user space: mmap*/ |
| map_off.offset = 0; |
| map_off.handle = gem.handle; |
| ret = ioctl(fd, DRM_IOCTL_EXYNOS_GEM_MAP_OFFSET, &map_off); |
| if (ret < 0) { |
| printf("Failed to map offset\n"); |
| return; |
| } |
| |
| usr_addr = mmap(0, gem.size, PROT_READ | PROT_WRITE, |
| MAP_SHARED, fd, map_off.offset); |
| |
| printf("vAddr of gem(%x) using mmap of offset(%x) is: %x\n", |
| gem.handle, (unsigned long)map_off.offset, usr_addr); |
| |
| /* Memset second 1/5th of the buffer*/ |
| if (usr_addr) |
| memset(usr_addr+size/5, 0x44, size/5); |
| |
| /* GEM object to FD using DRM PRIME interface*/ |
| memset(&prime, 0, sizeof(struct drm_prime_handle)); |
| prime.handle = gem.handle; |
| ret = ioctl(fd, DRM_IOCTL_PRIME_HANDLE_TO_FD, &prime); |
| if (ret < 0) { |
| printf("Failed to get FD from prime handle\n"); |
| return; |
| } |
| printf("Prime Handle: %x to FD: %d\n", prime.handle, prime.fd); |
| |
| /* Map gem object to user space: mmap FD*/ |
| usr_addr = mmap(0, size, PROT_READ|PROT_WRITE, |
| MAP_SHARED, prime.fd, 0); |
| printf("vAddr using mmap of FD(%x) is: %x\n", prime.fd, usr_addr); |
| |
| /* Memset third 1/5th of the buffer*/ |
| if (usr_addr) |
| memset(usr_addr+2*size/5, 0x88, size/5); |
| else { |
| printf("Failed to mmap FD(%x)\n", prime.fd); |
| return; |
| } |
| |
| /* FD to GEM object using DRM PRIME interface*/ |
| prime.handle = 0; |
| ret = ioctl(fd, DRM_IOCTL_PRIME_FD_TO_HANDLE, &prime); |
| if (ret < 0) { |
| printf("Failed to get prime handle from FD\n"); |
| return; |
| } |
| printf("FD: %x to Prime Handle: %x\n", prime.fd, prime.handle); |
| |
| /* Map gem object to user space: mmap ioctl*/ |
| mmapbuf.handle = prime.handle; |
| mmapbuf.size = size; |
| ret = ioctl(fd, DRM_IOCTL_EXYNOS_GEM_MMAP, &mmapbuf); |
| if (ret < 0) { |
| printf("Failed to mmap gem object\n"); |
| return; |
| } |
| usr_addr = (void *)(unsigned long)mmapbuf.mapped; |
| |
| printf("vAddr of gem(%x) using mmap ioctl is: %x\n", |
| prime.handle, usr_addr); |
| |
| /* Memset fourth 1/5th of the buffer*/ |
| if (usr_addr) |
| memset(usr_addr+3*size/5, 0xBB, size/5); |
| |
| /* Map gem object to user space: mmap*/ |
| map_off.handle = prime.handle; |
| ret = ioctl(fd, DRM_IOCTL_EXYNOS_GEM_MAP_OFFSET, &map_off); |
| if (ret < 0) { |
| printf("Failed to map offset\n"); |
| return; |
| } |
| |
| usr_addr = mmap(0, gem.size, PROT_READ | PROT_WRITE, |
| MAP_SHARED, fd, map_off.offset); |
| |
| printf("vAddr of gem(%x) using mmap of offset(%x) is: %x\n", |
| prime.handle, (unsigned long)map_off.offset, usr_addr); |
| |
| /* Memset last 1/5th of the buffer*/ |
| if (usr_addr) |
| memset(usr_addr+4*size/5, 0xFF, size/5); |
| |
| /* Create FB from gem object*/ |
| /* The framebuffer will contain 5 bands from each memset*/ |
| ret = drmModeAddFB(fd, width, height, 32, 32, 0, gem.handle, &fb_id); |
| if (ret) { |
| fprintf(stderr, "failed to add fb: %s\n", strerror(errno)); |
| return; |
| } |
| |
| /* Set FB to Crtc*/ |
| printf("setting mode %s on connector %d, crtc %d\n", |
| c[0].mode_str, c[0].id, c[0].crtc); |
| ret = drmModeSetCrtc(fd, c[0].crtc, fb_id, |
| 0, 0, &c[0].id, 1, c[0].mode); |
| if (ret) |
| fprintf(stderr, "failed to set mode: %s\n", strerror(errno)); |
| } |
| |
| void usage(char *name) |
| { |
| fprintf(stderr, "usage: %s [-ecpmf]\n", name); |
| fprintf(stderr, "\t-s <connector_id>@<crtc_id>:<mode>\tset a mode\n"); |
| exit(0); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| int c; |
| int count = 0; |
| struct connector con_args[2]; |
| |
| opterr = 0; |
| while ((c = getopt(argc, argv, optstr)) != -1) { |
| switch (c) { |
| case 's': |
| con_args[count].crtc = -1; |
| if (sscanf(optarg, "%d:%64s", |
| &con_args[count].id, |
| con_args[count].mode_str) != 2 && |
| sscanf(optarg, "%d@%d:%64s", |
| &con_args[count].id, |
| &con_args[count].crtc, |
| con_args[count].mode_str) != 3) |
| usage(argv[0]); |
| count++; |
| break; |
| default: |
| usage(argv[0]); |
| break; |
| } |
| } |
| |
| fd = drmOpen("exynos", NULL); |
| if (fd < 0) { |
| printf("Failed to open drm device\n"); |
| return 0; |
| } |
| |
| resources = drmModeGetResources(fd); |
| if (!resources) { |
| fprintf(stderr, "drmModeGetResources failed: %s\n", |
| strerror(errno)); |
| drmClose(fd); |
| return 1; |
| } |
| |
| if (count > 0) { |
| set_mode(con_args, count); |
| getchar(); |
| } |
| |
| drmModeFreeResources(resources); |
| |
| return 0; |
| } |