#define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "drmtools.h" #include "logind.h" #include "ttytools.h" #include "render.h" #include "complete.h" /* ------------------------------------------------------------------ */ #define ARRAY_SIZE(_x) (sizeof(_x)/sizeof(_x[0])) /* ------------------------------------------------------------------ */ static struct { uint64_t cap; const char *name; } virtio_caps[] = { { .cap = VIRTGPU_PARAM_3D_FEATURES, .name = "virgl-3d" }, #ifdef VIRTGPU_PARAM_CAPSET_QUERY_FIX { .cap = VIRTGPU_PARAM_CAPSET_QUERY_FIX, .name = "capset-fix" }, #endif }; static int virtio_get_cap(uint64_t cap, int *value) { struct drm_virtgpu_getparam args; int rc; args.param = cap; args.value = (intptr_t)(value); rc = drmIoctl(drm_fd, DRM_IOCTL_VIRTGPU_GETPARAM, &args); if (rc != 0) { if (errno == EINVAL) { return -1; } else { fprintf(stderr, "ioctl DRM_IOCTL_VIRTGPU_GETPARAM(%" PRId64 "): %s\n", cap, strerror(errno)); exit(1); } } return 0; } static void virtio_print_caps(void) { int i, rc, value; printf("virtio capabilities\n"); for (i = 0; i < ARRAY_SIZE(virtio_caps); i++) { rc = virtio_get_cap(virtio_caps[i].cap, &value); if (rc == -1) { printf(" %-12s: not available\n", virtio_caps[i].name); } else { printf(" %-12s: %d\n", virtio_caps[i].name, value); } } fprintf(stdout, "\n"); } static void virtio_list_format(void) { int i; drm_print_format_hdr(stdout, 0, false, true); for (i = 0; i < fmtcnt; i++) { if (!fmts[i].virtio) continue; drm_print_format(stdout, fmts + i, 0, false, true); } fprintf(stdout, "\n"); } static void virtio_check(int cardno) { char syspath[128]; char symlink[128]; int len; snprintf(syspath, sizeof(syspath), "/sys/class/drm/card%d", cardno); len = readlink(syspath, symlink, sizeof(symlink)); if (len < 0) { fprintf(stderr, "readlink %s: %s\n", syspath, strerror(errno)); exit(1); } symlink[len] = 0; if (strstr(symlink, "/virtio") == NULL) { fprintf(stderr, "card%d: not a virtio-gpu device\n", cardno); exit(1); } } /* ------------------------------------------------------------------ */ static struct drm_virtgpu_resource_create create; static uint8_t *fbmem; static const struct fbformat *fmt; static cairo_surface_t *cs; static void virtio_init_fb(void) { struct drm_virtgpu_resource_info info; struct drm_virtgpu_map map; uint32_t stride, zero = 0; int rc; /* create framebuffer */ memset(&create, 0, sizeof(create)); create.format = fmt->virtio; create.width = drm_mode->hdisplay; create.height = drm_mode->vdisplay; create.target = 2; /* ??? */ create.bind = (1 << 1); /* ??? */ create.depth = 1; create.array_size = 1; stride = create.width * fmt->bpp / 8; create.size = stride * create.height; rc = drmIoctl(drm_fd, DRM_IOCTL_VIRTGPU_RESOURCE_CREATE, &create); if (rc < 0) { fprintf(stderr, "DRM_IOCTL_VIRTGPU_RESOURCE_CREATE: %s\n", strerror(errno)); exit(1); } memset(&info, 0, sizeof(info)); info.bo_handle = create.bo_handle; rc = drmIoctl(drm_fd, DRM_IOCTL_VIRTGPU_RESOURCE_INFO, &info); if (rc < 0) { fprintf(stderr, "DRM_IOCTL_VIRTGPU_RESOURCE_INFO: %s\n", strerror(errno)); exit(1); } memset(&map, 0, sizeof(map)); map.handle = create.bo_handle; rc = drmIoctl(drm_fd, DRM_IOCTL_VIRTGPU_MAP, &map); if (rc < 0) { fprintf(stderr, "DRM_IOCTL_VIRTGPU_MAP: %s\n", strerror(errno)); exit(1); } fbmem = mmap(0, info.size, PROT_READ | PROT_WRITE, MAP_SHARED, drm_fd, map.offset); if (fbmem == MAP_FAILED) { fprintf(stderr, "framebuffer mmap: %s\n", strerror(errno)); exit(1); } if (info.stride) stride = info.stride; rc = drmModeAddFB2(drm_fd, create.width, create.height, fmt->fourcc, &create.bo_handle, &stride, &zero, &fb_id, 0); if (rc < 0) { fprintf(stderr, "drmModeAddFB2() failed: %s\n", strerror(errno)); exit(1); } cs = cairo_image_surface_create_for_data(fbmem, fmt->cairo, create.width, create.height, stride); } static void virtio_draw(void) { char info1[80], info2[80], info3[80]; cairo_t *cr; snprintf(info1, sizeof(info1), "virtio-gpu"); snprintf(info2, sizeof(info2), "%dx%d", drm_mode->hdisplay, drm_mode->vdisplay); snprintf(info3, sizeof(info3), "fourcc %c%c%c%c", (fmt->fourcc >> 0) & 0xff, (fmt->fourcc >> 8) & 0xff, (fmt->fourcc >> 16) & 0xff, (fmt->fourcc >> 24) & 0xff); cr = cairo_create(cs); render_test(cr, drm_mode->hdisplay, drm_mode->vdisplay, info1, info2, info3); cairo_destroy(cr); } static void virtio_transfer(void) { struct drm_virtgpu_3d_transfer_to_host xfer; int rc; memset(&xfer, 0, sizeof(xfer)); xfer.bo_handle = create.bo_handle; xfer.box.w = drm_mode->hdisplay; xfer.box.h = drm_mode->vdisplay; xfer.box.d = 1; rc = drmIoctl(drm_fd, DRM_IOCTL_VIRTGPU_TRANSFER_TO_HOST, &xfer); if (rc < 0) { fprintf(stderr, "DRM_IOCTL_VIRTGPU_TRANSFER_TO_HOST: %s\n", strerror(errno)); exit(1); } } /* ------------------------------------------------------------------ */ static void usage(FILE *fp) { fprintf(fp, "\n" "usage: virtiotest [ options ]\n" "\n" "options:\n" " -h | --help print this\n" " -a | --autotest autotest mode\n" " -i | --info print virtio device info\n" " -l | --list-formats list formats\n" " -c | --card pick card\n" " -s | --sleep set sleep time (default: 60)\n" "\n"); } enum { OPT_LONG_COMP_BASH = 0x100, }; static struct option long_opts[] = { { /* --- no argument --- */ .name = "help", .has_arg = false, .val = 'h', },{ .name = "autotest", .has_arg = false, .val = 'a', },{ .name = "info", .has_arg = false, .val = 'i', },{ .name = "list-formats", .has_arg = false, .val = 'l', },{ .name = "complete-bash", .has_arg = false, .val = OPT_LONG_COMP_BASH, },{ /* --- with argument --- */ .name = "card", .has_arg = true, .val = 'c', },{ .name = "sleep", .has_arg = true, .val = 's', },{ /* end of list */ } }; int main(int argc, char **argv) { int card = 0; int secs = 60; char *output = NULL; char *modename = NULL; bool printinfo = false; bool listformat = false; bool autotest = false; int c, i; for (;;) { c = getopt_long(argc, argv, "hailc:s:", long_opts, NULL); if (c == -1) break; switch (c) { case 'c': card = atoi(optarg); break; case 's': secs = atoi(optarg); break; case 'a': autotest = true; break; case 'i': printinfo = true; break; case 'l': listformat = true; break; case OPT_LONG_COMP_BASH: complete_bash("virtiotest", long_opts); exit(0); case 'h': usage(stdout); exit(0); default: usage(stderr); exit(1); } } virtio_check(card); for (i = 0; i < fmtcnt; i++) { if (fmts[i].cairo == CAIRO_FORMAT_RGB24 && fmts[i].virtio != 0) { fmt = &fmts[i]; } } assert(fmt != NULL); logind_init(); drm_init_dev(card, output, modename, false, -1); if (printinfo) virtio_print_caps(); if (listformat) virtio_list_format(); if (printinfo || listformat) goto done; virtio_init_fb(); virtio_draw(); virtio_transfer(); drm_show_fb(); if (autotest) fprintf(stdout, "---ok---\n"); tty_raw(); kbd_wait(secs); kbd_read(); tty_restore(); done: drm_fini_dev(); logind_fini(); return 0; }