per Intel policy to include file commit date using git cmd below. The policy does not apply to non-Intel (C) notices. git log --follow -C90% --format=%ad --date default <file> | tail -1 and then pull just the 4 digit year from the result. Intel copyrights were not added to files where Intel either had no contribution ot the contribution lacked substance (ie license header updates, formatting changes, etc). Contribution date used "--follow -C95%" to get the most accurate date. Note that several files in this patch didn't end the license/(c) block with a blank comment line so these were added as the vast majority of files do have this last blank line. Simply there for consistency. Signed-off-by: paul luse <paul.e.luse@intel.com> Change-Id: Id5b7ce4f658fe87132f14139ead58d6e285c04d4 Reviewed-on: https://review.spdk.io/gerrit/c/spdk/spdk/+/15192 Tested-by: SPDK CI Jenkins <sys_sgci@intel.com> Reviewed-by: Jim Harris <james.r.harris@intel.com> Reviewed-by: Ben Walker <benjamin.walker@intel.com> Community-CI: Mellanox Build Bot
380 lines
9.3 KiB
C
380 lines
9.3 KiB
C
/* SPDX-License-Identifier: BSD-3-Clause
|
|
* Copyright (C) 2020 Intel Corporation.
|
|
* All rights reserved.
|
|
*/
|
|
|
|
/*
|
|
* vfio-user client socket messages.
|
|
*/
|
|
|
|
#include "spdk/stdinc.h"
|
|
#include "spdk/queue.h"
|
|
#include "spdk/util.h"
|
|
#include "spdk/log.h"
|
|
#include "spdk/vfio_user_spec.h"
|
|
|
|
#include "vfio_user_internal.h"
|
|
|
|
struct vfio_user_request {
|
|
struct vfio_user_header hdr;
|
|
#define VFIO_USER_MAX_PAYLOAD_SIZE (4096)
|
|
uint8_t payload[VFIO_USER_MAX_PAYLOAD_SIZE];
|
|
int fds[VFIO_MAXIMUM_SPARSE_MMAP_REGIONS];
|
|
int fd_num;
|
|
};
|
|
|
|
#ifdef DEBUG
|
|
static const char *vfio_user_message_str[VFIO_USER_MAX] = {
|
|
[VFIO_USER_VERSION] = "VFIO_USER_VERSION",
|
|
[VFIO_USER_DMA_MAP] = "VFIO_USER_DMA_MAP",
|
|
[VFIO_USER_DMA_UNMAP] = "VFIO_USER_DMA_UNMAP",
|
|
[VFIO_USER_DEVICE_GET_INFO] = "VFIO_USER_DEVICE_GET_INFO",
|
|
[VFIO_USER_DEVICE_GET_REGION_INFO] = "VFIO_USER_DEVICE_GET_REGION_INFO",
|
|
[VFIO_USER_DEVICE_GET_IRQ_INFO] = "VFIO_USER_DEVICE_GET_IRQ_INFO",
|
|
[VFIO_USER_DEVICE_SET_IRQS] = "VFIO_USER_DEVICE_SET_IRQS",
|
|
[VFIO_USER_REGION_READ] = "VFIO_USER_REGION_READ",
|
|
[VFIO_USER_REGION_WRITE] = "VFIO_USER_REGION_WRITE",
|
|
[VFIO_USER_DMA_READ] = "VFIO_USER_DMA_READ",
|
|
[VFIO_USER_DMA_WRITE] = "VFIO_USER_DMA_WRITE",
|
|
[VFIO_USER_DEVICE_RESET] = "VFIO_USER_DEVICE_RESET",
|
|
};
|
|
#endif
|
|
|
|
static int
|
|
vfio_user_write(int fd, void *buf, int len, int *fds, int num_fds)
|
|
{
|
|
int r;
|
|
struct msghdr msgh;
|
|
struct iovec iov;
|
|
size_t fd_size = num_fds * sizeof(int);
|
|
char control[CMSG_SPACE(VFIO_MAXIMUM_SPARSE_MMAP_REGIONS * sizeof(int))];
|
|
struct cmsghdr *cmsg;
|
|
|
|
memset(&msgh, 0, sizeof(msgh));
|
|
memset(control, 0, sizeof(control));
|
|
|
|
iov.iov_base = (uint8_t *)buf;
|
|
iov.iov_len = len;
|
|
|
|
msgh.msg_iov = &iov;
|
|
msgh.msg_iovlen = 1;
|
|
|
|
assert(num_fds <= VFIO_MAXIMUM_SPARSE_MMAP_REGIONS);
|
|
|
|
if (fds && num_fds) {
|
|
msgh.msg_control = control;
|
|
msgh.msg_controllen = CMSG_SPACE(fd_size);
|
|
cmsg = CMSG_FIRSTHDR(&msgh);
|
|
assert(cmsg != NULL);
|
|
cmsg->cmsg_len = CMSG_LEN(fd_size);
|
|
cmsg->cmsg_level = SOL_SOCKET;
|
|
cmsg->cmsg_type = SCM_RIGHTS;
|
|
memcpy(CMSG_DATA(cmsg), fds, fd_size);
|
|
} else {
|
|
msgh.msg_control = NULL;
|
|
msgh.msg_controllen = 0;
|
|
}
|
|
|
|
do {
|
|
r = sendmsg(fd, &msgh, MSG_NOSIGNAL);
|
|
} while (r < 0 && errno == EINTR);
|
|
|
|
if (r == -1) {
|
|
return -errno;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
read_fd_message(int sockfd, char *buf, int buflen, int *fds, int *fd_num)
|
|
{
|
|
struct iovec iov;
|
|
struct msghdr msgh;
|
|
char control[CMSG_SPACE(VFIO_MAXIMUM_SPARSE_MMAP_REGIONS * sizeof(int))];
|
|
struct cmsghdr *cmsg;
|
|
int got_fds = 0;
|
|
int ret;
|
|
|
|
memset(&msgh, 0, sizeof(msgh));
|
|
iov.iov_base = buf;
|
|
iov.iov_len = buflen;
|
|
|
|
msgh.msg_iov = &iov;
|
|
msgh.msg_iovlen = 1;
|
|
msgh.msg_control = control;
|
|
msgh.msg_controllen = sizeof(control);
|
|
|
|
ret = recvmsg(sockfd, &msgh, 0);
|
|
if (ret <= 0) {
|
|
return ret;
|
|
}
|
|
|
|
if (msgh.msg_flags & (MSG_TRUNC | MSG_CTRUNC)) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
for (cmsg = CMSG_FIRSTHDR(&msgh); cmsg != NULL;
|
|
cmsg = CMSG_NXTHDR(&msgh, cmsg)) {
|
|
if ((cmsg->cmsg_level == SOL_SOCKET) &&
|
|
(cmsg->cmsg_type == SCM_RIGHTS)) {
|
|
got_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
|
|
*fd_num = got_fds;
|
|
assert(got_fds <= VFIO_MAXIMUM_SPARSE_MMAP_REGIONS);
|
|
memcpy(fds, CMSG_DATA(cmsg), got_fds * sizeof(int));
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
vfio_user_read(int fd, struct vfio_user_request *req)
|
|
{
|
|
int ret;
|
|
size_t sz_payload;
|
|
|
|
ret = read_fd_message(fd, (char *)req, sizeof(struct vfio_user_header), req->fds, &req->fd_num);
|
|
if (ret <= 0) {
|
|
return ret;
|
|
}
|
|
|
|
if (req->hdr.flags.error) {
|
|
SPDK_ERRLOG("Command %u return failure\n", req->hdr.cmd);
|
|
errno = req->hdr.error_no;
|
|
return -EFAULT;
|
|
}
|
|
|
|
if (req->hdr.msg_size > sizeof(struct vfio_user_header)) {
|
|
sz_payload = req->hdr.msg_size - sizeof(struct vfio_user_header);
|
|
ret = read(fd, req->payload, sz_payload);
|
|
if (ret <= 0) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
vfio_user_dev_send_request(struct vfio_device *dev, enum vfio_user_command command,
|
|
void *arg, size_t arg_len, size_t buf_len, int *fds, int max_fds)
|
|
{
|
|
struct vfio_user_request req = {};
|
|
size_t sz_payload;
|
|
int ret;
|
|
bool fds_write = false;
|
|
|
|
if (arg_len > VFIO_USER_MAX_PAYLOAD_SIZE) {
|
|
SPDK_ERRLOG("Oversized argument length, command %u\n", command);
|
|
return -EINVAL;
|
|
}
|
|
|
|
req.hdr.cmd = command;
|
|
req.hdr.msg_size = sizeof(struct vfio_user_header) + arg_len;
|
|
memcpy(req.payload, arg, arg_len);
|
|
|
|
if (command == VFIO_USER_DMA_MAP || command == VFIO_USER_DMA_UNMAP) {
|
|
fds_write = true;
|
|
}
|
|
|
|
SPDK_DEBUGLOG(vfio_user, "[I] Command %s, msg size %u, fds %p, max_fds %d\n",
|
|
vfio_user_message_str[command], req.hdr.msg_size, fds, max_fds);
|
|
|
|
if (fds_write && fds) {
|
|
ret = vfio_user_write(dev->fd, (void *)&req, req.hdr.msg_size, fds, max_fds);
|
|
} else {
|
|
ret = vfio_user_write(dev->fd, (void *)&req, req.hdr.msg_size, NULL, 0);
|
|
}
|
|
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/* a reply is mandatory */
|
|
memset(&req, 0, sizeof(req));
|
|
ret = vfio_user_read(dev->fd, &req);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
SPDK_DEBUGLOG(vfio_user, "[I] Command %s response, msg size %u\n",
|
|
vfio_user_message_str[req.hdr.cmd], req.hdr.msg_size);
|
|
|
|
assert(req.hdr.flags.type == VFIO_USER_MESSAGE_REPLY);
|
|
sz_payload = req.hdr.msg_size - sizeof(struct vfio_user_header);
|
|
if (!sz_payload) {
|
|
return 0;
|
|
}
|
|
|
|
if (!fds_write) {
|
|
if (sz_payload > buf_len) {
|
|
SPDK_ERRLOG("Payload size error sz %zd, buf_len %zd\n", sz_payload, buf_len);
|
|
return -EIO;
|
|
}
|
|
memcpy(arg, req.payload, sz_payload);
|
|
/* VFIO_USER_DEVICE_GET_REGION_INFO may contains BAR fd */
|
|
if (fds && req.fd_num) {
|
|
assert(req.fd_num < max_fds);
|
|
memcpy(fds, req.fds, sizeof(int) * req.fd_num);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
vfio_user_check_version(struct vfio_device *dev)
|
|
{
|
|
int ret;
|
|
struct vfio_user_request req = {};
|
|
struct vfio_user_version *version = (struct vfio_user_version *)req.payload;
|
|
|
|
version->major = VFIO_USER_MAJOR_VER;
|
|
version->minor = VFIO_USER_MINOR_VER;
|
|
|
|
ret = vfio_user_dev_send_request(dev, VFIO_USER_VERSION, req.payload,
|
|
sizeof(struct vfio_user_version), sizeof(req.payload), NULL, 0);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
SPDK_DEBUGLOG(vfio_user, "%s Negotiate version %u.%u\n", vfio_user_message_str[VFIO_USER_VERSION],
|
|
version->major, version->minor);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
vfio_user_get_dev_region_info(struct vfio_device *dev, struct vfio_region_info *region_info,
|
|
size_t buf_len, int *fds, int num_fds)
|
|
{
|
|
assert(buf_len > sizeof(struct vfio_region_info));
|
|
region_info->argsz = buf_len - sizeof(struct vfio_region_info);
|
|
return vfio_user_dev_send_request(dev, VFIO_USER_DEVICE_GET_REGION_INFO,
|
|
region_info, region_info->argsz, buf_len, fds, num_fds);
|
|
}
|
|
|
|
int
|
|
vfio_user_get_dev_info(struct vfio_device *dev, struct vfio_user_device_info *dev_info,
|
|
size_t buf_len)
|
|
{
|
|
dev_info->argsz = sizeof(struct vfio_user_device_info);
|
|
return vfio_user_dev_send_request(dev, VFIO_USER_DEVICE_GET_INFO,
|
|
dev_info, dev_info->argsz, buf_len, NULL, 0);
|
|
}
|
|
|
|
int
|
|
vfio_user_dev_dma_map_unmap(struct vfio_device *dev, struct vfio_memory_region *mr, bool map)
|
|
{
|
|
struct vfio_user_dma_map dma_map = { 0 };
|
|
struct vfio_user_dma_unmap dma_unmap = { 0 };
|
|
|
|
if (map) {
|
|
dma_map.argsz = sizeof(struct vfio_user_dma_map);
|
|
dma_map.addr = mr->iova;
|
|
dma_map.size = mr->size;
|
|
dma_map.offset = mr->offset;
|
|
dma_map.flags = VFIO_USER_F_DMA_REGION_READ | VFIO_USER_F_DMA_REGION_WRITE;
|
|
|
|
return vfio_user_dev_send_request(dev, VFIO_USER_DMA_MAP,
|
|
&dma_map, sizeof(dma_map), sizeof(dma_map), &mr->fd, 1);
|
|
} else {
|
|
dma_unmap.argsz = sizeof(struct vfio_user_dma_unmap);
|
|
dma_unmap.addr = mr->iova;
|
|
dma_unmap.size = mr->size;
|
|
return vfio_user_dev_send_request(dev, VFIO_USER_DMA_UNMAP,
|
|
&dma_unmap, sizeof(dma_unmap), sizeof(dma_unmap), &mr->fd, 1);
|
|
}
|
|
}
|
|
|
|
int
|
|
vfio_user_dev_mmio_access(struct vfio_device *dev, uint32_t index, uint64_t offset,
|
|
size_t len, void *buf, bool is_write)
|
|
{
|
|
struct vfio_user_region_access *access;
|
|
size_t arg_len;
|
|
int ret;
|
|
|
|
arg_len = sizeof(*access) + len;
|
|
access = calloc(1, arg_len);
|
|
if (!access) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
access->offset = offset;
|
|
access->region = index;
|
|
access->count = len;
|
|
if (is_write) {
|
|
memcpy(access->data, buf, len);
|
|
ret = vfio_user_dev_send_request(dev, VFIO_USER_REGION_WRITE,
|
|
access, arg_len, arg_len, NULL, 0);
|
|
} else {
|
|
ret = vfio_user_dev_send_request(dev, VFIO_USER_REGION_READ,
|
|
access, sizeof(*access), arg_len, NULL, 0);
|
|
}
|
|
|
|
if (ret) {
|
|
free(access);
|
|
return ret;
|
|
}
|
|
|
|
if (!is_write) {
|
|
memcpy(buf, (void *)access->data, len);
|
|
}
|
|
|
|
free(access);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
vfio_user_dev_setup(struct vfio_device *dev)
|
|
{
|
|
int fd;
|
|
int flag;
|
|
struct sockaddr_un un;
|
|
ssize_t rc;
|
|
|
|
fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
if (fd < 0) {
|
|
SPDK_ERRLOG("socket() error\n");
|
|
return -errno;
|
|
}
|
|
|
|
flag = fcntl(fd, F_GETFD);
|
|
if (fcntl(fd, F_SETFD, flag | FD_CLOEXEC) < 0) {
|
|
SPDK_ERRLOG("fcntl failed\n");
|
|
}
|
|
|
|
memset(&un, 0, sizeof(un));
|
|
un.sun_family = AF_UNIX;
|
|
rc = snprintf(un.sun_path, sizeof(un.sun_path), "%s", dev->path);
|
|
if (rc < 0 || (size_t)rc >= sizeof(un.sun_path)) {
|
|
SPDK_ERRLOG("socket path too long\n");
|
|
close(fd);
|
|
if (rc < 0) {
|
|
return -errno;
|
|
} else {
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
if (connect(fd, (struct sockaddr *)&un, sizeof(un)) < 0) {
|
|
SPDK_ERRLOG("connect error\n");
|
|
close(fd);
|
|
return -errno;
|
|
}
|
|
|
|
dev->fd = fd;
|
|
|
|
if (vfio_user_check_version(dev)) {
|
|
SPDK_ERRLOG("Check VFIO_USER_VERSION message failed\n");
|
|
close(fd);
|
|
return -EFAULT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
SPDK_LOG_REGISTER_COMPONENT(vfio_user)
|