| /* |
| * Copyright 2014, 2015 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 |
| * on the rights to use, copy, modify, merge, publish, distribute, sub |
| * license, 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 NON-INFRINGEMENT. IN NO EVENT SHALL |
| * THE AUTHOR(S) AND/OR THEIR SUPPLIERS 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 <sys/socket.h> |
| #include <errno.h> |
| #include <stdio.h> |
| #include <netinet/in.h> |
| #include <sys/un.h> |
| #include <unistd.h> |
| |
| #include <os/os_process.h> |
| #include <util/format/u_format.h> |
| |
| #include "virgl_vtest_winsys.h" |
| #include "virgl_vtest_public.h" |
| |
| /* block read/write routines */ |
| static int virgl_block_write(int fd, void *buf, int size) |
| { |
| void *ptr = buf; |
| int left; |
| int ret; |
| left = size; |
| do { |
| ret = write(fd, ptr, left); |
| if (ret < 0) |
| return -errno; |
| left -= ret; |
| ptr += ret; |
| } while (left); |
| return size; |
| } |
| |
| static int virgl_block_read(int fd, void *buf, int size) |
| { |
| void *ptr = buf; |
| int left; |
| int ret; |
| left = size; |
| do { |
| ret = read(fd, ptr, left); |
| if (ret <= 0) { |
| fprintf(stderr, |
| "lost connection to rendering server on %d read %d %d\n", |
| size, ret, errno); |
| abort(); |
| return ret < 0 ? -errno : 0; |
| } |
| left -= ret; |
| ptr += ret; |
| } while (left); |
| return size; |
| } |
| |
| static int virgl_vtest_receive_fd(int socket_fd) |
| { |
| struct cmsghdr *cmsgh; |
| struct msghdr msgh = { 0 }; |
| char buf[CMSG_SPACE(sizeof(int))], c; |
| struct iovec iovec; |
| |
| iovec.iov_base = &c; |
| iovec.iov_len = sizeof(char); |
| |
| msgh.msg_name = NULL; |
| msgh.msg_namelen = 0; |
| msgh.msg_iov = &iovec; |
| msgh.msg_iovlen = 1; |
| msgh.msg_control = buf; |
| msgh.msg_controllen = sizeof(buf); |
| msgh.msg_flags = 0; |
| |
| int size = recvmsg(socket_fd, &msgh, 0); |
| if (size < 0) { |
| fprintf(stderr, "Failed with %s\n", strerror(errno)); |
| return -1; |
| } |
| |
| cmsgh = CMSG_FIRSTHDR(&msgh); |
| if (!cmsgh) { |
| fprintf(stderr, "No headers available\n"); |
| return -1; |
| } |
| |
| if (cmsgh->cmsg_level != SOL_SOCKET) { |
| fprintf(stderr, "invalid cmsg_level %d\n", cmsgh->cmsg_level); |
| return -1; |
| } |
| |
| if (cmsgh->cmsg_type != SCM_RIGHTS) { |
| fprintf(stderr, "invalid cmsg_type %d\n", cmsgh->cmsg_type); |
| return -1; |
| } |
| |
| return *((int *) CMSG_DATA(cmsgh)); |
| } |
| |
| static int virgl_vtest_send_init(struct virgl_vtest_winsys *vws) |
| { |
| uint32_t buf[VTEST_HDR_SIZE]; |
| const char *nstr = "virtest"; |
| char cmdline[64]; |
| int ret; |
| |
| ret = os_get_process_name(cmdline, 63); |
| if (ret == FALSE) |
| strcpy(cmdline, nstr); |
| #if defined(HAVE_PROGRAM_INVOCATION_NAME) |
| if (!strcmp(cmdline, "shader_runner")) { |
| const char *name; |
| /* hack to get better testname */ |
| name = program_invocation_short_name; |
| name += strlen(name) + 1; |
| strncpy(cmdline, name, 63); |
| } |
| #endif |
| buf[VTEST_CMD_LEN] = strlen(cmdline) + 1; |
| buf[VTEST_CMD_ID] = VCMD_CREATE_RENDERER; |
| |
| virgl_block_write(vws->sock_fd, &buf, sizeof(buf)); |
| virgl_block_write(vws->sock_fd, (void *)cmdline, strlen(cmdline) + 1); |
| return 0; |
| } |
| |
| static int virgl_vtest_negotiate_version(struct virgl_vtest_winsys *vws) |
| { |
| uint32_t vtest_hdr[VTEST_HDR_SIZE]; |
| uint32_t version_buf[VCMD_PROTOCOL_VERSION_SIZE]; |
| uint32_t busy_wait_buf[VCMD_BUSY_WAIT_SIZE]; |
| uint32_t busy_wait_result[1]; |
| ASSERTED int ret; |
| |
| vtest_hdr[VTEST_CMD_LEN] = VCMD_PING_PROTOCOL_VERSION_SIZE; |
| vtest_hdr[VTEST_CMD_ID] = VCMD_PING_PROTOCOL_VERSION; |
| virgl_block_write(vws->sock_fd, &vtest_hdr, sizeof(vtest_hdr)); |
| |
| vtest_hdr[VTEST_CMD_LEN] = VCMD_BUSY_WAIT_SIZE; |
| vtest_hdr[VTEST_CMD_ID] = VCMD_RESOURCE_BUSY_WAIT; |
| busy_wait_buf[VCMD_BUSY_WAIT_HANDLE] = 0; |
| busy_wait_buf[VCMD_BUSY_WAIT_FLAGS] = 0; |
| virgl_block_write(vws->sock_fd, &vtest_hdr, sizeof(vtest_hdr)); |
| virgl_block_write(vws->sock_fd, &busy_wait_buf, sizeof(busy_wait_buf)); |
| |
| ret = virgl_block_read(vws->sock_fd, vtest_hdr, sizeof(vtest_hdr)); |
| assert(ret); |
| |
| if (vtest_hdr[VTEST_CMD_ID] == VCMD_PING_PROTOCOL_VERSION) { |
| /* Read dummy busy_wait response */ |
| ret = virgl_block_read(vws->sock_fd, vtest_hdr, sizeof(vtest_hdr)); |
| assert(ret); |
| ret = virgl_block_read(vws->sock_fd, busy_wait_result, sizeof(busy_wait_result)); |
| assert(ret); |
| |
| vtest_hdr[VTEST_CMD_LEN] = VCMD_PROTOCOL_VERSION_SIZE; |
| vtest_hdr[VTEST_CMD_ID] = VCMD_PROTOCOL_VERSION; |
| version_buf[VCMD_PROTOCOL_VERSION_VERSION] = VTEST_PROTOCOL_VERSION; |
| virgl_block_write(vws->sock_fd, &vtest_hdr, sizeof(vtest_hdr)); |
| virgl_block_write(vws->sock_fd, &version_buf, sizeof(version_buf)); |
| |
| ret = virgl_block_read(vws->sock_fd, vtest_hdr, sizeof(vtest_hdr)); |
| assert(ret); |
| ret = virgl_block_read(vws->sock_fd, version_buf, sizeof(version_buf)); |
| assert(ret); |
| return version_buf[VCMD_PROTOCOL_VERSION_VERSION]; |
| } |
| |
| /* Read dummy busy_wait response */ |
| assert(vtest_hdr[VTEST_CMD_ID] == VCMD_RESOURCE_BUSY_WAIT); |
| ret = virgl_block_read(vws->sock_fd, busy_wait_result, sizeof(busy_wait_result)); |
| assert(ret); |
| |
| /* Old server, return version 0 */ |
| return 0; |
| } |
| |
| int virgl_vtest_connect(struct virgl_vtest_winsys *vws) |
| { |
| struct sockaddr_un un; |
| int sock, ret; |
| |
| sock = socket(PF_UNIX, SOCK_STREAM, 0); |
| if (sock < 0) |
| return -1; |
| |
| memset(&un, 0, sizeof(un)); |
| un.sun_family = AF_UNIX; |
| snprintf(un.sun_path, sizeof(un.sun_path), "%s", VTEST_DEFAULT_SOCKET_NAME); |
| |
| do { |
| ret = 0; |
| if (connect(sock, (struct sockaddr *)&un, sizeof(un)) < 0) { |
| ret = -errno; |
| } |
| } while (ret == -EINTR); |
| |
| vws->sock_fd = sock; |
| virgl_vtest_send_init(vws); |
| vws->protocol_version = virgl_vtest_negotiate_version(vws); |
| |
| /* Version 1 is deprecated. */ |
| if (vws->protocol_version == 1) |
| vws->protocol_version = 0; |
| |
| return 0; |
| } |
| |
| int virgl_vtest_send_get_caps(struct virgl_vtest_winsys *vws, |
| struct virgl_drm_caps *caps) |
| { |
| uint32_t get_caps_buf[VTEST_HDR_SIZE * 2]; |
| uint32_t resp_buf[VTEST_HDR_SIZE]; |
| uint32_t caps_size = sizeof(struct virgl_caps_v2); |
| int ret; |
| get_caps_buf[VTEST_CMD_LEN] = 0; |
| get_caps_buf[VTEST_CMD_ID] = VCMD_GET_CAPS2; |
| get_caps_buf[VTEST_CMD_LEN + 2] = 0; |
| get_caps_buf[VTEST_CMD_ID + 2] = VCMD_GET_CAPS; |
| |
| virgl_block_write(vws->sock_fd, &get_caps_buf, sizeof(get_caps_buf)); |
| |
| ret = virgl_block_read(vws->sock_fd, resp_buf, sizeof(resp_buf)); |
| if (ret <= 0) |
| return 0; |
| |
| if (resp_buf[1] == 2) { |
| struct virgl_caps_v1 dummy; |
| uint32_t resp_size = resp_buf[0] - 1; |
| uint32_t dummy_size = 0; |
| if (resp_size > caps_size) { |
| dummy_size = resp_size - caps_size; |
| resp_size = caps_size; |
| } |
| |
| ret = virgl_block_read(vws->sock_fd, &caps->caps, resp_size); |
| |
| if (dummy_size) |
| ret = virgl_block_read(vws->sock_fd, &dummy, dummy_size); |
| |
| /* now read back the pointless caps v1 we requested */ |
| ret = virgl_block_read(vws->sock_fd, resp_buf, sizeof(resp_buf)); |
| if (ret <= 0) |
| return 0; |
| ret = virgl_block_read(vws->sock_fd, &dummy, sizeof(struct virgl_caps_v1)); |
| } else |
| ret = virgl_block_read(vws->sock_fd, &caps->caps, sizeof(struct virgl_caps_v1)); |
| |
| return 0; |
| } |
| |
| static int virgl_vtest_send_resource_create2(struct virgl_vtest_winsys *vws, |
| uint32_t handle, |
| enum pipe_texture_target target, |
| uint32_t format, |
| uint32_t bind, |
| uint32_t width, |
| uint32_t height, |
| uint32_t depth, |
| uint32_t array_size, |
| uint32_t last_level, |
| uint32_t nr_samples, |
| uint32_t size, |
| int *out_fd) |
| { |
| uint32_t res_create_buf[VCMD_RES_CREATE2_SIZE], vtest_hdr[VTEST_HDR_SIZE]; |
| |
| vtest_hdr[VTEST_CMD_LEN] = VCMD_RES_CREATE2_SIZE; |
| vtest_hdr[VTEST_CMD_ID] = VCMD_RESOURCE_CREATE2; |
| |
| res_create_buf[VCMD_RES_CREATE2_RES_HANDLE] = handle; |
| res_create_buf[VCMD_RES_CREATE2_TARGET] = target; |
| res_create_buf[VCMD_RES_CREATE2_FORMAT] = format; |
| res_create_buf[VCMD_RES_CREATE2_BIND] = bind; |
| res_create_buf[VCMD_RES_CREATE2_WIDTH] = width; |
| res_create_buf[VCMD_RES_CREATE2_HEIGHT] = height; |
| res_create_buf[VCMD_RES_CREATE2_DEPTH] = depth; |
| res_create_buf[VCMD_RES_CREATE2_ARRAY_SIZE] = array_size; |
| res_create_buf[VCMD_RES_CREATE2_LAST_LEVEL] = last_level; |
| res_create_buf[VCMD_RES_CREATE2_NR_SAMPLES] = nr_samples; |
| res_create_buf[VCMD_RES_CREATE2_DATA_SIZE] = size; |
| |
| virgl_block_write(vws->sock_fd, &vtest_hdr, sizeof(vtest_hdr)); |
| virgl_block_write(vws->sock_fd, &res_create_buf, sizeof(res_create_buf)); |
| |
| /* Multi-sampled textures have no backing store attached. */ |
| if (size == 0) |
| return 0; |
| |
| *out_fd = virgl_vtest_receive_fd(vws->sock_fd); |
| if (*out_fd < 0) { |
| fprintf(stderr, "failed to get fd\n"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| int virgl_vtest_send_resource_create(struct virgl_vtest_winsys *vws, |
| uint32_t handle, |
| enum pipe_texture_target target, |
| uint32_t format, |
| uint32_t bind, |
| uint32_t width, |
| uint32_t height, |
| uint32_t depth, |
| uint32_t array_size, |
| uint32_t last_level, |
| uint32_t nr_samples, |
| uint32_t size, |
| int *out_fd) |
| { |
| uint32_t res_create_buf[VCMD_RES_CREATE_SIZE], vtest_hdr[VTEST_HDR_SIZE]; |
| |
| if (vws->protocol_version >= 2) |
| return virgl_vtest_send_resource_create2(vws, handle, target, format, |
| bind, width, height, depth, |
| array_size, last_level, |
| nr_samples, size, out_fd); |
| |
| vtest_hdr[VTEST_CMD_LEN] = VCMD_RES_CREATE_SIZE; |
| vtest_hdr[VTEST_CMD_ID] = VCMD_RESOURCE_CREATE; |
| |
| res_create_buf[VCMD_RES_CREATE_RES_HANDLE] = handle; |
| res_create_buf[VCMD_RES_CREATE_TARGET] = target; |
| res_create_buf[VCMD_RES_CREATE_FORMAT] = format; |
| res_create_buf[VCMD_RES_CREATE_BIND] = bind; |
| res_create_buf[VCMD_RES_CREATE_WIDTH] = width; |
| res_create_buf[VCMD_RES_CREATE_HEIGHT] = height; |
| res_create_buf[VCMD_RES_CREATE_DEPTH] = depth; |
| res_create_buf[VCMD_RES_CREATE_ARRAY_SIZE] = array_size; |
| res_create_buf[VCMD_RES_CREATE_LAST_LEVEL] = last_level; |
| res_create_buf[VCMD_RES_CREATE_NR_SAMPLES] = nr_samples; |
| |
| virgl_block_write(vws->sock_fd, &vtest_hdr, sizeof(vtest_hdr)); |
| virgl_block_write(vws->sock_fd, &res_create_buf, sizeof(res_create_buf)); |
| |
| return 0; |
| } |
| |
| int virgl_vtest_submit_cmd(struct virgl_vtest_winsys *vws, |
| struct virgl_vtest_cmd_buf *cbuf) |
| { |
| uint32_t vtest_hdr[VTEST_HDR_SIZE]; |
| |
| vtest_hdr[VTEST_CMD_LEN] = cbuf->base.cdw; |
| vtest_hdr[VTEST_CMD_ID] = VCMD_SUBMIT_CMD; |
| |
| virgl_block_write(vws->sock_fd, &vtest_hdr, sizeof(vtest_hdr)); |
| virgl_block_write(vws->sock_fd, cbuf->buf, cbuf->base.cdw * 4); |
| return 0; |
| } |
| |
| int virgl_vtest_send_resource_unref(struct virgl_vtest_winsys *vws, |
| uint32_t handle) |
| { |
| uint32_t vtest_hdr[VTEST_HDR_SIZE]; |
| uint32_t cmd[1]; |
| vtest_hdr[VTEST_CMD_LEN] = 1; |
| vtest_hdr[VTEST_CMD_ID] = VCMD_RESOURCE_UNREF; |
| |
| cmd[0] = handle; |
| virgl_block_write(vws->sock_fd, &vtest_hdr, sizeof(vtest_hdr)); |
| virgl_block_write(vws->sock_fd, &cmd, sizeof(cmd)); |
| return 0; |
| } |
| |
| static int virgl_vtest_send_transfer_cmd(struct virgl_vtest_winsys *vws, |
| uint32_t vcmd, |
| uint32_t handle, |
| uint32_t level, uint32_t stride, |
| uint32_t layer_stride, |
| const struct pipe_box *box, |
| uint32_t data_size) |
| { |
| uint32_t vtest_hdr[VTEST_HDR_SIZE]; |
| uint32_t cmd[VCMD_TRANSFER_HDR_SIZE]; |
| vtest_hdr[VTEST_CMD_LEN] = VCMD_TRANSFER_HDR_SIZE; |
| vtest_hdr[VTEST_CMD_ID] = vcmd; |
| |
| /* The host expects the size in dwords so calculate the rounded up |
| * value here. */ |
| if (vcmd == VCMD_TRANSFER_PUT) |
| vtest_hdr[VTEST_CMD_LEN] += (data_size + 3) / 4; |
| |
| cmd[0] = handle; |
| cmd[1] = level; |
| cmd[2] = stride; |
| cmd[3] = layer_stride; |
| cmd[4] = box->x; |
| cmd[5] = box->y; |
| cmd[6] = box->z; |
| cmd[7] = box->width; |
| cmd[8] = box->height; |
| cmd[9] = box->depth; |
| cmd[10] = data_size; |
| virgl_block_write(vws->sock_fd, &vtest_hdr, sizeof(vtest_hdr)); |
| virgl_block_write(vws->sock_fd, &cmd, sizeof(cmd)); |
| |
| return 0; |
| } |
| |
| static int virgl_vtest_send_transfer_cmd2(struct virgl_vtest_winsys *vws, |
| uint32_t vcmd, |
| uint32_t handle, |
| uint32_t level, |
| const struct pipe_box *box, |
| uint32_t data_size, |
| uint32_t offset) |
| { |
| uint32_t vtest_hdr[VTEST_HDR_SIZE]; |
| uint32_t cmd[VCMD_TRANSFER2_HDR_SIZE]; |
| vtest_hdr[VTEST_CMD_LEN] = VCMD_TRANSFER2_HDR_SIZE; |
| vtest_hdr[VTEST_CMD_ID] = vcmd; |
| |
| /* The host expects the size in dwords so calculate the rounded up |
| * value here. */ |
| if (vcmd == VCMD_TRANSFER_PUT2) |
| vtest_hdr[VTEST_CMD_LEN] += (data_size + 3) / 4; |
| |
| cmd[VCMD_TRANSFER2_RES_HANDLE] = handle; |
| cmd[VCMD_TRANSFER2_LEVEL] = level; |
| cmd[VCMD_TRANSFER2_X] = box->x; |
| cmd[VCMD_TRANSFER2_Y] = box->y; |
| cmd[VCMD_TRANSFER2_Z] = box->z; |
| cmd[VCMD_TRANSFER2_WIDTH] = box->width; |
| cmd[VCMD_TRANSFER2_HEIGHT] = box->height; |
| cmd[VCMD_TRANSFER2_DEPTH] = box->depth; |
| cmd[VCMD_TRANSFER2_DATA_SIZE] = data_size; |
| cmd[VCMD_TRANSFER2_OFFSET] = offset; |
| virgl_block_write(vws->sock_fd, &vtest_hdr, sizeof(vtest_hdr)); |
| virgl_block_write(vws->sock_fd, &cmd, sizeof(cmd)); |
| |
| return 0; |
| } |
| |
| int virgl_vtest_send_transfer_get(struct virgl_vtest_winsys *vws, |
| uint32_t handle, |
| uint32_t level, uint32_t stride, |
| uint32_t layer_stride, |
| const struct pipe_box *box, |
| uint32_t data_size, |
| uint32_t offset) |
| { |
| if (vws->protocol_version < 2) |
| return virgl_vtest_send_transfer_cmd(vws, VCMD_TRANSFER_GET, handle, |
| level, stride, layer_stride, box, |
| data_size); |
| |
| return virgl_vtest_send_transfer_cmd2(vws, VCMD_TRANSFER_GET2, handle, |
| level, box, data_size, offset); |
| } |
| |
| int virgl_vtest_send_transfer_put(struct virgl_vtest_winsys *vws, |
| uint32_t handle, |
| uint32_t level, uint32_t stride, |
| uint32_t layer_stride, |
| const struct pipe_box *box, |
| uint32_t data_size, |
| uint32_t offset) |
| { |
| if (vws->protocol_version < 2) |
| return virgl_vtest_send_transfer_cmd(vws, VCMD_TRANSFER_PUT, handle, |
| level, stride, layer_stride, box, |
| data_size); |
| |
| return virgl_vtest_send_transfer_cmd2(vws, VCMD_TRANSFER_PUT2, handle, |
| level, box, data_size, offset); |
| } |
| |
| int virgl_vtest_send_transfer_put_data(struct virgl_vtest_winsys *vws, |
| void *data, |
| uint32_t data_size) |
| { |
| return virgl_block_write(vws->sock_fd, data, data_size); |
| } |
| |
| int virgl_vtest_recv_transfer_get_data(struct virgl_vtest_winsys *vws, |
| void *data, |
| uint32_t data_size, |
| uint32_t stride, |
| const struct pipe_box *box, |
| uint32_t format) |
| { |
| void *line; |
| void *ptr = data; |
| int hblocks = util_format_get_nblocksy(format, box->height); |
| |
| line = malloc(stride); |
| while (hblocks) { |
| virgl_block_read(vws->sock_fd, line, stride); |
| memcpy(ptr, line, util_format_get_stride(format, box->width)); |
| ptr += stride; |
| hblocks--; |
| } |
| free(line); |
| return 0; |
| } |
| |
| int virgl_vtest_busy_wait(struct virgl_vtest_winsys *vws, int handle, |
| int flags) |
| { |
| uint32_t vtest_hdr[VTEST_HDR_SIZE]; |
| uint32_t cmd[VCMD_BUSY_WAIT_SIZE]; |
| uint32_t result[1]; |
| ASSERTED int ret; |
| vtest_hdr[VTEST_CMD_LEN] = VCMD_BUSY_WAIT_SIZE; |
| vtest_hdr[VTEST_CMD_ID] = VCMD_RESOURCE_BUSY_WAIT; |
| cmd[VCMD_BUSY_WAIT_HANDLE] = handle; |
| cmd[VCMD_BUSY_WAIT_FLAGS] = flags; |
| |
| virgl_block_write(vws->sock_fd, &vtest_hdr, sizeof(vtest_hdr)); |
| virgl_block_write(vws->sock_fd, &cmd, sizeof(cmd)); |
| |
| ret = virgl_block_read(vws->sock_fd, vtest_hdr, sizeof(vtest_hdr)); |
| assert(ret); |
| ret = virgl_block_read(vws->sock_fd, result, sizeof(result)); |
| assert(ret); |
| return result[0]; |
| } |