├── .github └── workflows │ └── core-ci.yml ├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── LICENSE ├── Makefile ├── README.md ├── configs ├── 1vcs_2mlds.yaml ├── 1vcs_2mlds_unbound.yaml ├── 1vcs_4sld.yaml ├── 1vcs_4sld_2mlds.yaml ├── 1vcs_8sld_unbound.yaml ├── 2vcs_1mld.yaml └── 2vcs_8sld.yaml ├── cxl-util ├── demos └── image-classification │ ├── accel-t1.py │ ├── accel-t2.py │ ├── host-t1.py │ ├── host-t2.py │ ├── run-demo.py │ └── switch.py ├── docker ├── Dockerfile-ci ├── Dockerfile-dev └── Dockerfile-svc ├── opencis ├── __init__.py ├── apps │ ├── __init__.py │ ├── accelerator.py │ ├── cxl_simple_host.py │ ├── cxl_switch.py │ ├── fabric_manager.py │ ├── memory_pooling.py │ ├── multi_logical_device.py │ ├── multiheaded_single_logical_device.py │ ├── packet_trace_runner.py │ ├── pci_device.py │ └── single_logical_device.py ├── bin │ ├── __init__.py │ ├── accelerator.py │ ├── cli.py │ ├── common.py │ ├── cxl_host.py │ ├── cxl_switch.py │ ├── cxl_type1_test.py │ ├── cxl_type2_test.py │ ├── cxl_type3_test.py │ ├── fabric_manager.py │ ├── get_info.py │ ├── mem.py │ ├── multi_logical_device.py │ ├── packet_runner.py │ ├── single_logical_device.py │ ├── socketio_client.py │ ├── test_external_switch_cxl.py │ └── test_external_switch_pcie.py ├── cpu.py ├── cxl │ ├── __init__.py │ ├── cci │ │ ├── __init__.py │ │ ├── common.py │ │ ├── fabric_manager │ │ │ ├── __init__.py │ │ │ ├── dcd_management │ │ │ │ ├── __init__.py │ │ │ │ ├── dc_region.py │ │ │ │ ├── get_dcd_info.py │ │ │ │ └── initiate_dynamic_capacity.py │ │ │ ├── mld_components │ │ │ │ ├── __init__.py │ │ │ │ ├── get_ld_allocations.py │ │ │ │ ├── get_ld_info.py │ │ │ │ └── set_ld_allocations.py │ │ │ ├── mld_port │ │ │ │ ├── __init__.py │ │ │ │ └── tunnel_management.py │ │ │ ├── multi_headed_devices │ │ │ │ └── __init__.py │ │ │ ├── physical_switch │ │ │ │ ├── __init__.py │ │ │ │ ├── get_physical_port_state.py │ │ │ │ ├── identify_switch_device.py │ │ │ │ ├── physical_port_control.py │ │ │ │ └── send_ppb_cxl_io_configuration_request.py │ │ │ └── virtual_switch │ │ │ │ ├── __init__.py │ │ │ │ ├── bind_vppb.py │ │ │ │ ├── freeze_vppb.py │ │ │ │ ├── generate_aer_event.py │ │ │ │ ├── get_virtual_cxl_switch_info.py │ │ │ │ ├── tunnel_management.py │ │ │ │ ├── unbind_vppb.py │ │ │ │ └── unfreeze_vppb.py │ │ ├── generic │ │ │ ├── __init__.py │ │ │ ├── events.py │ │ │ ├── features.py │ │ │ ├── firmware_update.py │ │ │ ├── information_and_status │ │ │ │ ├── __init__.py │ │ │ │ ├── background_command_status.py │ │ │ │ └── identify.py │ │ │ ├── information_and_status_command_set.py │ │ │ ├── logs.py │ │ │ ├── maintenance.py │ │ │ └── timestamp.py │ │ ├── memory_device │ │ │ ├── __init__.py │ │ │ ├── dynamic_capacity.py │ │ │ └── identify_memory_device.py │ │ └── vendor_specfic │ │ │ ├── __init__.py │ │ │ ├── get_connected_devices.py │ │ │ ├── notify_device_update.py │ │ │ ├── notify_port_update.py │ │ │ └── notify_switch_update.py │ ├── component │ │ ├── __init__.py │ │ ├── bi_decoder.py │ │ ├── bind_processor.py │ │ ├── cache_controller.py │ │ ├── cache_id_decoder_capability.py │ │ ├── cache_route_table.py │ │ ├── cache_route_table_manager.py │ │ ├── cci_executor.py │ │ ├── common.py │ │ ├── cxl_bridge_component.py │ │ ├── cxl_cache_dcoh.py │ │ ├── cxl_cache_manager.py │ │ ├── cxl_component.py │ │ ├── cxl_connection.py │ │ ├── cxl_host.py │ │ ├── cxl_io_callback_data.py │ │ ├── cxl_io_manager.py │ │ ├── cxl_mem_dcoh.py │ │ ├── cxl_mem_manager.py │ │ ├── cxl_memory_device_component.py │ │ ├── cxl_memory_hub.py │ │ ├── cxl_packet_processor.py │ │ ├── device_llc_iogen.py │ │ ├── fabric_manager │ │ │ ├── __init__.py │ │ │ └── socketio_server.py │ │ ├── fmld.py │ │ ├── hdm_decoder.py │ │ ├── host_llc_iogen.py │ │ ├── host_manager.py │ │ ├── irq_manager.py │ │ ├── mctp │ │ │ ├── __init__.py │ │ │ ├── mctp_cci_api_client.py │ │ │ ├── mctp_cci_executor.py │ │ │ ├── mctp_connection.py │ │ │ ├── mctp_connection_client.py │ │ │ ├── mctp_connection_manager.py │ │ │ ├── mctp_packet_processor.py │ │ │ └── mctp_packet_reader.py │ │ ├── packet_reader.py │ │ ├── physical_port_manager.py │ │ ├── root_complex │ │ │ ├── __init__.py │ │ │ ├── cache_coherency_bridge.py │ │ │ ├── home_agent.py │ │ │ ├── io_bridge.py │ │ │ ├── memory_controller.py │ │ │ ├── root_complex.py │ │ │ ├── root_port_client_manager.py │ │ │ └── root_port_switch.py │ │ ├── short_msg_conn.py │ │ ├── switch_connection_client.py │ │ ├── switch_connection_manager.py │ │ ├── virtual_switch │ │ │ ├── __init__.py │ │ │ ├── downstream_vppb.py │ │ │ ├── port_binder.py │ │ │ ├── routers.py │ │ │ ├── routing_table.py │ │ │ ├── upstream_vppb.py │ │ │ ├── virtual_switch.py │ │ │ ├── vppb.py │ │ │ └── vppb_routing_info.py │ │ └── virtual_switch_manager.py │ ├── config_space │ │ ├── __init__.py │ │ ├── cfg.py │ │ ├── device.py │ │ ├── doe │ │ │ ├── __init__.py │ │ │ ├── cdat.py │ │ │ ├── doe.py │ │ │ └── doe_table_access.py │ │ ├── dvsec │ │ │ ├── __init__.py │ │ │ ├── common.py │ │ │ ├── cxl_devices.py │ │ │ ├── cxl_extension_dvsec_for_ports.py │ │ │ ├── flex_bus_port.py │ │ │ ├── mld_dvsec.py │ │ │ └── register_locator.py │ │ ├── port.py │ │ └── serial_number │ │ │ ├── __init__.py │ │ │ └── common.py │ ├── device │ │ ├── __init__.py │ │ ├── config │ │ │ ├── __init__.py │ │ │ ├── dynamic_capacity_device.py │ │ │ └── logical_device.py │ │ ├── cxl_type1_device.py │ │ ├── cxl_type2_device.py │ │ ├── cxl_type3_device.py │ │ ├── downstream_port_device.py │ │ ├── pci_to_pci_bridge_device.py │ │ ├── port_device.py │ │ ├── root_port_device.py │ │ └── upstream_port_device.py │ ├── environment │ │ ├── __init__.py │ │ └── environment.py │ ├── features │ │ ├── README.md │ │ ├── __init__.py │ │ ├── event_manager.py │ │ ├── log_manager.py │ │ └── mailbox.py │ ├── mmio │ │ ├── __init__.py │ │ ├── component_register │ │ │ ├── __init__.py │ │ │ └── memcache_register │ │ │ │ ├── __init__.py │ │ │ │ ├── capability.py │ │ │ │ ├── hdm_decoder_capability.py │ │ │ │ ├── link_capability.py │ │ │ │ └── ras_capability.py │ │ └── device_register │ │ │ ├── __init__.py │ │ │ ├── device_capabilities.py │ │ │ ├── device_status_register.py │ │ │ ├── mailbox_register.py │ │ │ └── memory_device_capabilities.py │ └── transport │ │ ├── __init__.py │ │ ├── cache_fifo.py │ │ ├── common.py │ │ ├── ethernet.py │ │ ├── memory_fifo.py │ │ ├── packet.py │ │ ├── socket_server.py │ │ └── transaction.py ├── drivers │ ├── __init__.py │ ├── cxl_bus_driver.py │ ├── cxl_mem_driver.py │ └── pci_bus_driver.py ├── msim │ ├── __init__.py │ └── emulator.py ├── pci │ ├── __init__.py │ ├── component │ │ ├── __init__.py │ │ ├── config_space_manager.py │ │ ├── doe_mailbox.py │ │ ├── fifo_pair.py │ │ ├── mmio_manager.py │ │ ├── packet_processor.py │ │ ├── pci.py │ │ ├── pci_connection.py │ │ └── routing_table.py │ ├── config_space │ │ ├── __init__.py │ │ ├── pci.py │ │ └── pcie │ │ │ ├── __init__.py │ │ │ ├── doe.py │ │ │ ├── msi.py │ │ │ └── pcie_capability.py │ └── device │ │ ├── __init__.py │ │ └── pci_device.py └── util │ ├── __init__.py │ ├── accessor.py │ ├── async_gatherer.py │ ├── bound_event.py │ ├── component.py │ ├── logger.py │ ├── memory.py │ ├── number.py │ ├── number_const.py │ ├── pci.py │ ├── server.py │ └── unaligned_bit_structure.py ├── pyproject.toml ├── tests ├── conftest.py ├── regvals.txt ├── test_cxl_cache_coherency_bridge.py ├── test_cxl_cache_controller.py ├── test_cxl_cci.py ├── test_cxl_complex_host.py ├── test_cxl_component_hdm_decoder.py ├── test_cxl_component_physical_port_manager.py ├── test_cxl_component_switch_bind.py ├── test_cxl_component_switch_connection_manager.py ├── test_cxl_component_virtual_switch.py ├── test_cxl_component_virtual_switch_manager.py ├── test_cxl_device_downstream_port.py ├── test_cxl_device_multi_logical_device.py ├── test_cxl_device_multiheaded_single_logical_device.py ├── test_cxl_device_single_logical_device.py ├── test_cxl_device_type1_device.py ├── test_cxl_device_type2_device.py ├── test_cxl_device_upstream_port.py ├── test_cxl_doe_table_access.py ├── test_cxl_host.py ├── test_cxl_log_manager.py ├── test_cxl_mailbox.py ├── test_cxl_mem_dcoh.py ├── test_cxl_mmio.py ├── test_cxl_mmio_cachemem_register.py ├── test_cxl_mmio_component_register.py ├── test_cxl_mmio_device_register.py ├── test_dynamic_fields.py ├── test_pci_component_mmio_manager.py ├── test_pci_config_space_headers.py ├── test_pci_config_space_msi.py ├── test_pci_device_pci_device.py ├── test_pcie_doe.py ├── test_tunnel.py ├── test_unaligned_bit_structure.py └── test_util_number.py ├── traces └── qemu-s8000-h40026.pcap └── uv.lock /.github/workflows/core-ci.yml: -------------------------------------------------------------------------------- 1 | name: Run CI for OpenCIS core 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | pull_request: 7 | jobs: 8 | pytest: 9 | name: pytest 10 | runs-on: ubuntu-22.04 11 | container: 12 | image: ghcr.io/opencis/core-ci:0.2 13 | steps: 14 | - name: Check out repository code 15 | uses: actions/checkout@v4 16 | - name: Install Python packages via uv 17 | run: uv python pin 3.13 && uv sync 18 | - name: Run pytest 19 | run: make test 20 | code-quality: 21 | name: code-quality 22 | runs-on: ubuntu-22.04 23 | container: 24 | image: ghcr.io/opencis/core-ci:0.2 25 | steps: 26 | - name: Check out repository code 27 | uses: actions/checkout@v4 28 | - name: Install Python packages via uv 29 | run: uv python pin 3.13 && uv sync 30 | - name: Run pylint 31 | run: make lint 32 | - name: Run black 33 | run: uv run black --check opencis tests demos 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.log 3 | .coverage* 4 | *.bin 5 | *.patch 6 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | 5 | { 6 | "name": "poetry install", 7 | "type": "debugpy", 8 | "request": "launch", 9 | "module": "poetry", 10 | "args": [ 11 | "-vvv", 12 | "install" 13 | ] 14 | }, 15 | { 16 | "name": "poetry update", 17 | "type": "debugpy", 18 | "request": "launch", 19 | "module": "poetry", 20 | "args": [ 21 | "-vvv", 22 | "update" 23 | ] 24 | }, 25 | { 26 | "name": "Run w/ QEMU Host", 27 | "type": "debugpy", 28 | "request": "launch", 29 | "module": "poetry", 30 | "args": ["run", "python", "opencxl/bin/cli.py", "start", "-c", "switch", "-c", "sld-group", "--config-file", "configs/1vcs_4sld.yaml", "--log-file=log_name.log", "--log-level=debug"] 31 | } 32 | , 33 | { 34 | "name": "Run Type 2", 35 | "type": "debugpy", 36 | "request": "launch", 37 | "module": "poetry", 38 | "cwd": "${workspaceFolder}/demos/image-classification", 39 | "args": ["run", "python", "run-demo.py", "-p", "~/Downloads/imagenette2-160-fast", "-t", "2"] 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[python]": { 3 | "editor.defaultFormatter": "ms-python.black-formatter", 4 | "editor.formatOnSave": true, 5 | }, 6 | "black-formatter.args": [ 7 | "-l 100" 8 | ], 9 | "python.testing.pytestEnabled": true, 10 | "python.testing.pytestArgs": [ 11 | "tests" 12 | ], 13 | } 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024-2025 EEUM, Inc. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | 3. Neither the name of the copyright holder nor the names of its contributors 14 | may be used to endorse or promote products derived from this software 15 | without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” 18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 21 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifeq ($(shell uname), Darwin) 2 | NPROC = $(shell sysctl -n hw.logicalcpu) 3 | else 4 | NPROC = $$(nproc) 5 | endif 6 | 7 | test: 8 | uv run python -O -m compileall -q opencis tests 9 | uv run pytest --cov --cov-report=term-missing -n $(NPROC) 10 | rm -f *.bin 11 | 12 | lint: 13 | uv run pylint opencis 14 | uv run pylint demos 15 | uv run pylint tests 16 | 17 | format: 18 | uv run black opencis tests demos 19 | 20 | clean: 21 | rm -rf *.bin logs *.log *.pcap 22 | find . | grep -E "(/__pycache__$$|\.pyc$$|\.pyo$$)" | xargs rm -rf 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenCIS Core 2 | 3 | ## Getting Started 4 | 5 | Setup and Run Instructions: https://www.opencis.io 6 | -------------------------------------------------------------------------------- /configs/1vcs_2mlds.yaml: -------------------------------------------------------------------------------- 1 | port_configs: 2 | - type: USP 3 | - type: DSP 4 | - type: DSP 5 | virtual_switch_configs: 6 | - upstream_port_index: 0 7 | vppb_counts: 6 8 | initial_bounds: [[1,0],[1,1],[1,2],[1,3],[2,0],[2,1]] 9 | devices: 10 | multi_logical_devices: 11 | - port_index: 1 12 | serial_number: 3FB95963144632BF 13 | logical_devices: 14 | - memory_size: 256M 15 | ld_id: 0 16 | - memory_size: 256M 17 | ld_id: 1 18 | - memory_size: 256M 19 | ld_id: 2 20 | - memory_size: 256M 21 | ld_id: 3 22 | - port_index: 2 23 | serial_number: 8A440DABC4DBEAFB 24 | logical_devices: 25 | - memory_size: 512M 26 | ld_id: 0 27 | - memory_size: 512M 28 | ld_id: 1 29 | -------------------------------------------------------------------------------- /configs/1vcs_2mlds_unbound.yaml: -------------------------------------------------------------------------------- 1 | port_configs: 2 | - type: USP 3 | - type: DSP 4 | - type: DSP 5 | virtual_switch_configs: 6 | - upstream_port_index: 0 7 | vppb_counts: 6 8 | initial_bounds: [-1,-1,-1,-1,-1,-1] 9 | devices: 10 | multi_logical_devices: 11 | - port_index: 1 12 | serial_number: 3FB95963144632BF 13 | logical_devices: 14 | - memory_size: 256M 15 | ld_id: 0 16 | - memory_size: 256M 17 | ld_id: 1 18 | - memory_size: 256M 19 | ld_id: 2 20 | - memory_size: 256M 21 | ld_id: 3 22 | - port_index: 2 23 | serial_number: 8A440DABC4DBEAFB 24 | logical_devices: 25 | - memory_size: 512M 26 | ld_id: 0 27 | - memory_size: 512M 28 | ld_id: 1 29 | -------------------------------------------------------------------------------- /configs/1vcs_4sld.yaml: -------------------------------------------------------------------------------- 1 | port_configs: 2 | - type: USP 3 | - type: DSP 4 | - type: DSP 5 | - type: DSP 6 | - type: DSP 7 | virtual_switch_configs: 8 | - upstream_port_index: 0 9 | vppb_counts: 4 10 | initial_bounds: [1,2,3,4] 11 | devices: 12 | single_logical_devices: 13 | - port_index: 1 14 | memory_size: 256M 15 | serial_number: 4E9FF1671694A385 16 | - port_index: 2 17 | memory_size: 256M 18 | serial_number: 2200B90031B3ABD8 19 | - port_index: 3 20 | memory_size: 256M 21 | serial_number: 3B0357B80F50A534 22 | - port_index: 4 23 | memory_size: 256M 24 | serial_number: CB6575D3D9A4FC7A -------------------------------------------------------------------------------- /configs/1vcs_4sld_2mlds.yaml: -------------------------------------------------------------------------------- 1 | port_configs: 2 | - type: USP 3 | - type: DSP 4 | - type: DSP 5 | - type: DSP 6 | - type: DSP 7 | - type: DSP 8 | - type: DSP 9 | virtual_switch_configs: 10 | - upstream_port_index: 0 11 | vppb_counts: 10 12 | initial_bounds: [[1],[2],[3],[4],[5,0],[5,1],[5,2],[5,3],[6,0],[6,1]] 13 | devices: 14 | single_logical_devices: 15 | - port_index: 1 16 | memory_size: 256M 17 | serial_number: 4E9FF1671694A385 18 | - port_index: 2 19 | memory_size: 256M 20 | serial_number: 2200B90031B3ABD8 21 | - port_index: 3 22 | memory_size: 256M 23 | serial_number: 3B0357B80F50A534 24 | - port_index: 4 25 | memory_size: 256M 26 | serial_number: CB6575D3D9A4FC7A 27 | multi_logical_devices: 28 | - port_index: 5 29 | serial_number: 3FB95963144632BF 30 | logical_devices: 31 | - memory_size: 256M 32 | ld_id: 0 33 | - memory_size: 256M 34 | ld_id: 1 35 | - memory_size: 256M 36 | ld_id: 2 37 | - memory_size: 256M 38 | ld_id: 3 39 | - port_index: 6 40 | serial_number: 8A440DABC4DBEAFB 41 | logical_devices: 42 | - memory_size: 512M 43 | ld_id: 0 44 | - memory_size: 512M 45 | ld_id: 1 46 | -------------------------------------------------------------------------------- /configs/1vcs_8sld_unbound.yaml: -------------------------------------------------------------------------------- 1 | port_configs: 2 | - type: USP 3 | - type: DSP 4 | - type: DSP 5 | - type: DSP 6 | - type: DSP 7 | - type: DSP 8 | - type: DSP 9 | - type: DSP 10 | - type: DSP 11 | virtual_switch_configs: 12 | - upstream_port_index: 0 13 | vppb_counts: 8 14 | initial_bounds: [-1, -1, -1, -1, -1, -1, -1, -1] 15 | devices: 16 | single_logical_devices: 17 | - port_index: 1 18 | memory_size: 256M 19 | serial_number: 4E9FF1671694A385 20 | - port_index: 2 21 | memory_size: 256M 22 | serial_number: 2200B90031B3ABD8 23 | - port_index: 3 24 | memory_size: 256M 25 | serial_number: 3B0357B80F50A534 26 | - port_index: 4 27 | memory_size: 256M 28 | serial_number: CB6575D3D9A4FC7A 29 | - port_index: 5 30 | memory_size: 256M 31 | serial_number: 3FB95963144632BF 32 | - port_index: 6 33 | memory_size: 256M 34 | serial_number: BDFF0BABB1E6EB94 35 | - port_index: 7 36 | memory_size: 256M 37 | serial_number: 8A440DABC4DBEAFB 38 | - port_index: 8 39 | memory_size: 256M 40 | serial_number: CBC9436B5B325BEC -------------------------------------------------------------------------------- /configs/2vcs_1mld.yaml: -------------------------------------------------------------------------------- 1 | port_configs: 2 | - type: USP 3 | - type: DSP 4 | - type: USP 5 | virtual_switch_configs: 6 | - upstream_port_index: 0 7 | vppb_counts: 4 8 | initial_bounds: [[1,0],[1,1],[1,2],[1,3]] 9 | - upstream_port_index: 2 10 | vppb_counts: 4 11 | initial_bounds: [[1,4],[1,5],[1,6],[1,7]] 12 | devices: 13 | multi_logical_devices: 14 | - port_index: 1 15 | serial_number: 3FB95963144632BF 16 | logical_devices: 17 | - memory_size: 256M 18 | ld_id: 0 19 | - memory_size: 256M 20 | ld_id: 1 21 | - memory_size: 256M 22 | ld_id: 2 23 | - memory_size: 256M 24 | ld_id: 3 25 | - memory_size: 256M 26 | ld_id: 4 27 | - memory_size: 256M 28 | ld_id: 5 29 | - memory_size: 256M 30 | ld_id: 6 31 | - memory_size: 256M 32 | ld_id: 7 33 | -------------------------------------------------------------------------------- /configs/2vcs_8sld.yaml: -------------------------------------------------------------------------------- 1 | port_configs: 2 | - type: USP 3 | - type: DSP 4 | - type: DSP 5 | - type: DSP 6 | - type: DSP 7 | - type: USP 8 | - type: DSP 9 | - type: DSP 10 | - type: DSP 11 | - type: DSP 12 | virtual_switch_configs: 13 | - upstream_port_index: 0 14 | vppb_counts: 4 15 | initial_bounds: [1,2,3,4] 16 | - upstream_port_index: 5 17 | vppb_counts: 4 18 | initial_bounds: [6,7,8,9] 19 | devices: 20 | single_logical_devices: 21 | - port_index: 1 22 | memory_size: 256M 23 | serial_number: 4E9FF1671694A385 24 | - port_index: 2 25 | memory_size: 256M 26 | serial_number: 2200B90031B3ABD8 27 | - port_index: 3 28 | memory_size: 256M 29 | serial_number: 3B0357B80F50A534 30 | - port_index: 4 31 | memory_size: 256M 32 | serial_number: CB6575D3D9A4FC7A 33 | - port_index: 6 34 | memory_size: 256M 35 | serial_number: 3FB95963144632BF 36 | - port_index: 7 37 | memory_size: 256M 38 | serial_number: BDFF0BABB1E6EB94 39 | - port_index: 8 40 | memory_size: 256M 41 | serial_number: 8A440DABC4DBEAFB 42 | - port_index: 9 43 | memory_size: 256M 44 | serial_number: CBC9436B5B325BEC -------------------------------------------------------------------------------- /cxl-util: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | uv run opencis/bin/cli.py "$@" 3 | -------------------------------------------------------------------------------- /demos/image-classification/accel-t1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Copyright (c) 2024-2025, Eeum, Inc. 4 | 5 | This software is licensed under the terms of the Revised BSD License. 6 | See LICENSE for details. 7 | """ 8 | 9 | import logging 10 | from signal import SIGCONT, SIGINT 11 | import asyncio 12 | import os 13 | import sys 14 | 15 | from opencis.apps.accelerator import MyType1Accelerator 16 | from opencis.util.logger import logger 17 | 18 | logger.setLevel(logging.INFO) 19 | device: MyType1Accelerator = None 20 | start_tasks = [] 21 | stop_signal = asyncio.Event() 22 | 23 | 24 | async def shutdown(signame=None): 25 | # pylint: disable=unused-argument 26 | try: 27 | device.set_stop_flag() 28 | stop_tasks = [ 29 | asyncio.create_task(device.stop()), 30 | ] 31 | await asyncio.gather(*stop_tasks) 32 | stop_signal.set() 33 | except Exception as exc: 34 | logger.debug(f"[ACCEL] {exc.__traceback__}") 35 | finally: 36 | os._exit(0) 37 | 38 | 39 | async def main(): 40 | # pylint: disable=global-statement, duplicate-code 41 | lp = asyncio.get_event_loop() 42 | lp.add_signal_handler(SIGINT, lambda signame="SIGINT": asyncio.create_task(shutdown(signame))) 43 | 44 | sw_portno = int(sys.argv[1]) 45 | portidx = int(sys.argv[2]) 46 | train_data_path = sys.argv[3] 47 | 48 | logger.debug(f"[ACCEL] listening on port {sw_portno} and physical port {portidx}") 49 | 50 | global device 51 | device = MyType1Accelerator( 52 | port_index=portidx, 53 | port=sw_portno, 54 | irq_port=8500, 55 | device_id=portidx - 1, 56 | train_data_path=train_data_path, 57 | ) 58 | 59 | global start_tasks 60 | start_tasks = [ 61 | asyncio.create_task(device.run()), 62 | ] 63 | ready_tasks = [ 64 | asyncio.create_task(device.wait_for_ready()), 65 | ] 66 | 67 | os.kill(os.getppid(), SIGCONT) 68 | 69 | await asyncio.gather(*ready_tasks) 70 | logger.debug("[ACCEL] ready!") 71 | 72 | await stop_signal.wait() 73 | await asyncio.gather(*start_tasks) 74 | 75 | 76 | if __name__ == "__main__": 77 | asyncio.run(main()) 78 | -------------------------------------------------------------------------------- /demos/image-classification/accel-t2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Copyright (c) 2024-2025, Eeum, Inc. 4 | 5 | This software is licensed under the terms of the Revised BSD License. 6 | See LICENSE for details. 7 | """ 8 | 9 | import asyncio 10 | from signal import SIGCONT, SIGINT 11 | import os 12 | import sys 13 | 14 | from opencis.util.logger import logger 15 | from opencis.apps.accelerator import MyType2Accelerator 16 | from opencis.util.number_const import MB 17 | 18 | device: MyType2Accelerator = None 19 | start_tasks = [] 20 | 21 | 22 | async def shutdown(signame=None): 23 | # pylint: disable=unused-argument 24 | try: 25 | stop_tasks = [ 26 | asyncio.create_task(device.stop()), 27 | ] 28 | await asyncio.gather(*stop_tasks, return_exceptions=True) 29 | await asyncio.gather(*start_tasks) 30 | except Exception as exc: 31 | logger.info(f"[ACCEL] {exc.__traceback__}") 32 | finally: 33 | os._exit(0) 34 | 35 | 36 | async def main(): 37 | # pylint: disable=global-statement, duplicate-code 38 | lp = asyncio.get_event_loop() 39 | lp.add_signal_handler(SIGINT, lambda signame="SIGINT": asyncio.create_task(shutdown(signame))) 40 | 41 | sw_portno = int(sys.argv[1]) 42 | portidx = int(sys.argv[2]) 43 | train_data_path = sys.argv[3] 44 | 45 | global device 46 | global start_tasks 47 | 48 | start_tasks = [] 49 | ready_tasks = [] 50 | mempath = f"mem{portidx}.bin" 51 | with open(mempath, "a") as _: 52 | pass 53 | device = MyType2Accelerator( 54 | port_index=portidx, 55 | memory_size=256 * MB, # min 256MB, or will cause error for DVSEC 56 | memory_file=f"mem{portidx}.bin", 57 | host="localhost", 58 | port=sw_portno, 59 | train_data_path=train_data_path, 60 | device_id=portidx - 1, 61 | ) 62 | start_tasks.append(asyncio.create_task(device.run())) 63 | ready_tasks.append(asyncio.create_task(device.wait_for_ready())) 64 | await asyncio.gather(*ready_tasks) 65 | 66 | os.kill(os.getppid(), SIGCONT) 67 | 68 | await asyncio.Event().wait() # blocks 69 | 70 | 71 | if __name__ == "__main__": 72 | asyncio.run(main()) 73 | -------------------------------------------------------------------------------- /demos/image-classification/run-demo.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | import argparse 9 | from signal import SIGCONT, SIGINT, SIGIO, SIG_BLOCK, SIG_UNBLOCK, pthread_sigmask, signal, pause 10 | 11 | import os 12 | 13 | from opencis.util.logger import logger 14 | 15 | run_progress = 0 16 | pids = {} 17 | RUN_LIST = {} 18 | 19 | 20 | def clean_shutdown(signum=None, frame=None): 21 | # pylint: disable=unused-argument, global-statement 22 | pthread_sigmask(SIG_BLOCK, [SIGINT]) 23 | 24 | global pids 25 | pids = dict(reversed(pids.items())) 26 | for _, pid in pids.items(): 27 | os.kill(pid, SIGINT) 28 | os.waitpid(pid, 0) 29 | os._exit(0) 30 | 31 | 32 | def run_next_app(signum=None, frame=None): 33 | # pylint: disable=unused-argument, global-statement 34 | pthread_sigmask(SIG_BLOCK, [SIGCONT]) 35 | 36 | global run_progress 37 | if run_progress >= len(RUN_LIST): 38 | # signal the host that IO is ready 39 | host_pid = pids["host"] 40 | os.kill(host_pid, SIGIO) 41 | return 42 | 43 | component_name, program, args = RUN_LIST[run_progress] 44 | 45 | if not (chld := os.fork()): 46 | # child process 47 | try: 48 | if os.execvp(program, (program, *args)) == -1: 49 | logger.info("EXECVE FAIL!!!") 50 | except PermissionError as exc: 51 | raise RuntimeError(f'Failed to invoke "{program}" with args {args}') from exc 52 | except FileNotFoundError as exc: 53 | raise RuntimeError(f'Couldn\'t find "{program}"') from exc 54 | else: 55 | run_progress += 1 56 | pids[component_name] = chld 57 | pthread_sigmask(SIG_UNBLOCK, [SIGCONT]) 58 | 59 | 60 | def main(): 61 | # pylint: disable=global-statement 62 | parser = argparse.ArgumentParser() 63 | parser.add_argument( 64 | "-p", 65 | "--img-folder-path", 66 | dest="ifp", 67 | action="store", 68 | required=True, 69 | help="The folder path to the image training data.", 70 | metavar="IMG_FOLDER_PATH", 71 | ) 72 | parser.add_argument( 73 | "-n", 74 | "--num-accels", 75 | dest="na", 76 | default="2", 77 | action="store", 78 | help="The number of accelerators.", 79 | metavar="NUM_ACCELS", 80 | ) 81 | parser.add_argument( 82 | "-t", 83 | "--accel-type", 84 | dest="at", 85 | default="2", 86 | action="store", 87 | help="Accelerator CXL device type.", 88 | metavar="ACCEL_TYPE", 89 | ) 90 | 91 | args = vars(parser.parse_args()) 92 | train_data_path = args["ifp"] 93 | num_accels = args["na"] 94 | accel_type = args["at"] 95 | sw_port = "22500" 96 | 97 | if not os.path.exists(train_data_path) or not os.path.isdir(train_data_path): 98 | logger.info(f"Path {train_data_path} does not exist, or is not a folder.") 99 | os._exit(0) 100 | 101 | host_file = f"./host-t{accel_type}.py" 102 | accel_file = f"./accel-t{accel_type}.py" 103 | 104 | global RUN_LIST 105 | RUN_LIST = [ 106 | ("switch", "./switch.py", (sw_port, num_accels)), 107 | ("host", host_file, (sw_port, num_accels, train_data_path)), 108 | ] 109 | for i in range(int(num_accels)): 110 | RUN_LIST.append((f"accel{i + 1}", accel_file, (sw_port, f"{i + 1}", train_data_path))) 111 | signal(SIGCONT, run_next_app) 112 | signal(SIGINT, clean_shutdown) 113 | 114 | run_next_app() 115 | 116 | while True: 117 | pause() 118 | 119 | 120 | if __name__ == "__main__": 121 | main() 122 | -------------------------------------------------------------------------------- /demos/image-classification/switch.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Copyright (c) 2024-2025, Eeum, Inc. 4 | 5 | This software is licensed under the terms of the Revised BSD License. 6 | See LICENSE for details. 7 | """ 8 | 9 | import asyncio 10 | import os 11 | import sys 12 | from signal import SIGCONT, SIGINT 13 | 14 | from opencis.cxl.component.cxl_component import PORT_TYPE, PortConfig 15 | from opencis.cxl.component.physical_port_manager import PhysicalPortManager 16 | from opencis.cxl.component.switch_connection_manager import SwitchConnectionManager 17 | from opencis.cxl.component.virtual_switch_manager import VirtualSwitchConfig, VirtualSwitchManager 18 | 19 | # pylint: disable=duplicate-code 20 | sw_conn_manager = None 21 | physical_port_manager = None 22 | virtual_switch_manager = None 23 | start_tasks = [] 24 | 25 | 26 | async def shutdown(signame=None): 27 | # pylint: disable=unused-argument 28 | try: 29 | stop_tasks = [ 30 | asyncio.create_task(sw_conn_manager.stop(), name="sw_conn_manager"), 31 | asyncio.create_task(physical_port_manager.stop(), name="phys_port_manager"), 32 | asyncio.create_task(virtual_switch_manager.stop(), name="virtual_switch_manager"), 33 | ] 34 | await asyncio.gather(*stop_tasks, return_exceptions=True) 35 | await asyncio.gather(*start_tasks) 36 | except Exception as exc: 37 | print("[SWITCH]", exc.__traceback__) 38 | finally: 39 | os._exit(0) 40 | 41 | 42 | async def main(): 43 | # pylint: disable=global-statement 44 | lp = asyncio.get_event_loop() 45 | lp.add_signal_handler(SIGINT, lambda signame="SIGINT": asyncio.create_task(shutdown(signame))) 46 | 47 | portno = int(sys.argv[1]) 48 | dev_count = int(sys.argv[2]) 49 | 50 | global sw_conn_manager 51 | global physical_port_manager 52 | global virtual_switch_manager 53 | global start_tasks 54 | 55 | port_configs = [PortConfig(PORT_TYPE.USP)] 56 | for _ in range(dev_count): 57 | port_configs.append(PortConfig(PORT_TYPE.DSP)) 58 | 59 | switch_configs = [ 60 | VirtualSwitchConfig( 61 | upstream_port_index=0, 62 | vppb_counts=dev_count, 63 | initial_bounds=list(range(1, dev_count + 1)), 64 | irq_host="0.0.0.0", 65 | irq_port=8500, 66 | ) 67 | ] 68 | allocated_ld = {} 69 | for index in range(dev_count): 70 | allocated_ld[index + 1] = [0] 71 | 72 | sw_conn_manager = SwitchConnectionManager(port_configs, host="localhost", port=portno) 73 | physical_port_manager = PhysicalPortManager( 74 | switch_connection_manager=sw_conn_manager, port_configs=port_configs 75 | ) 76 | virtual_switch_manager = VirtualSwitchManager( 77 | switch_configs=switch_configs, 78 | physical_port_manager=physical_port_manager, 79 | allocated_ld=allocated_ld, 80 | ) 81 | 82 | start_tasks = [ 83 | asyncio.create_task(sw_conn_manager.run()), 84 | asyncio.create_task(physical_port_manager.run()), 85 | asyncio.create_task(virtual_switch_manager.run()), 86 | ] 87 | ready_tasks = [ 88 | asyncio.create_task(sw_conn_manager.wait_for_ready()), 89 | asyncio.create_task(physical_port_manager.wait_for_ready()), 90 | asyncio.create_task(virtual_switch_manager.wait_for_ready()), 91 | ] 92 | await asyncio.gather(*ready_tasks) 93 | 94 | os.kill(os.getppid(), SIGCONT) 95 | 96 | await asyncio.Event().wait() # blocks 97 | 98 | 99 | if __name__ == "__main__": 100 | asyncio.run(main()) 101 | -------------------------------------------------------------------------------- /docker/Dockerfile-ci: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | RUN apt update && \ 4 | apt install git python3-pip python3.11-dev libpcap-dev -y 5 | 6 | RUN pip install uv==0.6.3 7 | -------------------------------------------------------------------------------- /docker/Dockerfile-dev: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | EXPOSE 8000 8100 8200 4 | 5 | RUN apt update && \ 6 | apt install -y git python3-pip python3.11-dev libpcap-dev iproute2 7 | RUN pip install poetry==1.8.0 8 | 9 | 10 | WORKDIR /opencis-core 11 | 12 | ENTRYPOINT ["/bin/bash", "-c" , "poetry env use 3.11 && poetry install && sleep infinity"] 13 | -------------------------------------------------------------------------------- /docker/Dockerfile-svc: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | EXPOSE 8000 8100 8200 4 | 5 | RUN apt update && \ 6 | apt install -y python3-pip python3.11-dev libpcap-dev 7 | RUN pip install poetry==1.8.0 8 | 9 | WORKDIR /opencis-core 10 | COPY . /opencis-core 11 | RUN poetry env use 3.11 && poetry install 12 | 13 | ENTRYPOINT ["/bin/bash", "-c" , "poetry env info && ./cxl-util --help"] 14 | -------------------------------------------------------------------------------- /opencis/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | -------------------------------------------------------------------------------- /opencis/apps/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | -------------------------------------------------------------------------------- /opencis/apps/multiheaded_single_logical_device.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from asyncio import gather, create_task 9 | from typing import List 10 | 11 | from opencis.apps.single_logical_device import SingleLogicalDevice 12 | from opencis.util.component import RunnableComponent 13 | 14 | 15 | class MultiHeadedSingleLogicalDevice(RunnableComponent): 16 | def __init__( 17 | self, 18 | num_ports, 19 | memory_size: int, 20 | memory_file: str, 21 | serial_number: str, 22 | host: str = "0.0.0.0", 23 | port: int = 8000, 24 | port_indexes: List[int] = None, 25 | test_mode: bool = False, 26 | cxl_connection=None, 27 | ): 28 | if port_indexes is None: 29 | port_indexes = [-1] * num_ports 30 | 31 | label = f"Port{','.join(map(str, port_indexes))}" 32 | super().__init__(label) 33 | 34 | self._sld_devices = [] 35 | for i in range(num_ports): 36 | _memory_file = f"multiheaded_{i}_{memory_file}" 37 | self._sld_devices.append( 38 | SingleLogicalDevice( 39 | memory_size=memory_size, 40 | memory_file=_memory_file, 41 | serial_number=serial_number, 42 | host=host, 43 | port=port, 44 | port_index=port_indexes[i], 45 | test_mode=test_mode, 46 | cxl_connection=cxl_connection, 47 | ) 48 | ) 49 | 50 | async def _run(self): 51 | run_tasks = [] 52 | for sld_device in self._sld_devices: 53 | run_tasks.append(create_task(sld_device.run())) 54 | wait_tasks = [] 55 | for sld_device in self._sld_devices: 56 | wait_tasks.append(create_task(sld_device.wait_for_ready())) 57 | 58 | await gather(*wait_tasks) 59 | await self._change_status_to_running() 60 | await gather(*run_tasks) 61 | 62 | async def _stop(self): 63 | tasks = [] 64 | for sld_device in self._sld_devices: 65 | tasks.append(create_task(sld_device.stop())) 66 | await gather(*tasks) 67 | 68 | def get_sld_devices(self): 69 | return self._sld_devices 70 | -------------------------------------------------------------------------------- /opencis/apps/packet_trace_runner.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | import asyncio 9 | from scapy.all import PcapReader 10 | 11 | from opencis.util.logger import logger 12 | from opencis.util.component import LabeledComponent 13 | 14 | 15 | class PacketTraceRunner(LabeledComponent): 16 | def __init__( 17 | self, 18 | pcap_file: str, 19 | switch_host: str, 20 | switch_port: int, 21 | trace_switch_port: int, 22 | trace_device_port: int, 23 | label: str = None, 24 | ): 25 | super().__init__(label) 26 | self._pcap_file = pcap_file 27 | self._switch_port = switch_port 28 | self._switch_host = switch_host 29 | self._trace_switch_port = trace_switch_port 30 | self._trace_device_port = trace_device_port 31 | 32 | async def run(self): 33 | try: 34 | reader, writer = await asyncio.open_connection(self._switch_host, self._switch_port) 35 | except Exception as e: 36 | raise RuntimeError("Failed to connect to switch") from e 37 | 38 | local_ip, local_port = writer.get_extra_info("sockname") 39 | logger.info(self._create_message(f"Local address: {local_ip}, Local port: {local_port}")) 40 | 41 | with PcapReader(self._pcap_file) as pr: 42 | for n, packet in enumerate(pr): 43 | if packet.haslayer("TCP") and packet["TCP"].flags == 0x18: 44 | tcp = packet.getlayer("TCP") 45 | data_bytes = bytes(tcp.payload) 46 | data = int.from_bytes(data_bytes) 47 | if ( 48 | tcp.sport == self._trace_device_port 49 | and tcp.dport == self._trace_switch_port 50 | ): 51 | logger.info(self._create_message(f"({n + 1}) Tx: 0x{data:x}")) 52 | writer.write(data_bytes) 53 | await writer.drain() 54 | elif ( 55 | tcp.sport == self._trace_switch_port 56 | and tcp.dport == self._trace_device_port 57 | ): 58 | try: 59 | recv_data_bytes = await asyncio.wait_for( 60 | reader.read(len(data_bytes)), timeout=5 61 | ) 62 | except TimeoutError as e: 63 | raise ValueError(f"Timed out waiting for Packet {n+1}") from e 64 | 65 | recv_data = int.from_bytes(recv_data_bytes, "big") 66 | logger.info(self._create_message(f"({n + 1}) Rx: 0x{recv_data:x}")) 67 | if recv_data != data: 68 | logger.error(self._create_message("Packet Trace Mismatch detected.")) 69 | raise ValueError( 70 | f"Packet {n + 1}\n Expected (in BE): 0x{data:x}\n" 71 | f" Received (in BE): 0x{recv_data:x}" 72 | ) 73 | writer.close() 74 | logger.info("The packet trace run finished successfully!") 75 | -------------------------------------------------------------------------------- /opencis/apps/pci_device.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from asyncio import gather, create_task 9 | from opencis.util.component import RunnableComponent 10 | from opencis.pci.device.pci_device import PciDevice as PciDeviceInternal 11 | from opencis.pci.component.pci import ( 12 | PciComponentIdentity, 13 | EEUM_VID, 14 | SW_EP_DID, 15 | PCI_CLASS, 16 | PCI_SYSTEM_PERIPHERAL_SUBCLASS, 17 | ) 18 | from opencis.cxl.component.switch_connection_client import SwitchConnectionClient 19 | from opencis.cxl.component.common import CXL_COMPONENT_TYPE 20 | 21 | 22 | class PciDevice(RunnableComponent): 23 | def __init__( 24 | self, 25 | port_index: int, 26 | bar_size: int, 27 | host: str = "0.0.0.0", 28 | port: int = 8000, 29 | ): 30 | label = f"Port{port_index}" 31 | super().__init__(label) 32 | self._sw_conn_client = SwitchConnectionClient( 33 | port_index, 34 | CXL_COMPONENT_TYPE.P, 35 | host=host, 36 | port=port, 37 | parent_name=f"PciDevice{port_index}", 38 | ) 39 | self._pci_device = PciDeviceInternal( 40 | transport_connection=self._sw_conn_client.get_cxl_connection(), 41 | identity=PciComponentIdentity( 42 | EEUM_VID, 43 | SW_EP_DID, 44 | PCI_CLASS.SYSTEM_PERIPHERAL, 45 | PCI_SYSTEM_PERIPHERAL_SUBCLASS.OTHER, 46 | ), 47 | bar_size=bar_size, 48 | label=f"PCIDevice{port_index}", 49 | ) 50 | 51 | async def _run(self): 52 | tasks = [ 53 | create_task(self._sw_conn_client.run()), 54 | create_task(self._pci_device.run()), 55 | ] 56 | wait_tasks = [ 57 | create_task(self._sw_conn_client.wait_for_ready()), 58 | create_task(self._pci_device.wait_for_ready()), 59 | ] 60 | await gather(*wait_tasks) 61 | await self._change_status_to_running() 62 | await gather(*tasks) 63 | 64 | async def _stop(self): 65 | tasks = [ 66 | create_task(self._sw_conn_client.stop()), 67 | create_task(self._pci_device.stop()), 68 | ] 69 | await gather(*tasks) 70 | -------------------------------------------------------------------------------- /opencis/apps/single_logical_device.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from asyncio import gather, create_task 9 | 10 | from opencis.util.component import RunnableComponent 11 | from opencis.cxl.device.cxl_type3_device import CxlType3Device, CXL_T3_DEV_TYPE 12 | from opencis.cxl.component.switch_connection_client import SwitchConnectionClient 13 | from opencis.cxl.component.common import CXL_COMPONENT_TYPE 14 | 15 | 16 | class SingleLogicalDevice(RunnableComponent): 17 | def __init__( 18 | self, 19 | memory_size: int, 20 | memory_file: str, 21 | serial_number: str, 22 | host: str = "0.0.0.0", 23 | port: int = 8000, 24 | port_index: int = -1, 25 | test_mode: bool = False, 26 | cxl_connection=None, 27 | ): 28 | label = f"Port{port_index}" 29 | super().__init__(label) 30 | 31 | self._test_mode = test_mode 32 | 33 | assert ( 34 | not test_mode or cxl_connection is not None 35 | ), "cxl_connection must be passed in test mode" 36 | assert ( 37 | test_mode or cxl_connection is None 38 | ), "cxl_connection must not be passed in non-test mode" 39 | 40 | if cxl_connection is not None: 41 | self._cxl_connection = cxl_connection 42 | else: 43 | self._sw_conn_client = SwitchConnectionClient( 44 | port_index, CXL_COMPONENT_TYPE.D2, host=host, port=port 45 | ) 46 | self._cxl_connection = self._sw_conn_client.get_cxl_connection() 47 | 48 | self._cxl_type3_device = CxlType3Device( 49 | transport_connection=self._cxl_connection, 50 | memory_size=memory_size, 51 | memory_file=memory_file, 52 | serial_number=serial_number, 53 | dev_type=CXL_T3_DEV_TYPE.SLD, 54 | label=label, 55 | ) 56 | 57 | async def _run(self): 58 | # pylint: disable=duplicate-code 59 | run_tasks = [create_task(self._cxl_type3_device.run())] 60 | wait_tasks = [create_task(self._cxl_type3_device.wait_for_ready())] 61 | if not self._test_mode: 62 | run_tasks += [create_task(self._sw_conn_client.run())] 63 | wait_tasks += [create_task(self._sw_conn_client.wait_for_ready())] 64 | 65 | await gather(*wait_tasks) 66 | await self._change_status_to_running() 67 | await gather(*run_tasks) 68 | 69 | async def _stop(self): 70 | stop_tasks = [create_task(self._cxl_type3_device.stop())] 71 | if not self._test_mode: 72 | stop_tasks += [create_task(self._sw_conn_client.stop())] 73 | 74 | await gather(*stop_tasks) 75 | 76 | def get_reg_vals(self): 77 | return self._cxl_type3_device.get_reg_vals() 78 | -------------------------------------------------------------------------------- /opencis/bin/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opencis/opencis-core/cbcc15acffe5f0cadd770ae960ad1293479feb4a/opencis/bin/__init__.py -------------------------------------------------------------------------------- /opencis/bin/accelerator.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | import asyncio 9 | from enum import Enum 10 | from typing import List 11 | import click 12 | 13 | from opencis.util.logger import logger 14 | from opencis.cxl.environment import parse_cxl_environment 15 | from opencis.apps.accelerator import MyType1Accelerator, MyType2Accelerator 16 | 17 | 18 | class ACCEL_TYPE(Enum): 19 | T1 = 1 20 | T2 = 2 21 | 22 | 23 | @click.group(name="accel") 24 | def accel_group(): 25 | """Command group for managing logical devices.""" 26 | 27 | 28 | async def run_devices(accels: List[MyType1Accelerator | MyType2Accelerator]): 29 | try: 30 | await asyncio.gather(*(accel.run() for accel in accels)) 31 | except Exception as e: 32 | logger.error("Error while running Accelerator Device", exc_info=e) 33 | finally: 34 | await asyncio.gather(*(accel.stop() for accel in accels)) 35 | 36 | 37 | def start_group(config_file, dev_type): 38 | logger.info(f"Starting CXL Accelerator Group - Config: {config_file}") 39 | cxl_env = parse_cxl_environment(config_file) 40 | accels = [] 41 | for device_config in cxl_env.logical_device_configs: 42 | if dev_type == ACCEL_TYPE.T1: 43 | accel = MyType1Accelerator( 44 | port_index=device_config.port_index, 45 | host=cxl_env.switch_config.host, 46 | port=cxl_env.switch_config.port, 47 | ) 48 | elif dev_type == ACCEL_TYPE.T2: 49 | accel = MyType2Accelerator( 50 | port_index=device_config.port_index, 51 | memory_size=device_config.memory_size, 52 | memory_file=device_config.memory_file, 53 | host=cxl_env.switch_config.host, 54 | port=cxl_env.switch_config.port, 55 | ) 56 | else: 57 | raise Exception("Invalid Aceelerator Type") 58 | accels.append(accel) 59 | asyncio.run(run_devices(accels)) 60 | -------------------------------------------------------------------------------- /opencis/bin/common.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | import click 9 | 10 | from opencis.util.logger import logger 11 | 12 | 13 | class BasedInt(click.ParamType): 14 | def convert(self, value, param, ctx): 15 | if isinstance(value, int): 16 | return value 17 | try: 18 | if value[:2].lower() == "0x": 19 | return int(value[2:], 16) 20 | return int(value, 10) 21 | except ValueError: 22 | logger.error(f"{value!r} is not a valid integer") 23 | return None 24 | 25 | 26 | BASED_INT = BasedInt() 27 | -------------------------------------------------------------------------------- /opencis/bin/cxl_host.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | import asyncio 9 | import click 10 | 11 | from opencis.util.logger import logger 12 | from opencis.cxl.environment import parse_cxl_environment 13 | from opencis.cxl.component.cxl_component import PORT_TYPE 14 | from opencis.cxl.component.host_manager import HostManager 15 | from opencis.apps.memory_pooling import run_host 16 | 17 | 18 | @click.group(name="host") 19 | def host_group(): 20 | """Command group for managing CXL Host""" 21 | 22 | 23 | def start(port, ig, iw): 24 | logger.info(f"Starting CXL Host on Port{port}") 25 | asyncio.run(run_host(port_index=port, irq_port=8500, ig=ig, iw=iw)) 26 | 27 | 28 | def start_host_manager(): 29 | logger.info("Starting CXL HostManager") 30 | host_manager = HostManager() 31 | asyncio.run(host_manager.run()) 32 | asyncio.run(host_manager.wait_for_ready()) 33 | 34 | 35 | async def run_host_group(ports, ig, iw): 36 | irq_port = 8500 37 | tasks = [] 38 | for idx in ports: 39 | tasks.append( 40 | asyncio.create_task( 41 | run_host( 42 | port_index=idx, 43 | irq_port=irq_port, 44 | ig=ig, 45 | iw=iw, 46 | ) 47 | ) 48 | ) 49 | irq_port += 1 50 | await asyncio.gather(*tasks) 51 | 52 | 53 | def start_group(config_file: str, ig: int = 0, iw: int = 0): 54 | logger.info(f"Starting CXL Host Group - Config: {config_file}") 55 | try: 56 | environment = parse_cxl_environment(config_file) 57 | except Exception as e: 58 | logger.error(f"Failed to parse environment configuration: {e}") 59 | return 60 | 61 | ports = [] 62 | for idx, port_config in enumerate(environment.switch_config.port_configs): 63 | if port_config.type == PORT_TYPE.USP: 64 | ports.append(idx) 65 | asyncio.run(run_host_group(ports, ig, iw)) 66 | -------------------------------------------------------------------------------- /opencis/bin/cxl_switch.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | import asyncio 9 | import click 10 | from opencis.util.logger import logger 11 | from opencis.apps.cxl_switch import CxlSwitch 12 | from opencis.cxl.environment import parse_cxl_environment, CxlEnvironment 13 | 14 | 15 | # Switch command group 16 | @click.group(name="switch") 17 | def switch_group(): 18 | """Command group for CXL Switch.""" 19 | 20 | 21 | @switch_group.command(name="start") 22 | @click.argument("config_file", type=click.Path(exists=True)) 23 | def start(config_file): 24 | """Run the CXL Switch with the given configuration file.""" 25 | logger.info(f"Starting CXL Switch - Config: {config_file}") 26 | try: 27 | environment: CxlEnvironment = parse_cxl_environment(config_file) 28 | except ValueError as e: 29 | logger.error(f"Configuration error: {e}") 30 | return 31 | 32 | switch = CxlSwitch(environment.switch_config, environment.logical_device_configs) 33 | asyncio.run(switch.run()) 34 | -------------------------------------------------------------------------------- /opencis/bin/fabric_manager.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | import asyncio 9 | import click 10 | 11 | from opencis.util.logger import logger 12 | from opencis.apps.fabric_manager import CxlFabricManager 13 | from opencis.bin import socketio_client 14 | from opencis.bin.common import BASED_INT 15 | 16 | 17 | # Fabric Manager command group 18 | @click.group(name="fm") 19 | def fabric_manager_group(): 20 | """Command group for Fabric Manager.""" 21 | 22 | 23 | @fabric_manager_group.command(name="start") 24 | @click.option("--use-test-runner", is_flag=True, help="Run with the test runner.") 25 | def start(use_test_runner): 26 | """Run the Fabric Manager.""" 27 | logger.info("Starting CXL FabricManager") 28 | fabric_manager = CxlFabricManager(use_test_runner=use_test_runner) 29 | asyncio.run(fabric_manager.run()) 30 | 31 | 32 | @fabric_manager_group.command(name="bind") 33 | @click.argument("vcs", nargs=1, type=BASED_INT) 34 | @click.argument("vppb", nargs=1, type=BASED_INT) 35 | @click.argument("physical", nargs=1, type=BASED_INT) 36 | @click.argument( 37 | "ld_id", 38 | nargs=1, 39 | type=BASED_INT, 40 | default=0, 41 | ) 42 | def fm_bind(vcs: int, vppb: int, physical: int, ld_id: int): 43 | asyncio.run(socketio_client.bind(vcs, vppb, physical, ld_id)) 44 | 45 | 46 | @fabric_manager_group.command(name="unbind") 47 | @click.argument("vcs", nargs=1, type=BASED_INT) 48 | @click.argument("vppb", nargs=1, type=BASED_INT) 49 | def fm_unbind(vcs: int, vppb: int): 50 | asyncio.run(socketio_client.unbind(vcs, vppb)) 51 | 52 | 53 | @fabric_manager_group.command(name="get-ld-info") 54 | @click.argument("port_index", nargs=1, type=BASED_INT) 55 | def get_ld_info(port_index: int): 56 | asyncio.run(socketio_client.get_ld_info(port_index)) 57 | 58 | 59 | @fabric_manager_group.command(name="get-ld-allocation") 60 | @click.argument("port_index", nargs=1, type=BASED_INT) 61 | @click.argument("start_ld_id", nargs=1, type=BASED_INT) 62 | @click.argument("ld_allocation_list_limit", nargs=1, type=BASED_INT) 63 | def get_ld_allocation(port_index: int, start_ld_id: int, ld_allocation_list_limit: int): 64 | asyncio.run( 65 | socketio_client.get_ld_allocation(port_index, start_ld_id, ld_allocation_list_limit) 66 | ) 67 | 68 | 69 | @fabric_manager_group.command(name="freeze") 70 | @click.argument("vcs", nargs=1, type=BASED_INT) 71 | @click.argument("vppb", nargs=1, type=BASED_INT) 72 | def fm_freeze(vcs: int, vppb: int): 73 | asyncio.run(socketio_client.freeze(vcs, vppb)) 74 | 75 | 76 | @fabric_manager_group.command(name="unfreeze") 77 | @click.argument("vcs", nargs=1, type=BASED_INT) 78 | @click.argument("vppb", nargs=1, type=BASED_INT) 79 | def fm_unfreeze(vcs: int, vppb: int): 80 | asyncio.run(socketio_client.unfreeze(vcs, vppb)) 81 | 82 | 83 | # TODO: Implement set_ld_allocation 84 | # @fabric_manager_group.command(name="set-ld-allocation") 85 | # @click.argument("port_index", nargs=1, type=BASED_INT) 86 | # @click.argument("number_of_lds", nargs=1, type=BASED_INT) 87 | # @click.argument("start_ld_id", nargs=1, type=BASED_INT) 88 | # @click.argument("ld_allocation_list", nargs=1, type=BASED_INT) 89 | # def set_ld_allocation( 90 | # port_index: int, number_of_lds: int, start_ld_id: int, ld_allocation_list: int 91 | # ): 92 | # asyncio.run( 93 | # socketio_client.set_ld_allocation( 94 | # port_index, number_of_lds, start_ld_id, ld_allocation_list 95 | # ) 96 | # ) 97 | -------------------------------------------------------------------------------- /opencis/bin/get_info.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | import asyncio 9 | import click 10 | 11 | from opencis.bin import socketio_client 12 | 13 | 14 | @click.group(name="get-info") 15 | def get_info_group(): 16 | """Command group for component info""" 17 | 18 | 19 | @get_info_group.command(name="port") 20 | def get_port(): 21 | asyncio.run(socketio_client.get_port()) 22 | 23 | 24 | @get_info_group.command(name="vcs") 25 | def get_vcs(): 26 | asyncio.run(socketio_client.get_vcs()) 27 | 28 | 29 | @get_info_group.command(name="device") 30 | def get_device(): 31 | asyncio.run(socketio_client.get_device()) 32 | -------------------------------------------------------------------------------- /opencis/bin/mem.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | import asyncio 9 | import click 10 | 11 | from opencis.util.logger import logger 12 | from opencis.cxl.component.host_manager import UtilConnClient 13 | from opencis.bin.common import BASED_INT 14 | 15 | 16 | @click.group(name="mem") 17 | def mem_group(): 18 | """Command group for CXL.mem Commands""" 19 | 20 | 21 | @mem_group.command(name="write") 22 | @click.argument("port", nargs=1, type=BASED_INT) 23 | @click.argument("addr", nargs=1, type=BASED_INT) 24 | @click.argument("data", nargs=1, type=BASED_INT) 25 | @click.option("--util-host", type=str, default="0.0.0.0", help="Host for util server") 26 | @click.option("--util-port", type=BASED_INT, default=8400, help="Port for util server") 27 | def cxl_mem_write(port: int, addr: int, data: int, util_host: str, util_port: int): 28 | """CXL.mem Write Command""" 29 | client = UtilConnClient(host=util_host, port=util_port) 30 | if len(f"{data:x}") > 128: 31 | logger.info(f"CXL-Host[Port{port}]: Error - Data length greater than 0x40 bytes") 32 | return 33 | try: 34 | asyncio.run(client.cxl_mem_write(port, addr, data)) 35 | except Exception as e: 36 | logger.info(f"CXL-Host[Port{port}]: {e}") 37 | return 38 | logger.info(f"CXL-Host[Port{port}]: CXL.mem Write success") 39 | 40 | 41 | @mem_group.command(name="read") 42 | @click.argument("port", nargs=1, type=BASED_INT) 43 | @click.argument("addr", nargs=1, type=BASED_INT) 44 | @click.option("--util-host", type=str, default="0.0.0.0", help="Host for util server") 45 | @click.option("--util-port", type=BASED_INT, default=8400, help="Port for util server") 46 | def cxl_mem_read(port: int, addr: int, util_host: str, util_port: int): 47 | """CXL.mem Read Command""" 48 | client = UtilConnClient(host=util_host, port=util_port) 49 | try: 50 | res = asyncio.run(client.cxl_mem_read(port, addr)) 51 | except Exception as e: 52 | logger.info(f"CXL-Host[Port{port}]: {e}") 53 | return 54 | logger.info(f"CXL-Host[Port{port}]: CXL.mem Read success") 55 | logger.info("Data:") 56 | res = f"{res:x}" 57 | data = list(map(lambda x: int(x, 16), [res[i : i + 2] for i in range(0, len(res), 2)])) 58 | logger.hexdump("INFO", data) 59 | -------------------------------------------------------------------------------- /opencis/bin/multi_logical_device.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | import asyncio 9 | from typing import List 10 | import humanfriendly 11 | import click 12 | 13 | from opencis.util.logger import logger 14 | from opencis.cxl.environment import parse_cxl_environment 15 | from opencis.apps.multi_logical_device import MultiLogicalDevice 16 | 17 | 18 | @click.group(name="mld") 19 | def mld_group(): 20 | """Command group for managing single logical devices.""" 21 | 22 | 23 | async def run_devices(mlds: List[MultiLogicalDevice]): 24 | try: 25 | await asyncio.gather(*(mld.run() for mld in mlds)) 26 | except Exception as e: 27 | logger.error( 28 | "An error occurred while running the Single Logical Device clients.", 29 | exc_info=e, 30 | ) 31 | finally: 32 | await asyncio.gather(*(mld.stop() for mld in mlds)) 33 | 34 | 35 | def start_group(config_file): 36 | logger.info(f"Starting CXL Multi Logical Device Group - Config: {config_file}") 37 | cxl_env = parse_cxl_environment(config_file) 38 | mlds = [] 39 | for device_config in cxl_env.multi_logical_device_configs: 40 | mld = MultiLogicalDevice( 41 | port_index=device_config.port_index, 42 | memory_sizes=device_config.memory_sizes, 43 | memory_files=device_config.memory_files, 44 | serial_numbers=device_config.serial_numbers, 45 | host=cxl_env.switch_config.host, 46 | port=cxl_env.switch_config.port, 47 | ) 48 | mlds.append(mld) 49 | asyncio.run(run_devices(mlds)) 50 | 51 | 52 | @mld_group.command(name="start") 53 | @click.option("--port", default=1, help="Port number for the service.", show_default=True) 54 | @click.option("--memfile", type=str, default=None, help="Memory file name.") 55 | @click.option("--memsize", type=str, default="256M", help="Memory file size.") 56 | def start(port, memfile, memsize): 57 | logger.info(f"Starting CXL Single Logical Device at port {port}") 58 | if memfile is None: 59 | memfile = f"mld-mem{port}.bin" 60 | memsize = humanfriendly.parse_size(memsize, binary=True) 61 | mld = MultiLogicalDevice(port, memsize, memfile, serial_numbers=[]) 62 | asyncio.run(mld.run()) 63 | -------------------------------------------------------------------------------- /opencis/bin/packet_runner.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | import asyncio 9 | import click 10 | 11 | from opencis.bin.common import BASED_INT 12 | from opencis.apps.packet_trace_runner import PacketTraceRunner 13 | 14 | 15 | @click.group(name="packet-runner") 16 | def ptr_group(): 17 | """Command group for packet-runner""" 18 | 19 | 20 | @ptr_group.command(name="start") 21 | @click.argument("pcap-file", nargs=1, type=str) 22 | @click.option("--switch-host", type=str, default="0.0.0.0", help="Host for switch") 23 | @click.option("--switch-port", type=BASED_INT, default=8000, help="TCP port for switch") 24 | @click.option("--device-port", type=BASED_INT, default=3000, help="TCP port for device") 25 | @click.option("--label", type=str, default=None, help="Label to attach to the log lines") 26 | def start(pcap_file, switch_host, switch_port, device_port, label): 27 | trace_runner = PacketTraceRunner(pcap_file, device_port, switch_port, switch_host, label) 28 | asyncio.run(trace_runner.run()) 29 | -------------------------------------------------------------------------------- /opencis/bin/single_logical_device.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | import asyncio 9 | from typing import List 10 | import humanfriendly 11 | import click 12 | 13 | from opencis.util.logger import logger 14 | from opencis.cxl.environment import parse_cxl_environment 15 | from opencis.apps.single_logical_device import SingleLogicalDevice 16 | 17 | 18 | @click.group(name="sld") 19 | def sld_group(): 20 | """Command group for managing single logical devices.""" 21 | 22 | 23 | async def run_devices(slds: List[SingleLogicalDevice]): 24 | try: 25 | await asyncio.gather(*(sld.run() for sld in slds)) 26 | except Exception as e: 27 | logger.error("Error while running Single Logical Device", exc_info=e) 28 | finally: 29 | await asyncio.gather(*(sld.stop() for sld in slds)) 30 | 31 | 32 | def start_group(config_file): 33 | logger.info(f"Starting CXL Single Logical Device Group - Config: {config_file}") 34 | cxl_env = parse_cxl_environment(config_file) 35 | slds = [] 36 | for device_config in cxl_env.single_logical_device_configs: 37 | sld = SingleLogicalDevice( 38 | port_index=device_config.port_index, 39 | memory_size=device_config.memory_size, 40 | memory_file=device_config.memory_file, 41 | serial_number=device_config.serial_number, 42 | host=cxl_env.switch_config.host, 43 | port=cxl_env.switch_config.port, 44 | ) 45 | slds.append(sld) 46 | asyncio.run(run_devices(slds)) 47 | 48 | 49 | @sld_group.command(name="start") 50 | @click.option("--port", default=1, help="Port number for the service.", show_default=True) 51 | @click.option("--memfile", type=str, default=None, help="Memory file name.") 52 | @click.option("--memsize", type=str, default="256M", help="Memory file size.") 53 | def start(port, memfile, memsize): 54 | logger.info(f"Starting CXL Single Logical Device at port {port}") 55 | if memfile is None: 56 | memfile = f"mem{port}.bin" 57 | memsize = humanfriendly.parse_size(memsize, binary=True) 58 | sld = SingleLogicalDevice(port, memsize, memfile) 59 | asyncio.run(sld.run()) 60 | -------------------------------------------------------------------------------- /opencis/bin/test_external_switch_pcie.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | import asyncio 9 | from typing import List, cast 10 | 11 | from opencis.cxl.component.cxl_host import CxlHost 12 | from opencis.apps.pci_device import PciDevice 13 | from opencis.util.logger import logger 14 | from opencis.util.component import RunnableComponent 15 | from opencis.drivers.pci_bus_driver import PciBusDriver 16 | 17 | # pylint: disable=duplicate-code 18 | 19 | 20 | class TestRunner: 21 | def __init__(self, apps: List[RunnableComponent]): 22 | self._apps = apps 23 | 24 | async def run(self): 25 | tasks = [] 26 | for app in self._apps: 27 | tasks.append(asyncio.create_task(app.run())) 28 | tasks.append(asyncio.create_task(self.run_test())) 29 | await asyncio.gather(*tasks) 30 | 31 | async def wait_for_ready(self): 32 | tasks = [] 33 | for app in self._apps: 34 | tasks.append(asyncio.create_task(app.wait_for_ready())) 35 | await asyncio.gather(*tasks) 36 | 37 | async def run_test(self): 38 | logger.info("Waiting for Apps to be ready") 39 | await self.wait_for_ready() 40 | host = cast(CxlHost, self._apps[0]) 41 | pci_bus_driver = PciBusDriver(host.get_root_complex()) 42 | logger.info("Starting PCI bus driver init") 43 | await pci_bus_driver.init(mmio_base_address=0) 44 | logger.info("Completed PCI bus driver init") 45 | 46 | 47 | def main(): 48 | # Set up logger 49 | log_file = "test.log" 50 | log_level = "DEBUG" 51 | show_timestamp = True 52 | show_loglevel = True 53 | show_linenumber = True 54 | logger.create_log_file( 55 | f"logs/{log_file}", 56 | loglevel=log_level if log_level else "INFO", 57 | show_timestamp=show_timestamp, 58 | show_loglevel=show_loglevel, 59 | show_linenumber=show_linenumber, 60 | ) 61 | 62 | apps = [] 63 | 64 | # Add Host 65 | # switch_host = "0.0.0.0" 66 | # switch_port = 8000 67 | # host_config = CxlHostConfig( 68 | # host_name="CXLHost", 69 | # root_bus=0, 70 | # root_port_switch_type=ROOT_PORT_SWITCH_TYPE.PASS_THROUGH, 71 | # root_ports=[ 72 | # RootPortClientConfig(port_index=0, switch_host=switch_host, switch_port=switch_port) 73 | # ], 74 | # memory_ranges=[], 75 | # memory_controller=RootComplexMemoryControllerConfig( 76 | # memory_size=0x10000, memory_filename="memory_dram.bin" 77 | # ), 78 | # ) 79 | # host = CxlHost(host_config) 80 | # apps.append(host) 81 | 82 | # Add PCI devices 83 | for port in range(1, 5): 84 | device = PciDevice(port, 0x1000) 85 | apps.append(device) 86 | 87 | test_runner = TestRunner(apps) 88 | asyncio.run(test_runner.run()) 89 | 90 | 91 | if __name__ == "__main__": 92 | main() 93 | -------------------------------------------------------------------------------- /opencis/cxl/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | -------------------------------------------------------------------------------- /opencis/cxl/cci/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opencis/opencis-core/cbcc15acffe5f0cadd770ae960ad1293479feb4a/opencis/cxl/cci/__init__.py -------------------------------------------------------------------------------- /opencis/cxl/cci/fabric_manager/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | -------------------------------------------------------------------------------- /opencis/cxl/cci/fabric_manager/dcd_management/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | -------------------------------------------------------------------------------- /opencis/cxl/cci/fabric_manager/mld_components/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from .get_ld_info import ( 9 | GetLdInfoCommand, 10 | GetLdInfoResponsePayload, 11 | ) 12 | 13 | from .get_ld_allocations import ( 14 | GetLdAllocationsCommand, 15 | GetLdAllocationsRequestPayload, 16 | GetLdAllocationsResponsePayload, 17 | ) 18 | 19 | from .set_ld_allocations import ( 20 | SetLdAllocationsCommand, 21 | SetLdAllocationsRequestPayload, 22 | SetLdAllocationsResponsePayload, 23 | ) 24 | -------------------------------------------------------------------------------- /opencis/cxl/cci/fabric_manager/mld_components/get_ld_info.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from dataclasses import dataclass, field 9 | from struct import pack, unpack 10 | from typing import TypedDict 11 | 12 | # from opencis.util.logger import logger 13 | from opencis.cxl.cci.common import CCI_FM_API_COMMAND_OPCODE 14 | from opencis.cxl.component.cci_executor import ( 15 | CciForegroundCommand, 16 | CciRequest, 17 | CciResponse, 18 | ) 19 | 20 | 21 | class GetLdInfoResponsePayloadDict(TypedDict): 22 | memorySize: int 23 | ldCount: int 24 | qosTelemetryCapability: int 25 | 26 | 27 | @dataclass 28 | class GetLdInfoResponsePayload: 29 | memory_size: int = field(default=0) # 8bytes 30 | ld_count: int = field(default=0) # 2bytes 31 | qos_telemetry_capability: int = field(default=0) # 1byte 32 | 33 | @classmethod 34 | def parse(cls, data: bytes): 35 | if len(data) < 11: 36 | raise ValueError("Data provided is too short to parse.") 37 | 38 | memory_size = unpack(" GetLdInfoResponsePayloadDict: 58 | return { 59 | "memorySize": self.memory_size, 60 | "ldCount": self.ld_count, 61 | "qosTelemetryCapability": self.qos_telemetry_capability, 62 | } 63 | 64 | 65 | class GetLdInfoCommand(CciForegroundCommand): 66 | OPCODE = CCI_FM_API_COMMAND_OPCODE.GET_LD_INFO 67 | 68 | def __init__(self): 69 | super().__init__(self.OPCODE) 70 | 71 | async def _execute(self, request: CciRequest) -> CciResponse: 72 | pass 73 | 74 | @classmethod 75 | def create_cci_request(cls) -> CciRequest: 76 | cci_request = CciRequest() 77 | cci_request.opcode = cls.OPCODE 78 | # get_ld_info request has no payload 79 | return cci_request 80 | 81 | @classmethod 82 | def parse_response_payload(cls, payload: bytes) -> GetLdInfoResponsePayload: 83 | return GetLdInfoResponsePayload.parse(payload) 84 | -------------------------------------------------------------------------------- /opencis/cxl/cci/fabric_manager/mld_port/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from .tunnel_management import ( 9 | TunnelManagementCommand, 10 | TunnelManagementRequestPayload, 11 | TunnelManagementResponsePayload, 12 | ) 13 | -------------------------------------------------------------------------------- /opencis/cxl/cci/fabric_manager/mld_port/tunnel_management.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from typing import List, Optional, cast 9 | from opencis.cxl.component.cxl_connection import CxlConnection 10 | from opencis.cxl.cci.common import ( 11 | CCI_FM_API_COMMAND_OPCODE, 12 | ) 13 | from opencis.cxl.component.cci_executor import ( 14 | CciRequest, 15 | CciResponse, 16 | CciForegroundCommand, 17 | ) 18 | from opencis.cxl.device.cxl_type3_device import CxlType3Device 19 | from opencis.cxl.transport.transaction import CciMessagePacket 20 | from opencis.cxl.cci.common import TunnelManagementRequestPayload, TunnelManagementResponsePayload 21 | 22 | 23 | class TunnelManagementCommand(CciForegroundCommand): 24 | OPCODE = CCI_FM_API_COMMAND_OPCODE.TUNNEL_MANAGEMENT_COMMAND 25 | 26 | def __init__( 27 | self, 28 | label: Optional[str] = None, 29 | cxl_type3_devices: List[CxlType3Device] = None, 30 | cxl_connections: List[CxlConnection] = None, 31 | ): 32 | super().__init__(self.OPCODE, label=label) 33 | if cxl_type3_devices is None: 34 | cxl_type3_devices = [] 35 | if cxl_connections is None: 36 | cxl_connections = [] 37 | self._cxl_type3_devices = cxl_type3_devices 38 | self._cxl_connections = cxl_connections 39 | 40 | async def _execute(self, request: CciRequest) -> CciResponse: 41 | request_payload = self.parse_request_payload(request.payload) 42 | port_or_ld_id = request_payload.port_or_ld_id 43 | 44 | real_payload = request_payload.command_payload 45 | connection = self._cxl_connections[port_or_ld_id] 46 | 47 | await connection.cci_fifo.host_to_target.put(cast(CciMessagePacket, real_payload)) 48 | 49 | dev_response: CciMessagePacket = await connection.cci_fifo.target_to_host.get() 50 | 51 | payload = TunnelManagementResponsePayload( 52 | dev_response.get_size(), payload=bytes(CciMessagePacket) 53 | ) 54 | 55 | return CciResponse(payload=payload) 56 | 57 | @classmethod 58 | def create_cci_request(cls, request: TunnelManagementRequestPayload) -> CciRequest: 59 | return CciRequest(opcode=cls.OPCODE, payload=request.dump()) 60 | 61 | @staticmethod 62 | def parse_request_payload(payload: bytes) -> TunnelManagementRequestPayload: 63 | return TunnelManagementRequestPayload.parse(payload) 64 | 65 | @staticmethod 66 | def parse_response_payload( 67 | payload: bytes, 68 | ) -> TunnelManagementResponsePayload: 69 | return TunnelManagementResponsePayload.parse(payload) 70 | -------------------------------------------------------------------------------- /opencis/cxl/cci/fabric_manager/multi_headed_devices/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | -------------------------------------------------------------------------------- /opencis/cxl/cci/fabric_manager/physical_switch/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from .identify_switch_device import ( 9 | IdentifySwitchDeviceCommand, 10 | IdentifySwitchDeviceResponsePayload, 11 | ) 12 | from .get_physical_port_state import ( 13 | GetPhysicalPortStateCommand, 14 | GetPhysicalPortStateRequestPayload, 15 | GetPhysicalPortStateResponsePayload, 16 | ) 17 | -------------------------------------------------------------------------------- /opencis/cxl/cci/fabric_manager/physical_switch/physical_port_control.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | -------------------------------------------------------------------------------- /opencis/cxl/cci/fabric_manager/physical_switch/send_ppb_cxl_io_configuration_request.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | -------------------------------------------------------------------------------- /opencis/cxl/cci/fabric_manager/virtual_switch/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from .get_virtual_cxl_switch_info import ( 9 | GetVirtualCxlSwitchInfoCommand, 10 | GetVirtualCxlSwitchInfoResponsePayload, 11 | GetVirtualCxlSwitchInfoRequestPayload, 12 | ) 13 | from .bind_vppb import BindVppbCommand, BindVppbRequestPayload 14 | from .unbind_vppb import UnbindVppbCommand, UnbindVppbRequestPayload 15 | from .freeze_vppb import FreezeVppbCommand, FreezeVppbRequestPayload 16 | from .unfreeze_vppb import UnfreezeVppbCommand, UnfreezeVppbRequestPayload 17 | -------------------------------------------------------------------------------- /opencis/cxl/cci/fabric_manager/virtual_switch/freeze_vppb.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from dataclasses import dataclass 9 | import struct 10 | 11 | from opencis.cxl.component.cci_executor import ( 12 | CciBackgroundCommand, 13 | CciRequest, 14 | CciResponse, 15 | ProgressCallback, 16 | ) 17 | from opencis.cxl.component.virtual_switch_manager import VirtualSwitchManager 18 | from opencis.cxl.cci.common import CCI_FM_API_COMMAND_OPCODE, CCI_RETURN_CODE 19 | from opencis.util.logger import logger 20 | 21 | """ 22 | 23 | The following dataclass is generated by ChatGPT (GPT-4) 24 | 25 | """ 26 | 27 | 28 | # pylint: disable=duplicate-code 29 | 30 | 31 | @dataclass 32 | class FreezeVppbRequestPayload: 33 | vcs_id: int = 0 34 | vppb_id: int = 0 35 | pack_mask: str = " "FreezeVppbRequestPayload": 39 | if len(data) != struct.calcsize(cls.pack_mask): 40 | raise ValueError("Data is too short to parse.") 41 | ( 42 | vcs_id, 43 | vppb_id, 44 | ) = struct.unpack(cls.pack_mask, data) 45 | 46 | return cls( 47 | vcs_id=vcs_id, 48 | vppb_id=vppb_id, 49 | ) 50 | 51 | def dump(self) -> bytes: 52 | databytes = struct.pack( 53 | self.pack_mask, 54 | self.vcs_id, 55 | self.vppb_id, 56 | ) 57 | return databytes 58 | 59 | def get_pretty_print(self) -> str: 60 | return f"- Virtual CXL Switch ID: {self.vcs_id}\n" f"- vPPB ID: {self.vppb_id}\n" 61 | 62 | 63 | class FreezeVppbCommand(CciBackgroundCommand): 64 | OPCODE = CCI_FM_API_COMMAND_OPCODE.FREEZE_VPPB 65 | 66 | def __init__(self, virtual_switch_manager: VirtualSwitchManager): 67 | super().__init__(self.OPCODE) 68 | self._virtual_switch_manager = virtual_switch_manager 69 | 70 | async def _execute(self, request: CciRequest, callback: ProgressCallback) -> CciResponse: 71 | request_payload = self.parse_request_payload(request.payload) 72 | vcs_id = request_payload.vcs_id 73 | vppb_id = request_payload.vppb_id 74 | vcs_count = self._virtual_switch_manager.get_virtual_switch_counts() 75 | if vcs_id >= vcs_count: 76 | logger.debug(self._create_message("VCS ID is out of bound")) 77 | return CciResponse(return_code=CCI_RETURN_CODE.INVALID_INPUT) 78 | 79 | vcs = self._virtual_switch_manager.get_virtual_switch(vcs_id) 80 | if vppb_id >= vcs.get_vppb_counts(): 81 | logger.debug(self._create_message("vPPB ID is out of bound")) 82 | return CciResponse(return_code=CCI_RETURN_CODE.INVALID_INPUT) 83 | 84 | if not vcs.is_vppb_bound(vppb_id): 85 | logger.error( 86 | self._create_message(f"vPPB {vppb_id} is unbound, unable to send freeze command") 87 | ) 88 | return CciResponse(return_code=CCI_RETURN_CODE.INVALID_INPUT) 89 | 90 | await callback(50) 91 | 92 | await vcs.freeze_vppb(vppb_id) 93 | response = CciResponse() 94 | return response 95 | 96 | @classmethod 97 | def create_cci_request( 98 | cls, 99 | request: FreezeVppbRequestPayload, 100 | ) -> CciRequest: 101 | cci_request = CciRequest() 102 | cci_request.opcode = cls.OPCODE 103 | cci_request.payload = request.dump() 104 | return cci_request 105 | 106 | @staticmethod 107 | def parse_request_payload(payload: bytes) -> FreezeVppbRequestPayload: 108 | return FreezeVppbRequestPayload.parse(payload) 109 | -------------------------------------------------------------------------------- /opencis/cxl/cci/fabric_manager/virtual_switch/generate_aer_event.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | -------------------------------------------------------------------------------- /opencis/cxl/cci/fabric_manager/virtual_switch/tunnel_management.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from typing import Optional, cast 9 | 10 | from opencis.cxl.component.physical_port_manager import PhysicalPortManager 11 | from opencis.cxl.component.virtual_switch_manager import VirtualSwitchManager 12 | from opencis.cxl.cci.common import ( 13 | CCI_FM_API_COMMAND_OPCODE, 14 | ) 15 | from opencis.cxl.component.cci_executor import ( 16 | CciRequest, 17 | CciResponse, 18 | CciForegroundCommand, 19 | ) 20 | from opencis.cxl.transport.transaction import CciMessagePacket 21 | from opencis.cxl.cci.common import TunnelManagementRequestPayload, TunnelManagementResponsePayload 22 | 23 | 24 | class TunnelManagementCommand(CciForegroundCommand): 25 | OPCODE = CCI_FM_API_COMMAND_OPCODE.TUNNEL_MANAGEMENT_COMMAND 26 | 27 | def __init__( 28 | self, 29 | physical_port_manager: PhysicalPortManager, 30 | virtual_switch_manager: VirtualSwitchManager, 31 | label: Optional[str] = None, 32 | ): 33 | super().__init__(self.OPCODE, label=label) 34 | self._physical_port_manager = physical_port_manager 35 | self._virtual_switch_manager = virtual_switch_manager 36 | 37 | async def _execute(self, request: CciRequest) -> CciResponse: 38 | request_payload = self.parse_request_payload(request.payload) 39 | port_or_ld_id = request_payload.port_or_ld_id 40 | port_device = self._physical_port_manager.get_port_device(port_or_ld_id) 41 | 42 | real_payload = request_payload.command_payload 43 | real_payload_packet = cast(CciMessagePacket, real_payload) 44 | await port_device.get_downstream_connection().cci_fifo.host_to_target.put( 45 | real_payload_packet 46 | ) 47 | 48 | dev_response: CciMessagePacket = ( 49 | await port_device.get_downstream_connection().cci_fifo.target_to_host.get() 50 | ) 51 | 52 | payload = TunnelManagementResponsePayload( 53 | dev_response.get_size(), payload=bytes(dev_response) 54 | ) 55 | 56 | return CciResponse(payload=payload) 57 | 58 | @classmethod 59 | def create_cci_request(cls, request: TunnelManagementRequestPayload) -> CciRequest: 60 | return CciRequest(opcode=cls.OPCODE, payload=request.dump()) 61 | 62 | @staticmethod 63 | def parse_request_payload(payload: bytes) -> TunnelManagementRequestPayload: 64 | return TunnelManagementRequestPayload.parse(payload) 65 | 66 | @staticmethod 67 | def parse_response_payload( 68 | payload: bytes, 69 | ) -> TunnelManagementResponsePayload: 70 | return TunnelManagementResponsePayload.parse(payload) 71 | -------------------------------------------------------------------------------- /opencis/cxl/cci/fabric_manager/virtual_switch/unfreeze_vppb.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from dataclasses import dataclass 9 | import struct 10 | 11 | from opencis.cxl.component.cci_executor import ( 12 | CciBackgroundCommand, 13 | CciRequest, 14 | CciResponse, 15 | ProgressCallback, 16 | ) 17 | from opencis.cxl.component.virtual_switch_manager import VirtualSwitchManager 18 | from opencis.cxl.cci.common import CCI_FM_API_COMMAND_OPCODE, CCI_RETURN_CODE 19 | from opencis.util.logger import logger 20 | 21 | """ 22 | 23 | The following dataclass is generated by ChatGPT (GPT-4) 24 | 25 | """ 26 | 27 | 28 | # pylint: disable=duplicate-code 29 | 30 | 31 | @dataclass 32 | class UnfreezeVppbRequestPayload: 33 | vcs_id: int = 0 34 | vppb_id: int = 0 35 | pack_mask: str = " "UnfreezeVppbRequestPayload": 39 | if len(data) != struct.calcsize(cls.pack_mask): 40 | raise ValueError("Data is too short to parse.") 41 | ( 42 | vcs_id, 43 | vppb_id, 44 | ) = struct.unpack(cls.pack_mask, data) 45 | 46 | return cls( 47 | vcs_id=vcs_id, 48 | vppb_id=vppb_id, 49 | ) 50 | 51 | def dump(self) -> bytes: 52 | databytes = struct.pack( 53 | self.pack_mask, 54 | self.vcs_id, 55 | self.vppb_id, 56 | ) 57 | return databytes 58 | 59 | def get_pretty_print(self) -> str: 60 | return f"- Virtual CXL Switch ID: {self.vcs_id}\n" f"- vPPB ID: {self.vppb_id}\n" 61 | 62 | 63 | class UnfreezeVppbCommand(CciBackgroundCommand): 64 | OPCODE = CCI_FM_API_COMMAND_OPCODE.UNFREEZE_VPPB 65 | 66 | def __init__(self, virtual_switch_manager: VirtualSwitchManager): 67 | super().__init__(self.OPCODE) 68 | self._virtual_switch_manager = virtual_switch_manager 69 | 70 | async def _execute(self, request: CciRequest, callback: ProgressCallback) -> CciResponse: 71 | request_payload = self.parse_request_payload(request.payload) 72 | vcs_id = request_payload.vcs_id 73 | vppb_id = request_payload.vppb_id 74 | vcs_count = self._virtual_switch_manager.get_virtual_switch_counts() 75 | if vcs_id >= vcs_count: 76 | logger.debug(self._create_message("VCS ID is out of bound")) 77 | return CciResponse(return_code=CCI_RETURN_CODE.INVALID_INPUT) 78 | 79 | vcs = self._virtual_switch_manager.get_virtual_switch(vcs_id) 80 | if vppb_id >= vcs.get_vppb_counts(): 81 | logger.debug(self._create_message("vPPB ID is out of bound")) 82 | return CciResponse(return_code=CCI_RETURN_CODE.INVALID_INPUT) 83 | 84 | if not vcs.is_vppb_bound(vppb_id): 85 | logger.error( 86 | self._create_message(f"vPPB {vppb_id} is unbound, unable to send unfreeze command") 87 | ) 88 | return CciResponse(return_code=CCI_RETURN_CODE.INVALID_INPUT) 89 | 90 | await callback(50) 91 | 92 | await vcs.unfreeze_vppb(vppb_id) 93 | response = CciResponse() 94 | return response 95 | 96 | @classmethod 97 | def create_cci_request( 98 | cls, 99 | request: UnfreezeVppbRequestPayload, 100 | ) -> CciRequest: 101 | cci_request = CciRequest() 102 | cci_request.opcode = cls.OPCODE 103 | cci_request.payload = request.dump() 104 | return cci_request 105 | 106 | @staticmethod 107 | def parse_request_payload(payload: bytes) -> UnfreezeVppbRequestPayload: 108 | return UnfreezeVppbRequestPayload.parse(payload) 109 | -------------------------------------------------------------------------------- /opencis/cxl/cci/generic/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opencis/opencis-core/cbcc15acffe5f0cadd770ae960ad1293479feb4a/opencis/cxl/cci/generic/__init__.py -------------------------------------------------------------------------------- /opencis/cxl/cci/generic/features.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | -------------------------------------------------------------------------------- /opencis/cxl/cci/generic/firmware_update.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | -------------------------------------------------------------------------------- /opencis/cxl/cci/generic/information_and_status/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from .identify import ( 9 | IdentifyComponentType, 10 | IdentifyCommand, 11 | IdentifyResponsePayload, 12 | ) 13 | 14 | from .background_command_status import ( 15 | BackgroundOperationStatusCommand, 16 | BackgroundOperationStatusResponsePayload, 17 | ) 18 | -------------------------------------------------------------------------------- /opencis/cxl/cci/generic/information_and_status_command_set.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | -------------------------------------------------------------------------------- /opencis/cxl/cci/generic/maintenance.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | -------------------------------------------------------------------------------- /opencis/cxl/cci/generic/timestamp.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | -------------------------------------------------------------------------------- /opencis/cxl/cci/memory_device/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opencis/opencis-core/cbcc15acffe5f0cadd770ae960ad1293479feb4a/opencis/cxl/cci/memory_device/__init__.py -------------------------------------------------------------------------------- /opencis/cxl/cci/memory_device/identify_memory_device.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from opencis.cxl.features.mailbox import ( 9 | CxlMailboxContext, 10 | CxlMailboxCommandBase, 11 | MAILBOX_RETURN_CODE, 12 | ) 13 | from opencis.util.unaligned_bit_structure import UnalignedBitStructure 14 | 15 | # 16 | # IdentifyMemoryDevice command (Opcode 4000h) 17 | # 18 | 19 | 20 | class IdentifyMemoryDevice(CxlMailboxCommandBase): 21 | identity: UnalignedBitStructure 22 | 23 | def __init__(self, identity: UnalignedBitStructure): 24 | super().__init__(0x4000) 25 | self.identity = identity 26 | 27 | def process(self, context: CxlMailboxContext) -> bool: 28 | if context.command["payload_length"] != 0: 29 | context.status["return_code"] = MAILBOX_RETURN_CODE.INVALID_INPUT 30 | return True 31 | 32 | context.payloads.copy_from(self.identity) 33 | context.command["payload_length"] = len(self.identity) 34 | return True 35 | -------------------------------------------------------------------------------- /opencis/cxl/cci/vendor_specfic/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from .notify_port_update import NotifyPortUpdateRequestPayload 9 | from .notify_switch_update import NotifySwitchUpdateRequestPayload 10 | from .notify_device_update import NotifyDeviceUpdateRequestPayload 11 | from .get_connected_devices import ( 12 | GetConnectedDevicesResponsePayload, 13 | GetConnectedDevicesCommand, 14 | ) 15 | -------------------------------------------------------------------------------- /opencis/cxl/cci/vendor_specfic/notify_device_update.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from dataclasses import dataclass 9 | from typing import ClassVar 10 | from opencis.cxl.component.cci_executor import CciRequest 11 | from opencis.cxl.cci.common import CCI_VENDOR_SPECIFIC_OPCODE 12 | 13 | 14 | @dataclass 15 | class NotifyDeviceUpdateRequestPayload: 16 | OPCODE: ClassVar[int] = CCI_VENDOR_SPECIFIC_OPCODE.NOTIFY_DEVICE_UPDATE 17 | 18 | def create_request(self) -> CciRequest: 19 | request = CciRequest(opcode=self.OPCODE) 20 | return request 21 | -------------------------------------------------------------------------------- /opencis/cxl/cci/vendor_specfic/notify_port_update.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from dataclasses import dataclass, field, fields 9 | from typing import ClassVar, List, Tuple 10 | import struct 11 | from opencis.cxl.component.cci_executor import CciRequest 12 | from opencis.cxl.cci.common import CCI_VENDOR_SPECIFIC_OPCODE 13 | 14 | # pylint: disable=duplicate-code 15 | 16 | 17 | @dataclass 18 | class NotifyPortUpdateRequestPayload: 19 | OPCODE: ClassVar[int] = CCI_VENDOR_SPECIFIC_OPCODE.NOTIFY_PORT_UPDATE 20 | 21 | # Class constants for the struct format 22 | _STRUCT_FORMAT: ClassVar[str] = "BB" # Little-endian, 1 byte integer x 2 23 | _FIELD_SIZES: ClassVar[List[Tuple[str, int]]] = [ 24 | ("port_id", 1), # 1 byte 25 | ("connected", 1), # 1 byte 26 | ] 27 | 28 | # Fields 29 | port_id: int = field(default=0, metadata={"size": 1}) 30 | connected: int = field(default=0, metadata={"size": 1}) 31 | 32 | @classmethod 33 | def parse(cls, data: bytes): 34 | expected_size = sum(size for _, size in cls._FIELD_SIZES) 35 | if len(data) != expected_size: 36 | raise ValueError( 37 | f"Data size does not match the expected struct size of {expected_size} bytes" 38 | ) 39 | 40 | values = struct.unpack(cls._STRUCT_FORMAT, data) 41 | return cls(*values) 42 | 43 | def dump(self) -> bytes: 44 | values = (self.port_id, self.connected) 45 | return struct.pack(self._STRUCT_FORMAT, *values) 46 | 47 | def get_pretty_print(self): 48 | field_values = {f.name: getattr(self, f.name) for f in fields(self)} 49 | return "\n".join(f"{name}: {value}" for name, value in field_values.items()) 50 | 51 | def create_request(self) -> CciRequest: 52 | payload = self.dump() 53 | request = CciRequest(opcode=self.OPCODE, payload=payload) 54 | return request 55 | -------------------------------------------------------------------------------- /opencis/cxl/cci/vendor_specfic/notify_switch_update.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from dataclasses import dataclass, field, fields 9 | import struct 10 | from typing import ClassVar, List, Tuple 11 | 12 | from opencis.cxl.component.virtual_switch.virtual_switch import ( 13 | PPB_BINDING_STATUS, 14 | ) 15 | from opencis.cxl.component.cci_executor import CciRequest 16 | from opencis.cxl.cci.common import CCI_VENDOR_SPECIFIC_OPCODE 17 | 18 | 19 | @dataclass 20 | class NotifySwitchUpdateRequestPayload: 21 | OPCODE: ClassVar[int] = CCI_VENDOR_SPECIFIC_OPCODE.NOTIFY_SWITCH_UPDATE 22 | 23 | # Class constants for the struct format 24 | _STRUCT_FORMAT: ClassVar[str] = "BBB" # Little-endian, 1 byte integer x 3 25 | _FIELD_SIZES: ClassVar[List[Tuple[str, int]]] = [ 26 | ("vcs_id", 1), # 1 byte 27 | ("vppb_id", 1), # 1 byte 28 | ("binding_status", 1), # 1 byte 29 | ] 30 | 31 | # Fields 32 | vcs_id: int = field(default=0, metadata={"size": 1}) 33 | vppb_id: int = field(default=0, metadata={"size": 1}) 34 | binding_status: PPB_BINDING_STATUS = field( 35 | default=PPB_BINDING_STATUS.UNBOUND, metadata={"size": 1} 36 | ) 37 | 38 | @classmethod 39 | def parse(cls, data: bytes): 40 | expected_size = sum(size for _, size in cls._FIELD_SIZES) 41 | if len(data) != expected_size: 42 | raise ValueError( 43 | f"Data size does not match the expected struct size of {expected_size} bytes" 44 | ) 45 | 46 | # Unpack the data using the struct format, then use the first two bytes as is 47 | # and convert the third byte to the PPB_BINDING_STATUS enum 48 | values = struct.unpack(cls._STRUCT_FORMAT, data) 49 | values = values[0], values[1], PPB_BINDING_STATUS(values[2]) 50 | return cls(*values) 51 | 52 | def dump(self) -> bytes: 53 | # Convert the binding_status enum to its integer value before packing 54 | values = (self.vcs_id, self.vppb_id, int(self.binding_status)) 55 | return struct.pack(self._STRUCT_FORMAT, *values) 56 | 57 | def get_pretty_print(self): 58 | field_values = {f.name: getattr(self, f.name) for f in fields(self)} 59 | # Special handling for enum to print its name instead of the value 60 | field_values["binding_status"] = self.binding_status.name 61 | return "\n".join(f"{name}: {value}" for name, value in field_values.items()) 62 | 63 | def create_request(self) -> CciRequest: 64 | payload = self.dump() 65 | request = CciRequest(opcode=self.OPCODE, payload=payload) 66 | return request 67 | -------------------------------------------------------------------------------- /opencis/cxl/component/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | -------------------------------------------------------------------------------- /opencis/cxl/component/bind_processor.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from asyncio import Queue, create_task, gather 9 | from dataclasses import dataclass 10 | 11 | from opencis.cxl.component.cxl_connection import CxlConnection 12 | from opencis.util.component import RunnableComponent 13 | 14 | 15 | @dataclass 16 | class BindPair: 17 | source: Queue 18 | destination: Queue 19 | 20 | 21 | class GenericBindProcessor(RunnableComponent): 22 | def __init__( 23 | self, 24 | downsteam_connection: CxlConnection, 25 | upstream_connection: CxlConnection, 26 | ): 27 | super().__init__() 28 | self._dsc = downsteam_connection 29 | self._usc = upstream_connection 30 | 31 | self._pairs = [ 32 | BindPair(self._dsc.cfg_fifo.host_to_target, self._usc.cfg_fifo.host_to_target), 33 | BindPair(self._usc.cfg_fifo.target_to_host, self._dsc.cfg_fifo.target_to_host), 34 | BindPair(self._dsc.mmio_fifo.host_to_target, self._usc.mmio_fifo.host_to_target), 35 | BindPair(self._usc.mmio_fifo.target_to_host, self._dsc.mmio_fifo.target_to_host), 36 | BindPair( 37 | self._dsc.cxl_mem_fifo.host_to_target, 38 | self._usc.cxl_mem_fifo.host_to_target, 39 | ), 40 | BindPair( 41 | self._usc.cxl_mem_fifo.target_to_host, 42 | self._dsc.cxl_mem_fifo.target_to_host, 43 | ), 44 | BindPair( 45 | self._dsc.cxl_cache_fifo.host_to_target, 46 | self._usc.cxl_cache_fifo.host_to_target, 47 | ), 48 | BindPair( 49 | self._usc.cxl_cache_fifo.target_to_host, 50 | self._dsc.cxl_cache_fifo.target_to_host, 51 | ), 52 | ] 53 | 54 | def _create_message(self, message): 55 | message = f"[{self.__class__.__name__}] {message}" 56 | return message 57 | 58 | # Similar code with pci_to_pci_bridge_device.py:*_process() 59 | # pylint: disable=duplicate-code 60 | async def _process(self, source: Queue, destination: Queue): 61 | while True: 62 | packet = await source.get() 63 | if packet is None: 64 | break 65 | await destination.put(packet) 66 | 67 | async def _run(self): 68 | tasks = [] 69 | for pair in self._pairs: 70 | task = create_task(self._process(pair.source, pair.destination)) 71 | tasks.append(task) 72 | await self._change_status_to_running() 73 | await gather(*tasks) 74 | 75 | async def _stop(self): 76 | for pair in self._pairs: 77 | await pair.source.put(None) 78 | 79 | 80 | class PpbDspBindProcessor(GenericBindProcessor): 81 | # vcs_id and vppb_id are not needed in PPB-DSP relation 82 | def _create_message(self, message): 83 | message = f"[{self.__class__.__name__}] {message}" 84 | return message 85 | 86 | 87 | class VppbPpbBindProcessor(GenericBindProcessor): 88 | def __init__( 89 | self, 90 | vcs_id: int, 91 | vppb_id: int, 92 | downsteam_connection: CxlConnection, 93 | upstream_connection: CxlConnection, 94 | ): 95 | super().__init__(downsteam_connection, upstream_connection) 96 | self._vcs_id = vcs_id 97 | self._vppb_id = vppb_id 98 | 99 | def _create_message(self, message): 100 | message = f"[{self.__class__.__name__}:VCS{self._vcs_id}:vPPB{self._vppb_id}] {message}" 101 | return message 102 | -------------------------------------------------------------------------------- /opencis/cxl/component/cache_route_table_manager.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from dataclasses import dataclass 9 | from typing import List 10 | 11 | from opencis.cxl.component.cache_route_table import ( 12 | CacheRouteTableCapabilityRegisterOptions, 13 | ) 14 | from opencis.util.component import LabeledComponent 15 | from opencis.util.logger import logger 16 | 17 | 18 | @dataclass 19 | class CacheRouteTableBase(LabeledComponent): 20 | _capabilities: CacheRouteTableCapabilityRegisterOptions 21 | _target_count: int 22 | _cache_id_to_port_mapping: List[int] 23 | _port_to_cache_id_mapping: dict[int, int] 24 | 25 | def __init__(self, capabilities, label): 26 | super().__init__(label) 27 | self._capabilities = capabilities 28 | self._target_count = capabilities["cache_id_target_count"] 29 | self._cache_id_to_port_mapping = [0] * self._target_count 30 | self._port_to_cache_id_mapping = {} 31 | 32 | def get_target(self, cache_id) -> int: 33 | return self._cache_id_to_port_mapping[cache_id] 34 | 35 | def get_cache_id(self, target) -> int: 36 | return self._port_to_cache_id_mapping[target] 37 | 38 | 39 | @dataclass 40 | class SwitchCacheRouteTable(CacheRouteTableBase): 41 | """ 42 | For a CXL Switch, we have 16 cache route table entries (for 256B flit mode). 43 | """ 44 | 45 | def __init__(self, capabilities, label): 46 | if capabilities["cache_id_target_count"] != 0x10: 47 | raise ValueError( 48 | "CXL switch routing table must have 16 cache route table entries " 49 | f"but {capabilities.cache_id_target_count} were given" 50 | ) 51 | super().__init__(capabilities, label) 52 | 53 | def commit(self, index: int, new_port) -> bool: 54 | if index > len(self._cache_id_to_port_mapping): 55 | logger.warning(f"Cache ID ({index}) is out of bound") 56 | return False 57 | 58 | self._cache_id_to_port_mapping[index] = new_port 59 | self._port_to_cache_id_mapping[new_port] = index 60 | 61 | decoder_commit_info = ( 62 | f"[Cache Route Table Commit] Cache ID: {index}, Mapped Port: {new_port}" 63 | ) 64 | logger.info(self._create_message(decoder_commit_info)) 65 | return True 66 | -------------------------------------------------------------------------------- /opencis/cxl/component/common.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from enum import Enum, auto 9 | 10 | 11 | class CXL_COMPONENT_TYPE(Enum): 12 | P = auto() 13 | D1 = auto() 14 | D2 = auto() # SLD 15 | LD = auto() # LDs within MLD 16 | FMLD = auto() 17 | UP1 = auto() 18 | DP1 = auto() 19 | R = auto() 20 | RC = auto() 21 | USP = auto() 22 | DSP = auto() 23 | T1 = auto() # reserved for type 1 24 | T2 = auto() # reserved for type 2 25 | -------------------------------------------------------------------------------- /opencis/cxl/component/cxl_cache_manager.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | # pylint: disable=unused-import 9 | from typing import Optional 10 | 11 | from opencis.pci.component.fifo_pair import FifoPair 12 | from opencis.pci.component.packet_processor import PacketProcessor 13 | 14 | 15 | class CxlCacheManager(PacketProcessor): 16 | def __init__( 17 | self, 18 | upstream_fifo: FifoPair, 19 | downstream_fifo: Optional[FifoPair] = None, 20 | label: Optional[str] = None, 21 | ): 22 | self._downstream_fifo: Optional[FifoPair] 23 | self._upstream_fifo: FifoPair 24 | self._cache_device_component = None 25 | 26 | super().__init__(upstream_fifo, downstream_fifo, label) 27 | -------------------------------------------------------------------------------- /opencis/cxl/component/cxl_component.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from abc import abstractmethod 9 | from dataclasses import dataclass 10 | from enum import Enum, IntEnum, auto 11 | from typing import Optional, List 12 | 13 | from opencis.cxl.config_space.doe.cdat import CDAT_ENTRY 14 | from opencis.cxl.features.mailbox import CxlMailbox 15 | from opencis.cxl.features.event_manager import EventManager 16 | from opencis.cxl.features.log_manager import LogManager 17 | from opencis.cxl.component.bi_decoder import ( 18 | CxlBIDecoderCapabilityStructureOptions, 19 | CxlBIRTCapabilityStructureOptions, 20 | ) 21 | from opencis.cxl.component.common import CXL_COMPONENT_TYPE 22 | from opencis.cxl.component.hdm_decoder import HdmDecoderManagerBase 23 | from opencis.util.component import LabeledComponent 24 | from opencis.cxl.component.cache_route_table import ( 25 | CacheRouteTableCapabilityStructureOptions, 26 | ) 27 | from opencis.cxl.component.cache_id_decoder_capability import ( 28 | CxlCacheIdDecoderCapabilityStructureOptions, 29 | ) 30 | 31 | 32 | class PORT_TYPE(Enum): 33 | USP = auto() 34 | DSP = auto() 35 | 36 | 37 | @dataclass 38 | class PortConfig: 39 | type: PORT_TYPE 40 | 41 | 42 | class CxlComponent(LabeledComponent): 43 | def get_primary_mailbox(self) -> Optional[CxlMailbox]: 44 | return None 45 | 46 | def get_secondary_mailbox(self) -> Optional[CxlMailbox]: 47 | return None 48 | 49 | def get_hdm_decoder_manager(self) -> Optional[HdmDecoderManagerBase]: 50 | return None 51 | 52 | def get_bi_decoder_options(self) -> Optional[CxlBIDecoderCapabilityStructureOptions]: 53 | return None 54 | 55 | def get_bi_rt_options(self) -> Optional[CxlBIRTCapabilityStructureOptions]: 56 | return None 57 | 58 | def get_cache_route_table_options(self) -> Optional[CacheRouteTableCapabilityStructureOptions]: 59 | return None 60 | 61 | def get_cache_decoder_options(self) -> Optional[CxlCacheIdDecoderCapabilityStructureOptions]: 62 | return None 63 | 64 | def get_cdat_entries(self) -> List[CDAT_ENTRY]: 65 | return [] 66 | 67 | @abstractmethod 68 | def get_component_type(self) -> CXL_COMPONENT_TYPE: 69 | """This must be implemented in the child class""" 70 | 71 | 72 | class CXL_DEVICE_CAPABILITY_TYPE(IntEnum): 73 | INFER_PCI_CLASS_CODE = 0 74 | MEMORY_DEVICE = 1 75 | SWITCH_MAILBOX_CCI = 2 76 | 77 | 78 | class CxlDeviceComponent(CxlComponent): 79 | @abstractmethod 80 | def get_capability_type(self) -> CXL_DEVICE_CAPABILITY_TYPE: 81 | """This must be implemented in the child class""" 82 | 83 | @abstractmethod 84 | def get_event_manager(self) -> EventManager: 85 | """This must be implemented in the child class""" 86 | 87 | @abstractmethod 88 | def get_log_manager(self) -> LogManager: 89 | """This must be implemented in the child class""" 90 | -------------------------------------------------------------------------------- /opencis/cxl/component/cxl_connection.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from dataclasses import dataclass, field 9 | from opencis.pci.component.fifo_pair import FifoPair 10 | from opencis.pci.component.pci_connection import PciConnection 11 | 12 | 13 | @dataclass 14 | class CxlConnection(PciConnection): 15 | cxl_mem_fifo: FifoPair = field(default_factory=FifoPair) 16 | cxl_cache_fifo: FifoPair = field(default_factory=FifoPair) 17 | cci_fifo: FifoPair = field(default_factory=FifoPair) 18 | -------------------------------------------------------------------------------- /opencis/cxl/component/cxl_io_callback_data.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from dataclasses import dataclass 9 | 10 | from opencis.pci.component.config_space_manager import ConfigSpaceManager 11 | from opencis.pci.component.mmio_manager import MmioManager 12 | 13 | 14 | @dataclass 15 | class CxlIoCallbackData: 16 | mmio_manager: MmioManager 17 | config_space_manager: ConfigSpaceManager 18 | ld_id: int = 0 # ld_id is used meaningfully only used for MLD, default is 0 19 | -------------------------------------------------------------------------------- /opencis/cxl/component/cxl_io_manager.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | import asyncio 9 | from typing import Optional, Callable 10 | 11 | from opencis.cxl.component.cxl_io_callback_data import CxlIoCallbackData 12 | from opencis.pci.component.mmio_manager import MmioManager 13 | from opencis.pci.component.config_space_manager import ConfigSpaceManager, PCI_DEVICE_TYPE 14 | from opencis.pci.component.fifo_pair import FifoPair 15 | from opencis.util.component import RunnableComponent 16 | 17 | 18 | class CxlIoManager(RunnableComponent): 19 | def __init__( 20 | self, 21 | mmio_upstream_fifo: FifoPair, 22 | mmio_downstream_fifo: Optional[FifoPair], 23 | cfg_upstream_fifo: FifoPair, 24 | cfg_downstream_fifo: Optional[FifoPair], 25 | device_type: PCI_DEVICE_TYPE, 26 | init_callback: Callable[[CxlIoCallbackData], None], 27 | label: Optional[str] = None, 28 | ld_id: int = 0, 29 | ): 30 | super().__init__(label) 31 | self._mmio_manager = MmioManager( 32 | mmio_upstream_fifo, 33 | mmio_downstream_fifo, 34 | label=label, 35 | ) 36 | self._config_space_manager = ConfigSpaceManager( 37 | cfg_upstream_fifo, 38 | cfg_downstream_fifo, 39 | device_type=device_type, 40 | label=label, 41 | ) 42 | init_callback(CxlIoCallbackData(self._mmio_manager, self._config_space_manager, ld_id)) 43 | 44 | def get_cfg_reg_vals(self): 45 | return self._config_space_manager.get_register() 46 | 47 | async def _run(self): 48 | run_tasks = [ 49 | asyncio.create_task(self._mmio_manager.run()), 50 | asyncio.create_task(self._config_space_manager.run()), 51 | ] 52 | wait_tasks = [ 53 | asyncio.create_task(self._mmio_manager.wait_for_ready()), 54 | asyncio.create_task(self._config_space_manager.wait_for_ready()), 55 | ] 56 | await asyncio.gather(*wait_tasks) 57 | await self._change_status_to_running() 58 | await asyncio.gather(*run_tasks) 59 | 60 | async def _stop(self): 61 | tasks = [ 62 | asyncio.create_task(self._mmio_manager.stop()), 63 | asyncio.create_task(self._config_space_manager.stop()), 64 | ] 65 | await asyncio.gather(*tasks) 66 | -------------------------------------------------------------------------------- /opencis/cxl/component/fabric_manager/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opencis/opencis-core/cbcc15acffe5f0cadd770ae960ad1293479feb4a/opencis/cxl/component/fabric_manager/__init__.py -------------------------------------------------------------------------------- /opencis/cxl/component/irq_manager.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from opencis.cxl.component.short_msg_conn import ShortMsgBase, ShortMsgConn 9 | 10 | 11 | class Irq(ShortMsgBase): 12 | NULL = 0x00 13 | 14 | # Host-side file ready to be read by device using CXL.cache 15 | HOST_READY = 0x01 16 | 17 | # Device-side results ready to be read by host using CXL.mem 18 | ACCEL_VALIDATION_FINISHED = 0x02 19 | 20 | # Host finished writing file to device via CXL.mem 21 | HOST_SENT = 0x03 22 | 23 | # Accelerator finished training, waiting for host to send validation pics 24 | ACCEL_TRAINING_FINISHED = 0x04 25 | 26 | # Interrupt for Removed Device 27 | DEV_REMOVED = 0x05 28 | 29 | # Interrupt for Plugged Device 30 | DEV_ADDED = 0x06 31 | 32 | 33 | class IrqManager(ShortMsgConn): 34 | def __init__( 35 | self, 36 | device_name, 37 | addr: str = "0.0.0.0", 38 | port: int = 8500, 39 | server: bool = False, 40 | device_id: int = 0, 41 | ): 42 | super().__init__( 43 | f"{device_name}:IrqHandler", 44 | addr, 45 | port, 46 | server, 47 | device_id, 48 | msg_width=1, 49 | msg_type=Irq, 50 | ) 51 | -------------------------------------------------------------------------------- /opencis/cxl/component/mctp/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opencis/opencis-core/cbcc15acffe5f0cadd770ae960ad1293479feb4a/opencis/cxl/component/mctp/__init__.py -------------------------------------------------------------------------------- /opencis/cxl/component/mctp/mctp_connection.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from dataclasses import dataclass, field 9 | from asyncio import Queue 10 | from opencis.pci.component.pci_connection import PciConnection 11 | 12 | 13 | @dataclass 14 | class MctpConnection(PciConnection): 15 | controller_to_ep: Queue = field(default_factory=Queue) 16 | ep_to_controller: Queue = field(default_factory=Queue) 17 | -------------------------------------------------------------------------------- /opencis/cxl/component/mctp/mctp_connection_client.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | import asyncio 9 | from opencis.cxl.component.mctp.mctp_connection import MctpConnection 10 | from opencis.cxl.component.mctp.mctp_packet_processor import ( 11 | MctpPacketProcessor, 12 | MCTP_PACKET_PROCESSOR_TYPE, 13 | ) 14 | from opencis.util.component import RunnableComponent 15 | from opencis.util.logger import logger 16 | 17 | 18 | class MctpConnectionClient(RunnableComponent): 19 | def __init__( 20 | self, 21 | host: str = "0.0.0.0", 22 | port: int = 8100, 23 | auto_reconnect: bool = True, 24 | reconnect_delay: float = 0.1, 25 | ): 26 | super().__init__() 27 | self._host = host 28 | self._port = port 29 | self._auto_reconnect = auto_reconnect 30 | self._reconnect_delay = reconnect_delay 31 | self._mctp_connection = MctpConnection() 32 | self._packet_processor = None 33 | self._running = False 34 | 35 | async def _connect(self): 36 | return await asyncio.open_connection(self._host, self._port) 37 | 38 | def get_mctp_connection(self): 39 | return self._mctp_connection 40 | 41 | async def _run(self): 42 | self._running = True 43 | if self._auto_reconnect: 44 | logger.debug(self._create_message("Enabled auto-reconnect")) 45 | 46 | while self._running: 47 | try: 48 | (reader, writer) = await self._connect() 49 | self._packet_processor = MctpPacketProcessor( 50 | reader, 51 | writer, 52 | self._mctp_connection, 53 | MCTP_PACKET_PROCESSOR_TYPE.ENDPOINT, 54 | ) 55 | await self._change_status_to_running() 56 | await self._packet_processor.run() 57 | self._packet_processor = None 58 | except Exception as e: 59 | if not self._auto_reconnect: 60 | logger.warning(self._create_message(str(e))) 61 | 62 | if self._packet_processor is not None: 63 | break # Normal termination 64 | 65 | if not self._auto_reconnect: 66 | logger.error(self._create_message("Connection attempt failed")) 67 | break 68 | 69 | logger.warning(self._create_message("Attempting to reconnect")) 70 | await asyncio.sleep(self._reconnect_delay) 71 | 72 | async def _stop(self): 73 | self._running = False 74 | if self._packet_processor: 75 | await self._packet_processor.stop() 76 | -------------------------------------------------------------------------------- /opencis/cxl/component/mctp/mctp_packet_processor.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from asyncio import StreamReader, StreamWriter, create_task, gather 9 | from enum import Enum, auto 10 | from typing import Optional 11 | 12 | from opencis.util.logger import logger 13 | from opencis.cxl.component.mctp.mctp_connection import MctpConnection 14 | from opencis.cxl.component.mctp.mctp_packet_reader import ( 15 | MctpPacketReader, 16 | ) 17 | from opencis.util.component import RunnableComponent 18 | 19 | 20 | class MCTP_PACKET_PROCESSOR_TYPE(Enum): 21 | CONTROLLER = auto() 22 | ENDPOINT = auto() 23 | 24 | 25 | class MctpPacketProcessor(RunnableComponent): 26 | def __init__( 27 | self, 28 | reader: StreamReader, 29 | writer: StreamWriter, 30 | mctp_connection: MctpConnection, 31 | processor_type: MCTP_PACKET_PROCESSOR_TYPE, 32 | label: Optional[str] = None, 33 | ): 34 | super().__init__(label) 35 | self._reader = MctpPacketReader(reader, label=label) 36 | self._writer = writer 37 | self._mctp_connection = mctp_connection 38 | if processor_type == MCTP_PACKET_PROCESSOR_TYPE.CONTROLLER: 39 | self._incoming = self._mctp_connection.ep_to_controller 40 | self._outgoing = self._mctp_connection.controller_to_ep 41 | else: 42 | self._incoming = self._mctp_connection.controller_to_ep 43 | self._outgoing = self._mctp_connection.ep_to_controller 44 | 45 | async def _process_incoming_packets(self): 46 | logger.debug(self._create_message("Starting incoming packet processor")) 47 | while True: 48 | try: 49 | packet = await self._reader.get_packet() 50 | await self._incoming.put(packet) 51 | except Exception as e: 52 | logger.debug(self._create_message(str(e))) 53 | await self._stop_outgoing_processor() 54 | break 55 | logger.debug(self._create_message("Stopped incoming packet processor")) 56 | 57 | async def _stop_outgoing_processor(self): 58 | await self._outgoing.put(None) 59 | 60 | async def _process_outgoing_packets(self): 61 | logger.debug(self._create_message("Starting outgoing packet processor")) 62 | while True: 63 | packet = await self._outgoing.get() 64 | if packet is None: 65 | break 66 | self._writer.write(bytes(packet)) 67 | await self._writer.drain() 68 | logger.debug(self._create_message("Stopped outgoing packet processor")) 69 | 70 | async def _run(self): 71 | tasks = [ 72 | create_task(self._process_incoming_packets()), 73 | create_task(self._process_outgoing_packets()), 74 | ] 75 | await self._change_status_to_running() 76 | await gather(*tasks) 77 | 78 | async def _stop(self): 79 | self._reader.abort() 80 | -------------------------------------------------------------------------------- /opencis/cxl/component/mctp/mctp_packet_reader.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from asyncio import StreamReader, create_task 9 | from typing import Optional 10 | 11 | from opencis.cxl.transport.transaction import ( 12 | CciMessageHeaderPacket, 13 | CciMessagePacket, 14 | CciHeaderPacket, 15 | CciPayloadPacket, 16 | ) 17 | from opencis.util.logger import logger 18 | from opencis.util.component import LabeledComponent 19 | from opencis.cxl.transport.common import BasePacket 20 | 21 | # pylint: disable=duplicate-code 22 | 23 | 24 | class MctpPacketReader(LabeledComponent): 25 | def __init__(self, reader: StreamReader, label: Optional[str] = None): 26 | super().__init__(label) 27 | self._reader = reader 28 | self._aborted = False 29 | self._task = None 30 | 31 | async def get_packet(self) -> CciMessagePacket: 32 | if self._aborted: 33 | raise Exception("PacketReader is already aborted") 34 | try: 35 | self._task = create_task(self._get_packet_in_task()) 36 | packet = await self._task 37 | except Exception as e: 38 | logger.debug(self._create_message("Aborted")) 39 | raise Exception("PacketReader is aborted") from e 40 | finally: 41 | self._task = None 42 | return packet 43 | 44 | def abort(self): 45 | if self._aborted: 46 | return 47 | logger.debug(self._create_message("Aborting")) 48 | self._aborted = True 49 | if self._task is not None: 50 | self._task.cancel() 51 | 52 | async def _get_packet_in_task(self): 53 | logger.debug(self._create_message("Waiting Packet")) 54 | header_load = await self._read_payload(BasePacket.get_size()) 55 | base_packet = BasePacket() 56 | base_packet.reset(header_load) 57 | remaining_length = base_packet.system_header.payload_length - len(base_packet) 58 | if remaining_length < 0: 59 | raise Exception("remaining length is less than 0") 60 | payload = bytes(base_packet) + await self._read_payload(remaining_length) 61 | logger.debug(self._create_message("Received Packet")) 62 | 63 | # Wrap the payload with CciPayloadPacket 64 | packet = CciPayloadPacket() 65 | packet.reset(payload) 66 | return packet 67 | 68 | async def _get_cci_message_header(self) -> CciMessageHeaderPacket: 69 | logger.debug(self._create_message("Waiting for CCI Message Header")) 70 | payload = await self._read_payload(CciHeaderPacket.get_size()) 71 | message_header = CciHeaderPacket() 72 | message_header.reset(payload) 73 | logger.debug(self._create_message("Received CCI Message Header")) 74 | return message_header 75 | 76 | async def _read_payload(self, size: int) -> bytes: 77 | payload = await self._reader.read(size) 78 | if not payload: 79 | raise Exception("Connection disconnected") 80 | return payload 81 | -------------------------------------------------------------------------------- /opencis/cxl/component/root_complex/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opencis/opencis-core/cbcc15acffe5f0cadd770ae960ad1293479feb4a/opencis/cxl/component/root_complex/__init__.py -------------------------------------------------------------------------------- /opencis/cxl/component/root_complex/memory_controller.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from dataclasses import dataclass 9 | from asyncio import create_task, gather 10 | from opencis.util.component import RunnableComponent 11 | from opencis.cxl.transport.memory_fifo import ( 12 | MemoryFifoPair, 13 | MEMORY_REQUEST_TYPE, 14 | MemoryResponse, 15 | MEMORY_RESPONSE_STATUS, 16 | ) 17 | from opencis.util.logger import logger 18 | from opencis.util.accessor import FileAccessor 19 | 20 | 21 | @dataclass 22 | class MemoryControllerConfig: 23 | memory_size: int 24 | memory_filename: str 25 | host_name: str 26 | memory_consumer_fifos: MemoryFifoPair 27 | 28 | 29 | class MemoryController(RunnableComponent): 30 | def __init__(self, config: MemoryControllerConfig): 31 | super().__init__(lambda class_name: f"{config.host_name}:{class_name}") 32 | self._memory_size = config.memory_size 33 | self._memory_consumer_fifos = config.memory_consumer_fifos 34 | self._file_accessor = FileAccessor(config.memory_filename, config.memory_size) 35 | 36 | def get_mem_size(self) -> int: 37 | return self._memory_size 38 | 39 | async def _process_memory_requests(self): 40 | while True: 41 | packet = await self._memory_consumer_fifos.request.get() 42 | if packet is None: 43 | logger.debug(self._create_message("Stopped processing memory access requests")) 44 | break 45 | 46 | addr = packet.addr 47 | if packet.type == MEMORY_REQUEST_TYPE.WRITE: 48 | await self._file_accessor.write(addr, packet.data, packet.size) 49 | response = MemoryResponse(MEMORY_RESPONSE_STATUS.OK) 50 | elif packet.type == MEMORY_REQUEST_TYPE.READ: 51 | data = await self._file_accessor.read(addr, packet.size) 52 | response = MemoryResponse(MEMORY_RESPONSE_STATUS.OK, data) 53 | await self._memory_consumer_fifos.response.put(response) 54 | 55 | async def _run(self): 56 | tasks = [create_task(self._process_memory_requests())] 57 | await self._change_status_to_running() 58 | await gather(*tasks) 59 | 60 | async def _stop(self): 61 | await self._memory_consumer_fifos.request.put(None) 62 | await self._memory_consumer_fifos.response.put(None) 63 | -------------------------------------------------------------------------------- /opencis/cxl/component/root_complex/root_port_client_manager.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | import asyncio 9 | from dataclasses import dataclass, field 10 | from typing import List 11 | from opencis.util.component import RunnableComponent 12 | from opencis.cxl.component.cxl_connection import CxlConnection 13 | from opencis.cxl.component.switch_connection_client import SwitchConnectionClient 14 | from opencis.cxl.component.common import CXL_COMPONENT_TYPE 15 | 16 | 17 | @dataclass 18 | class RootPortClientConfig: 19 | port_index: int 20 | switch_host: str 21 | switch_port: int 22 | 23 | 24 | @dataclass 25 | class RootPortClientManagerConfig: 26 | host_name: str 27 | client_configs: List[RootPortClientConfig] = field(default_factory=list) 28 | 29 | 30 | @dataclass 31 | class RootPortConnection: 32 | connection: CxlConnection 33 | port_index: int 34 | 35 | 36 | class RootPortClientManager(RunnableComponent): 37 | def __init__(self, config: RootPortClientManagerConfig): 38 | super().__init__(lambda class_name: f"{config.host_name}:{class_name}:") 39 | 40 | self._sw_conn_clients: List[SwitchConnectionClient] = [] 41 | for client_config in config.client_configs: 42 | connection_client = SwitchConnectionClient( 43 | client_config.port_index, 44 | CXL_COMPONENT_TYPE.R, 45 | host=client_config.switch_host, 46 | port=client_config.switch_port, 47 | parent_name=self.get_message_label(), 48 | ) 49 | self._sw_conn_clients.append(connection_client) 50 | 51 | def get_cxl_connections(self) -> List[RootPortConnection]: 52 | connections = [] 53 | for client in self._sw_conn_clients: 54 | connections.append( 55 | RootPortConnection( 56 | connection=client.get_cxl_connection(), port_index=client.get_port_index() 57 | ) 58 | ) 59 | return connections 60 | 61 | async def _run(self): 62 | run_tasks = [asyncio.create_task(client.run()) for client in self._sw_conn_clients] 63 | wait_tasks = [ 64 | asyncio.create_task(client.wait_for_ready()) for client in self._sw_conn_clients 65 | ] 66 | await asyncio.gather(*wait_tasks) 67 | await self._change_status_to_running() 68 | await asyncio.gather(*run_tasks) 69 | 70 | async def _stop(self): 71 | stop_tasks = [asyncio.create_task(client.stop()) for client in self._sw_conn_clients] 72 | await asyncio.gather(*stop_tasks) 73 | -------------------------------------------------------------------------------- /opencis/cxl/component/virtual_switch/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | -------------------------------------------------------------------------------- /opencis/cxl/component/virtual_switch/downstream_vppb.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from opencis.cxl.component.common import CXL_COMPONENT_TYPE 9 | from opencis.cxl.component.virtual_switch.vppb import Vppb, VppbRoutingInfo 10 | from opencis.cxl.component.cxl_bridge_component import ( 11 | CxlDownstreamPortComponent, 12 | ) 13 | 14 | 15 | # DownstreamVppb class will have many similar methods to DownstreamPortDevice class 16 | # pylint: disable=duplicate-code 17 | class DownstreamVppb(Vppb): 18 | def __init__(self, vppb_index: int, vcs_id: int): 19 | super().__init__() 20 | self._vppb_index = vppb_index 21 | self._vcs_id = vcs_id 22 | self._ld_id = 0 23 | 24 | def _get_label(self) -> str: 25 | vcs_str = f"VCS{self._vcs_id}" 26 | vppb_str = f"vPPB{self._vppb_index}(DSP)" 27 | return f"{vcs_str}:{vppb_str}" 28 | 29 | def _create_message(self, message: str) -> str: 30 | message = f"[{self.__class__.__name__}:{self._get_label()}] {message}" 31 | return message 32 | 33 | def get_reg_vals(self, ld_id: int): 34 | return self._cxl_io_manager[ld_id].get_cfg_reg_vals() 35 | 36 | def set_vppb_index(self, vppb_index: int): 37 | self._vppb_index = vppb_index 38 | self._pci_bridge_component.set_port_number(self._vppb_index) 39 | 40 | def get_device_type(self) -> CXL_COMPONENT_TYPE: 41 | return CXL_COMPONENT_TYPE.DSP 42 | 43 | def set_routing_table(self, vppb_routing_info: VppbRoutingInfo): 44 | self._pci_bridge_component.set_routing_table(vppb_routing_info) 45 | 46 | def get_secondary_bus_number(self): 47 | return self._pci_registers.pci.secondary_bus_number 48 | 49 | def get_cxl_component(self) -> CxlDownstreamPortComponent: 50 | return self._cxl_component 51 | 52 | def set_ld_id(self, ld_id: int): 53 | self._ld_id = ld_id 54 | 55 | def get_ld_id(self): 56 | return self._ld_id 57 | -------------------------------------------------------------------------------- /opencis/cxl/component/virtual_switch/routing_table.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from typing import Optional 9 | 10 | from opencis.pci.component.routing_table import PciRoutingTable 11 | from opencis.cxl.component.hdm_decoder import SwitchHdmDecoderManager 12 | from opencis.cxl.component.cache_route_table_manager import SwitchCacheRouteTable 13 | 14 | 15 | class RoutingTable(PciRoutingTable): 16 | def __init__(self, table_size: int, label: Optional[str] = None): 17 | super().__init__(table_size, label=label) 18 | self._hdm_decoder_manager: Optional[SwitchHdmDecoderManager] = None 19 | self._cache_route_table: Optional[SwitchCacheRouteTable] = None 20 | 21 | def set_hdm_decoder(self, hdm_decoder_manager: SwitchHdmDecoderManager): 22 | self._hdm_decoder_manager = hdm_decoder_manager 23 | 24 | def set_cache_route_table(self, cache_route_table: Optional[SwitchCacheRouteTable]): 25 | self._cache_route_table = cache_route_table 26 | 27 | def get_cxl_mem_target_port(self, memory_addr: int) -> Optional[int]: 28 | if self._hdm_decoder_manager is None: 29 | raise Exception("HDM Decoder Manager is not initialized") 30 | return self._hdm_decoder_manager.get_target(memory_addr) 31 | 32 | def get_cxl_cache_target_port(self, cache_id: int) -> Optional[int]: 33 | if self._cache_route_table is None: 34 | raise Exception("Port has no associated cache route table") 35 | return self._cache_route_table.get_target(cache_id) 36 | 37 | def get_cxl_cache_cache_id(self, target: int) -> Optional[int]: 38 | if self._cache_route_table is None: 39 | raise Exception("Port has no associated cache route table") 40 | return self._cache_route_table.get_cache_id(target) 41 | -------------------------------------------------------------------------------- /opencis/cxl/component/virtual_switch/upstream_vppb.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | import re 9 | 10 | from opencis.util.logger import logger 11 | from opencis.cxl.component.common import CXL_COMPONENT_TYPE 12 | from opencis.cxl.component.virtual_switch.vppb import Vppb, VppbRoutingInfo 13 | from opencis.cxl.component.cxl_connection import CxlConnection 14 | from opencis.cxl.component.cxl_bridge_component import ( 15 | CxlUpstreamPortComponent, 16 | HDM_DECODER_COUNT, 17 | ) 18 | 19 | 20 | # UpstreamVppb class will have many similar methods to UpstreamPortDevice class 21 | # pylint: disable=duplicate-code 22 | class UpstreamVppb(Vppb): 23 | def __init__(self, upstream_port_index: int): 24 | super().__init__() 25 | self._decoder_count = HDM_DECODER_COUNT.DECODER_32 26 | 27 | self._port_index = upstream_port_index 28 | label = f"USP{self._port_index}" 29 | self._label = label 30 | 31 | def get_reg_vals(self, ld_id: int): 32 | return self._cxl_io_manager[ld_id].get_cfg_reg_vals() 33 | 34 | def get_port_index(self): 35 | return self._port_index 36 | 37 | def get_downstream_connection(self) -> CxlConnection: 38 | return self._downstream_connection 39 | 40 | def set_routing_table(self, vppb_routing_info: VppbRoutingInfo): 41 | logger.debug(f"[UpstreamPort{self.get_port_index()}] Setting routing table") 42 | self._pci_bridge_component.set_routing_table(vppb_routing_info) 43 | self._cxl_component.set_routing_table(vppb_routing_info) 44 | 45 | def get_device_type(self) -> CXL_COMPONENT_TYPE: 46 | return CXL_COMPONENT_TYPE.USP 47 | 48 | def get_hdm_decoder_count(self) -> int: 49 | name = HDM_DECODER_COUNT(self._decoder_count).name 50 | return int(re.search(r"\d+", name).group()) 51 | 52 | def get_cxl_component(self) -> CxlUpstreamPortComponent: 53 | return self._cxl_component 54 | -------------------------------------------------------------------------------- /opencis/cxl/component/virtual_switch/vppb_routing_info.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from dataclasses import dataclass 9 | 10 | from opencis.cxl.component.virtual_switch.routing_table import RoutingTable 11 | 12 | 13 | @dataclass 14 | class VppbRoutingInfo: 15 | routing_table: RoutingTable 16 | ld_id: int = 0 # ld_id is used meaningfully only used for MLD, default is 0 17 | -------------------------------------------------------------------------------- /opencis/cxl/config_space/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | -------------------------------------------------------------------------------- /opencis/cxl/config_space/cfg.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from typing import Optional 9 | 10 | from opencis.cxl.config_space.dvsec import ( 11 | DvsecConfigSpace, 12 | DvsecConfigSpaceOptions, 13 | CXL_DEVICE_TYPE, 14 | ) 15 | from opencis.cxl.config_space.doe.doe import ( 16 | CxlDoeExtendedCapability, 17 | CxlDoeExtendedCapabilityOptions, 18 | ) 19 | from opencis.cxl.config_space.serial_number.common import ( 20 | DeviceSNCapability, 21 | DeviceSNCapabilityOptions, 22 | ) 23 | from opencis.pci.config_space import PciExpressConfigSpace 24 | from opencis.util.unaligned_bit_structure import ( 25 | StructureField, 26 | ShareableByteArray, 27 | ) 28 | 29 | 30 | class CxlConfigSpace(PciExpressConfigSpace): 31 | def __init__( 32 | self, 33 | device_type: CXL_DEVICE_TYPE, 34 | data: Optional[ShareableByteArray] = None, 35 | parent_name: Optional[str] = None, 36 | ): 37 | self._dvsec_options: DvsecConfigSpaceOptions 38 | self._doe_options: CxlDoeExtendedCapabilityOptions 39 | self._sn_options: DeviceSNCapabilityOptions 40 | self._is_bridge = device_type in (CXL_DEVICE_TYPE.USP, CXL_DEVICE_TYPE.DSP) 41 | self._fields = [] 42 | start = self._append_pci_fields(self._pci_component.get_identity()) 43 | start = self._append_cxl_fields(start, device_type) 44 | self._append_reserved_pcix(start) 45 | 46 | # calls constructor for BitMaskedBitStructure 47 | super().__init__(data, parent_name) 48 | 49 | def _append_cxl_fields(self, start: int, device_type: CXL_DEVICE_TYPE) -> int: 50 | if device_type == CXL_DEVICE_TYPE.LD: 51 | if self._sn_options is not None: 52 | start = self._append_sn(start) 53 | else: 54 | # TODO: remove placeholder SN for non-LD devices 55 | self._sn_options = DeviceSNCapabilityOptions(sn="1111111111111111") 56 | start = self._append_sn(start) 57 | if self._dvsec_options is not None: 58 | start = self._append_dvsec(start, device_type) 59 | if self._doe_options is not None: 60 | start = self._append_doe(start) 61 | return start 62 | 63 | def _append_dvsec(self, start: int, device_type: CXL_DEVICE_TYPE) -> int: 64 | options = self._dvsec_options 65 | dvsec_size = DvsecConfigSpace.get_size_from_options(options) 66 | end = start + dvsec_size - 1 67 | options["next"] = end + 1 68 | options["offset"] = start 69 | options["device_type"] = device_type 70 | self._fields.append( 71 | StructureField( 72 | "dvsec", 73 | start, 74 | end, 75 | DvsecConfigSpace, 76 | options=options, 77 | ) 78 | ) 79 | return end + 1 80 | 81 | def _append_doe(self, start: int) -> int: 82 | options = self._doe_options 83 | doe_size = CxlDoeExtendedCapability.get_size() 84 | end = start + doe_size - 1 85 | self._fields.append( 86 | StructureField("doe", start, end, CxlDoeExtendedCapability, options=options) 87 | ) 88 | return end + 1 89 | 90 | def _append_sn(self, start: int) -> int: 91 | options = self._sn_options 92 | sn_size = DeviceSNCapability.get_size() 93 | end = start + sn_size - 1 94 | options["next"] = end + 1 95 | self._fields.append( 96 | StructureField("serial_number", start, end, DeviceSNCapability, options=options) 97 | ) 98 | return end + 1 99 | -------------------------------------------------------------------------------- /opencis/cxl/config_space/device.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from typing import Optional, TypedDict 9 | 10 | from opencis.cxl.config_space.serial_number.common import DeviceSNCapabilityOptions 11 | from opencis.util.unaligned_bit_structure import ShareableByteArray 12 | from opencis.pci.component.pci import PciComponent 13 | from opencis.cxl.config_space.cfg import CxlConfigSpace 14 | from opencis.cxl.config_space.doe.doe import CxlDoeExtendedCapabilityOptions 15 | from opencis.pci.config_space import PciExpressDeviceConfigSpaceOptions 16 | from opencis.cxl.config_space.dvsec import DvsecConfigSpaceOptions, CXL_DEVICE_TYPE 17 | 18 | 19 | class CxlDeviceConfigSpace(CxlConfigSpace): 20 | def __init__( 21 | self, 22 | options: PciExpressDeviceConfigSpaceOptions, 23 | data: Optional[ShareableByteArray] = None, 24 | parent_name: Optional[str] = None, 25 | ): 26 | self._pci_component = options["pci_component"] 27 | self._doe_options = options["doe"] 28 | self._dvsec_options = options["dvsec"] 29 | self._sn_options = None 30 | if "serial_number" in options: 31 | self._sn_options = options["serial_number"] 32 | super().__init__(CXL_DEVICE_TYPE.LD, data, parent_name) 33 | 34 | 35 | class CxlType3SldConfigSpaceOptions(TypedDict): 36 | pci_component: PciComponent 37 | dvsec: DvsecConfigSpaceOptions 38 | doe: CxlDoeExtendedCapabilityOptions 39 | serial_number: DeviceSNCapabilityOptions 40 | 41 | 42 | class CxlType3SldConfigSpace(CxlDeviceConfigSpace): 43 | def __init__( 44 | self, 45 | options: CxlType3SldConfigSpaceOptions, 46 | data: Optional[ShareableByteArray] = None, 47 | parent_name: Optional[str] = None, 48 | ): 49 | super().__init__(options, data, parent_name) 50 | -------------------------------------------------------------------------------- /opencis/cxl/config_space/doe/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | -------------------------------------------------------------------------------- /opencis/cxl/config_space/doe/cdat.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from enum import IntEnum 9 | from typing import Union 10 | 11 | from opencis.util.unaligned_bit_structure import ( 12 | UnalignedBitStructure, 13 | ByteField, 14 | ) 15 | 16 | 17 | class CdatHeader(UnalignedBitStructure): 18 | length: int 19 | revision: int 20 | checksum: int 21 | sequence: int 22 | 23 | _fields = [ 24 | ByteField("length", 0, 3), 25 | ByteField("revision", 4, 4), 26 | ByteField("checksum", 5, 5), 27 | ByteField("reserved1", 6, 11), 28 | ByteField("sequence", 12, 15), 29 | ] 30 | 31 | 32 | class DSMAS_FLAG(IntEnum): 33 | NON_VOLATILE = 0b10 34 | 35 | 36 | class DeviceScopedMemoryAffinity(UnalignedBitStructure): 37 | type: int 38 | length: int 39 | dsmad_handle: int 40 | flags: int 41 | dpa_base: int 42 | dpa_length: int 43 | 44 | _fields = [ 45 | ByteField("type", 0, 0, default=0), 46 | ByteField("reserved1", 1, 1), 47 | ByteField("length", 2, 3, default=24), 48 | ByteField("dsmad_handle", 4, 4), 49 | ByteField("flags", 5, 5), 50 | ByteField("reserved", 6, 7), 51 | ByteField("dpa_base", 8, 15), 52 | ByteField("dpa_length", 16, 23), 53 | ] 54 | 55 | 56 | class HMAT_SLLB_DATA_TYPE(IntEnum): 57 | ACCESS_LATENCY = 0 58 | READ_LATENCY = 1 59 | WRITE_LATENCY = 2 60 | ACCESS_BANDWIDTH = 3 61 | READ_BANDWIDTH = 4 62 | WRITE_BANDWIDTH = 5 63 | 64 | 65 | class HMAT_SLLB_FLAG(IntEnum): 66 | MEMORY = 0x00 67 | FIRST_LEVEL_MEMORY_SIDE_CACHE = 0x01 68 | SECOND_LEVEL_MEMORY_SIDE_CACHE = 0x02 69 | THIRD_LEVEL_MEMORY_SIDE_CACHE = 0x03 70 | MINIMUM_SIZE_TRANSFER_TO_ACHIEVE_VALUES = 0x10 71 | NON_SEQUENTIAL_TRANSFER = 0x20 72 | 73 | 74 | class DeviceScropedLatencyBandwidthInformation(UnalignedBitStructure): 75 | type: int 76 | length: int 77 | handle: int 78 | flags: int 79 | data_type: int 80 | entry_base_unit: int 81 | entry0: int 82 | entry1: int 83 | entry2: int 84 | 85 | _fields = [ 86 | ByteField("type", 0, 0, default=1), 87 | ByteField("reserved1", 1, 1), 88 | ByteField("length", 2, 3, default=24), 89 | ByteField("handle", 4, 4), 90 | ByteField("flags", 5, 5), 91 | ByteField("date_type", 6, 6), 92 | ByteField("reserved2", 7, 7), 93 | ByteField("entry_base_unit", 8, 15), 94 | ByteField("entry0", 16, 17), 95 | ByteField("entry1", 18, 19), 96 | ByteField("entry2", 20, 21), 97 | ByteField("reserved3", 22, 23), 98 | ] 99 | 100 | 101 | class DeviceScopedMemorySideCacheInformation(UnalignedBitStructure): 102 | pass 103 | 104 | 105 | class DeviceScopedEfiMemoryType(UnalignedBitStructure): 106 | type: int 107 | length: int 108 | dsmas_handle: int 109 | efi_memory_type_and_attribute: int 110 | dpa_offset: int 111 | dpa_length: int 112 | 113 | _fields = [ 114 | ByteField("type", 0, 0, default=0), 115 | ByteField("reserved1", 1, 1), 116 | ByteField("length", 2, 3, default=24), 117 | ByteField("dsmas_handle", 4, 4), 118 | ByteField("efi_memory_type_and_attribute", 5, 5), 119 | ByteField("reserved", 6, 7), 120 | ByteField("dpa_offset", 8, 15), 121 | ByteField("dpa_length", 16, 23), 122 | ] 123 | 124 | 125 | CDAT_ENTRY = Union[ 126 | DeviceScopedMemoryAffinity, 127 | DeviceScropedLatencyBandwidthInformation, 128 | DeviceScopedMemorySideCacheInformation, 129 | DeviceScopedEfiMemoryType, 130 | ] 131 | -------------------------------------------------------------------------------- /opencis/cxl/config_space/doe/doe.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from typing import Optional, TypedDict, List 9 | 10 | from opencis.pci.config_space.pcie.doe import ( 11 | DoeExtendedCapability, 12 | DoeExtendedCapabilityOptions, 13 | DoeExtendedCapabilityHeaderOptions, 14 | ) 15 | from opencis.util.unaligned_bit_structure import ( 16 | ShareableByteArray, 17 | ) 18 | from opencis.cxl.config_space.doe.cdat import CDAT_ENTRY 19 | from opencis.cxl.config_space.doe.doe_table_access import ( 20 | DoeTableAccessProtocol, 21 | ) 22 | 23 | 24 | class CxlDoeExtendedCapabilityOptions(TypedDict): 25 | next: Optional[int] 26 | cdat_entries: List[CDAT_ENTRY] 27 | 28 | 29 | class CxlDoeExtendedCapability(DoeExtendedCapability): 30 | def __init__( 31 | self, 32 | data: Optional[ShareableByteArray] = None, 33 | parent_name: Optional[str] = None, 34 | options: Optional[CxlDoeExtendedCapabilityOptions] = None, 35 | ): 36 | header_options: DoeExtendedCapabilityHeaderOptions = {} 37 | cdat_entries = [] 38 | if options: 39 | header_options["next_capability_offset"] = options.get("next", 0) 40 | cdat_entries = options.get("cdat_entries", cdat_entries) 41 | 42 | doe_options: DoeExtendedCapabilityOptions = { 43 | "header": header_options, 44 | "protocols": [DoeTableAccessProtocol(cdat_entries)], 45 | } 46 | super().__init__(data, parent_name, doe_options) 47 | -------------------------------------------------------------------------------- /opencis/cxl/config_space/dvsec/common.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from typing import Optional, TypedDict, cast 9 | 10 | from opencis.util.unaligned_bit_structure import ( 11 | BitMaskedBitStructure, 12 | ShareableByteArray, 13 | BitField, 14 | FIELD_ATTR, 15 | ) 16 | 17 | 18 | class DvsecCapabilityHeaderOptions(TypedDict): 19 | next_capability_offset: str 20 | 21 | 22 | class DvsecCapabilityHeader(BitMaskedBitStructure): 23 | def __init__( 24 | self, 25 | data: Optional[ShareableByteArray] = None, 26 | parent_name: Optional[str] = None, 27 | options: Optional[DvsecCapabilityHeaderOptions] = None, 28 | ): 29 | next_capability_offset = 0 30 | if options: 31 | casted_options = cast(DvsecCapabilityHeaderOptions, options) 32 | next_capability_offset = casted_options["next_capability_offset"] 33 | 34 | self._fields = [ 35 | BitField("capability_id", 0, 15, FIELD_ATTR.RO, 0x0023), 36 | BitField("capability_version", 16, 19, FIELD_ATTR.RO, 0x1), 37 | BitField("next_capability_offset", 20, 31, FIELD_ATTR.RO, next_capability_offset), 38 | ] 39 | 40 | super().__init__(data, parent_name) 41 | -------------------------------------------------------------------------------- /opencis/cxl/config_space/dvsec/mld_dvsec.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from typing import TypedDict, List, Optional 9 | 10 | from opencis.util.unaligned_bit_structure import ( 11 | ShareableByteArray, 12 | BitMaskedBitStructure, 13 | BitField, 14 | ByteField, 15 | StructureField, 16 | DataField, 17 | FIELD_ATTR, 18 | ) 19 | from opencis.cxl.config_space.dvsec.common import ( 20 | DvsecCapabilityHeader, 21 | DvsecCapabilityHeaderOptions, 22 | ) 23 | 24 | 25 | class CxlDvsecHeader1(BitMaskedBitStructure): 26 | _fields = [ 27 | BitField("dvsec_vendor_id", 0, 15, FIELD_ATTR.RO, 0x1E98), 28 | BitField("dvsec_revision_id", 16, 19, FIELD_ATTR.RO, 0x0), 29 | BitField("dvsec_length", 20, 31, FIELD_ATTR.RO, 0x010), 30 | ] 31 | 32 | 33 | class MldDvsecOptions(TypedDict): 34 | header: DvsecCapabilityHeaderOptions 35 | 36 | 37 | # cxl_extension_dvsec_for_ports.py looks similar 38 | # pylint: disable=duplicate-code 39 | class MldDvsec(BitMaskedBitStructure): 40 | def __init__( 41 | self, 42 | data: Optional[ShareableByteArray] = None, 43 | parent_name: Optional[str] = None, 44 | options: Optional[MldDvsecOptions] = None, 45 | ): 46 | if not options: 47 | raise Exception("options is required") 48 | 49 | header_options = options["header"] 50 | self._fields = [ 51 | StructureField( 52 | "capability_header", 53 | 0x00, 54 | 0x03, 55 | DvsecCapabilityHeader, 56 | options=header_options, 57 | ), 58 | StructureField("dvsec_header1", 0x04, 0x07, CxlDvsecHeader1), 59 | ByteField("dvsec_header2", 0x08, 0x09, attribute=FIELD_ATTR.RO, default=0x0009), 60 | ByteField("number_of_lds_supported", 0x0A, 0x0B, attribute=FIELD_ATTR.HW_INIT), 61 | ByteField("ld_id_hot_reset_vector", 0x0C, 0x0D, attribute=FIELD_ATTR.RW), 62 | ByteField("reserved1", 0x0E, 0x0F, attribute=FIELD_ATTR.RESERVED), 63 | ] 64 | 65 | super().__init__(data, parent_name) 66 | 67 | @staticmethod 68 | def get_size(fields: List[DataField] | None = None) -> int: 69 | return 0x10 70 | -------------------------------------------------------------------------------- /opencis/cxl/config_space/port.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from typing import Optional, TypedDict 9 | 10 | from opencis.cxl.config_space.cfg import CxlConfigSpace 11 | from opencis.cxl.config_space.dvsec import DvsecConfigSpaceOptions, CXL_DEVICE_TYPE 12 | from opencis.cxl.config_space.doe.doe import CxlDoeExtendedCapabilityOptions 13 | from opencis.pci.component.pci import PciBridgeComponent 14 | from opencis.pci.config_space import PciExpressPortConfigSpaceOptions 15 | from opencis.util.unaligned_bit_structure import ShareableByteArray 16 | 17 | 18 | class CxlUpstreamPortConfigSpaceOptions(TypedDict): 19 | pci_bridge_component: PciBridgeComponent 20 | dvsec: DvsecConfigSpaceOptions 21 | doe: CxlDoeExtendedCapabilityOptions 22 | 23 | 24 | class CxlDownstreamPortConfigSpaceOptions(TypedDict): 25 | pci_bridge_component: PciBridgeComponent 26 | dvsec: DvsecConfigSpaceOptions 27 | 28 | 29 | class CxlPortConfigSpace(CxlConfigSpace): 30 | def __init__( 31 | self, 32 | device_type: CXL_DEVICE_TYPE, 33 | options: PciExpressPortConfigSpaceOptions, 34 | data: Optional[ShareableByteArray] = None, 35 | parent_name: Optional[str] = None, 36 | ): 37 | self._pci_component = options["pci_bridge_component"] 38 | self._doe_options = None 39 | if device_type is CXL_DEVICE_TYPE.USP: 40 | self._doe_options = options["doe"] 41 | self._dvsec_options = options["dvsec"] 42 | super().__init__(device_type, data, parent_name) 43 | 44 | 45 | class CxlUpstreamPortConfigSpace(CxlPortConfigSpace): 46 | pci: PciBridgeComponent 47 | 48 | def __init__( 49 | self, 50 | options: CxlUpstreamPortConfigSpaceOptions, 51 | data: Optional[ShareableByteArray] = None, 52 | parent_name: Optional[str] = None, 53 | ): 54 | super().__init__(CXL_DEVICE_TYPE.USP, options, data, parent_name) 55 | 56 | 57 | class CxlDownstreamPortConfigSpace(CxlPortConfigSpace): 58 | pci: PciBridgeComponent 59 | 60 | def __init__( 61 | self, 62 | options: CxlDownstreamPortConfigSpaceOptions, 63 | data: Optional[ShareableByteArray] = None, 64 | parent_name: Optional[str] = None, 65 | ): 66 | super().__init__(CXL_DEVICE_TYPE.DSP, options, data, parent_name) 67 | -------------------------------------------------------------------------------- /opencis/cxl/config_space/serial_number/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opencis/opencis-core/cbcc15acffe5f0cadd770ae960ad1293479feb4a/opencis/cxl/config_space/serial_number/__init__.py -------------------------------------------------------------------------------- /opencis/cxl/config_space/serial_number/common.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from typing import Optional, TypedDict, cast 9 | 10 | from opencis.util.unaligned_bit_structure import ( 11 | BitMaskedBitStructure, 12 | ShareableByteArray, 13 | BitField, 14 | FIELD_ATTR, 15 | StructureField, 16 | ) 17 | 18 | 19 | class DeviceSNCapabilityHeaderOptions(TypedDict): 20 | next_capability_offset: int 21 | 22 | 23 | class DeviceSNCapabilityHeader(BitMaskedBitStructure): 24 | def __init__( 25 | self, 26 | data: Optional[ShareableByteArray] = None, 27 | parent_name: Optional[str] = None, 28 | options: Optional[DeviceSNCapabilityHeaderOptions] = None, 29 | ): 30 | next_capability_offset = 0 31 | if options: 32 | casted_options = cast(DeviceSNCapabilityHeaderOptions, options) 33 | next_capability_offset = casted_options["next_capability_offset"] 34 | 35 | self._fields = [ 36 | BitField("capability_id", 0, 15, FIELD_ATTR.RO, 0x0003), 37 | BitField("capability_version", 16, 19, FIELD_ATTR.RO, 0x1), 38 | BitField("next_capability_offset", 20, 31, FIELD_ATTR.RO, next_capability_offset), 39 | ] 40 | 41 | super().__init__(data, parent_name) 42 | 43 | 44 | class DeviceSNHeaderOptions(TypedDict): 45 | serial_number: str 46 | 47 | 48 | class DeviceSNHeader(BitMaskedBitStructure): 49 | def __init__( 50 | self, 51 | data: Optional[ShareableByteArray] = None, 52 | parent_name: Optional[str] = None, 53 | options: DeviceSNHeaderOptions = None, 54 | ): 55 | sn_int = 0 56 | if options: 57 | sn = options["serial_number"] 58 | try: 59 | sn_int = int(sn, base=16) 60 | except Exception as e: 61 | raise Exception( 62 | f"Failed to convert device SN: {sn} into an int. " 63 | "Check if it is a valid hex string." 64 | ) from e 65 | sn_low = sn_int & 0xFFFFFFFF 66 | sn_high = (sn_int >> 32) & 0xFFFFFFFF 67 | self._fields = [ 68 | BitField("serial_number_low", 0, 31, FIELD_ATTR.RO, sn_low), 69 | BitField("serial_number_high", 32, 63, FIELD_ATTR.RO, sn_high), 70 | ] 71 | super().__init__(data, parent_name) 72 | 73 | 74 | class DeviceSNCapabilityOptions(TypedDict): 75 | next: int 76 | sn: str 77 | 78 | 79 | class DeviceSNCapability(BitMaskedBitStructure): 80 | def __init__( 81 | self, 82 | data: Optional[ShareableByteArray] = None, 83 | parent_name: Optional[str] = None, 84 | options: Optional[DeviceSNCapabilityOptions] = None, 85 | ): 86 | header_options = DeviceSNCapabilityHeaderOptions() 87 | sn_options = DeviceSNHeaderOptions() 88 | if options: 89 | header_options["next_capability_offset"] = options["next"] 90 | sn_options["serial_number"] = options["sn"] 91 | 92 | self._fields = [ 93 | StructureField( 94 | "capability_header", 0, 3, DeviceSNCapabilityHeader, options=header_options 95 | ), 96 | StructureField("serial_number_header", 4, 11, DeviceSNHeader, options=sn_options), 97 | ] 98 | 99 | super().__init__(data, parent_name) 100 | 101 | @staticmethod 102 | def get_size(fields=None) -> int: 103 | return 12 104 | -------------------------------------------------------------------------------- /opencis/cxl/device/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | -------------------------------------------------------------------------------- /opencis/cxl/device/config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opencis/opencis-core/cbcc15acffe5f0cadd770ae960ad1293479feb4a/opencis/cxl/device/config/__init__.py -------------------------------------------------------------------------------- /opencis/cxl/device/config/dynamic_capacity_device.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from dataclasses import dataclass 9 | from opencis.util.unaligned_bit_structure import ( 10 | UnalignedBitStructure, 11 | BitField, 12 | ByteField, 13 | StructureField, 14 | ) 15 | 16 | 17 | # pylint: disable=duplicate-code 18 | @dataclass 19 | class RegionConfiguration: 20 | region_base: int 21 | region_decode_len: int 22 | region_len: int 23 | region_block_size: int 24 | dsmad_handle: int 25 | flags: int 26 | 27 | 28 | class DsmadHandleStruct(UnalignedBitStructure): 29 | reserved0: int 30 | nonvolatile: int 31 | sharable: int 32 | hw_managed_coherency: int 33 | interconnect_specific_dc_mgmt: int 34 | read_only: int 35 | reserved1: int 36 | 37 | _fields = [ 38 | BitField("reserved0", 0x00, 0x01), 39 | BitField("nonvolatile", 0x02, 0x02), 40 | BitField("sharable", 0x03, 0x03), 41 | BitField("hw_managed_coherency", 0x04, 0x04), 42 | BitField("interconnect_specific_dc_mgmt", 0x05, 0x05), 43 | BitField("read_only", 0x06, 0x06), 44 | BitField("reserved1", 0x07, 0x07), 45 | BitField("reserved2", 0x08, 0x1F), 46 | ] 47 | 48 | 49 | class RegionConfigFlags(UnalignedBitStructure): 50 | sanitize_on_release: int 51 | reserved: int 52 | 53 | _fields = [ 54 | BitField("sanitize_on_release", 0x00, 0x00), 55 | BitField("reserved", 0x01, 0x07), 56 | ] 57 | 58 | 59 | class RegionConfigStruct(UnalignedBitStructure): 60 | region_base: int 61 | region_decode_len: int 62 | region_len: int 63 | region_block_size: int 64 | dsmad_handle: int 65 | flags: int 66 | reserved: int 67 | 68 | _fields = [ 69 | ByteField("region_base", 0x00, 0x07), 70 | ByteField("region_decode_len", 0x08, 0x0F), 71 | ByteField("region_len", 0x10, 0x17), 72 | ByteField("region_block_size", 0x18, 0x1F), 73 | StructureField( 74 | "dsmad_handle", 75 | 0x20, 76 | 0x23, 77 | DsmadHandleStruct, 78 | ), 79 | StructureField( 80 | "flags", 81 | 0x24, 82 | 0x24, 83 | RegionConfigFlags, 84 | ), 85 | ByteField("reserved", 0x25, 0x27), 86 | ] 87 | 88 | 89 | @dataclass 90 | class DynamicCapacityExtent: 91 | start_dpa: int 92 | length: int 93 | tag: int 94 | shared_extent_seq: int 95 | 96 | 97 | class DynamicCapacityExtentStruct(UnalignedBitStructure): 98 | start_dpa: int 99 | length: int 100 | tag: int 101 | shared_extent_seq: int 102 | reserved: int 103 | 104 | _fields = [ 105 | ByteField("start_dpa", 0x00, 0x07), 106 | ByteField("length", 0x08, 0x0F), 107 | ByteField("tag", 0x10, 0x1F), 108 | ByteField("shared_extent_seq", 0x20, 0x21), 109 | ByteField("reserved", 0x22, 0x27), 110 | ] 111 | -------------------------------------------------------------------------------- /opencis/cxl/device/config/logical_device.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from dataclasses import dataclass 9 | from typing import List 10 | from opencis.pci.component.pci import EEUM_VID, SW_SLD_DID, SW_MLD_DID 11 | 12 | 13 | @dataclass(kw_only=True) 14 | class LogicalDeviceConfig: 15 | port_index: int 16 | device_id: int 17 | vendor_id: int = EEUM_VID 18 | subsystem_vendor_id: int = 0 19 | subsystem_id: int = 0 20 | 21 | 22 | @dataclass(kw_only=True) 23 | class SingleLogicalDeviceConfig(LogicalDeviceConfig): 24 | memory_size: int # in bytes 25 | memory_file: str 26 | serial_number: str 27 | device_id: int = SW_SLD_DID 28 | 29 | 30 | @dataclass(kw_only=True) 31 | class MultiLogicalDeviceConfig(LogicalDeviceConfig): 32 | memory_sizes: List[int] # in bytes 33 | ld_list: List[int] 34 | memory_files: List[str] 35 | serial_numbers: List[str] 36 | ld_count: int 37 | device_id: int = SW_MLD_DID 38 | -------------------------------------------------------------------------------- /opencis/cxl/environment/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from .environment import parse_cxl_environment, CxlEnvironment 9 | -------------------------------------------------------------------------------- /opencis/cxl/features/README.md: -------------------------------------------------------------------------------- 1 | Deprecate this directory and move everything into component directory instead. 2 | -------------------------------------------------------------------------------- /opencis/cxl/features/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | -------------------------------------------------------------------------------- /opencis/cxl/features/event_manager.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from typing import TypedDict 9 | 10 | 11 | class EventStatus(TypedDict): 12 | # pylint: disable=duplicate-code 13 | informational_event_log: int 14 | warning_event_log: int 15 | failure_event_log: int 16 | fatal_event_log: int 17 | dynamic_capacity_event_log: int 18 | 19 | 20 | class InterruptPolicy(TypedDict): 21 | # pylint: disable=duplicate-code 22 | informational_event_log_interrupt_settings: int 23 | warning_event_log_interrupt_settings: int 24 | failure_event_log_interrupt_settings: int 25 | fatal_event_log_interrupt_settings: int 26 | dynamic_capacity_event_log_interrupt_settings: int 27 | 28 | 29 | class EventManager: 30 | def __init__(self) -> None: 31 | self._interrupt_policy = InterruptPolicy( 32 | informational_event_log_interrupt_settings=0, 33 | warning_event_log_interrupt_settings=0, 34 | failure_event_log_interrupt_settings=0, 35 | fatal_event_log_interrupt_settings=0, 36 | dynamic_capacity_event_log_interrupt_settings=0, 37 | ) 38 | 39 | def get_status(self) -> EventStatus: 40 | status = EventStatus() 41 | return status 42 | 43 | def set_interrupt_policy(self, policy: InterruptPolicy): 44 | for key, value in policy.items(): 45 | self._interrupt_policy[key] = value 46 | 47 | def get_interrupt_policy(self) -> InterruptPolicy: 48 | return self._interrupt_policy.copy() 49 | -------------------------------------------------------------------------------- /opencis/cxl/mmio/component_register/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from typing import Dict, Optional, TypedDict 9 | 10 | from opencis.util.unaligned_bit_structure import ( 11 | ShareableByteArray, 12 | BitMaskedBitStructure, 13 | StructureField, 14 | ByteField, 15 | FIELD_ATTR, 16 | ) 17 | from opencis.cxl.mmio.component_register.memcache_register import ( 18 | CxlCacheMemRegister, 19 | CxlCacheMemRegisterOptions, 20 | ) 21 | from opencis.cxl.component.bi_decoder import ( 22 | CxlBIDecoderCapabilityRegisterOptions, 23 | CxlBIDecoderControlRegisterOptions, 24 | CxlBIDecoderStatusRegisterOptions, 25 | ) 26 | from opencis.cxl.component.cxl_component import CxlComponent 27 | 28 | CXL_COMPONENT_REGISTER_SIZE = 0x10000 29 | 30 | 31 | class CxlComponentRegisterOptions(TypedDict): 32 | cxl_component: CxlComponent 33 | 34 | 35 | class CxlComponentRegister(BitMaskedBitStructure): 36 | def __init__( 37 | self, 38 | data: Optional[ShareableByteArray] = None, 39 | parent_name: Optional[str] = None, 40 | options: Optional[CxlComponentRegisterOptions] = None, 41 | ): 42 | if not options: 43 | raise Exception("options is required") 44 | 45 | cxl_component = options["cxl_component"] 46 | cachemem_options = CxlCacheMemRegisterOptions() 47 | if cxl_component.get_hdm_decoder_manager(): 48 | cachemem_options["hdm_decoder"] = { 49 | "hdm_decoder_manager": cxl_component.get_hdm_decoder_manager() 50 | } 51 | 52 | cachemem_options["link"] = True 53 | cachemem_options["ras"] = True 54 | if cxl_component.get_bi_decoder_options(): 55 | cachemem_options["bi_decoder"] = cxl_component.get_bi_decoder_options() 56 | if cxl_component.get_bi_rt_options(): 57 | cachemem_options["bi_route_table"] = cxl_component.get_bi_rt_options() 58 | 59 | self._fields = [ 60 | ByteField("io", 0x0000, 0x0FFF, attribute=FIELD_ATTR.RESERVED), 61 | StructureField( 62 | "cachemem", 63 | 0x1000, 64 | 0x1FFF, 65 | CxlCacheMemRegister, 66 | options=cachemem_options, 67 | ), 68 | ByteField("cachemem_ext", 0x2000, 0xDFFF, attribute=FIELD_ATTR.RESERVED), 69 | ByteField("arb_mux", 0xE000, 0xE3FF, attribute=FIELD_ATTR.RESERVED), 70 | ByteField("reserved1", 0xE400, 0xFFFF, attribute=FIELD_ATTR.RESERVED), 71 | ] 72 | 73 | super().__init__(data, parent_name) 74 | 75 | @staticmethod 76 | def get_size_from_options(options: Optional[Dict] = None) -> int: 77 | return CXL_COMPONENT_REGISTER_SIZE 78 | -------------------------------------------------------------------------------- /opencis/cxl/mmio/device_register/device_status_register.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from typing import Dict, TypedDict, Optional 9 | from opencis.util.unaligned_bit_structure import ( 10 | BitMaskedBitStructure, 11 | BitField, 12 | ByteField, 13 | StructureField, 14 | FIELD_ATTR, 15 | ShareableByteArray, 16 | ) 17 | from opencis.cxl.component.cxl_component import CxlDeviceComponent 18 | from opencis.cxl.features.event_manager import EventStatus 19 | 20 | 21 | class DeviceStatusRegistersOptions(TypedDict): 22 | cxl_device_component: CxlDeviceComponent 23 | 24 | 25 | class EventStatusRegister(BitMaskedBitStructure): 26 | informational_event_log: int 27 | warning_event_log: int 28 | failure_event_log: int 29 | fatal_event_log: int 30 | dynamic_capacity_event_log: int 31 | 32 | _fields = [ 33 | BitField("informational_event_log", 0, 0, FIELD_ATTR.RO), 34 | BitField("warning_event_log", 1, 1, FIELD_ATTR.RO), 35 | BitField("failure_event_log", 2, 2, FIELD_ATTR.RO), 36 | BitField("fatal_event_log", 3, 3, FIELD_ATTR.RO), 37 | BitField("dynamic_capacity_event_log", 4, 4, FIELD_ATTR.RO), 38 | BitField("reserved1", 5, 63, FIELD_ATTR.RESERVED), 39 | ] 40 | 41 | def __init__( 42 | self, 43 | data: Optional[ShareableByteArray] = None, 44 | parent_name: Optional[str] = None, 45 | options: Optional[DeviceStatusRegistersOptions] = None, 46 | ): 47 | if not options: 48 | raise Exception("options is required") 49 | 50 | self.event_manager = options["cxl_device_component"].get_event_manager() 51 | 52 | super().__init__(data, parent_name) 53 | 54 | def read_bytes(self, start_offset: int, end_offset: int) -> int: 55 | status: EventStatus = self.event_manager.get_status() 56 | self.write_fields_from_dict(status) 57 | return super().read_bytes(start_offset, end_offset) 58 | 59 | 60 | class DeviceStatusRegisters(BitMaskedBitStructure): 61 | event_status: EventStatusRegister 62 | 63 | def __init__( 64 | self, 65 | data: Optional[ShareableByteArray] = None, 66 | parent_name: Optional[str] = None, 67 | options: Optional[DeviceStatusRegistersOptions] = None, 68 | ): 69 | if not options: 70 | raise Exception("options is required") 71 | 72 | self._fields = [ 73 | StructureField("event_status", 0, 7, EventStatusRegister, options=options), 74 | ByteField( 75 | "reserved1", 0x08, 0x0F, attribute=FIELD_ATTR.RESERVED 76 | ), # Extended reserved bits to 0x0F to make the register aligned to 0x10 boudnary 77 | ] 78 | 79 | super().__init__(data, parent_name) 80 | 81 | @staticmethod 82 | def get_size_from_options(options: Dict | None = None) -> int: 83 | return 16 84 | -------------------------------------------------------------------------------- /opencis/cxl/mmio/device_register/memory_device_capabilities.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from typing import Dict, TypedDict, Optional 9 | from opencis.util.unaligned_bit_structure import ( 10 | BitMaskedBitStructure, 11 | BitField, 12 | ByteField, 13 | StructureField, 14 | FIELD_ATTR, 15 | ShareableByteArray, 16 | ) 17 | from opencis.cxl.component.cxl_memory_device_component import ( 18 | CxlMemoryDeviceComponent, 19 | MemoryDeviceStatus, 20 | MEDIA_STATUS, 21 | RESET_REQUEST, 22 | ) 23 | 24 | 25 | class MemoryDeviceStatusRegistersOptions(TypedDict): 26 | cxl_memory_device_component: CxlMemoryDeviceComponent 27 | 28 | 29 | class MemoryDeviceStatusRegister(BitMaskedBitStructure): 30 | device_fatal: int 31 | fw_halt: int 32 | media_status: MEDIA_STATUS 33 | mailbox_interfaces_ready: RESET_REQUEST 34 | reset_needed: int 35 | 36 | _fields = [ 37 | BitField("device_fatal", 0, 0, FIELD_ATTR.RO), 38 | BitField("fw_halt", 1, 1, FIELD_ATTR.RO), 39 | BitField("media_status", 2, 3, FIELD_ATTR.RO), 40 | BitField("mailbox_interfaces_ready", 4, 4, FIELD_ATTR.RO), 41 | BitField("reset_needed", 5, 7, FIELD_ATTR.RO), 42 | BitField("reserved", 8, 63, FIELD_ATTR.RESERVED), 43 | ] 44 | 45 | def __init__( 46 | self, 47 | data: Optional[ShareableByteArray] = None, 48 | parent_name: Optional[str] = None, 49 | options: Optional[MemoryDeviceStatusRegistersOptions] = None, 50 | ): 51 | if not options: 52 | raise Exception("options is required") 53 | 54 | self.cxl_memory_device_component = options["cxl_memory_device_component"] 55 | 56 | super().__init__(data, parent_name) 57 | 58 | def read_bytes(self, start_offset: int, end_offset: int) -> int: 59 | status: MemoryDeviceStatus = self.cxl_memory_device_component.get_status() 60 | self.write_fields_from_dict(status) 61 | return super().read_bytes(start_offset, end_offset) 62 | 63 | 64 | class MemoryDeviceStatusRegisters(BitMaskedBitStructure): 65 | status: MemoryDeviceStatusRegister 66 | 67 | def __init__( 68 | self, 69 | data: Optional[ShareableByteArray] = None, 70 | parent_name: Optional[str] = None, 71 | options: Optional[MemoryDeviceStatusRegistersOptions] = None, 72 | ): 73 | if not options: 74 | raise Exception("options is required") 75 | 76 | self._fields = [ 77 | StructureField("status", 0x00, 0x07, MemoryDeviceStatusRegister, options=options), 78 | ByteField( 79 | "reserved1", 0x08, 0x0F, attribute=FIELD_ATTR.RESERVED 80 | ), # Extended reserved bits to 0x0F to make the register aligned to 0x10 boudnary 81 | ] 82 | 83 | super().__init__(data, parent_name) 84 | 85 | @staticmethod 86 | def get_size_from_options(options: Dict | None = None) -> int: 87 | return 16 88 | -------------------------------------------------------------------------------- /opencis/cxl/transport/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | -------------------------------------------------------------------------------- /opencis/cxl/transport/cache_fifo.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from asyncio import Queue 9 | from dataclasses import dataclass, field 10 | from enum import Enum, auto 11 | 12 | 13 | class CACHE_REQUEST_TYPE(Enum): 14 | READ = auto() 15 | WRITE = auto() 16 | SNP_DATA = auto() 17 | SNP_INV = auto() 18 | SNP_CUR = auto() 19 | WRITE_BACK = auto() 20 | WRITE_BACK_CLEAN = auto() 21 | UNCACHED_WRITE = auto() 22 | UNCACHED_READ = auto() 23 | 24 | 25 | @dataclass 26 | class CacheRequest: 27 | type: CACHE_REQUEST_TYPE 28 | addr: int 29 | size: int = 0 30 | data: int = 0 31 | 32 | def get_address(self) -> int: 33 | return self.addr 34 | 35 | 36 | class CACHE_RESPONSE_STATUS(Enum): 37 | OK = auto() 38 | FAILED = auto() 39 | RSP_V = auto() 40 | RSP_M = auto() 41 | RSP_E = auto() 42 | RSP_S = auto() 43 | RSP_I = auto() 44 | RSP_MISS = auto() 45 | 46 | 47 | @dataclass 48 | class CacheResponse: 49 | status: CACHE_RESPONSE_STATUS 50 | data: int = 0 51 | 52 | 53 | @dataclass 54 | class CacheFifoPair: 55 | request: Queue[CacheRequest] = field(default_factory=Queue) 56 | response: Queue[CacheResponse] = field(default_factory=Queue) 57 | -------------------------------------------------------------------------------- /opencis/cxl/transport/ethernet.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from opencis.util.unaligned_bit_structure import ( 9 | UnalignedBitStructure, 10 | ByteField, 11 | StructureField, 12 | ) 13 | from opencis.cxl.transport.common import ( 14 | SystemHeaderPacket, 15 | CxlHeaderPacket, 16 | ) 17 | 18 | 19 | class CxlEthernetPacket(UnalignedBitStructure): 20 | # NOTE: static attributes are for intellisense 21 | system_header: SystemHeaderPacket 22 | cxl_header: CxlHeaderPacket 23 | cxl_data: int 24 | data_parity: int 25 | 26 | _fields = [ 27 | # Byte offset [03:00] - System Header 28 | StructureField("system_header", 0, 3, SystemHeaderPacket), 29 | # Byte offset [23:04] - CXL Header 30 | StructureField("cxl_header", 4, 23, CxlHeaderPacket), 31 | # Byte offset [87:24] - CXL Data 32 | ByteField("cxl_data", 24, 87), 33 | # Byte offset [99:88] - Data Parity 34 | ByteField("data_parity", 88, 99), 35 | # Byte offset [235:100] - Reserved 36 | ByteField("reserved", 100, 235), 37 | ] 38 | -------------------------------------------------------------------------------- /opencis/cxl/transport/memory_fifo.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from asyncio import Queue 9 | from dataclasses import dataclass, field 10 | from enum import Enum, auto 11 | 12 | 13 | class MEMORY_REQUEST_TYPE(Enum): 14 | READ = auto() 15 | WRITE = auto() 16 | UNCACHED_READ = auto() 17 | UNCACHED_WRITE = auto() 18 | 19 | 20 | @dataclass 21 | class MemoryRequest: 22 | type: MEMORY_REQUEST_TYPE 23 | addr: int 24 | size: int 25 | data: int = 0 26 | 27 | 28 | class MEMORY_RESPONSE_STATUS(Enum): 29 | OK = auto() 30 | FAILED = auto() 31 | 32 | 33 | @dataclass 34 | class MemoryResponse: 35 | status: MEMORY_RESPONSE_STATUS 36 | data: int = 0 37 | 38 | 39 | @dataclass 40 | class MemoryFifoPair: 41 | request: Queue[MemoryRequest] = field(default_factory=Queue) 42 | response: Queue[MemoryResponse] = field(default_factory=Queue) 43 | -------------------------------------------------------------------------------- /opencis/cxl/transport/packet.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | -------------------------------------------------------------------------------- /opencis/cxl/transport/socket_server.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | import traceback 9 | from typing import List 10 | import socket 11 | from opencis.util.logger import logger 12 | 13 | 14 | class SocketServerTransport: 15 | def __init__(self, host="0.0.0.0", port=8000): 16 | server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 17 | server_socket.bind((host, port)) 18 | server_socket.listen(5) 19 | server_socket.setblocking(False) 20 | self._server_socket = server_socket 21 | self._incoming_connections = set() 22 | self._claimed_connections = set() 23 | 24 | logger.info(f"Socket server started at {host}:{port}") 25 | 26 | def check_incoming_connections(self): 27 | try: 28 | connection, _ = self._server_socket.accept() 29 | logger.info("Client connected") 30 | connection.setblocking(False) 31 | self._incoming_connections.add(connection) 32 | except Exception as e: 33 | logger.error(f"check_incoming_connections error: {str(e)}: {traceback.format_exc()}") 34 | 35 | def get_incoming_connections(self) -> List[socket.socket]: 36 | return list(self._incoming_connections) 37 | 38 | def claim_connection(self, connection: socket.socket) -> bool: 39 | if connection in self._claimed_connections: 40 | return False 41 | if connection not in self._incoming_connections: 42 | return False 43 | self._incoming_connections.remove(connection) 44 | self._claimed_connections.add(connection) 45 | return False 46 | 47 | @staticmethod 48 | def is_active_connection(connection: socket.socket) -> bool: 49 | try: 50 | error_code = connection.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) 51 | return error_code == 0 52 | except Exception as e: 53 | logger.error(f"is_active_connection error: {str(e)}: {traceback.format_exc()}") 54 | return False 55 | 56 | def unclaim_connection(self, connection: socket.socket) -> bool: 57 | if self.is_active_connection(connection): 58 | logger.info("Cannot unclaim an active connection") 59 | return False 60 | if connection not in self._claimed_connections: 61 | return False 62 | connection.close() 63 | self._claimed_connections.remove(connection) 64 | return True 65 | -------------------------------------------------------------------------------- /opencis/drivers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opencis/opencis-core/cbcc15acffe5f0cadd770ae960ad1293479feb4a/opencis/drivers/__init__.py -------------------------------------------------------------------------------- /opencis/msim/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opencis/opencis-core/cbcc15acffe5f0cadd770ae960ad1293479feb4a/opencis/msim/__init__.py -------------------------------------------------------------------------------- /opencis/pci/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | -------------------------------------------------------------------------------- /opencis/pci/component/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | -------------------------------------------------------------------------------- /opencis/pci/component/fifo_pair.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from asyncio import Queue 9 | from dataclasses import dataclass, field 10 | 11 | 12 | @dataclass 13 | class FifoPair: 14 | host_to_target: Queue = field(default_factory=Queue) 15 | target_to_host: Queue = field(default_factory=Queue) 16 | -------------------------------------------------------------------------------- /opencis/pci/component/packet_processor.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from asyncio import create_task, gather 9 | from typing import Optional, Union, Callable 10 | 11 | from opencis.util.logger import logger 12 | from opencis.pci.component.fifo_pair import FifoPair 13 | from opencis.util.component import RunnableComponent 14 | 15 | 16 | # PacketProcessor can be used a relay between two FifoPairs when it is used as is. 17 | # PacketProcessor can be inherited by another class when customized processing logics are needed. 18 | 19 | 20 | class PacketProcessor(RunnableComponent): 21 | def __init__( 22 | self, 23 | upstream_fifo: FifoPair, 24 | downstream_fifo: Optional[FifoPair] = None, 25 | label: Optional[Union[str, Callable]] = None, 26 | ): 27 | super().__init__(label) 28 | self._upstream_fifo = upstream_fifo 29 | self._downstream_fifo = downstream_fifo 30 | 31 | async def _process_host_to_target(self): 32 | if self._downstream_fifo is None: 33 | logger.debug(self._create_message("Skipped processing host to target packets")) 34 | return 35 | logger.debug(self._create_message("Started processing host to target packets")) 36 | while True: 37 | packet = await self._upstream_fifo.host_to_target.get() 38 | if packet is None: 39 | logger.debug(self._create_message("Stopped host to target packets")) 40 | break 41 | logger.debug(self._create_message("Received host to target Packet")) 42 | await self._downstream_fifo.host_to_target.put(packet) 43 | 44 | async def _process_target_to_host(self): 45 | if self._downstream_fifo is None: 46 | logger.debug(self._create_message("Skipped processing target to host packets")) 47 | return 48 | logger.debug(self._create_message("Started processing target to host packets")) 49 | while True: 50 | packet = await self._downstream_fifo.target_to_host.get() 51 | if packet is None: 52 | logger.debug(self._create_message("Stopped target to host packets")) 53 | break 54 | logger.debug(self._create_message("Received target to host Packet")) 55 | await self._upstream_fifo.target_to_host.put(packet) 56 | 57 | async def _run(self): 58 | tasks = [ 59 | create_task(self._process_host_to_target()), 60 | create_task(self._process_target_to_host()), 61 | ] 62 | await self._change_status_to_running() 63 | await gather(*tasks) 64 | 65 | async def _stop(self): 66 | if self._downstream_fifo is not None: 67 | await self._downstream_fifo.target_to_host.put(None) 68 | await self._upstream_fifo.host_to_target.put(None) 69 | -------------------------------------------------------------------------------- /opencis/pci/component/pci_connection.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from dataclasses import dataclass, field 9 | from opencis.pci.component.fifo_pair import FifoPair 10 | 11 | 12 | @dataclass 13 | class PciConnection: 14 | cfg_fifo: FifoPair = field(default_factory=FifoPair) 15 | mmio_fifo: FifoPair = field(default_factory=FifoPair) 16 | -------------------------------------------------------------------------------- /opencis/pci/config_space/pcie/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | -------------------------------------------------------------------------------- /opencis/pci/device/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opencis/opencis-core/cbcc15acffe5f0cadd770ae960ad1293479feb4a/opencis/pci/device/__init__.py -------------------------------------------------------------------------------- /opencis/pci/device/pci_device.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from asyncio import create_task, gather 9 | from typing import Optional 10 | 11 | from opencis.pci.component.pci import ( 12 | PciComponentIdentity, 13 | PciComponent, 14 | EEUM_VID, 15 | SW_EP_DID, 16 | PCI_CLASS, 17 | PCI_SYSTEM_PERIPHERAL_SUBCLASS, 18 | ) 19 | from opencis.pci.component.pci_connection import PciConnection 20 | from opencis.pci.component.mmio_manager import MmioManager, BarEntry 21 | from opencis.pci.component.config_space_manager import ( 22 | ConfigSpaceManager, 23 | PCI_DEVICE_TYPE, 24 | ) 25 | from opencis.util.component import RunnableComponent 26 | from opencis.util.unaligned_bit_structure import ( 27 | ShareableByteArray, 28 | UnalignedBitStructure, 29 | ) 30 | from opencis.pci.config_space import ( 31 | PciExpressConfigSpace, 32 | PciExpressDeviceConfigSpaceOptions, 33 | ) 34 | 35 | 36 | class PciDevice(RunnableComponent): 37 | def __init__( 38 | self, 39 | transport_connection: PciConnection, 40 | identity: Optional[PciComponentIdentity] = None, 41 | bar_size: int = 0, 42 | label: Optional[str] = None, 43 | ): 44 | super().__init__() 45 | if identity is None: 46 | identity = PciComponentIdentity( 47 | vendor_id=EEUM_VID, 48 | device_id=SW_EP_DID, 49 | base_class_code=PCI_CLASS.SYSTEM_PERIPHERAL, 50 | sub_class_coce=PCI_SYSTEM_PERIPHERAL_SUBCLASS.OTHER, 51 | ) 52 | self._upstream_connection = transport_connection 53 | self._label = label 54 | self._mmio_manager = MmioManager( 55 | upstream_fifo=self._upstream_connection.mmio_fifo, label=label 56 | ) 57 | pci_component = PciComponent(identity, self._mmio_manager) 58 | self._config_space_manager = ConfigSpaceManager( 59 | upstream_fifo=self._upstream_connection.cfg_fifo, 60 | label=self._label, 61 | device_type=PCI_DEVICE_TYPE.ENDPOINT, 62 | ) 63 | if bar_size > 0: 64 | register = UnalignedBitStructure(data=ShareableByteArray(bar_size)) 65 | self._mmio_manager.set_bar_entries([BarEntry(register=register)]) 66 | pci_register_options = PciExpressDeviceConfigSpaceOptions(pci_component=pci_component) 67 | pci_register = PciExpressConfigSpace(options=pci_register_options) 68 | self._config_space_manager.set_register(pci_register) 69 | 70 | async def _run(self): 71 | tasks = [ 72 | create_task(self._mmio_manager.run()), 73 | create_task(self._config_space_manager.run()), 74 | ] 75 | wait_tasks = [ 76 | create_task(self._mmio_manager.wait_for_ready()), 77 | create_task(self._config_space_manager.wait_for_ready()), 78 | ] 79 | await gather(*wait_tasks) 80 | await self._change_status_to_running() 81 | await gather(*tasks) 82 | 83 | async def _stop(self): 84 | tasks = [ 85 | create_task(self._mmio_manager.stop()), 86 | create_task(self._config_space_manager.stop()), 87 | ] 88 | await gather(*tasks) 89 | -------------------------------------------------------------------------------- /opencis/util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opencis/opencis-core/cbcc15acffe5f0cadd770ae960ad1293479feb4a/opencis/util/__init__.py -------------------------------------------------------------------------------- /opencis/util/accessor.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | 9 | class FileAccessor: 10 | def __init__(self, filename: str, size: int): 11 | self.filename = filename 12 | with open(filename, "wb") as file: 13 | file.write(b"\x00" * size) 14 | file.flush() 15 | 16 | async def write(self, offset: int, data: int, size: int): 17 | # TODO: Check for OOB and use asyncio 18 | with open(self.filename, "r+b") as file: 19 | file.seek(offset) 20 | file.write(data.to_bytes(size, byteorder="little")) 21 | 22 | async def read(self, offset: int, size: int) -> int: 23 | # TODO: Check for OOB and use asyncio 24 | with open(self.filename, "rb") as file: 25 | file.seek(offset) 26 | data = file.read(size) 27 | return int.from_bytes(data, byteorder="little") 28 | -------------------------------------------------------------------------------- /opencis/util/async_gatherer.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | import asyncio 9 | 10 | 11 | class AsyncGatherer: 12 | def __init__(self): 13 | self._tasks = set() 14 | self._event = asyncio.Event() 15 | 16 | def add_task(self, coro): 17 | task = asyncio.create_task(coro) 18 | self._tasks.add(task) 19 | task.add_done_callback(self._on_task_done) 20 | return task 21 | 22 | def _on_task_done(self, task): 23 | self._tasks.remove(task) 24 | if not self._tasks: 25 | self._event.set() 26 | 27 | async def wait_for_completion(self): 28 | while self._tasks: 29 | await self._event.wait() 30 | self._event.clear() 31 | -------------------------------------------------------------------------------- /opencis/util/bound_event.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from asyncio import Event, Lock 9 | 10 | 11 | class BoundEvent: 12 | def __init__(self): 13 | self.ev = Event() 14 | self._lock = Lock() 15 | self.res = None 16 | 17 | # def __await__(self): 18 | # return self.ev.wait().__await__() 19 | 20 | async def set_result(self, result): 21 | async with self._lock: 22 | self.res = result 23 | self.ev.set() 24 | 25 | async def result(self): 26 | # change to "claim_result" 27 | async with self._lock: 28 | ret = self.res 29 | self.ev.clear() 30 | return ret 31 | -------------------------------------------------------------------------------- /opencis/util/memory.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | import atexit 9 | from functools import partial 10 | import os 11 | import sys 12 | 13 | 14 | def get_memory_bin_name(index_primary: int = 0, index_secondary: int = -1) -> str: 15 | def cleanup(path): 16 | try: 17 | os.remove(path) 18 | except Exception: 19 | pass 20 | 21 | # Get caller function name 22 | # pylint: disable=protected-access 23 | func_name = sys._getframe(1).f_code.co_name 24 | 25 | while True: 26 | if index_secondary != -1: 27 | bin_name = f"mem_{func_name}_{index_primary}-{index_secondary}.bin" 28 | else: 29 | bin_name = f"mem_{func_name}_{index_primary}.bin" 30 | # Check if the bin name already exists 31 | if not os.path.exists(bin_name): 32 | break 33 | index_primary += 1 34 | 35 | # Make sure we remove it upon exit 36 | atexit.register(partial(cleanup, bin_name)) 37 | return bin_name 38 | -------------------------------------------------------------------------------- /opencis/util/number_const.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | BITS_IN_BYTE = 8 9 | DWORD_BYTES = 4 10 | KB = 1024 11 | MB = 1024 * 1024 12 | -------------------------------------------------------------------------------- /opencis/util/pci.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | 9 | def create_bdf(bus: int, device: int, function: int) -> int: 10 | """ 11 | Creates a PCI express BDF integer from bus, device, and function values. 12 | 13 | Args: 14 | - bus (int): The bus value. 15 | - device (int): The device (or number) value. 16 | - function (int): The function value. 17 | 18 | Returns: 19 | - int: The BDF represented as a 16-bit integer. 20 | """ 21 | return (bus << 8) | (device << 3) | function 22 | 23 | 24 | def bdf_to_string(bdf: int) -> str: 25 | """ 26 | Converts a PCI express BDF 16-bit integer into its string representation. 27 | 28 | Args: 29 | - bdf (int): The BDF represented as a 16-bit integer. 30 | 31 | Returns: 32 | - str: The BDF string in the format "bus:device.function". 33 | """ 34 | bus = (bdf >> 8) & 0xFF 35 | device = (bdf >> 3) & 0x1F 36 | function = bdf & 0x07 37 | 38 | return f"{bus:02x}:{device:02x}.{function:x}" 39 | 40 | 41 | def extract_device_from_bdf(bdf: int) -> int: 42 | """ 43 | Extracts the device number from a PCI express BDF 16-bit integer. 44 | 45 | Args: 46 | - bdf (int): The BDF represented as a 16-bit integer. 47 | 48 | Returns: 49 | - int: The device number. 50 | """ 51 | return (bdf >> 3) & 0x1F 52 | 53 | 54 | def extract_bus_from_bdf(bdf: int) -> int: 55 | """ 56 | Extracts the bus number from a PCI express BDF 16-bit integer. 57 | 58 | Args: 59 | - bdf (int): The BDF represented as a 16-bit integer. 60 | 61 | Returns: 62 | - int: The bus number. 63 | """ 64 | return (bdf >> 8) & 0xFF 65 | 66 | 67 | def extract_function_from_bdf(bdf: int) -> int: 68 | """ 69 | Extracts the function number from a PCI express BDF 16-bit integer. 70 | 71 | Args: 72 | - bdf (int): The BDF represented as a 16-bit integer. 73 | 74 | Returns: 75 | - int: The function number. 76 | """ 77 | return bdf & 0x07 78 | 79 | 80 | def generate_bdfs_for_bus(bus: int) -> list[int]: 81 | """ 82 | Generates all possible BDFs for a given bus number. 83 | 84 | Args: 85 | - bus (int): The bus number. 86 | 87 | Returns: 88 | - list[int]: A list of 16-bit integers representing all possible BDFs for the given bus number. 89 | """ 90 | bdfs = [] 91 | 92 | for device in range(0x20): # Device can range from 0x00 to 0x1F 93 | for function in range(0x8): # Function can range from 0x0 to 0x7 94 | bdf = (bus << 8) | (device << 3) | function 95 | bdfs.append(bdf) 96 | 97 | return bdfs 98 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["pdm-backend"] 3 | build-backend = "pdm.backend" 4 | 5 | [tool.pytest.ini_options] 6 | testpaths = "tests/" 7 | timeout = 45 8 | filterwarnings = [ 9 | "ignore::DeprecationWarning" 10 | ] 11 | asyncio_default_fixture_loop_scope = "session" 12 | 13 | [tool.pylint.main] 14 | disable = [ 15 | "broad-exception-caught", 16 | "broad-exception-raised", 17 | "disallowed-name", 18 | "fixme", 19 | "invalid-name", 20 | "logging-fstring-interpolation", 21 | "missing-class-docstring", 22 | "missing-function-docstring", 23 | "pointless-string-statement", 24 | "possibly-used-before-assignment", 25 | "redefined-builtin", 26 | "too-few-public-methods", 27 | "too-many-arguments", 28 | "too-many-branches", 29 | "too-many-instance-attributes", 30 | "too-many-lines", 31 | "too-many-locals", 32 | "too-many-positional-arguments", 33 | "too-many-public-methods", 34 | "too-many-statements", 35 | "unspecified-encoding", 36 | ] 37 | jobs = 4 38 | 39 | [tool.black] 40 | line-length = 100 41 | target-version = ['py311'] 42 | 43 | [project] 44 | authors = [ 45 | {name = "Paul Kang"}, 46 | {name = "Kyeyoon Park"}, 47 | ] 48 | license = {text = "BSD-3"} 49 | requires-python = "<4.0,>=3.11" 50 | dependencies = [ 51 | "click>=8.1.7", 52 | "humanfriendly>=10.0", 53 | "pyyaml>=6.0.1", 54 | "python-socketio>=5.10.0", 55 | "aiohttp>=3.8.6", 56 | "pytest-timeout>=2.2.0", 57 | "dill>=0.3.7", 58 | "websockets>=12.0", 59 | "jsonrpcserver>=5.0.9", 60 | "jsonrpcclient>=4.0.3", 61 | "black>=24.4.2", 62 | "sortedcontainers>=2.4.0", 63 | "readerwriterlock>=1.0.9", 64 | "torch>=2.3.1", 65 | "torchvision>=0.18.1", 66 | "torchinfo>=1.8.0", 67 | "tqdm>=4.66.4", 68 | "pyshark>=0.6", 69 | "scapy>=2.6.1", 70 | "pytest-xdist>=3.5.0", 71 | ] 72 | name = "opencis" 73 | version = "0.4.0" 74 | description = "OpenCXL" 75 | readme = "README.md" 76 | 77 | [dependency-groups] 78 | dev = [ 79 | "pytest>=7.3.1", 80 | "pytest-cov>=4.0.0", 81 | "pytest-asyncio>=0.21.1", 82 | "pylint>=3.0.1", 83 | ] 84 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | import pytest 9 | 10 | 11 | @pytest.fixture 12 | def get_gold_std_reg_vals(): 13 | def _get_gold_std_reg_vals(device_type: str): 14 | with open("tests/regvals.txt") as f: 15 | for line in f: 16 | (k, v) = line.strip().split(":") 17 | if k == device_type: 18 | return v 19 | return None 20 | 21 | return _get_gold_std_reg_vals 22 | -------------------------------------------------------------------------------- /tests/test_cxl_cci.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | -------------------------------------------------------------------------------- /tests/test_cxl_component_hdm_decoder.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from opencis.cxl.component.hdm_decoder import ( 9 | DeviceHdmDecoderManager, 10 | HdmDecoderCapabilities, 11 | CXL_DEVICE_TYPE, 12 | DecoderInfo, 13 | ) 14 | 15 | 16 | def test_device_hdm_decoder_manager(): 17 | # pylint: disable=duplicate-code 18 | capabilities = HdmDecoderCapabilities( 19 | decoder_count=1, 20 | target_count=0, 21 | a11to8_interleave_capable=0, 22 | a14to12_interleave_capable=0, 23 | poison_on_decoder_error_capability=0, 24 | three_six_twelve_way_interleave_capable=0, 25 | sixteen_way_interleave_capable=0, 26 | uio_capable=0, 27 | uio_capable_decoder_count=0, 28 | mem_data_nxm_capable=0, 29 | bi_capable=True, 30 | ) 31 | decoder = DeviceHdmDecoderManager(capabilities) 32 | assert decoder.get_device_type() == CXL_DEVICE_TYPE.MEM_DEVICE 33 | assert decoder.is_bi_capable() is True 34 | assert decoder.get_capabilities() == capabilities 35 | assert decoder.decoder_enable(True) is None 36 | assert decoder.poison_enable(True) is None 37 | assert decoder.is_uio_capable() is False 38 | 39 | decoder_info = DecoderInfo(size=0x1000, base=0x2000) 40 | decoder_index = 0 41 | assert decoder.commit(decoder_index, decoder_info) is True 42 | assert decoder.is_hpa_in_range(0x2000 - 1) is False 43 | assert decoder.is_hpa_in_range(0x2000) is True 44 | assert decoder.is_hpa_in_range(0x3000 - 1) is True 45 | assert decoder.is_hpa_in_range(0x3000) is False 46 | assert decoder.get_dpa(0x3000) is None 47 | 48 | decoder_info = DecoderInfo(size=0x1000, base=0x2000, iw=1) 49 | decoder_index = 0 50 | assert decoder.commit(decoder_index, decoder_info) is True 51 | assert decoder.get_dpa(0x2012) == 0x12 52 | assert decoder.get_dpa(0x2212) == 0x112 53 | 54 | decoder_info = DecoderInfo(size=0x1000, base=0x2000, iw=8) 55 | decoder_index = 0 56 | assert decoder.commit(decoder_index, decoder_info) is True 57 | assert decoder.get_dpa(0x2023) == 0x023 58 | assert decoder.get_dpa(0x2323) == 0x123 59 | 60 | decoder_info = DecoderInfo(size=0x1000, base=0x2000, iw=1, ig=1) 61 | decoder_index = 0 62 | assert decoder.commit(decoder_index, decoder_info) is True 63 | assert decoder.get_dpa(0x2012) == 0x12 64 | assert decoder.get_dpa(0x2412) == 0x212 65 | -------------------------------------------------------------------------------- /tests/test_cxl_device_downstream_port.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from asyncio import gather, create_task 9 | import pytest 10 | 11 | from opencis.cxl.device.downstream_port_device import ( 12 | DownstreamPortDevice, 13 | CXL_COMPONENT_TYPE, 14 | ) 15 | from opencis.cxl.component.cxl_connection import CxlConnection 16 | 17 | 18 | def test_downstream_port_device(): 19 | transport_connection = CxlConnection() 20 | device = DownstreamPortDevice(transport_connection=transport_connection, port_index=0) 21 | assert device.get_device_type() == CXL_COMPONENT_TYPE.DSP 22 | 23 | 24 | @pytest.mark.asyncio 25 | async def test_downstream_port_device_run_stop(): 26 | transport_connection = CxlConnection() 27 | device = DownstreamPortDevice(transport_connection=transport_connection, port_index=0) 28 | 29 | async def wait_and_stop(): 30 | await device.wait_for_ready() 31 | await device.stop() 32 | 33 | tasks = [create_task(device.run()), create_task(wait_and_stop())] 34 | await gather(*tasks) 35 | -------------------------------------------------------------------------------- /tests/test_cxl_device_multiheaded_single_logical_device.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from asyncio import gather, create_task 9 | import pytest 10 | 11 | from opencis.apps.multiheaded_single_logical_device import MultiHeadedSingleLogicalDevice 12 | from opencis.cxl.device.root_port_device import CxlRootPortDevice 13 | from opencis.cxl.component.cxl_connection import CxlConnection 14 | from opencis.util.memory import get_memory_bin_name 15 | from opencis.util.number_const import MB 16 | 17 | # Test with 4 ports 18 | num_ports = 4 19 | 20 | 21 | def test_multiheaded_single_logical_device(): 22 | memory_size = 256 * MB 23 | memory_file = get_memory_bin_name() 24 | transport_connection = CxlConnection() 25 | MultiHeadedSingleLogicalDevice( 26 | num_ports, 27 | memory_size=memory_size, 28 | memory_file=memory_file, 29 | serial_number="AAAAAAAAAAAAAAAA", 30 | test_mode=True, 31 | cxl_connection=transport_connection, 32 | ) 33 | 34 | 35 | @pytest.mark.asyncio 36 | async def test_multiheaded_single_logical_device_run_stop(get_gold_std_reg_vals): 37 | memory_size = 256 * MB 38 | memory_file = get_memory_bin_name() 39 | transport_connection = CxlConnection() 40 | mhsld_device = MultiHeadedSingleLogicalDevice( 41 | num_ports, 42 | memory_size=memory_size, 43 | memory_file=memory_file, 44 | serial_number="AAAAAAAAAAAAAAAA", 45 | test_mode=True, 46 | cxl_connection=transport_connection, 47 | ) 48 | 49 | # check register values after initialization 50 | for sld_device in mhsld_device.get_sld_devices(): 51 | reg_vals = str(sld_device.get_reg_vals()) 52 | reg_vals_expected = get_gold_std_reg_vals("SLD") 53 | assert reg_vals == reg_vals_expected 54 | 55 | async def wait_and_stop(): 56 | await mhsld_device.wait_for_ready() 57 | await mhsld_device.stop() 58 | 59 | tasks = [create_task(mhsld_device.run()), create_task(wait_and_stop())] 60 | await gather(*tasks) 61 | 62 | 63 | @pytest.mark.asyncio 64 | async def test_multiheaded_single_logical_device_enumeration(): 65 | memory_size = 256 * MB 66 | memory_file = get_memory_bin_name() 67 | transport_connection = CxlConnection() 68 | root_port_device = CxlRootPortDevice(downstream_connection=transport_connection, label="Port0") 69 | mhsld_device = MultiHeadedSingleLogicalDevice( 70 | num_ports, 71 | memory_size=memory_size, 72 | memory_file=memory_file, 73 | serial_number="AAAAAAAAAAAAAAAA", 74 | test_mode=True, 75 | cxl_connection=transport_connection, 76 | ) 77 | memory_base_address = 0xFE000000 78 | 79 | async def wait_and_stop(): 80 | await mhsld_device.wait_for_ready() 81 | await root_port_device.enumerate(memory_base_address) 82 | await mhsld_device.stop() 83 | 84 | tasks = [create_task(mhsld_device.run()), create_task(wait_and_stop())] 85 | await gather(*tasks) 86 | -------------------------------------------------------------------------------- /tests/test_cxl_device_single_logical_device.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from asyncio import gather, create_task 9 | import pytest 10 | 11 | from opencis.apps.single_logical_device import SingleLogicalDevice 12 | from opencis.cxl.device.root_port_device import CxlRootPortDevice 13 | from opencis.cxl.component.cxl_connection import CxlConnection 14 | from opencis.util.memory import get_memory_bin_name 15 | from opencis.util.number_const import MB 16 | 17 | # This test will cause many duplicate code between MH-SLD, disable duplicate-code lint here 18 | # pylint: disable=duplicate-code 19 | 20 | 21 | def test_single_logical_device(): 22 | memory_size = 256 * MB 23 | memory_file = get_memory_bin_name() 24 | transport_connection = CxlConnection() 25 | SingleLogicalDevice( 26 | memory_size=memory_size, 27 | memory_file=memory_file, 28 | serial_number="BBBBBBBBBBBBBBBB", 29 | test_mode=True, 30 | cxl_connection=transport_connection, 31 | ) 32 | 33 | 34 | @pytest.mark.asyncio 35 | async def test_single_logical_device_run_stop(get_gold_std_reg_vals): 36 | memory_size = 256 * MB 37 | memory_file = get_memory_bin_name() 38 | transport_connection = CxlConnection() 39 | device = SingleLogicalDevice( 40 | memory_size=memory_size, 41 | memory_file=memory_file, 42 | serial_number="AAAAAAAAAAAAAAAA", 43 | test_mode=True, 44 | cxl_connection=transport_connection, 45 | ) 46 | 47 | # check register values after initialization 48 | reg_vals = str(device.get_reg_vals()) 49 | reg_vals_expected = get_gold_std_reg_vals("SLD") 50 | assert reg_vals == reg_vals_expected 51 | 52 | async def wait_and_stop(): 53 | await device.wait_for_ready() 54 | await device.stop() 55 | 56 | tasks = [create_task(device.run()), create_task(wait_and_stop())] 57 | await gather(*tasks) 58 | 59 | 60 | @pytest.mark.asyncio 61 | async def test_single_logical_device_enumeration(): 62 | memory_size = 256 * MB 63 | memory_file = get_memory_bin_name() 64 | transport_connection = CxlConnection() 65 | root_port_device = CxlRootPortDevice(downstream_connection=transport_connection, label="Port0") 66 | device = SingleLogicalDevice( 67 | memory_size=memory_size, 68 | memory_file=memory_file, 69 | serial_number="BBBBBBBBBBBBBBBB", 70 | test_mode=True, 71 | cxl_connection=transport_connection, 72 | ) 73 | memory_base_address = 0xFE000000 74 | 75 | async def wait_and_stop(): 76 | await device.wait_for_ready() 77 | await root_port_device.enumerate(memory_base_address) 78 | await device.stop() 79 | 80 | tasks = [create_task(device.run()), create_task(wait_and_stop())] 81 | await gather(*tasks) 82 | -------------------------------------------------------------------------------- /tests/test_cxl_device_type1_device.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | # pylint: disable=duplicate-code 9 | from asyncio import gather, create_task 10 | import pytest 11 | 12 | from opencis.cxl.device.cxl_type1_device import ( 13 | CxlType1Device, 14 | CxlType1DeviceConfig, 15 | ) 16 | from opencis.cxl.device.root_port_device import CxlRootPortDevice 17 | from opencis.cxl.component.cxl_connection import CxlConnection 18 | 19 | 20 | def test_type1_device(): 21 | device_config = CxlType1DeviceConfig( 22 | device_name="CXLType1Device", 23 | transport_connection=CxlConnection(), 24 | ) 25 | CxlType1Device(device_config) 26 | 27 | 28 | @pytest.mark.asyncio 29 | async def test_type1_device_run_stop(get_gold_std_reg_vals): 30 | device_config = CxlType1DeviceConfig( 31 | device_name="CXLType1Device", 32 | transport_connection=CxlConnection(), 33 | ) 34 | device = CxlType1Device(device_config) 35 | 36 | # check register values after initialization 37 | reg_vals = str(device.get_reg_vals()) 38 | reg_vals_expected = get_gold_std_reg_vals("ACCEL_TYPE_1") 39 | assert reg_vals == reg_vals_expected 40 | 41 | async def wait_and_stop(): 42 | await device.wait_for_ready() 43 | await device.stop() 44 | 45 | tasks = [create_task(device.run()), create_task(wait_and_stop())] 46 | await gather(*tasks) 47 | 48 | 49 | @pytest.mark.asyncio 50 | async def test_type1_device_enumeration(): 51 | transport_connection = CxlConnection() 52 | device_config = CxlType1DeviceConfig( 53 | device_name="CXLType1Device", 54 | transport_connection=transport_connection, 55 | ) 56 | root_port_device = CxlRootPortDevice(downstream_connection=transport_connection, label="Port0") 57 | device = CxlType1Device(device_config) 58 | memory_base_address = 0xFE000000 59 | 60 | async def wait_and_stop(): 61 | await device.wait_for_ready() 62 | await root_port_device.enumerate(memory_base_address) 63 | await device.stop() 64 | 65 | tasks = [create_task(device.run()), create_task(wait_and_stop())] 66 | await gather(*tasks) 67 | -------------------------------------------------------------------------------- /tests/test_cxl_device_type2_device.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | # pylint: disable=duplicate-code 9 | from asyncio import gather, create_task 10 | import pytest 11 | 12 | from opencis.cxl.device.cxl_type2_device import ( 13 | CxlType2Device, 14 | CxlType2DeviceConfig, 15 | ) 16 | from opencis.cxl.device.root_port_device import CxlRootPortDevice 17 | from opencis.cxl.component.cxl_connection import CxlConnection 18 | from opencis.util.memory import get_memory_bin_name 19 | from opencis.util.number_const import MB 20 | 21 | 22 | def test_type2_device(): 23 | device_config = CxlType2DeviceConfig( 24 | device_name="CXLType2Device", 25 | transport_connection=CxlConnection(), 26 | memory_size=256 * MB, 27 | memory_file=get_memory_bin_name(), 28 | ) 29 | CxlType2Device(device_config) 30 | 31 | 32 | @pytest.mark.asyncio 33 | async def test_type2_device_run_stop(get_gold_std_reg_vals): 34 | device_config = CxlType2DeviceConfig( 35 | device_name="CXLType2Device", 36 | transport_connection=CxlConnection(), 37 | memory_size=256 * MB, 38 | memory_file=get_memory_bin_name(), 39 | ) 40 | device = CxlType2Device(device_config) 41 | 42 | # check register values after initialization 43 | reg_vals = str(device.get_reg_vals()) 44 | reg_vals_expected = get_gold_std_reg_vals("ACCEL_TYPE_2") 45 | assert reg_vals == reg_vals_expected 46 | 47 | async def wait_and_stop(): 48 | await device.wait_for_ready() 49 | await device.stop() 50 | 51 | tasks = [create_task(device.run()), create_task(wait_and_stop())] 52 | await gather(*tasks) 53 | 54 | 55 | @pytest.mark.asyncio 56 | async def test_type2_device_enumeration(): 57 | transport_connection = CxlConnection() 58 | device_config = CxlType2DeviceConfig( 59 | device_name="CXLType2Device", 60 | transport_connection=transport_connection, 61 | memory_size=256 * MB, 62 | memory_file=get_memory_bin_name(), 63 | ) 64 | root_port_device = CxlRootPortDevice(downstream_connection=transport_connection, label="Port0") 65 | device = CxlType2Device(device_config) 66 | memory_base_address = 0xFE000000 67 | 68 | async def wait_and_stop(): 69 | await device.wait_for_ready() 70 | await root_port_device.enumerate(memory_base_address) 71 | await device.stop() 72 | 73 | tasks = [create_task(device.run()), create_task(wait_and_stop())] 74 | await gather(*tasks) 75 | -------------------------------------------------------------------------------- /tests/test_cxl_device_upstream_port.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from asyncio import gather, create_task 9 | import pytest 10 | 11 | from opencis.cxl.device.upstream_port_device import ( 12 | UpstreamPortDevice, 13 | CXL_COMPONENT_TYPE, 14 | ) 15 | from opencis.cxl.component.cxl_connection import CxlConnection 16 | 17 | 18 | def test_upstream_port_device(): 19 | transport_connection = CxlConnection() 20 | device = UpstreamPortDevice(transport_connection=transport_connection, port_index=0) 21 | assert device.get_device_type() == CXL_COMPONENT_TYPE.USP 22 | 23 | 24 | @pytest.mark.asyncio 25 | async def test_upstream_port_device_run_stop(): 26 | transport_connection = CxlConnection() 27 | device = UpstreamPortDevice(transport_connection=transport_connection, port_index=0) 28 | 29 | async def wait_and_stop(): 30 | await device.wait_for_ready() 31 | await device.stop() 32 | 33 | tasks = [create_task(device.run()), create_task(wait_and_stop())] 34 | await gather(*tasks) 35 | -------------------------------------------------------------------------------- /tests/test_cxl_doe_table_access.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from opencis.pci.config_space.pcie.doe import DoeStatus 9 | from opencis.cxl.config_space.doe.doe import ( 10 | DoeExtendedCapability, 11 | CxlDoeExtendedCapability, 12 | ) 13 | from opencis.cxl.config_space.doe.doe_table_access import ( 14 | DoeTableAccessRequest, 15 | DoeTableAccessResponse, 16 | DoeTableAccessResponseOptions, 17 | DOE_CXL_VENDOR_ID, 18 | DOE_CXL_OBJECT_TYPE_TABLE_ACCESS, 19 | ) 20 | from opencis.cxl.config_space.doe.cdat import CdatHeader 21 | from opencis.pci.config_space.pcie.doe import ( 22 | DOE_REGISTER_OFFSET, 23 | ) 24 | from opencis.util.number_const import DWORD_BYTES 25 | 26 | 27 | def read_doe_status(doe: DoeExtendedCapability) -> DoeStatus: 28 | doe.read_bytes(DOE_REGISTER_OFFSET.STATUS, DOE_REGISTER_OFFSET.STATUS + DWORD_BYTES - 1) 29 | return doe.doe_status 30 | 31 | 32 | def test_doe_table_access(): 33 | # pylint: disable=duplicate-code 34 | doe = CxlDoeExtendedCapability() 35 | 36 | request = DoeTableAccessRequest() 37 | request.header.vendor_id = DOE_CXL_VENDOR_ID 38 | request.header.data_object_type = DOE_CXL_OBJECT_TYPE_TABLE_ACCESS 39 | request.header.length = len(request) // DWORD_BYTES 40 | 41 | for dword_index in range(3): 42 | doe.write_bytes( 43 | DOE_REGISTER_OFFSET.WRITE_DATA_MAILBOX, 44 | DOE_REGISTER_OFFSET.WRITE_DATA_MAILBOX + DWORD_BYTES - 1, 45 | request.read_bytes(dword_index * DWORD_BYTES, (dword_index + 1) * DWORD_BYTES - 1), 46 | ) 47 | 48 | doe.write_bytes( 49 | DOE_REGISTER_OFFSET.CONTROL, 50 | DOE_REGISTER_OFFSET.CONTROL + DWORD_BYTES - 1, 51 | 0x80000000, 52 | ) 53 | assert read_doe_status(doe).data_object_ready == 1 54 | 55 | options: DoeTableAccessResponseOptions = {"structure_size": len(CdatHeader())} 56 | response = DoeTableAccessResponse(options=options) 57 | 58 | offset = 0 59 | while read_doe_status(doe).data_object_ready == 1: 60 | data = doe.read_bytes( 61 | DOE_REGISTER_OFFSET.READ_DATA_MAILBOX, 62 | DOE_REGISTER_OFFSET.READ_DATA_MAILBOX + DWORD_BYTES - 1, 63 | ) 64 | response.write_bytes(offset, offset + DWORD_BYTES - 1, data) 65 | offset += DWORD_BYTES 66 | doe.write_bytes( 67 | DOE_REGISTER_OFFSET.READ_DATA_MAILBOX, 68 | DOE_REGISTER_OFFSET.READ_DATA_MAILBOX + DWORD_BYTES - 1, 69 | 0, 70 | ) 71 | assert response.header.vendor_id == DOE_CXL_VENDOR_ID 72 | assert response.header.data_object_type == DOE_CXL_OBJECT_TYPE_TABLE_ACCESS 73 | assert response.header.length == len(response) // DWORD_BYTES 74 | assert response.entry_handle == 0xFFFF 75 | -------------------------------------------------------------------------------- /tests/test_cxl_log_manager.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | -------------------------------------------------------------------------------- /tests/test_cxl_mmio.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from opencis.cxl.mmio import CombinedMmioRegister, CombinedMmioRegiterOptions 9 | from opencis.cxl.component.cxl_memory_device_component import ( 10 | CxlMemoryDeviceComponent, 11 | MemoryDeviceIdentity, 12 | ) 13 | 14 | 15 | def test_mmio_register_with_cxl_memory_device_component(): 16 | # pylint: disable=duplicate-code 17 | # CE-94 18 | identity = MemoryDeviceIdentity() 19 | identity.fw_revision = MemoryDeviceIdentity.ascii_str_to_int("EEUM EMU 1.0", 16) 20 | identity.total_capacity = 256 * 1024 * 1024 21 | identity.volatile_only_capacity = 256 * 1024 * 1024 22 | identity.persistent_only_capacity = 0 23 | identity.partition_alignment = 0 24 | cxl_component = CxlMemoryDeviceComponent(identity) 25 | options = CombinedMmioRegiterOptions(cxl_component=cxl_component) 26 | register = CombinedMmioRegister(options=options) 27 | len_expected = CombinedMmioRegister.get_size_from_options(options) 28 | assert len_expected == len(register) 29 | -------------------------------------------------------------------------------- /tests/test_cxl_mmio_component_register.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from opencis.cxl.mmio.component_register import ( 9 | CxlComponentRegister, 10 | CxlComponentRegisterOptions, 11 | ) 12 | from opencis.cxl.component.cxl_memory_device_component import ( 13 | CxlMemoryDeviceComponent, 14 | MemoryDeviceIdentity, 15 | ) 16 | 17 | 18 | def test_cxl_component_register(): 19 | # pylint: disable=duplicate-code 20 | identity = MemoryDeviceIdentity() 21 | identity.fw_revision = MemoryDeviceIdentity.ascii_str_to_int("EEUM EMU 1.0", 16) 22 | identity.total_capacity = 256 * 1024 * 1024 23 | identity.volatile_only_capacity = 256 * 1024 * 1024 24 | identity.persistent_only_capacity = 0 25 | identity.partition_alignment = 0 26 | cxl_memory_device_component = CxlMemoryDeviceComponent(identity) 27 | options = CxlComponentRegisterOptions(cxl_component=cxl_memory_device_component) 28 | CxlComponentRegister(options=options) 29 | -------------------------------------------------------------------------------- /tests/test_dynamic_fields.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | import pytest 9 | 10 | from opencis.util.unaligned_bit_structure import ( 11 | UnalignedBitStructure, 12 | ByteField, 13 | DynamicByteField, 14 | ) 15 | 16 | from opencis.cxl.transport.transaction import ( 17 | CxlIoMemWrPacket, 18 | ) 19 | 20 | 21 | class DynamicByteStructure(UnalignedBitStructure): 22 | field1: int 23 | field2: int 24 | field3: int 25 | payload: int 26 | _fields = [ 27 | ByteField("field1", 0, 0), # 1 Byte 28 | ByteField("field2", 1, 2), # 2 Bytes 29 | ByteField("field3", 3, 5), # 3 Bytes 30 | DynamicByteField("payload", 6, 1), 31 | ] 32 | 33 | 34 | def test_basic_dbf(): 35 | pckt = bytes([35, 25, 85, 90, 15, 100, 200, 210, 95]) 36 | DBS = DynamicByteStructure() 37 | DBS.reset(pckt) 38 | assert DBS.field1 == 0x23 39 | assert DBS.field2 == 0x5519 40 | assert DBS.field3 == 0x640F5A 41 | assert DBS.payload == 0x5FD2C8 42 | assert len(DBS) == len(pckt) 43 | 44 | pckt2 = bytes([1, 1, 1, 1, 1, 1, 2, 3]) 45 | DBS.reset(pckt2) 46 | assert DBS.field3 == 0x10101 47 | assert DBS.payload == 0x302 48 | assert len(DBS) == len(pckt2) 49 | 50 | pckt3 = bytes([1, 1, 1, 1, 1, 1, 8, 16, 24, 32, 110, 251]) 51 | DBS.reset(pckt3) 52 | assert DBS.field3 == 0x10101 53 | assert DBS.payload == 0xFB6E20181008 54 | assert len(DBS) == len(pckt3) 55 | 56 | 57 | class DisallowedDyBStruct(UnalignedBitStructure): 58 | field1: int 59 | field2: int 60 | field3: int 61 | payload: int 62 | _fields = [ 63 | ByteField("field1", 0, 0), # 1 Byte 64 | ByteField("field2", 1, 2), # 2 Bytes 65 | ByteField("field3", 3, 5), # 3 Bytes 66 | DynamicByteField("payload", 6, 2), 67 | DynamicByteField("payload2", 8, 4), 68 | ] 69 | 70 | 71 | class DisallowedDyBStruct2(UnalignedBitStructure): 72 | field1: int 73 | field2: int 74 | field3: int 75 | payload: int 76 | _fields = [ 77 | ByteField("field1", 0, 0), # 1 Byte 78 | ByteField("field2", 1, 2), # 2 Bytes 79 | DynamicByteField("payload", 3, 2), 80 | ByteField("field3", 5, 8), # 4 Bytes 81 | ] 82 | 83 | 84 | def test_unpleasant_dbf_failure(): 85 | # pylint: disable=unused-variable 86 | with pytest.raises(Exception) as exc_info: 87 | DBS_Bad = DisallowedDyBStruct() 88 | assert ( 89 | exc_info.value.args[0] 90 | == "The current implementation does not allow for more than one dynamic byte field." 91 | ) 92 | 93 | with pytest.raises(Exception) as exc_info: 94 | DBS_Bad2 = DisallowedDyBStruct2() 95 | assert ( 96 | exc_info.value.args[0][-68:] 97 | == "DynamicByteFields must be the last field in their respective packets" 98 | ) 99 | 100 | 101 | def test_io_mem_wr(): 102 | addr = 0x0 103 | data = 0xDEADBEEF 104 | packet = CxlIoMemWrPacket.create(addr, 4, data=data) 105 | assert packet.data == data 106 | -------------------------------------------------------------------------------- /tests/test_pci_config_space_headers.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from unittest.mock import MagicMock 9 | import pytest 10 | 11 | from opencis.pci.config_space.pci import ( 12 | PciConfigSpaceClassCode, 13 | PciConfigSpaceType0, 14 | PciConfigSpaceOptions, 15 | PCI_CONFIG_SPACE_HEADER_SIZE, 16 | ) 17 | from opencis.pci.component.mmio_manager import BarInfo 18 | from opencis.pci.component.pci import PciComponent, PciComponentIdentity 19 | 20 | 21 | def test_pci_config_space_class_code(): 22 | with pytest.raises(Exception) as exception_info: 23 | PciConfigSpaceClassCode() 24 | 25 | assert str(exception_info.value) == "options is required" 26 | 27 | 28 | def test_pci_config_space_type0(): 29 | mmio_manager = MagicMock() 30 | identity = PciComponentIdentity( 31 | vendor_id=0x1234, 32 | device_id=0x5678, 33 | base_class_code=1, 34 | sub_class_coce=2, 35 | programming_interface=3, 36 | subsystem_vendor_id=4, 37 | subsystem_id=5, 38 | ) 39 | 40 | pci_component = PciComponent(identity=identity, mmio_manager=mmio_manager) 41 | get_bar_size = MagicMock() 42 | get_bar_size.side_effect = [0x1000, 0, 0, 0, 0, 0] 43 | mmio_manager.get_bar_size = get_bar_size 44 | get_bar_info = MagicMock() 45 | get_bar_info.side_effect = [BarInfo(), None, None, None, None, None] 46 | mmio_manager.get_bar_info = get_bar_info 47 | 48 | options = PciConfigSpaceOptions(capability_pointer=0x40, pci_component=pci_component) 49 | register = PciConfigSpaceType0(options=options) 50 | assert register.vendor_id == 0x1234 51 | assert register.device_id == 0x5678 52 | 53 | options = PciConfigSpaceOptions(capability_pointer=0x40) 54 | register = PciConfigSpaceType0(options=options) 55 | assert register.vendor_id == 0 56 | assert register.device_id == 0 57 | 58 | expected_size = PciConfigSpaceType0.get_size_from_options(options) 59 | assert expected_size == PCI_CONFIG_SPACE_HEADER_SIZE 60 | -------------------------------------------------------------------------------- /tests/test_pci_config_space_msi.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from opencis.pci.config_space.pcie.msi import ( 9 | MsiCapability, 10 | MsiCapabilityHeader, 11 | MsiCapabilityOptions, 12 | ) 13 | 14 | 15 | def test_msi_without_options(): 16 | msi = MsiCapability() 17 | assert len(msi) == msi.get_size_from_options() 18 | 19 | 20 | def test_msi_with_options(): 21 | options: MsiCapabilityOptions = {"next_capability_offset": 1} 22 | msi = MsiCapability(options=options) 23 | assert len(msi) == msi.get_size_from_options(options) 24 | assert msi.capability_header.next_capability_offset == 1 25 | 26 | 27 | def test_msi_cap_header_without_options(): 28 | msi_cap_header = MsiCapabilityHeader() 29 | assert len(msi_cap_header) == msi_cap_header.get_size_from_options() 30 | -------------------------------------------------------------------------------- /tests/test_util_number.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2024-2025, Eeum, Inc. 3 | 4 | This software is licensed under the terms of the Revised BSD License. 5 | See LICENSE for details. 6 | """ 7 | 8 | from opencis.util.number import round_up_to_power_of_2 9 | 10 | 11 | def test_round_up_to_power_of_2(): 12 | assert round_up_to_power_of_2(7) == 8 13 | assert round_up_to_power_of_2(20) == 32 14 | assert round_up_to_power_of_2(100) == 128 15 | assert round_up_to_power_of_2(200) == 256 16 | assert round_up_to_power_of_2(300) == 512 17 | assert round_up_to_power_of_2(600) == 1024 18 | assert round_up_to_power_of_2(2000) == 2048 19 | assert round_up_to_power_of_2(4000) == 4096 20 | assert round_up_to_power_of_2(8000) == 8192 21 | assert round_up_to_power_of_2(12000) == 16384 22 | assert round_up_to_power_of_2(20000) == 32768 23 | -------------------------------------------------------------------------------- /traces/qemu-s8000-h40026.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opencis/opencis-core/cbcc15acffe5f0cadd770ae960ad1293479feb4a/traces/qemu-s8000-h40026.pcap --------------------------------------------------------------------------------