Spdk/lib/vfio_user/host/vfio_user.c
paul luse a6dbe3721e update Intel copyright notices
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
2022-11-10 08:28:53 +00:00

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)