blob: 2400080afbd5d669206f834b36e71ed913186652 [file] [log] [blame]
#
# Copyright (c) 2023 Nutanix Inc. All rights reserved.
# Copyright (c) 2023 Rivos Inc. All rights reserved.
#
# Authors: Mattias Nissler <mnissler@rivosinc.com>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of Nutanix nor the names of its contributors may be
# used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
# DAMAGE.
#
from libvfio_user import *
import select
import threading
MAP_ADDR = 0x10000000
MAP_SIZE = 16 << PAGE_SHIFT
ctx = None
client = None
class DMARegionHandler:
"""
A helper to service DMA region accesses arriving over a socket. Accesses
are performed against an internal bytearray buffer. DMA request processing
takes place on a separate thread so as to not block the test code.
"""
def __handle_requests(sock, pipe, buf, lock, addr, error_no):
while True:
(ready, _, _) = select.select([sock, pipe], [], [])
if pipe in ready:
break
# Read a command from the socket and service it.
_, msg_id, cmd, payload = get_msg_fds(sock,
VFIO_USER_F_TYPE_COMMAND)
assert cmd in [VFIO_USER_DMA_READ, VFIO_USER_DMA_WRITE]
access, data = vfio_user_dma_region_access.pop_from_buffer(payload)
assert access.addr >= addr
assert access.addr + access.count <= addr + len(buf)
offset = access.addr - addr
with lock:
if cmd == VFIO_USER_DMA_READ:
data = buf[offset:offset + access.count]
else:
buf[offset:offset + access.count] = data
data = bytearray()
send_msg(sock,
cmd,
VFIO_USER_F_TYPE_REPLY,
payload=payload[:c.sizeof(access)] + data,
msg_id=msg_id,
error_no=error_no)
os.close(pipe)
sock.close()
def __init__(self, sock, addr, size, error_no=0):
self.data = bytearray(size)
self.data_lock = threading.Lock()
self.addr = addr
(pipe_r, self.pipe_w) = os.pipe()
# Duplicate the socket file descriptor so the thread can own it and
# make sure it gets closed only when terminating the thread.
sock = socket.socket(fileno=os.dup(sock.fileno()))
thread = threading.Thread(
target=DMARegionHandler.__handle_requests,
args=[sock, pipe_r, self.data, self.data_lock, addr, error_no])
thread.start()
def shutdown(self):
# Closing the pipe's write end will signal the thread to terminate.
os.close(self.pipe_w)
def read(self, addr, size):
offset = addr - self.addr
with self.data_lock:
return self.data[offset:offset + size]
def setup_function(function):
global ctx, client, dma_handler
ctx = prepare_ctx_for_dma()
assert ctx is not None
caps = {
"capabilities": {
"max_data_xfer_size": PAGE_SIZE,
"twin_socket": {
"supported": True,
},
}
}
client = connect_client(ctx, caps)
assert client.client_cmd_socket is not None
payload = vfio_user_dma_map(argsz=len(vfio_user_dma_map()),
flags=(VFIO_USER_F_DMA_REGION_READ
| VFIO_USER_F_DMA_REGION_WRITE),
offset=0,
addr=MAP_ADDR,
size=MAP_SIZE)
msg(ctx, client.sock, VFIO_USER_DMA_MAP, payload)
dma_handler = DMARegionHandler(client.client_cmd_socket, payload.addr,
payload.size)
def teardown_function(function):
dma_handler.shutdown()
client.disconnect(ctx)
vfu_destroy_ctx(ctx)
def test_dma_read_write():
ret, sg = vfu_addr_to_sgl(ctx,
dma_addr=MAP_ADDR + 0x1000,
length=64,
max_nr_sgs=1,
prot=mmap.PROT_READ | mmap.PROT_WRITE)
assert ret == 1
data = bytearray([x & 0xff for x in range(0, sg[0].length)])
assert vfu_sgl_write(ctx, sg, 1, data) == 0
assert vfu_sgl_read(ctx, sg, 1) == (0, data)
assert dma_handler.read(sg[0].dma_addr + sg[0].offset,
sg[0].length) == data
def test_dma_read_write_large():
ret, sg = vfu_addr_to_sgl(ctx,
dma_addr=MAP_ADDR + 0x1000,
length=2 * PAGE_SIZE + 42,
max_nr_sgs=1,
prot=mmap.PROT_READ | mmap.PROT_WRITE)
assert ret == 1
data = bytearray([x & 0xff for x in range(0, sg[0].length)])
assert vfu_sgl_write(ctx, sg, 1, data) == 0
assert vfu_sgl_read(ctx, sg, 1) == (0, data)
assert dma_handler.read(sg[0].dma_addr + sg[0].offset,
sg[0].length) == data
def test_dma_read_write_error():
# Reinitialize the handler to return EIO.
global dma_handler
dma_handler.shutdown()
dma_handler = DMARegionHandler(client.client_cmd_socket, MAP_ADDR,
MAP_SIZE, error_no=errno.EIO)
ret, sg = vfu_addr_to_sgl(ctx,
dma_addr=MAP_ADDR + 0x1000,
length=64,
max_nr_sgs=1,
prot=mmap.PROT_READ | mmap.PROT_WRITE)
assert ret == 1
ret, _ = vfu_sgl_read(ctx, sg, 1)
assert ret == -1
assert c.get_errno() == errno.EIO
# ex: set tabstop=4 shiftwidth=4 softtabstop=4 expandtab: #