| # |
| # 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: # |