├── python ├── __init__.py ├── web │ ├── __init__.py │ ├── src │ │ ├── __init__.py │ │ ├── pwa │ │ │ ├── favicon.ico │ │ │ ├── apple-icon.png │ │ │ ├── favicon-16x16.png │ │ │ ├── favicon-32x32.png │ │ │ ├── favicon-96x96.png │ │ │ ├── ms-icon-70x70.png │ │ │ ├── apple-icon-57x57.png │ │ │ ├── apple-icon-60x60.png │ │ │ ├── apple-icon-72x72.png │ │ │ ├── apple-icon-76x76.png │ │ │ ├── ms-icon-144x144.png │ │ │ ├── ms-icon-150x150.png │ │ │ ├── ms-icon-310x310.png │ │ │ ├── android-icon-36x36.png │ │ │ ├── android-icon-48x48.png │ │ │ ├── android-icon-72x72.png │ │ │ ├── android-icon-96x96.png │ │ │ ├── apple-icon-114x114.png │ │ │ ├── apple-icon-120x120.png │ │ │ ├── apple-icon-144x144.png │ │ │ ├── apple-icon-152x152.png │ │ │ ├── apple-icon-180x180.png │ │ │ ├── android-icon-144x144.png │ │ │ ├── android-icon-192x192.png │ │ │ ├── apple-icon-precomposed.png │ │ │ ├── browserconfig.xml │ │ │ └── manifest.json │ │ ├── templates │ │ │ ├── diskinfo.html │ │ │ ├── manpage.html │ │ │ ├── logs.html │ │ │ └── deviceinfo.html │ │ ├── static │ │ │ └── themes │ │ │ │ └── modern │ │ │ │ └── icons │ │ │ │ ├── cloud.svg │ │ │ │ ├── success.svg │ │ │ │ ├── device-optical.svg │ │ │ │ ├── device-reserved.svg │ │ │ │ ├── home.svg │ │ │ │ ├── upload-success.svg │ │ │ │ ├── cloud-off.svg │ │ │ │ ├── info.svg │ │ │ │ ├── upload-error.svg │ │ │ │ ├── file-copy.svg │ │ │ │ ├── file-delete.svg │ │ │ │ ├── log-out.svg │ │ │ │ ├── upload-queued.svg │ │ │ │ ├── file-rename.svg │ │ │ │ ├── upload-in-progress.svg │ │ │ │ ├── command.svg │ │ │ │ ├── external-link.svg │ │ │ │ ├── device-removable.svg │ │ │ │ ├── error.svg │ │ │ │ ├── device-printer.svg │ │ │ │ ├── device-network.svg │ │ │ │ ├── warning.svg │ │ │ │ ├── device-other.svg │ │ │ │ ├── device-hard-drive.svg │ │ │ │ ├── file-info.svg │ │ │ │ ├── LICENSE │ │ │ │ ├── file-device-attach.svg │ │ │ │ └── file-extract.svg │ │ └── settings.py │ ├── tests │ │ ├── output │ │ │ └── .gitkeep │ │ ├── assets │ │ │ ├── test_image.7z │ │ │ ├── test_image.sit │ │ │ └── test_image.zip │ │ └── api │ │ │ └── test_auth.py │ ├── mock │ │ └── bin │ │ │ ├── git │ │ │ ├── journalctl │ │ │ ├── systemctl │ │ │ ├── brctl │ │ │ ├── hostnamectl │ │ │ └── vcgencmd │ ├── .prettierrc.json │ ├── babel.cfg │ ├── package.json │ ├── pyproject.toml │ ├── .stylelintrc.json │ ├── service-infra │ │ ├── piscsi-web.service │ │ ├── 502.html │ │ └── nginx-default.conf │ ├── requirements.txt │ ├── requirements-dev.txt │ └── translation_update.sh ├── common │ ├── tests │ │ └── .gitkeep │ ├── src │ │ ├── __init__.py │ │ ├── piscsi │ │ │ ├── __init__.py │ │ │ ├── exceptions.py │ │ │ ├── common_settings.py │ │ │ └── return_codes.py │ │ ├── util │ │ │ ├── __init__.py │ │ │ └── run.py │ │ └── README.md │ ├── resources │ │ ├── type_writer.ttf │ │ ├── splash_stop_32.bmp │ │ ├── splash_stop_64.bmp │ │ ├── splash_start_32.bmp │ │ ├── splash_start_64.bmp │ │ ├── DejaVuSansMono-Bold.ttf │ │ └── README.md │ └── requirements.txt ├── ctrlboard │ ├── __init__.py │ ├── src │ │ ├── __init__.py │ │ ├── menu │ │ │ ├── __init__.py │ │ │ ├── menu_builder.py │ │ │ ├── timer.py │ │ │ ├── blank_screensaver.py │ │ │ ├── menu_renderer_adafruit_ssd1306.py │ │ │ ├── menu.py │ │ │ ├── screensaver.py │ │ │ ├── menu_renderer_luma_oled.py │ │ │ └── menu_renderer_config.py │ │ ├── ctrlboard_hw │ │ │ ├── __init__.py │ │ │ ├── hardware_button.py │ │ │ ├── ctrlboard_hw_constants.py │ │ │ └── encoder.py │ │ ├── ctrlboard_event_handler │ │ │ ├── __init__.py │ │ │ ├── ctrlboard_print_event_handler.py │ │ │ ├── piscsi_profile_cycler.py │ │ │ └── piscsi_shutdown_cycler.py │ │ ├── observer.py │ │ ├── observable.py │ │ ├── config.py │ │ └── piscsi_menu_controller.py │ ├── requirements.txt │ ├── service-infra │ │ └── piscsi-ctrlboard.service │ └── README.md ├── oled │ ├── src │ │ ├── __init__.py │ │ └── interrupt_handler.py │ ├── tests │ │ └── .gitkeep │ ├── service-infra │ │ └── piscsi-oled.service │ ├── requirements.txt │ └── README.md ├── .flake8 ├── pyproject.toml └── README.md ├── docker ├── volumes │ ├── config │ │ └── .gitkeep │ └── images │ │ └── .gitkeep ├── docker-compose.override.yml.example ├── pytest │ └── Dockerfile ├── backend │ ├── piscsi_wrapper.sh │ └── Dockerfile ├── web │ ├── web_start_wrapper.sh │ └── Dockerfile ├── docker-compose.ci.yml └── docker-compose.yml ├── _config.yml ├── .github ├── CODEOWNERS ├── FUNDING.yml ├── ISSUE_TEMPLATE.md ├── dependabot.yml └── workflows │ └── build_code.yml ├── os_integration ├── piscsi.conf ├── dhcpcd.conf.patch ├── piscsi_bridge └── piscsi.service ├── doc ├── logo │ ├── redpanda-16x16.png │ ├── redpanda-32x32.png │ ├── redpanda-mono.ai │ ├── redpanda-mono.png │ └── redpanda-rgb.png ├── scsimon.1 ├── scsiloop.1 ├── scsimon_man_page.txt └── scsiloop_man_page.txt ├── hw └── README.md ├── cpp ├── .gitignore ├── hal │ ├── gpiobus_factory.h │ ├── data_sample.cpp │ ├── log.h │ ├── gpiobus_factory.cpp │ ├── systimer.cpp │ ├── data_sample.h │ ├── systimer.h │ ├── sbc_version.h │ ├── pin_control.h │ ├── connection_type │ │ ├── connection_aibom.h │ │ ├── connection_fullspec.h │ │ ├── connection_gamernium.h │ │ └── connection_standard.h │ └── systimer_raspberry.h ├── piscsi │ ├── piscsi.cpp │ ├── piscsi_service.h │ ├── command_context.h │ ├── piscsi_image.h │ ├── piscsi_core.h │ └── localizer.h ├── scsimon │ ├── scsimon.cpp │ ├── sm_reports.h │ └── sm_core.h ├── scsictl │ ├── scsictl.cpp │ ├── scsictl_core.h │ ├── scsictl_parser.cpp │ ├── scsictl_parser.h │ ├── scsictl_display.h │ └── scsictl_commands.h ├── scsiloop │ ├── scsiloop_timer.h │ ├── scsiloop.cpp │ ├── scsiloop_core.h │ ├── scsiloop_cout.cpp │ ├── scsiloop_cout.h │ └── scsiloop_gpio.h ├── scsidump │ └── scsidump.cpp ├── shared │ ├── piscsi_version.h │ ├── network_util.h │ ├── config.h │ ├── piscsi_version.cpp │ ├── piscsi_exceptions.h │ ├── piscsi_util.h │ ├── protobuf_util.h │ └── network_util.cpp ├── devices │ ├── interfaces │ │ ├── scsi_mmc_commands.h │ │ ├── scsi_printer_commands.h │ │ ├── scsi_block_commands.h │ │ └── scsi_primary_commands.h │ ├── device_logger.h │ ├── scsihd.h │ ├── mode_page_device.h │ ├── cd_track.h │ ├── scsimo.h │ ├── device_logger.cpp │ ├── host_services.h │ ├── device_factory.h │ ├── scsihd_nec.h │ ├── disk_track.h │ ├── scsicd.h │ ├── ctapdriver.h │ ├── scsi_printer.h │ ├── scsi_command_util.h │ └── scsi_streamer.h ├── test │ ├── linux_os_stubs.h │ ├── network_util_test.cpp │ ├── test_setup.cpp │ ├── localizer_test.cpp │ ├── ctapdriver_test.cpp │ ├── piscsi_exceptions_test.cpp │ ├── test_shared.h │ ├── linux_os_stubs.cpp │ ├── scsictl_parser_test.cpp │ └── scsidump_test.cpp └── controllers │ ├── phase_handler.cpp │ ├── controller_manager.h │ └── phase_handler.h ├── ide_setup └── README ├── .dockerignore ├── .gitignore └── LICENSE /python/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/web/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/common/tests/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/ctrlboard/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/oled/src/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/oled/tests/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/web/src/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docker/volumes/config/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docker/volumes/images/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/common/src/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/ctrlboard/src/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/web/tests/output/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/common/src/piscsi/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/common/src/util/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/ctrlboard/src/menu/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-midnight -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @akuker @rdmark 2 | -------------------------------------------------------------------------------- /python/ctrlboard/src/ctrlboard_hw/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/ctrlboard/src/ctrlboard_event_handler/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/web/mock/bin/git: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | exit 0 3 | -------------------------------------------------------------------------------- /python/web/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100 3 | } 4 | -------------------------------------------------------------------------------- /python/web/babel.cfg: -------------------------------------------------------------------------------- 1 | [python: **.py] 2 | [jinja2: **/templates/**.html] 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: akuker 4 | -------------------------------------------------------------------------------- /os_integration/piscsi.conf: -------------------------------------------------------------------------------- 1 | if $programname == 'PISCSI' then /var/log/piscsi.log 2 | & stop 3 | -------------------------------------------------------------------------------- /doc/logo/redpanda-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/doc/logo/redpanda-16x16.png -------------------------------------------------------------------------------- /doc/logo/redpanda-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/doc/logo/redpanda-32x32.png -------------------------------------------------------------------------------- /doc/logo/redpanda-mono.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/doc/logo/redpanda-mono.ai -------------------------------------------------------------------------------- /doc/logo/redpanda-mono.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/doc/logo/redpanda-mono.png -------------------------------------------------------------------------------- /doc/logo/redpanda-rgb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/doc/logo/redpanda-rgb.png -------------------------------------------------------------------------------- /python/.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 100 3 | exclude = 4 | venv 5 | piscsi_interface_pb2.py -------------------------------------------------------------------------------- /python/web/src/pwa/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/python/web/src/pwa/favicon.ico -------------------------------------------------------------------------------- /python/web/src/pwa/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/python/web/src/pwa/apple-icon.png -------------------------------------------------------------------------------- /python/web/src/pwa/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/python/web/src/pwa/favicon-16x16.png -------------------------------------------------------------------------------- /python/web/src/pwa/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/python/web/src/pwa/favicon-32x32.png -------------------------------------------------------------------------------- /python/web/src/pwa/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/python/web/src/pwa/favicon-96x96.png -------------------------------------------------------------------------------- /python/web/src/pwa/ms-icon-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/python/web/src/pwa/ms-icon-70x70.png -------------------------------------------------------------------------------- /python/web/tests/assets/test_image.7z: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/python/web/tests/assets/test_image.7z -------------------------------------------------------------------------------- /python/common/resources/type_writer.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/python/common/resources/type_writer.ttf -------------------------------------------------------------------------------- /python/web/src/pwa/apple-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/python/web/src/pwa/apple-icon-57x57.png -------------------------------------------------------------------------------- /python/web/src/pwa/apple-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/python/web/src/pwa/apple-icon-60x60.png -------------------------------------------------------------------------------- /python/web/src/pwa/apple-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/python/web/src/pwa/apple-icon-72x72.png -------------------------------------------------------------------------------- /python/web/src/pwa/apple-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/python/web/src/pwa/apple-icon-76x76.png -------------------------------------------------------------------------------- /python/web/src/pwa/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/python/web/src/pwa/ms-icon-144x144.png -------------------------------------------------------------------------------- /python/web/src/pwa/ms-icon-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/python/web/src/pwa/ms-icon-150x150.png -------------------------------------------------------------------------------- /python/web/src/pwa/ms-icon-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/python/web/src/pwa/ms-icon-310x310.png -------------------------------------------------------------------------------- /python/web/tests/assets/test_image.sit: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/python/web/tests/assets/test_image.sit -------------------------------------------------------------------------------- /python/web/tests/assets/test_image.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/python/web/tests/assets/test_image.zip -------------------------------------------------------------------------------- /python/common/resources/splash_stop_32.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/python/common/resources/splash_stop_32.bmp -------------------------------------------------------------------------------- /python/common/resources/splash_stop_64.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/python/common/resources/splash_stop_64.bmp -------------------------------------------------------------------------------- /python/web/src/pwa/android-icon-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/python/web/src/pwa/android-icon-36x36.png -------------------------------------------------------------------------------- /python/web/src/pwa/android-icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/python/web/src/pwa/android-icon-48x48.png -------------------------------------------------------------------------------- /python/web/src/pwa/android-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/python/web/src/pwa/android-icon-72x72.png -------------------------------------------------------------------------------- /python/web/src/pwa/android-icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/python/web/src/pwa/android-icon-96x96.png -------------------------------------------------------------------------------- /python/web/src/pwa/apple-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/python/web/src/pwa/apple-icon-114x114.png -------------------------------------------------------------------------------- /python/web/src/pwa/apple-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/python/web/src/pwa/apple-icon-120x120.png -------------------------------------------------------------------------------- /python/web/src/pwa/apple-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/python/web/src/pwa/apple-icon-144x144.png -------------------------------------------------------------------------------- /python/web/src/pwa/apple-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/python/web/src/pwa/apple-icon-152x152.png -------------------------------------------------------------------------------- /python/web/src/pwa/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/python/web/src/pwa/apple-icon-180x180.png -------------------------------------------------------------------------------- /python/common/resources/splash_start_32.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/python/common/resources/splash_start_32.bmp -------------------------------------------------------------------------------- /python/common/resources/splash_start_64.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/python/common/resources/splash_start_64.bmp -------------------------------------------------------------------------------- /python/web/src/pwa/android-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/python/web/src/pwa/android-icon-144x144.png -------------------------------------------------------------------------------- /python/web/src/pwa/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/python/web/src/pwa/android-icon-192x192.png -------------------------------------------------------------------------------- /python/common/resources/DejaVuSansMono-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/python/common/resources/DejaVuSansMono-Bold.ttf -------------------------------------------------------------------------------- /python/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | line-length = 100 3 | target-version = ['py39', 'py310', 'py311'] 4 | extend-exclude = ".*_pb2.py" 5 | -------------------------------------------------------------------------------- /python/web/src/pwa/apple-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiSCSI/piscsi/HEAD/python/web/src/pwa/apple-icon-precomposed.png -------------------------------------------------------------------------------- /hw/README.md: -------------------------------------------------------------------------------- 1 | # Where's the hardware? 2 | 3 | The hardware design files have been moved to a separate repository at: 4 | https://github.com/PiSCSI/piscsi-hw 5 | -------------------------------------------------------------------------------- /python/common/requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2024.7.4 2 | charset-normalizer==3.3.2 3 | idna==3.11 4 | protobuf==3.20.3 5 | requests==2.32.4 6 | urllib3==2.6.0 7 | vcgencmd==0.1.1 8 | -------------------------------------------------------------------------------- /cpp/.gitignore: -------------------------------------------------------------------------------- 1 | *.bak 2 | *.HDA 3 | *.save 4 | *.cbp 5 | *.layout 6 | *.log 7 | *.vcd 8 | *.json 9 | *.html 10 | piscsi.dat 11 | obj 12 | bin 13 | coverage 14 | generated 15 | -------------------------------------------------------------------------------- /python/web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "prettier": "3.7.3", 4 | "stylelint": "^16.26.1", 5 | "stylelint-config-standard": "^39.0.1" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /python/web/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.pytest.ini_options] 2 | addopts = "--junitxml=tests/output/report.xml --log-file=tests/output/pytest.log" 3 | log_cli = true 4 | log_cli_level = "warn" -------------------------------------------------------------------------------- /python/web/.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["stylelint-config-standard"], 3 | "rules": { 4 | "no-descending-specificity": null, 5 | "media-feature-range-notation": "prefix" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /docker/docker-compose.override.yml.example: -------------------------------------------------------------------------------- 1 | services: 2 | web: 3 | volumes: 4 | - ../python:/home/pi/piscsi/python:delegated 5 | pytest: 6 | volumes: 7 | - ../python/web:/src:delegated 8 | -------------------------------------------------------------------------------- /python/web/mock/bin/journalctl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Mock responses to piscsi-web 4 | case $1 in 5 | -n) 6 | echo "logs $*" 7 | ;; 8 | 9 | **) 10 | echo "default" 11 | ;; 12 | esac 13 | -------------------------------------------------------------------------------- /python/web/mock/bin/systemctl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Mock responses to piscsi-web 4 | case $1 in 5 | is-active) 6 | echo "is-active" 7 | ;; 8 | 9 | **) 10 | echo "default" 11 | ;; 12 | esac 13 | -------------------------------------------------------------------------------- /os_integration/dhcpcd.conf.patch: -------------------------------------------------------------------------------- 1 | --- dhcpcd_orig.conf 2021-02-26 17:32:14.065284400 -0600 2 | +++ dhcpcd.conf 2021-02-26 17:32:30.925039567 -0600 3 | @@ -58,3 +58,4 @@ 4 | #interface eth0 5 | #fallback static_eth0 6 | # 7 | +denyinterfaces eth0 8 | -------------------------------------------------------------------------------- /python/web/mock/bin/brctl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Mock responses to piscsi-web 4 | case $1 in 5 | "show") 6 | echo "piscsi_bridge 8000.dca632b05dd1 no eth0" 7 | ;; 8 | 9 | **) 10 | echo "default" 11 | ;; 12 | esac 13 | -------------------------------------------------------------------------------- /python/web/src/templates/diskinfo.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

{{ _("Disk Image Details: %(file_name)s", file_name=file_name) }}

5 |

{{ diskinfo }}

6 | 7 |
8 | {% endblock content %} 9 | -------------------------------------------------------------------------------- /python/ctrlboard/requirements.txt: -------------------------------------------------------------------------------- 1 | luma.core==2.4.1 2 | luma.oled==3.14.0 3 | pillow==11.3.0 4 | protobuf==3.20.3 5 | pyftdi==0.55.0 6 | pyserial==3.5 7 | pyusb==1.3.1 8 | RPi.GPIO==0.7.1 9 | smbus==1.1.post2 10 | smbus2==0.5.0 11 | Unidecode==1.3.2 12 | -------------------------------------------------------------------------------- /ide_setup/README: -------------------------------------------------------------------------------- 1 | The Eclipse code formatter configuration shall be used together with 2 | Eclipse CDT in order to unify the formatting of the C++ code. Ensure to keep 3 | your formatting rules up to date. 4 | 5 | This formatter can also be imported into Intellij IDEA. 6 | -------------------------------------------------------------------------------- /python/web/src/templates/manpage.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

{{ _("Manual for %(app)s", app=app) }}

5 | 6 |
7 | {{ manpage | safe }} 8 |
9 | 10 |
11 | {% endblock content %} 12 | -------------------------------------------------------------------------------- /python/web/src/static/themes/modern/icons/cloud.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Info 2 | 3 | - Which version of Pi are you using: 4 | - Which github revision of software: 5 | - Which board version: 6 | - Which computer is the PiSCSI connected to: 7 | - Which OS you are using (output of 'lsb_release -a'): 8 | 9 | 10 | # Describe the issue 11 | -------------------------------------------------------------------------------- /python/web/src/static/themes/modern/icons/success.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/web/service-infra/piscsi-web.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=PiSCSI-Web service 3 | After=network-online.target piscsi.service 4 | 5 | [Service] 6 | Type=simple 7 | Restart=always 8 | ExecStart=/home/pi/piscsi/python/web/start.sh 9 | SyslogIdentifier=PISCSIWEB 10 | 11 | [Install] 12 | WantedBy=multi-user.target 13 | -------------------------------------------------------------------------------- /python/web/src/pwa/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | #ffffff 3 | -------------------------------------------------------------------------------- /python/web/src/static/themes/modern/icons/device-optical.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docker/pytest/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11-slim 2 | ENV DOCKER=1 3 | 4 | WORKDIR /src 5 | 6 | COPY python/web/requirements-dev.txt /src/requirements-dev.txt 7 | COPY python/web/pyproject.toml /src/pyproject.toml 8 | COPY python/web/tests /src/tests 9 | 10 | RUN pip install --no-cache-dir -r /src/requirements-dev.txt 11 | 12 | ENTRYPOINT ["pytest"] 13 | -------------------------------------------------------------------------------- /python/web/src/static/themes/modern/icons/device-reserved.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/web/src/static/themes/modern/icons/home.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/web/src/static/themes/modern/icons/upload-success.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/web/src/static/themes/modern/icons/cloud-off.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docker/backend/piscsi_wrapper.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [[ $BACKEND_PASSWORD ]]; then 4 | TOKEN_FILE="/home/pi/.config/piscsi/piscsi_secret" 5 | mkdir -p /home/pi/.config/piscsi || true 6 | echo $BACKEND_PASSWORD > $TOKEN_FILE 7 | chmod 700 $TOKEN_FILE 8 | /usr/local/bin/piscsi "$@" -P $TOKEN_FILE 9 | else 10 | /usr/local/bin/piscsi "$@" 11 | fi 12 | -------------------------------------------------------------------------------- /python/web/src/static/themes/modern/icons/info.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/web/src/static/themes/modern/icons/upload-error.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/web/src/static/themes/modern/icons/file-copy.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/web/src/static/themes/modern/icons/file-delete.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/web/src/static/themes/modern/icons/log-out.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/web/src/static/themes/modern/icons/upload-queued.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/web/src/static/themes/modern/icons/file-rename.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/web/src/static/themes/modern/icons/upload-in-progress.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/web/src/static/themes/modern/icons/command.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /python/ctrlboard/service-infra/piscsi-ctrlboard.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=PiSCSI Control Board service 3 | After=network-online.target piscsi.service 4 | 5 | [Service] 6 | Type=simple 7 | Restart=always 8 | RestartSec=2s 9 | ExecStart=/home/pi/piscsi/python/ctrlboard/start.sh 10 | ExecStop=/bin/pkill --signal 2 -f "python3 src/main.py" 11 | SyslogIdentifier=PISCSICTRLB 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | -------------------------------------------------------------------------------- /python/web/src/static/themes/modern/icons/external-link.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/web/src/static/themes/modern/icons/device-removable.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/web/src/static/themes/modern/icons/error.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/ctrlboard/src/observer.py: -------------------------------------------------------------------------------- 1 | """Module implementing the Observer part of the Observer pattern""" 2 | 3 | from abc import ABC, abstractmethod 4 | 5 | 6 | # pylint: disable=too-few-public-methods 7 | class Observer(ABC): 8 | """Class implementing an abserver""" 9 | 10 | @abstractmethod 11 | def update(self, updated_object) -> None: 12 | """Abstract method for updating an observer. Needs to be implemented by subclasses.""" 13 | -------------------------------------------------------------------------------- /python/web/src/static/themes/modern/icons/device-printer.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/web/src/static/themes/modern/icons/device-network.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/web/src/static/themes/modern/icons/warning.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /os_integration/piscsi_bridge: -------------------------------------------------------------------------------- 1 | # 2 | # Defines the 'piscsi_bridge' bridge that connects the PiSCSI network 3 | # interface (ex DaynaPort SCSI/Link) to the outside world. 4 | # 5 | # Depending upon your system configuration, you may need to update this 6 | # file to change 'eth0' to your Ethernet interface 7 | # 8 | # This file should be place in /etc/network/interfaces.d 9 | 10 | auto piscsi_bridge 11 | iface piscsi_bridge inet dhcp 12 | bridge_ports eth0 13 | 14 | -------------------------------------------------------------------------------- /python/web/src/static/themes/modern/icons/device-other.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/ctrlboard/src/menu/menu_builder.py: -------------------------------------------------------------------------------- 1 | """Module for creating menus""" 2 | 3 | from abc import ABC, abstractmethod 4 | from menu.menu import Menu 5 | 6 | 7 | # pylint: disable=too-few-public-methods 8 | class MenuBuilder(ABC): 9 | """Base class for menu builders""" 10 | 11 | def __init__(self): 12 | pass 13 | 14 | @abstractmethod 15 | def build(self, name: str, context_object=None) -> Menu: 16 | """builds a menu and gives it a name and a context object""" 17 | -------------------------------------------------------------------------------- /cpp/hal/gpiobus_factory.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2022 akuker 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #pragma once 11 | 12 | #include "hal/bus.h" 13 | #include 14 | 15 | class GPIOBUS_Factory 16 | { 17 | public: 18 | 19 | static unique_ptr Create(BUS::mode_e mode); 20 | }; 21 | -------------------------------------------------------------------------------- /python/web/mock/bin/hostnamectl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | TMP_FILE="/tmp/hostnamectl_pretty.tmp" 4 | 5 | if [[ "$1" == "set-hostname" && "$2" == "--pretty" ]]; then 6 | if [[ -z "$3" ]]; then 7 | rm "$TMP_FILE" 2>/dev/null || true 8 | else 9 | echo "$3" > $TMP_FILE 10 | fi 11 | 12 | exit 0 13 | fi 14 | 15 | if [[ "$1" == "status" ]]; then 16 | cat "$TMP_FILE" 2>/dev/null 17 | exit 0 18 | fi 19 | 20 | echo "Mock does not recognize: $0 $@" 21 | exit 1 22 | -------------------------------------------------------------------------------- /python/web/requirements.txt: -------------------------------------------------------------------------------- 1 | babel==2.17.0 2 | bjoern==3.2.2 3 | blinker==1.9.0 4 | certifi==2025.11.12 5 | charset-normalizer==2.1.1 6 | click==8.1.8 7 | flask==3.1.2 8 | flask-babel==4.0.0 9 | idna==3.11 10 | importlib-metadata==8.7.0 11 | itsdangerous==2.2.0 12 | jinja2==3.1.6 13 | MarkupSafe==3.0.3 14 | protobuf==3.20.3 15 | python-pam==2.0.2 16 | pytz==2023.3.post1 17 | requests==2.32.4 18 | six==1.17.0 19 | ua-parser==0.16.1 20 | urllib3==2.6.0 21 | vcgencmd==0.1.1 22 | werkzeug==3.1.4 23 | zipp==3.23.0 24 | -------------------------------------------------------------------------------- /cpp/piscsi/piscsi.cpp: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2022-2023 Uwe Seimet 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #include "piscsi/piscsi_core.h" 11 | 12 | using namespace std; 13 | 14 | int main(int argc, char *argv[]) 15 | { 16 | vector args(argv, argv + argc); 17 | 18 | return Piscsi().run(args); 19 | } 20 | -------------------------------------------------------------------------------- /cpp/scsimon/scsimon.cpp: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2022 Uwe Seimet 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #include "scsimon/sm_core.h" 11 | 12 | using namespace std; 13 | 14 | int main(int argc, char *argv[]) 15 | { 16 | const vector args(argv, argv + argc); 17 | 18 | return ScsiMon().run(args); 19 | } 20 | -------------------------------------------------------------------------------- /cpp/scsictl/scsictl.cpp: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2022 Uwe Seimet 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #include "scsictl/scsictl_core.h" 11 | 12 | using namespace std; 13 | 14 | int main(int argc, char *argv[]) 15 | { 16 | const vector args(argv, argv + argc); 17 | 18 | return ScsiCtl().run(args); 19 | } 20 | -------------------------------------------------------------------------------- /cpp/scsiloop/scsiloop_timer.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI for Raspberry Pi 4 | // Loopback tester utility 5 | // 6 | // Copyright (C) 2022 akuker 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #include 11 | #include 12 | 13 | using namespace std; 14 | 15 | class ScsiLoop_Timer 16 | { 17 | public: 18 | static int RunTimerTest(vector &error_list); 19 | }; -------------------------------------------------------------------------------- /cpp/scsidump/scsidump.cpp: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2022-2023 Uwe Seimet 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #include "scsidump/scsidump_core.h" 11 | 12 | using namespace std; 13 | 14 | int main(int argc, char *argv[]) 15 | { 16 | vector args(argv, argv + argc); 17 | 18 | return ScsiDump().run(args); 19 | } 20 | -------------------------------------------------------------------------------- /python/web/src/static/themes/modern/icons/device-hard-drive.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/oled/service-infra/piscsi-oled.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=PiSCSI-OLED Monitor service 3 | After=network-online.target piscsi.service 4 | 5 | [Service] 6 | Type=simple 7 | Restart=always 8 | RestartSec=2s 9 | ExecStart=/home/pi/piscsi/python/oled/start.sh 10 | ExecStop=/bin/pkill --signal 2 -f "python3 src/piscsi_oled_monitor.py" 11 | # Sleep 2s as a crude way for the python interrupt handler to take effect and show the shutdown splash 12 | ExecStop=/bin/sleep 2 13 | SyslogIdentifier=PISCSIMON 14 | 15 | [Install] 16 | WantedBy=multi-user.target 17 | -------------------------------------------------------------------------------- /cpp/scsiloop/scsiloop.cpp: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2022 Uwe Seimet 7 | // Copyright (C) 2022 akuker 8 | // 9 | //--------------------------------------------------------------------------- 10 | 11 | #include "scsiloop/scsiloop_core.h" 12 | 13 | using namespace std; 14 | 15 | int main(int argc, char *argv[]) 16 | { 17 | const vector args(argv, argv + argc); 18 | 19 | return ScsiLoop().run(args); 20 | } 21 | -------------------------------------------------------------------------------- /python/common/src/piscsi/exceptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module for custom exceptions raised by the piscsi module 3 | """ 4 | 5 | 6 | class FailedSocketConnectionException(Exception): 7 | """Raise when a piscsi protobuf socket connection cannot be established after multiple tries.""" 8 | 9 | 10 | class EmptySocketChunkException(Exception): 11 | """Raise when a socket payload contains an empty chunk which implies a possible problem.""" 12 | 13 | 14 | class InvalidProtobufResponse(Exception): 15 | """Raise when a piscsi socket payload contains unpexpected data.""" 16 | -------------------------------------------------------------------------------- /python/oled/requirements.txt: -------------------------------------------------------------------------------- 1 | Adafruit-Blinka==8.24.0 2 | adafruit-circuitpython-busdevice==5.2.14 3 | adafruit-circuitpython-connectionmanager==3.1.6 4 | adafruit-circuitpython-framebuf==1.6.4 5 | adafruit-circuitpython-requests==4.1.15 6 | adafruit-circuitpython-ssd1306==2.12.22 7 | adafruit-circuitpython-typing==1.12.3 8 | adafruit-platformdetect==3.84.1 9 | Adafruit-PureIO==1.1.11 10 | pillow==11.3.0 11 | protobuf==3.20.3 12 | pyftdi==0.55.0 13 | pyserial==3.5 14 | pyusb==1.3.1 15 | rpi-ws281x==5.0.0 16 | RPi.GPIO==0.7.1 17 | sysv-ipc==1.1.0 18 | typing-extensions==4.15.0 19 | Unidecode==1.3.6 20 | -------------------------------------------------------------------------------- /python/web/service-infra/502.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | PiSCSI Web is Starting 5 | 6 | 7 | 8 | 9 |
10 |

PiSCSI Web Interface is Starting....

11 |

This page will automatically refresh.

12 |

First boot and upgrades can take a second while resolving dependencies.

13 |

If you're seeing this page for over a minute, please check the logs at sudo journalctl -f

14 |
15 | 16 | -------------------------------------------------------------------------------- /cpp/shared/piscsi_version.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2020 akuker 7 | // [ Define the version string ] 8 | // 9 | //--------------------------------------------------------------------------- 10 | #pragma once 11 | 12 | #include 13 | 14 | extern const int piscsi_major_version; // Last two digits of year 15 | extern const int piscsi_minor_version; // Month 16 | extern const int piscsi_patch_version; // Patch number 17 | 18 | std::string piscsi_get_version_string(); 19 | -------------------------------------------------------------------------------- /cpp/shared/network_util.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2023 Uwe Seimet 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #pragma once 11 | 12 | #include 13 | #include 14 | 15 | using namespace std; 16 | 17 | struct sockaddr_in; 18 | 19 | namespace network_util 20 | { 21 | bool IsInterfaceUp(const string&); 22 | set> GetNetworkInterfaces(); 23 | bool ResolveHostName(const string&, sockaddr_in *); 24 | } 25 | -------------------------------------------------------------------------------- /cpp/devices/interfaces/scsi_mmc_commands.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2021-2022 Uwe Seimet 7 | // 8 | // Interface for SCSI Multi-Media commands (see https://www.t10.org/drafts.htm, MMC-6) 9 | // 10 | //--------------------------------------------------------------------------- 11 | 12 | #pragma once 13 | 14 | class ScsiMmcCommands 15 | { 16 | 17 | public: 18 | 19 | ScsiMmcCommands() = default; 20 | virtual ~ScsiMmcCommands() = default; 21 | 22 | virtual void ReadToc() = 0; 23 | }; 24 | -------------------------------------------------------------------------------- /python/web/requirements-dev.txt: -------------------------------------------------------------------------------- 1 | babel==2.17.0 2 | black==25.11.0 3 | certifi==2024.7.4 4 | charset-normalizer==3.3.2 5 | click==8.1.8 6 | exceptiongroup==1.3.0 7 | flake8==7.3.0 8 | idna==3.11 9 | iniconfig==2.0.0 10 | MarkupSafe==3.0.3 11 | mccabe==0.7.0 12 | mypy-extensions==1.1.0 13 | packaging==25.0 14 | pathspec==0.12.1 15 | platformdirs==4.2.0 16 | pluggy==1.6.0 17 | pycodestyle==2.14.0 18 | pyflakes==3.4.0 19 | pygments==2.19.2 20 | pytest==8.4.2 21 | pytest-httpserver==1.1.3 22 | pytokens==0.3.0 23 | requests==2.32.4 24 | tomli==2.3.0 25 | typing-extensions==4.15.0 26 | urllib3==2.6.0 27 | vcgencmd==0.1.1 28 | watchdog==6.0.0 29 | werkzeug==3.1.4 30 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Exclude all by default 2 | /* 3 | 4 | # Paths to include 5 | !/docker/backend/piscsi_wrapper.sh 6 | !/docker/web/web_start_wrapper.sh 7 | !/cpp 8 | !/doc 9 | !/os_integration 10 | !/proto 11 | !/python 12 | !/test 13 | !/easyinstall.sh 14 | !/LICENCE 15 | !/README.md 16 | 17 | # Dev artifacts to exclude 18 | **/.git 19 | 20 | /cpp/bin 21 | /cpp/obj 22 | 23 | **/venv* 24 | **/*.pyc 25 | **/__pycache__ 26 | **/.pytest_cache 27 | **/piscsi_interface_pb2.py 28 | **/report.xml 29 | 30 | **/.idea 31 | **/.vscode 32 | **/.DS_Store 33 | 34 | **/core 35 | **/*.swp 36 | **/current 37 | 38 | **/node_modules 39 | **/messages.pot 40 | **/messages.mo 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | *.pyc 3 | *.swp 4 | *.log 5 | *~ 6 | core 7 | .idea/ 8 | .vscode 9 | .DS_Store 10 | __pycache__ 11 | current 12 | rascsi_interface_pb2.py 13 | piscsi_interface_pb2.py 14 | messages.pot 15 | messages.mo 16 | report.xml 17 | node_modules 18 | 19 | # Intermediate files from astyle 20 | *.orig 21 | 22 | docker/docker-compose.override.yml 23 | /docker/volumes/images/* 24 | !/docker/volumes/images/.gitkeep 25 | /docker/volumes/config/* 26 | !/docker/volumes/config/.gitkeep 27 | 28 | # temporary user files 29 | s.sh 30 | 31 | # temporary kicad files 32 | *-backups 33 | 34 | # VSCode temp file 35 | settings.json 36 | 37 | # submodules 38 | hfdisk* 39 | mac-hard-disk-drivers 40 | -------------------------------------------------------------------------------- /python/web/translation_update.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | cd "$(dirname "$0")" 5 | 6 | # Create the venv if it doesn't exist 7 | if ! test -e venv; then 8 | echo "Creating python venv for PiSCSI-Web development" 9 | python3 -m venv venv 10 | echo "Activating venv" 11 | source venv/bin/activate 12 | echo "Installing requirements-dev.txt" 13 | pip3 install wheel 14 | pip3 install -r requirements.txt 15 | fi 16 | 17 | source venv/bin/activate 18 | 19 | pybabel extract -F babel.cfg -o messages.pot src 20 | pybabel update -i messages.pot -d src/translations 21 | 22 | echo 23 | echo "Translation stats:" 24 | find . -name \*.po -print -execdir msgfmt --statistics messages.po \; 25 | -------------------------------------------------------------------------------- /cpp/scsictl/scsictl_core.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2022 Uwe Seimet 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #pragma once 11 | 12 | #include "generated/piscsi_interface.pb.h" 13 | #include 14 | #include 15 | 16 | using namespace std; 17 | using namespace piscsi_interface; 18 | 19 | class ScsiCtl 20 | { 21 | public: 22 | 23 | ScsiCtl() = default; 24 | ~ScsiCtl() = default; 25 | 26 | int run(const vector&) const; 27 | 28 | private: 29 | 30 | void Banner(const vector&) const; 31 | }; 32 | -------------------------------------------------------------------------------- /python/ctrlboard/src/ctrlboard_hw/hardware_button.py: -------------------------------------------------------------------------------- 1 | """Module containing an abstraction for the hardware button through the i2c multiplexer""" 2 | 3 | 4 | # pylint: disable=too-few-public-methods 5 | class HardwareButton: 6 | """Class implementing a hardware button interface that uses the i2c multiplexer""" 7 | 8 | def __init__(self, pca_driver, pin): 9 | self.pca_driver = pca_driver 10 | self.pin = pin 11 | self.state = True 12 | self.state_interrupt = True 13 | self.name = "n/a" 14 | self.last_press = None 15 | 16 | def read(self): 17 | """Reads the configured port of the i2c multiplexer""" 18 | return self.pca_driver.read_input_register_port(self.pin) 19 | -------------------------------------------------------------------------------- /docker/web/web_start_wrapper.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if ! [[ -f "/home/pi/piscsi/python/common/src/piscsi_interface_pb2.py" ]]; then 4 | # Build piscsi_interface_pb2.py with the protobuf compiler 5 | protoc \ 6 | --python_out=/home/pi/piscsi/python/common/src \ 7 | --proto_path=/home/pi/piscsi/proto \ 8 | /home/pi/piscsi/proto/piscsi_interface.proto 9 | fi 10 | 11 | # Start Nginx service 12 | nginx 13 | 14 | # Use mock commands 15 | export PATH="/home/pi/piscsi/python/web/mock/bin:$PATH" 16 | 17 | # Pass args to web UI start script 18 | if [[ $BACKEND_PASSWORD ]]; then 19 | /home/pi/piscsi/python/web/start.sh "$@" --password=$BACKEND_PASSWORD 20 | else 21 | /home/pi/piscsi/python/web/start.sh "$@" 22 | fi 23 | -------------------------------------------------------------------------------- /cpp/test/linux_os_stubs.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2022 akuker 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | // This header file should ONLY be used in test procedures. It bypasses the 11 | // standard c library functionality. DO NOT USE THIS IN PRODUCTION CODE. 12 | #pragma once 13 | #include 14 | 15 | extern "C" { 16 | #ifdef __USE_LARGEFILE64 17 | FILE *__real_fopen64(const char *__restrict __filename, const char *__restrict __modes); 18 | #else 19 | FILE *__real_fopen(const char *__restrict __filename, const char *__restrict __modes); 20 | #endif 21 | } 22 | -------------------------------------------------------------------------------- /cpp/devices/interfaces/scsi_printer_commands.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2021-2022 Uwe Seimet 7 | // 8 | // Interface for SCSI printer commands (see SCSI-2 specification) 9 | // 10 | //--------------------------------------------------------------------------- 11 | 12 | #pragma once 13 | 14 | class ScsiPrinterCommands 15 | { 16 | 17 | public: 18 | 19 | ScsiPrinterCommands() = default; 20 | virtual ~ScsiPrinterCommands() = default; 21 | 22 | // Mandatory commands 23 | virtual void Print() = 0; 24 | virtual void ReleaseUnit() = 0; 25 | virtual void ReserveUnit() = 0; 26 | virtual void SendDiagnostic() = 0; 27 | }; 28 | -------------------------------------------------------------------------------- /os_integration/piscsi.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=PiSCSI service 3 | After=network.target 4 | 5 | [Service] 6 | Type=simple 7 | Restart=always 8 | ExecStart=/usr/local/bin/piscsi -r 7 9 | # Example 1: If you want to automatically attach a hard disk at startup, 10 | # say an image called harddisk.hds on SCSI ID 1, change the ExecStart line to: 11 | # 12 | # ExecStart=/usr/local/bin/piscsi -ID1 /home/pi/images/harddisk.hds 13 | # 14 | # Example 2: If you want to reserve SCSI IDs to prevent usage, add '-r' followed by 15 | # comma-separated SCSI ID numbers; for instance IDs 0 and 7: 16 | # 17 | # ExecStart=/usr/local/bin/piscsi -r 0,7 18 | # 19 | ExecStop=/usr/local/bin/scsictl -X 20 | SyslogIdentifier=PISCSI 21 | 22 | [Install] 23 | WantedBy=multi-user.target 24 | 25 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "docker" 4 | directory: "/docker/backend" 5 | schedule: 6 | interval: "weekly" 7 | 8 | - package-ecosystem: "docker" 9 | directory: "/docker/pytest" 10 | schedule: 11 | interval: "weekly" 12 | 13 | - package-ecosystem: "docker" 14 | directory: "/docker/web" 15 | schedule: 16 | interval: "weekly" 17 | 18 | - package-ecosystem: "github-actions" 19 | directory: "/.github/workflows" 20 | schedule: 21 | interval: "weekly" 22 | 23 | - package-ecosystem: "npm" 24 | directory: "/python/web" 25 | schedule: 26 | interval: "weekly" 27 | 28 | - package-ecosystem: "pip" 29 | directory: "/python" 30 | schedule: 31 | interval: "weekly" 32 | -------------------------------------------------------------------------------- /python/ctrlboard/src/ctrlboard_event_handler/ctrlboard_print_event_handler.py: -------------------------------------------------------------------------------- 1 | """Module for test printing events when buttons from the PiSCSI Control Board are pressed""" 2 | 3 | import observer 4 | from ctrlboard_hw.hardware_button import HardwareButton 5 | from ctrlboard_hw.encoder import Encoder 6 | 7 | 8 | # pylint: disable=too-few-public-methods 9 | class CtrlBoardPrintEventHandler(observer.Observer): 10 | """Class implements a basic event handler that prints button presses from the PiSCSI 11 | Control Board hardware.""" 12 | 13 | def update(self, updated_object): 14 | if isinstance(updated_object, HardwareButton): 15 | print(updated_object.name + " has been pressed!") 16 | if isinstance(updated_object, Encoder): 17 | print(updated_object.pos) 18 | -------------------------------------------------------------------------------- /python/ctrlboard/src/ctrlboard_hw/ctrlboard_hw_constants.py: -------------------------------------------------------------------------------- 1 | """Module containing the PiSCSI Control Board hardware constants""" 2 | 3 | 4 | # pylint: disable=too-few-public-methods 5 | class CtrlBoardHardwareConstants: 6 | """Class containing the PiSCSI Control Board hardware constants""" 7 | 8 | DISPLAY_I2C_ADDRESS = 0x3C 9 | PCA9554_I2C_ADDRESS = 0x3F 10 | PCA9554_PIN_ENC_A = 0 11 | PCA9554_PIN_ENC_B = 1 12 | PCA9554_PIN_BUTTON_1 = 2 13 | PCA9554_PIN_BUTTON_2 = 3 14 | PCA9554_PIN_BUTTON_ROTARY = 5 15 | PCA9554_PIN_LED_1 = 6 16 | PCA9554_PIN_LED_2 = 7 17 | 18 | PI_PIN_INTERRUPT = 9 # BCM 19 | 20 | BUTTON_1 = "Bt1" 21 | BUTTON_2 = "Bt2" 22 | ROTARY_A = "RotA" 23 | ROTARY_B = "RotB" 24 | ROTARY_BUTTON = "RotBtn" 25 | ROTARY = "Rot" 26 | -------------------------------------------------------------------------------- /python/ctrlboard/src/observable.py: -------------------------------------------------------------------------------- 1 | """Module for Observable part of the Observer pattern functionality""" 2 | 3 | from typing import List 4 | from observer import Observer 5 | 6 | 7 | class Observable: 8 | """Class implementing the Observable pattern""" 9 | 10 | _observers: List[Observer] = [] 11 | 12 | def attach(self, observer: Observer): 13 | """Attaches an observer to an obserable object""" 14 | self._observers.append(observer) 15 | 16 | def detach(self, observer: Observer): 17 | """detaches an observer from an observable object""" 18 | self._observers.remove(observer) 19 | 20 | def notify(self, updated_object): 21 | """Notifies all observers with a given object parameter""" 22 | for observer in self._observers: 23 | observer.update(updated_object) 24 | -------------------------------------------------------------------------------- /python/web/mock/bin/vcgencmd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Info: https://www.raspberrypi.com/documentation/computers/os.html#vcgencmd 4 | # 5 | # Bit Hex value Meaning 6 | # ----- ----------- ------------------------ 7 | # 0 0x1 Under-voltage detected 8 | # 1 0x2 Arm frequency capped 9 | # 2 0x4 Currently throttled 10 | # 3 0x8 Soft temperature limit active 11 | # 16 0x10000 Under-voltage has occurred 12 | # 17 0x20000 Arm frequency capping has occurred 13 | # 18 0x40000 Throttling has occurred 14 | # 19 0x80000 Soft temperature limit has occurred 15 | 16 | if [[ "$1" == "get_throttled" ]] 17 | then 18 | # Return 'Under-voltage detected' & 'Under-voltage has occurred' 19 | echo "throttled=0x10001" 20 | fi 21 | 22 | echo "Mock does not recognize: $0 $@" 23 | exit 1 -------------------------------------------------------------------------------- /cpp/scsiloop/scsiloop_core.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2022 Uwe Seimet 7 | // Copyright (C) 2022 akuker 8 | // 9 | //--------------------------------------------------------------------------- 10 | 11 | #pragma once 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | using namespace std; 18 | 19 | class ScsiLoop 20 | { 21 | public: 22 | ScsiLoop() = default; 23 | ~ScsiLoop() = default; 24 | 25 | int run(const vector &args); 26 | 27 | private: 28 | void Banner(const vector &) const; 29 | static void TerminationHandler(int signum); 30 | bool ParseArgument(const vector &); 31 | bool SetLogLevel(const string &); 32 | }; 33 | -------------------------------------------------------------------------------- /python/common/src/piscsi/common_settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module for general settings used in the piscsi module 3 | """ 4 | 5 | from os import getcwd 6 | 7 | # There may be a more elegant way to get the HOME dir of the user that installed PiSCSI 8 | HOME_DIR = "/".join(getcwd().split("/")[0:3]) 9 | CFG_DIR = f"{HOME_DIR}/.config/piscsi" 10 | CONFIG_FILE_SUFFIX = "json" 11 | 12 | # File ending used for drive properties files 13 | PROPERTIES_SUFFIX = "properties" 14 | 15 | # Supported archive file suffixes 16 | ARCHIVE_FILE_SUFFIXES = ["zip", "sit", "tar", "gz", "7z"] 17 | 18 | # The RESERVATIONS list is used to keep track of the reserved ID memos. 19 | # Initialize with a list of 8 empty strings. 20 | RESERVATIONS = ["" for _ in range(0, 8)] 21 | 22 | # Standard error message for shell commands 23 | SHELL_ERROR = 'Shell command: "%s" led to error: %s' 24 | -------------------------------------------------------------------------------- /cpp/devices/interfaces/scsi_block_commands.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2021-2022 Uwe Seimet 7 | // 8 | // Interface for SCSI block commands (see https://www.t10.org/drafts.htm, SBC-5) 9 | // 10 | //--------------------------------------------------------------------------- 11 | 12 | #pragma once 13 | 14 | class ScsiBlockCommands 15 | { 16 | 17 | public: 18 | 19 | ScsiBlockCommands() = default; 20 | virtual ~ScsiBlockCommands() = default; 21 | 22 | // Mandatory commands 23 | virtual void FormatUnit() = 0; 24 | virtual void ReadCapacity10() = 0; 25 | virtual void ReadCapacity16() = 0; 26 | virtual void Read10() = 0; 27 | virtual void Read16() = 0; 28 | virtual void Write10() = 0; 29 | virtual void Write16() = 0; 30 | }; 31 | -------------------------------------------------------------------------------- /cpp/scsimon/sm_reports.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2020-2021 akuker 7 | // 8 | // [ SCSI Bus Monitor ] 9 | // 10 | //--------------------------------------------------------------------------- 11 | 12 | #include "hal/data_sample.h" 13 | #include 14 | 15 | uint32_t scsimon_read_json(const string &json_filename, vector> &data_capture_array); 16 | 17 | void scsimon_generate_html(const string &filename, const vector> &data_capture_array); 18 | void scsimon_generate_json(const string &filename, const vector> &data_capture_array); 19 | void scsimon_generate_value_change_dump(const string &filename, const vector> &data_capture_array); 20 | -------------------------------------------------------------------------------- /python/web/src/static/themes/modern/icons/file-info.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 13 | 19 | 20 | -------------------------------------------------------------------------------- /cpp/shared/config.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Powered by XM6 TypeG Technology. 7 | // Copyright (C) 2016-2020 GIMONS 8 | // 9 | //--------------------------------------------------------------------------- 10 | 11 | #pragma once 12 | 13 | //--------------------------------------------------------------------------- 14 | // 15 | // Various Operation Settings 16 | // 17 | //--------------------------------------------------------------------------- 18 | #define USE_SEL_EVENT_ENABLE // Check SEL signal by event 19 | // This avoids an indefinite loop with warnings if there is no PiSCSI hardware 20 | // and thus helps with running scsictl and unit test on x86 hardware. 21 | #if defined(__x86_64__) || defined(__X86__) || !defined(__linux__) 22 | #undef USE_SEL_EVENT_ENABLE 23 | #endif 24 | -------------------------------------------------------------------------------- /cpp/controllers/phase_handler.cpp: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2023 Uwe Seimet 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #include "phase_handler.h" 11 | 12 | void PhaseHandler::Init() 13 | { 14 | phase_executors[phase_t::busfree] = [this] () { BusFree(); }; 15 | phase_executors[phase_t::selection] = [this] () { Selection(); }; 16 | phase_executors[phase_t::dataout] = [this] () { DataOut(); }; 17 | phase_executors[phase_t::datain] = [this] () { DataIn(); }; 18 | phase_executors[phase_t::command] = [this] () { Command(); }; 19 | phase_executors[phase_t::status] = [this] () { Status(); }; 20 | phase_executors[phase_t::msgout] = [this] () { MsgOut(); }; 21 | phase_executors[phase_t::msgin] = [this] () { MsgIn(); }; 22 | } 23 | -------------------------------------------------------------------------------- /cpp/test/network_util_test.cpp: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2023 Uwe Seimet 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | #include "shared/network_util.h" 15 | 16 | using namespace network_util; 17 | 18 | TEST(NetworkUtilTest, IsInterfaceUp) 19 | { 20 | EXPECT_FALSE(IsInterfaceUp("foo_bar")); 21 | } 22 | 23 | TEST(NetworkUtilTest, GetNetworkInterfaces) 24 | { 25 | EXPECT_FALSE(GetNetworkInterfaces().empty()); 26 | } 27 | 28 | TEST(NetworkUtilTest, ResolveHostName) 29 | { 30 | sockaddr_in server_addr = {}; 31 | EXPECT_FALSE(ResolveHostName("foo.foobar", &server_addr)); 32 | EXPECT_TRUE(ResolveHostName("127.0.0.1", &server_addr)); 33 | } 34 | -------------------------------------------------------------------------------- /cpp/test/test_setup.cpp: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2022-2023 Uwe Seimet 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #include 11 | 12 | #include 13 | 14 | int main(int argc, char *[]) 15 | { 16 | const bool disable_logging = argc <= 1; 17 | 18 | // If any argument is provided the log level is set to trace 19 | spdlog::set_level(disable_logging ? spdlog::level::off : spdlog::level::trace); 20 | 21 | int fd = -1; 22 | if (disable_logging) { 23 | fd = open("/dev/null", O_WRONLY); 24 | dup2(fd, STDERR_FILENO); 25 | } 26 | 27 | testing::InitGoogleTest(); 28 | 29 | const int result = RUN_ALL_TESTS(); 30 | 31 | if (fd != -1) { 32 | close(fd); 33 | } 34 | 35 | return result; 36 | } 37 | -------------------------------------------------------------------------------- /python/web/service-infra/nginx-default.conf: -------------------------------------------------------------------------------- 1 | # /etc/nginx/sites-available/default 2 | # Simple proxy_pass for PiSCSI-web 3 | server { 4 | listen [::]:80 default_server; 5 | listen 80 default_server; 6 | listen 443 ssl http2; 7 | listen [::]:443 ssl http2; 8 | 9 | ssl_certificate /etc/ssl/certs/piscsi-web.crt; 10 | ssl_certificate_key /etc/ssl/private/piscsi-web.key; 11 | ssl_session_timeout 1d; 12 | ssl_session_cache shared:MozSSL:10m; 13 | ssl_session_tickets off; 14 | ssl_protocols TLSv1.3; 15 | ssl_prefer_server_ciphers off; 16 | 17 | location / { 18 | proxy_pass http://127.0.0.1:8080; 19 | } 20 | 21 | # Large files 22 | client_max_body_size 0; 23 | proxy_read_timeout 1000; 24 | proxy_connect_timeout 1000; 25 | proxy_send_timeout 1000; 26 | 27 | error_page 502 /502.html; 28 | location = /502.html { 29 | root /var/www/html/; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /python/ctrlboard/src/menu/timer.py: -------------------------------------------------------------------------------- 1 | """Module providing a timer class""" 2 | 3 | import time 4 | 5 | 6 | class Timer: 7 | """Class implementing a timer class. Takes an activation delay and 8 | sets a flag if the activation delay exprires.""" 9 | 10 | def __init__(self, activation_delay): 11 | self.start_timestamp = int(time.time()) 12 | self.activation_delay = activation_delay 13 | self.enabled = False 14 | 15 | def check_timer(self): 16 | """Checks the timer whether it has reached the activation delay.""" 17 | current_timestamp = int(time.time()) 18 | timestamp_diff = current_timestamp - self.start_timestamp 19 | 20 | if timestamp_diff >= self.activation_delay: 21 | self.enabled = True 22 | 23 | def reset_timer(self): 24 | """Resets the timer and starts from the beginning.""" 25 | self.start_timestamp = int(time.time()) 26 | self.enabled = False 27 | -------------------------------------------------------------------------------- /python/web/src/pwa/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PiSCSI", 3 | "icons": [ 4 | { 5 | "src": "\/pwa\/android-icon-36x36.png", 6 | "sizes": "36x36", 7 | "type": "image\/png", 8 | "density": "0.75" 9 | }, 10 | { 11 | "src": "\/pwa\/android-icon-48x48.png", 12 | "sizes": "48x48", 13 | "type": "image\/png", 14 | "density": "1.0" 15 | }, 16 | { 17 | "src": "\/pwa\/android-icon-72x72.png", 18 | "sizes": "72x72", 19 | "type": "image\/png", 20 | "density": "1.5" 21 | }, 22 | { 23 | "src": "\/pwa\/android-icon-96x96.png", 24 | "sizes": "96x96", 25 | "type": "image\/png", 26 | "density": "2.0" 27 | }, 28 | { 29 | "src": "\/pwa\/android-icon-144x144.png", 30 | "sizes": "144x144", 31 | "type": "image\/png", 32 | "density": "3.0" 33 | }, 34 | { 35 | "src": "\/pwa\/android-icon-192x192.png", 36 | "sizes": "192x192", 37 | "type": "image\/png", 38 | "density": "4.0" 39 | } 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /cpp/devices/interfaces/scsi_primary_commands.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2021-2022 Uwe Seimet 7 | // 8 | // Interface for SCSI primary commands (see https://www.t10.org/drafts.htm, SPC-6) 9 | // 10 | //--------------------------------------------------------------------------- 11 | 12 | #pragma once 13 | 14 | class ScsiPrimaryCommands 15 | { 16 | 17 | public: 18 | 19 | ScsiPrimaryCommands() = default; 20 | virtual ~ScsiPrimaryCommands() = default; 21 | 22 | // Mandatory commands 23 | virtual void TestUnitReady() = 0; 24 | virtual void Inquiry() = 0; 25 | virtual void ReportLuns() = 0; 26 | 27 | // Optional commands implemented by all PiSCSI device types 28 | virtual void RequestSense() = 0; 29 | virtual void ReleaseUnit() = 0; 30 | virtual void ReserveUnit() = 0; 31 | virtual void SendDiagnostic() = 0; 32 | }; 33 | -------------------------------------------------------------------------------- /cpp/scsictl/scsictl_parser.cpp: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2022-2023 Uwe Seimet 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #include "scsictl_parser.h" 11 | 12 | PbOperation ScsictlParser::ParseOperation(string_view operation) const 13 | { 14 | const auto& it = operations.find(tolower(operation[0])); 15 | return it != operations.end() ? it->second : NO_OPERATION; 16 | } 17 | 18 | PbDeviceType ScsictlParser::ParseType(const string& type) const 19 | { 20 | string t; 21 | ranges::transform(type, back_inserter(t), ::toupper); 22 | 23 | if (PbDeviceType parsed_type; PbDeviceType_Parse(t, &parsed_type)) { 24 | return parsed_type; 25 | } 26 | 27 | // Handle convenience device types (shortcuts) 28 | const auto& it = device_types.find(tolower(type[0])); 29 | return it != device_types.end() ? it->second : UNDEFINED; 30 | } 31 | -------------------------------------------------------------------------------- /cpp/piscsi/piscsi_service.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2022-2023 Uwe Seimet 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #pragma once 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | class CommandContext; 17 | 18 | using namespace std; 19 | 20 | class PiscsiService 21 | { 22 | using callback = function; 23 | 24 | public: 25 | 26 | PiscsiService() = default; 27 | ~PiscsiService() = default; 28 | 29 | string Init(const callback&, int); 30 | void Start(); 31 | void Stop(); 32 | bool IsRunning() const { return service_socket != -1 && service_thread.joinable(); } 33 | 34 | private: 35 | 36 | void Execute() const; 37 | void ExecuteCommand(int) const; 38 | 39 | callback execute; 40 | 41 | jthread service_thread; 42 | 43 | int service_socket = -1; 44 | }; 45 | -------------------------------------------------------------------------------- /python/ctrlboard/src/ctrlboard_event_handler/piscsi_profile_cycler.py: -------------------------------------------------------------------------------- 1 | """Module providing the profile cycler class for the PiSCSI Control Board UI""" 2 | 3 | from ctrlboard_menu_builder import CtrlBoardMenuBuilder 4 | from menu.cycler import Cycler 5 | 6 | 7 | class PiscsiProfileCycler(Cycler): 8 | """Class implementing the profile cycler for the PiSCSI Control Baord UI""" 9 | 10 | def populate_cycle_entries(self): 11 | cycle_entries = self.file_cmd.list_config_files() 12 | 13 | return cycle_entries 14 | 15 | def perform_selected_entry_action(self, selected_entry): 16 | result = self.file_cmd.read_config(selected_entry) 17 | self._menu_controller.show_timed_mini_message("") 18 | if result["status"] is True: 19 | return CtrlBoardMenuBuilder.SCSI_ID_MENU 20 | 21 | self._menu_controller.show_message("Failed!") 22 | return CtrlBoardMenuBuilder.SCSI_ID_MENU 23 | 24 | def perform_return_action(self): 25 | return CtrlBoardMenuBuilder.SCSI_ID_MENU 26 | -------------------------------------------------------------------------------- /python/ctrlboard/src/menu/blank_screensaver.py: -------------------------------------------------------------------------------- 1 | """Module implementing a blank screensaver""" 2 | 3 | from menu.screensaver import ScreenSaver 4 | 5 | 6 | class BlankScreenSaver(ScreenSaver): 7 | """Class implementing a blank screen safer that simply blanks the screen after a 8 | configured activation delay""" 9 | 10 | def __init__(self, activation_delay, menu_renderer): 11 | super().__init__(activation_delay, menu_renderer) 12 | self._initial_draw_call = None 13 | 14 | def draw_screensaver(self): 15 | if self._initial_draw_call is True: 16 | self.menu_renderer.blank_screen() 17 | else: 18 | self._initial_draw_call = False 19 | 20 | def check_timer(self): 21 | already_enabled = False 22 | if self.enabled is True: 23 | already_enabled = True 24 | 25 | super().check_timer() 26 | 27 | if self.enabled is True and already_enabled is False: # new switch to screensaver 28 | self._initial_draw_call = True 29 | -------------------------------------------------------------------------------- /cpp/hal/data_sample.cpp: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2022 akuker 7 | // 8 | // [ SCSI Bus Monitor ] 9 | // 10 | //--------------------------------------------------------------------------- 11 | 12 | #include "hal/bus.h" 13 | #include "hal/data_sample.h" 14 | #include 15 | 16 | using namespace std; 17 | 18 | string DataSample::GetPhaseStr() const 19 | { 20 | return BUS::GetPhaseStrRaw(GetPhase()); 21 | } 22 | 23 | phase_t DataSample::GetPhase() const 24 | { 25 | // Selection Phase 26 | if (GetSEL()) { 27 | return phase_t::selection; 28 | } 29 | 30 | // Bus busy phase 31 | if (!GetBSY()) { 32 | return phase_t::busfree; 33 | } 34 | 35 | // Get target phase from bus signal line 36 | uint32_t mci = GetMSG() ? 0x04 : 0x00; 37 | mci |= GetCD() ? 0x02 : 0x00; 38 | mci |= GetIO() ? 0x01 : 0x00; 39 | return BUS::GetPhase(mci); 40 | } 41 | -------------------------------------------------------------------------------- /python/common/resources/README.md: -------------------------------------------------------------------------------- 1 | # Common Resources 2 | 3 | This directory contains resources that are shared between multiple Python components in the PiSCSI project, 4 | such as fonts and splash screen images used by both the OLED monitor and the CtrlBoard interface. 5 | 6 | ## Credits 7 | 8 | ### DejaVuSansMono-Bold.ttf 9 | 10 | * _DejaVu Sans Mono Bold_ TrueType font by the DejaVu fonts project 11 | * Source: https://dejavu-fonts.github.io 12 | * Distributed under DejaVu Fonts License (see DejaVu Fonts License.txt for full text) 13 | 14 | ### type_writer.ttf 15 | 16 | * _Type Writer_ TrueType font by Mandy Smith (cutielou) 17 | * Source: https://fontstruct.com/fontstructions/show/212255/type_writer 18 | * Distributed under Creative Commons Attribution Share Alike license 19 | (http://creativecommons.org/licenses/by-sa/3.0/) 20 | 21 | ### splash_start_\*.bmp, splash_stop_\*.bmp 22 | 23 | * Drawn by Daniel Markstedt 24 | * Distributed under BSD 3-Clause by permission from author (see LICENSE in project root for full text) 25 | -------------------------------------------------------------------------------- /cpp/shared/piscsi_version.cpp: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2020 akuker 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #include "piscsi_version.h" 11 | #include 12 | #include 13 | 14 | // The following should be updated for each release 15 | const int piscsi_major_version = 24; // Last two digits of year 16 | const int piscsi_minor_version = 5; // Month 17 | const int piscsi_patch_version = -1; // Patch number - increment for each update 18 | 19 | using namespace std; 20 | 21 | string piscsi_get_version_string() 22 | { 23 | stringstream s; 24 | 25 | s << setw(2) << setfill('0') << piscsi_major_version << '.' << setw(2) << piscsi_minor_version; 26 | 27 | if (piscsi_patch_version < 0) { 28 | s << " --DEVELOPMENT BUILD--"; 29 | } 30 | else { 31 | s << '.' << setw(2) << piscsi_patch_version; 32 | } 33 | 34 | return s.str(); 35 | } 36 | 37 | 38 | -------------------------------------------------------------------------------- /docker/docker-compose.ci.yml: -------------------------------------------------------------------------------- 1 | services: 2 | backend: 3 | image: ${GHCR_IMAGE} 4 | build: 5 | context: .. 6 | dockerfile: docker/backend/Dockerfile 7 | init: true 8 | volumes: 9 | - ./volumes/images:/home/pi/images:delegated 10 | healthcheck: 11 | interval: 5s 12 | start_period: 5s 13 | 14 | web: 15 | build: 16 | context: .. 17 | dockerfile: docker/web/Dockerfile 18 | args: 19 | - OS_VERSION=bullseye 20 | volumes: 21 | - ./volumes/images:/home/pi/images:delegated 22 | init: true 23 | command: ["--backend-host=backend", "--log-level=debug"] 24 | healthcheck: 25 | interval: 5s 26 | start_period: 5s 27 | 28 | pytest: 29 | depends_on: 30 | web: 31 | condition: service_healthy 32 | backend: 33 | condition: service_healthy 34 | profiles: 35 | - webui-tests 36 | build: 37 | context: .. 38 | dockerfile: docker/pytest/Dockerfile 39 | working_dir: /src 40 | volumes: 41 | - ./volumes/pytest:/src/tests/output:delegated 42 | command: ["-vv"] 43 | -------------------------------------------------------------------------------- /python/common/src/util/run.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utility module for running system commands with basic logging 3 | """ 4 | 5 | import asyncio 6 | import logging 7 | 8 | 9 | def run(program, args=None): 10 | """Run a command and return its output""" 11 | return asyncio.run(run_async(program, args)) 12 | 13 | 14 | async def run_async(program, args=None): 15 | """Run a command in the background""" 16 | proc = await asyncio.create_subprocess_exec( 17 | program, *args, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE 18 | ) 19 | 20 | stdout, stderr = await proc.communicate() 21 | 22 | logging.info( 23 | 'Executed command "%s %s" with status code %d', 24 | program, 25 | " ".join(args), 26 | proc.returncode, 27 | ) 28 | 29 | if stdout: 30 | stdout = stdout.decode() 31 | logging.debug(stdout) 32 | 33 | if stderr: 34 | stderr = stderr.decode() 35 | logging.warning(stderr) 36 | 37 | return { 38 | "returncode": proc.returncode, 39 | "stdout": stdout, 40 | "stderr": stderr, 41 | } 42 | -------------------------------------------------------------------------------- /python/oled/README.md: -------------------------------------------------------------------------------- 1 | # PiSCSI OLED Screen 2 | 3 | ## Run as standalone script for development / troubleshooting 4 | 5 | ```bash 6 | # Make a virtual env named venv 7 | $ python3 -m venv venv 8 | # Use that virtual env in this shell 9 | $ source venv/bin/activate 10 | # Install requirements 11 | $ pip3 install -r requirements.txt 12 | $ PYTHONPATH=$PWD/src:$(dirname $PWD)/common/src python3 src/piscsi_oled_monitor.py 13 | ``` 14 | 15 | ### Parameters 16 | 17 | The script takes two positional parameters: 18 | * '0' or '180' which decides the screen rotation 19 | * '32' or '64' which decides the vertical screen resolution in pixels 20 | 21 | Ex. 22 | ``` 23 | $ python3 piscsi_oled_monitor.py --rotation 180 --height 64 24 | ``` 25 | 26 | _Note:_ Both parameters must be passed for the script to read them. Ordering is also important. 27 | 28 | ## Run the start.sh script standalone 29 | 30 | The start.sh script can also be run standalone, and will handle the venv creation/updating for you. It takes the same command line parameters in the following format: 31 | 32 | ``` 33 | $ ./start.sh --rotation=180 --height=64 34 | ``` 35 | -------------------------------------------------------------------------------- /python/web/src/templates/logs.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

{{ _("System Logs: %(scope)s %(lines)s lines", scope=scope, lines=lines) }}

5 |
6 |
7 | 8 | 9 | 10 | 27 | 28 |
29 |
30 |

{{ logs }}

31 | 32 |
33 | {% endblock content %} 34 | -------------------------------------------------------------------------------- /cpp/devices/device_logger.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2022-2023 Uwe Seimet 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #pragma once 11 | 12 | #include "spdlog/spdlog.h" 13 | #include 14 | 15 | using namespace std; 16 | 17 | class DeviceLogger 18 | { 19 | 20 | public: 21 | 22 | DeviceLogger() = default; 23 | ~DeviceLogger() = default; 24 | 25 | void Trace(const string&) const; 26 | void Debug(const string&) const; 27 | void Info(const string&) const; 28 | void Warn(const string&) const; 29 | void Error(const string&) const; 30 | 31 | void SetIdAndLun(int, int); 32 | static void SetLogIdAndLun(int, int); 33 | 34 | private: 35 | 36 | void Log(spdlog::level::level_enum, const string&) const; 37 | 38 | int id = -1; 39 | int lun = -1; 40 | 41 | // TODO Try to only have one shared instance, so that these fields do not have to be static 42 | static inline int log_device_id = -1; 43 | static inline int log_device_lun = -1; 44 | }; 45 | -------------------------------------------------------------------------------- /python/oled/src/interrupt_handler.py: -------------------------------------------------------------------------------- 1 | """ 2 | Linux interrupt handling module 3 | """ 4 | 5 | import signal 6 | 7 | 8 | class GracefulInterruptHandler: 9 | """ 10 | Class for handling Linux signal interrupts 11 | """ 12 | 13 | def __init__(self, signals=(signal.SIGINT, signal.SIGTERM)): 14 | self.signals = signals 15 | self.original_handlers = {} 16 | self.interrupted = False 17 | self.released = False 18 | 19 | def __enter__(self): 20 | for sig in self.signals: 21 | self.original_handlers[sig] = signal.getsignal(sig) 22 | signal.signal(sig, self.handler) 23 | 24 | return self 25 | 26 | def handler(self, signum, frame): 27 | self.release() 28 | self.interrupted = True 29 | 30 | def __exit__(self, exception_type, exception_value, traceback): 31 | self.release() 32 | 33 | def release(self): 34 | if self.released: 35 | return False 36 | 37 | for sig in self.signals: 38 | signal.signal(sig, self.original_handlers[sig]) 39 | 40 | self.released = True 41 | return True 42 | -------------------------------------------------------------------------------- /cpp/hal/log.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Powered by XM6 TypeG Technology. 7 | // Copyright (C) 2016-2020 GIMONS 8 | // Copyright (C) 2020 akuker 9 | // 10 | //--------------------------------------------------------------------------- 11 | 12 | // The legacy code in this file is deprecated and can cause a buffer overflow. Use spdlog directly instead. 13 | 14 | #pragma once 15 | 16 | #include 17 | 18 | static const int LOGBUF_SIZE = 512; 19 | 20 | #define SPDLOGWRAPPER(loglevel, ...) \ 21 | { \ 22 | char logbuf[LOGBUF_SIZE]; \ 23 | snprintf(logbuf, sizeof(logbuf), __VA_ARGS__); \ 24 | spdlog::log(loglevel, logbuf); \ 25 | }; 26 | 27 | #define LOGTRACE(...) SPDLOGWRAPPER(spdlog::level::trace, __VA_ARGS__) 28 | #define LOGDEBUG(...) SPDLOGWRAPPER(spdlog::level::debug, __VA_ARGS__) 29 | #define LOGINFO(...) SPDLOGWRAPPER(spdlog::level::info, __VA_ARGS__) 30 | #define LOGWARN(...) SPDLOGWRAPPER(spdlog::level::warn, __VA_ARGS__) 31 | #define LOGERROR(...) SPDLOGWRAPPER(spdlog::level::err, __VA_ARGS__) 32 | -------------------------------------------------------------------------------- /cpp/test/localizer_test.cpp: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2022 Uwe Seimet 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #include 11 | 12 | #include "piscsi/localizer.h" 13 | 14 | TEST(Localizer, Localize) 15 | { 16 | Localizer localizer; 17 | 18 | string message = localizer.Localize(LocalizationKey::ERROR_AUTHENTICATION, ""); 19 | EXPECT_FALSE(message.empty()); 20 | EXPECT_EQ(string::npos, message.find("enum value")); 21 | 22 | message = localizer.Localize(LocalizationKey::ERROR_AUTHENTICATION, "de_DE"); 23 | EXPECT_FALSE(message.empty()); 24 | EXPECT_EQ(string::npos, message.find("enum value")); 25 | 26 | message = localizer.Localize(LocalizationKey::ERROR_AUTHENTICATION, "en"); 27 | EXPECT_FALSE(message.empty()); 28 | EXPECT_EQ(string::npos, message.find("enum value")); 29 | 30 | message = localizer.Localize((LocalizationKey)1234, ""); 31 | EXPECT_FALSE(message.empty()); 32 | EXPECT_NE(string::npos, message.find("enum value")); 33 | } 34 | -------------------------------------------------------------------------------- /cpp/scsiloop/scsiloop_cout.cpp: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI for Raspberry Pi 4 | // Loopback tester utility 5 | // 6 | // Copyright (C) 2022 akuker 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #include "scsiloop_cout.h" 11 | #include 12 | 13 | using namespace std; 14 | 15 | void ScsiLoop_Cout::StartTest(const string &test_name) 16 | { 17 | cout << CYAN << "Testing " << test_name << ":" << WHITE; 18 | } 19 | void ScsiLoop_Cout::PrintUpdate() 20 | { 21 | cout << "."; 22 | } 23 | 24 | void ScsiLoop_Cout::FinishTest(const string &test_name, int failures) 25 | { 26 | if (failures == 0) { 27 | cout << GREEN << "OK!" << WHITE << endl; 28 | } else { 29 | cout << RED << test_name << " FAILED! - " << failures << " errors!" << WHITE << endl; 30 | } 31 | } 32 | 33 | void ScsiLoop_Cout::PrintErrors(const vector &test_errors) 34 | { 35 | if (!test_errors.empty()) { 36 | for (auto& err_string : test_errors) { 37 | cout << RED << err_string << endl; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /python/ctrlboard/src/menu/menu_renderer_adafruit_ssd1306.py: -------------------------------------------------------------------------------- 1 | """Module providing the Adafruit SSD1306 menu renderer class""" 2 | 3 | # pylint: disable=import-error 4 | from board import SCL, SDA 5 | import busio 6 | import adafruit_ssd1306 7 | from menu.menu_renderer import MenuRenderer 8 | 9 | 10 | class MenuRendererAdafruitSSD1306(MenuRenderer): 11 | """Class implementing a menu renderer using the Adafruit SSD1306 library""" 12 | 13 | def display_init(self): 14 | i2c = busio.I2C(SCL, SDA) 15 | self.disp = adafruit_ssd1306.SSD1306_I2C( 16 | self._config.width, self._config.height, i2c, addr=self._config.i2c_address 17 | ) 18 | self.disp.rotation = self._config.get_mapped_rotation() 19 | self.disp.fill(0) 20 | self.disp.show() 21 | 22 | return self.disp 23 | 24 | def update_display_image(self, image): 25 | self.disp.image(self.image) 26 | self.disp.show() 27 | 28 | def update_display(self): 29 | self.disp.show() 30 | 31 | def display_clear(self): 32 | self.disp.fill(0) 33 | 34 | def blank_screen(self): 35 | self.disp.fill(0) 36 | self.disp.show() 37 | -------------------------------------------------------------------------------- /python/ctrlboard/src/menu/menu.py: -------------------------------------------------------------------------------- 1 | """Module for creating a menu""" 2 | 3 | from typing import List 4 | 5 | 6 | class Menu: 7 | """Class implement the Menu class""" 8 | 9 | def __init__(self, name: str): 10 | self.entries: List = [] 11 | self.item_selection = 0 12 | self.name = name 13 | self.context_object = None 14 | 15 | def add_entry(self, text, data_object=None): 16 | """Adds an entry to a menu""" 17 | entry = {"text": text, "data_object": data_object} 18 | self.entries.append(entry) 19 | 20 | def get_current_text(self): 21 | """Returns the text content of the currently selected text in the menu.""" 22 | return self.entries[self.item_selection]["text"] 23 | 24 | def get_current_info_object(self): 25 | """Returns the data object to the currently selected menu item""" 26 | return self.entries[self.item_selection]["data_object"] 27 | 28 | def __repr__(self): 29 | print("entries: " + str(self.entries)) 30 | print("item_selection: " + str(self.item_selection)) 31 | print("name: " + self.name) 32 | print("context object: " + str(self.context_object)) 33 | -------------------------------------------------------------------------------- /cpp/shared/piscsi_exceptions.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2021-2022 Uwe Seimet 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #pragma once 11 | 12 | #include "scsi.h" 13 | #include 14 | 15 | using namespace std; 16 | 17 | class parser_exception : public runtime_error 18 | { 19 | using runtime_error::runtime_error; 20 | }; 21 | 22 | class io_exception : public runtime_error 23 | { 24 | using runtime_error::runtime_error; 25 | }; 26 | 27 | class file_not_found_exception : public io_exception 28 | { 29 | using io_exception::io_exception; 30 | }; 31 | 32 | class scsi_exception : public exception 33 | { 34 | scsi_defs::sense_key sense_key; 35 | scsi_defs::asc asc; 36 | 37 | public: 38 | 39 | scsi_exception(scsi_defs::sense_key sense_key, scsi_defs::asc asc = scsi_defs::asc::no_additional_sense_information) 40 | : sense_key(sense_key), asc(asc) {} 41 | ~scsi_exception() override = default; 42 | 43 | scsi_defs::sense_key get_sense_key() const { return sense_key; } 44 | scsi_defs::asc get_asc() const { return asc; } 45 | }; 46 | -------------------------------------------------------------------------------- /cpp/scsictl/scsictl_parser.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2022 Uwe Seimet 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #include "generated/piscsi_interface.pb.h" 11 | #include 12 | #include 13 | 14 | using namespace std; 15 | using namespace piscsi_interface; 16 | 17 | class ScsictlParser 18 | { 19 | 20 | public: 21 | 22 | ScsictlParser() = default; 23 | ~ScsictlParser() = default; 24 | 25 | PbOperation ParseOperation(string_view) const; 26 | PbDeviceType ParseType(const string&) const; 27 | 28 | private: 29 | 30 | const unordered_map operations = { 31 | { 'a', ATTACH }, 32 | { 'd', DETACH }, 33 | { 'e', EJECT }, 34 | { 'i', INSERT }, 35 | { 'p', PROTECT }, 36 | { 's', DEVICES_INFO }, 37 | { 'u', UNPROTECT } 38 | }; 39 | 40 | const unordered_map device_types = { 41 | { 'c', SCCD }, 42 | { 'd', SCDP }, 43 | { 'h', SCHD }, 44 | { 's', SCHS }, 45 | { 'm', SCMO }, 46 | { 'p', SCLP }, 47 | { 'r', SCRM }, 48 | { 't', SCTP } 49 | }; 50 | }; 51 | -------------------------------------------------------------------------------- /doc/scsimon.1: -------------------------------------------------------------------------------- 1 | .TH scsimon 1 2 | .SH NAME 3 | scsimon \- Acts as a data capture tool for all traffic on the SCSI bus. Data is stored in a Value Change Dump (VCD) file. 4 | .SH SYNOPSIS 5 | .B scsimon 6 | .SH DESCRIPTION 7 | .B scsimon 8 | monitors all of the traffic on the SCSI bus, using a PiSCSI device. The data is cached in memory while the tool is running. A circular buffer is used so that only the most recent 1,000,000 transactions are stored. The tool will continue to run until the user presses CTRL-C, or the process receives a SIGINT signal. 9 | .PP 10 | The logged data is stored in a file called "log.vcd" in the current working directory from where scsimon was launched. 11 | 12 | Currently, scsimon doesn't accept any arguments. 13 | 14 | To quit scsimon, press Control + C. 15 | 16 | .SH OPTIONS 17 | .TP 18 | None 19 | 20 | .SH EXAMPLES 21 | Make sure you've stopped the piscsi service. Then launch scsimon to capture all SCSI traffic available to the PiSCSI hardware: 22 | scsimon 23 | 24 | If you're trying to capture a specific scenario, you'll want to wait to start scsimon until immediately before the scenario. 25 | 26 | .SH SEE ALSO 27 | scsictl(1), piscsi(1), scsidump(1) 28 | 29 | Full documentation is available at: 30 | -------------------------------------------------------------------------------- /python/web/src/static/themes/modern/icons/LICENSE: -------------------------------------------------------------------------------- 1 | Feather Icons 2 | https://github.com/feathericons/feather 3 | 4 | --- 5 | 6 | The MIT License (MIT) 7 | 8 | Copyright (c) 2013-2017 Cole Bemis 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. -------------------------------------------------------------------------------- /docker/backend/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG DEBIAN_FRONTEND=noninteractive 2 | 3 | FROM debian:bullseye AS build 4 | RUN apt-get update && apt-get install --assume-yes --no-install-recommends sudo 5 | RUN groupadd pi \ 6 | && useradd --create-home --shell /bin/bash -g pi pi \ 7 | && echo "pi ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers 8 | 9 | USER pi 10 | WORKDIR /home/pi/piscsi 11 | 12 | COPY --chown=pi:pi easyinstall.sh . 13 | COPY --chown=pi:pi cpp cpp 14 | COPY --chown=pi:pi doc doc 15 | COPY --chown=pi:pi proto proto 16 | RUN ./easyinstall.sh --run_choice=15 --cores=`nproc` 17 | 18 | FROM debian:bullseye-slim AS runner 19 | USER root 20 | WORKDIR /home/pi 21 | 22 | COPY --from=build /home/pi/piscsi/cpp/bin/* /usr/local/bin/ 23 | COPY docker/backend/piscsi_wrapper.sh /usr/local/bin/piscsi_wrapper.sh 24 | RUN chmod +x /usr/local/bin/* 25 | RUN mkdir -p /home/pi/images 26 | 27 | RUN apt-get update \ 28 | && apt-get install --no-install-recommends --assume-yes libpcap-dev libprotobuf-dev \ 29 | && apt autoremove -y \ 30 | && apt-get clean \ 31 | && rm -rf /var/lib/apt/lists/* 32 | 33 | EXPOSE 6868 34 | ENTRYPOINT ["/usr/local/bin/piscsi_wrapper.sh", "-r", "7", "-F", "/home/pi/images"] 35 | CMD ["-L", "trace"] 36 | 37 | HEALTHCHECK --interval=5m --timeout=1s CMD scsictl -v 38 | -------------------------------------------------------------------------------- /python/ctrlboard/src/menu/screensaver.py: -------------------------------------------------------------------------------- 1 | """Module providing the menu screensaver class""" 2 | 3 | from abc import abstractmethod 4 | from menu.timer import Timer 5 | 6 | 7 | class ScreenSaver: 8 | """Class implementing the menu screensaver""" 9 | 10 | def __init__(self, activation_delay, menu_renderer): 11 | self.enabled = False 12 | self.menu_renderer = menu_renderer 13 | self.screensaver_activation_delay = activation_delay 14 | self.timer_flag = Timer(self.screensaver_activation_delay) 15 | 16 | def draw(self): 17 | """Draws the screen saver in a non-blocking way if enabled.""" 18 | if self.enabled is True: 19 | self.draw_screensaver() 20 | 21 | @abstractmethod 22 | def draw_screensaver(self): 23 | """Draws the screen saver. Must be implemented in subclasses.""" 24 | 25 | def check_timer(self): 26 | """Checks if the screen saver should be enabled given the configured 27 | activation delay.""" 28 | self.timer_flag.check_timer() 29 | self.enabled = self.timer_flag.enabled 30 | 31 | def reset_timer(self): 32 | """Resets the screen saver timer if an activitiy happend.""" 33 | self.timer_flag.reset_timer() 34 | self.enabled = self.timer_flag.enabled 35 | -------------------------------------------------------------------------------- /python/ctrlboard/src/ctrlboard_event_handler/piscsi_shutdown_cycler.py: -------------------------------------------------------------------------------- 1 | """Module providing the shutdown cycler for the PiSCSI Control Board UI""" 2 | 3 | from menu.cycler import Cycler 4 | 5 | 6 | class PiscsiShutdownCycler(Cycler): 7 | """Class implementing the shutdown cycler for the PiSCSI Control Board UI""" 8 | 9 | def __init__(self, menu_controller, sock_cmd, piscsi_cmd): 10 | super().__init__( 11 | menu_controller, 12 | sock_cmd, 13 | piscsi_cmd, 14 | return_entry=True, 15 | empty_messages=False, 16 | ) 17 | self.executed_once = False 18 | 19 | def populate_cycle_entries(self): 20 | cycle_entries = ["Shutdown"] 21 | 22 | return cycle_entries 23 | 24 | def perform_selected_entry_action(self, selected_entry): 25 | if self.executed_once is False: 26 | self.executed_once = True 27 | self._menu_controller.show_timed_message("Shutting down...") 28 | self.piscsi_cmd.shutdown("system") 29 | return "shutdown" 30 | 31 | return None 32 | 33 | def perform_return_action(self): 34 | self._menu_controller.show_timed_mini_message("") 35 | self._menu_controller.show_timed_message("") 36 | 37 | return "return" 38 | -------------------------------------------------------------------------------- /cpp/scsiloop/scsiloop_cout.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI for Raspberry Pi 4 | // Loopback tester utility 5 | // 6 | // Copyright (C) 2022 akuker 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #include 11 | #include 12 | 13 | using namespace std; 14 | 15 | class ScsiLoop_Cout 16 | { 17 | public: 18 | static void StartTest(const string &test_name); 19 | static void PrintUpdate(); 20 | static void FinishTest(const string &test_name, int failures); 21 | static void PrintErrors(const vector &test_errors); 22 | 23 | private: 24 | const static inline string RESET = "\033[0m"; 25 | const static inline string BLACK = "\033[30m"; /* Black */ 26 | const static inline string RED = "\033[31m"; /* Red */ 27 | const static inline string GREEN = "\033[32m"; /* Green */ 28 | const static inline string YELLOW = "\033[33m"; /* Yellow */ 29 | const static inline string BLUE = "\033[34m"; /* Blue */ 30 | const static inline string MAGENTA = "\033[35m"; /* Magenta */ 31 | const static inline string CYAN = "\033[36m"; /* Cyan */ 32 | const static inline string WHITE = "\033[37m"; /* White */ 33 | }; 34 | -------------------------------------------------------------------------------- /python/ctrlboard/src/menu/menu_renderer_luma_oled.py: -------------------------------------------------------------------------------- 1 | """Module providing the luma oled menu renderer class""" 2 | 3 | from luma.core.interface.serial import i2c 4 | from menu.menu_renderer import MenuRenderer 5 | 6 | 7 | class MenuRendererLumaOled(MenuRenderer): 8 | """Class implementing the luma oled menu renderer""" 9 | 10 | def display_init(self): 11 | serial = i2c(port=self._config.i2c_port, address=self._config.i2c_address) 12 | import luma.oled.device 13 | 14 | device = getattr(luma.oled.device, self._config.display_type) 15 | 16 | self.disp = device( 17 | serial_interface=serial, 18 | width=self._config.width, 19 | height=self._config.height, 20 | rotate=self._config.get_mapped_rotation(), 21 | ) 22 | 23 | self.disp.clear() 24 | self.disp.show() 25 | 26 | return self.disp 27 | 28 | def update_display_image(self, image): 29 | self.disp.display(image) 30 | 31 | def update_display(self): 32 | self.disp.display(self.image) 33 | 34 | def display_clear(self): 35 | pass 36 | 37 | def blank_screen(self): 38 | self.disp.clear() 39 | self.draw.rectangle((0, 0, self.disp.width, self.disp.height), outline=0, fill=0) 40 | self.disp.show() 41 | -------------------------------------------------------------------------------- /python/common/src/README.md: -------------------------------------------------------------------------------- 1 | # PiSCSI Common Python Module 2 | 3 | The common module contains python modules that are shared among multiple Python 4 | applications such as the OLED or the Web application. It contains shared functionality. 5 | For example, the piscsi python module provides functionality for accessing piscsi through its 6 | protobuf interface and provides convenient classes for that purpose. 7 | 8 | ### Usage 9 | 10 | To make use of the piscsi python module, it needs to be found by the Python scripts using it. 11 | This can be achieved in multiple ways. One way is to simply adapt the PYTHONPATH environment 12 | variable to include the common/src directory: 13 | 14 | ``` 15 | PYTHON_COMMON_PATH=${path_to_common_directory}/common/src 16 | export PYTHONPATH=$PWD:${PYTHON_COMMON_PATH} 17 | python3 myapp.py 18 | ``` 19 | 20 | The most interesting functions are likely found in the classes PiscsiCmds and FileCmds. Classes 21 | can be instantiated, for example, as follows 22 | (assuming that piscsi host, piscsi port and token are somehow retrieved from a command line 23 | argument): 24 | 25 | ``` 26 | sock_cmd = SocketCmds(host=args.piscsi_host, port=args.piscsi_port) 27 | piscsi_cmd = PiscsiCmds(sock_cmd=sock_cmd, token=args.token) 28 | ``` 29 | 30 | Usage examples can be found in the existing PiSCSI Python applications. 31 | -------------------------------------------------------------------------------- /doc/scsiloop.1: -------------------------------------------------------------------------------- 1 | .TH scsiloop 1 2 | .SH NAME 3 | scsiloop \- Tool for testing the PiSCSI board with a loopback adapter installed 4 | .SH SYNOPSIS 5 | .B scsiloop 6 | [\fB\-L\fR \fILOG_LEVEL\fR] 7 | .SH DESCRIPTION 8 | .B scsiloop 9 | Performs a self-test of the PiSCSI hardware to ensure that the board is functioning properly. In order for this tool to work, a special loopback cable MUST be attached to the PiSCSI SCSI connector. 10 | 11 | In addition to testing the GPIO signals, scsiloop will perform a self-test of the hardware timers that are built into the system on a chip (SoC). 12 | 13 | The loopback connections for the DB25 connector are shown here: 14 | 15 | |Pin | Name | Pin | Name | 16 | +----+------+-----+------+ 17 | | 1 | REQ | 13 | DB7 | 18 | | 2 | MSG | 12 | DB6 | 19 | | 3 | I/O | 11 | DB5 | 20 | | 4 | RST | 10 | DB3 | 21 | | 5 | ACK | 8 | DB0 | 22 | | 6 | BSY | 20 | DBP | 23 | | 15 | C/D | 23 | DB4 | 24 | | 17 | ATN | 22 | DB2 | 25 | | 19 | SEL | 21 | DB1 | 26 | 27 | .SH OPTIONS 28 | .TP 29 | .BR \-L\fI " " \fILOG_LEVEL 30 | The piscsi log level (trace, debug, info, warn, err, off). The default log level is 'info'. 31 | 32 | .SH SEE ALSO 33 | scsictl(1), piscsi(1), scsimon(1) 34 | 35 | Full documentation is available at: 36 | -------------------------------------------------------------------------------- /python/ctrlboard/src/config.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module for central PiSCSI control board configuration parameters 3 | """ 4 | 5 | from ctrlboard_hw.ctrlboard_hw_constants import CtrlBoardHardwareConstants 6 | 7 | 8 | # pylint: disable=too-few-public-methods 9 | class CtrlboardConfig: 10 | """Class for central PiSCSI control board configuration parameters""" 11 | 12 | ROTATION = 0 13 | WIDTH = 128 14 | HEIGHT = 64 15 | LINES = 8 16 | TOKEN = "" 17 | BORDER = 5 18 | TRANSITIONS = 1 19 | DISPLAY_I2C_ADDRESS = CtrlBoardHardwareConstants.DISPLAY_I2C_ADDRESS 20 | PCA9554_I2C_ADDRESS = CtrlBoardHardwareConstants.PCA9554_I2C_ADDRESS 21 | MENU_REFRESH_INTERVAL = 6 22 | LOG_LEVEL = 30 # Warning 23 | 24 | PISCSI_HOST = "localhost" 25 | PISCSI_PORT = "6868" 26 | 27 | def __str__(self): 28 | result = "rotation: " + str(self.ROTATION) + "\n" 29 | result += "width: " + str(self.WIDTH) + "\n" 30 | result += "height: " + str(self.HEIGHT) + "\n" 31 | result += "lines: " + str(self.LINES) + "\n" 32 | result += "border: " + str(self.BORDER) + "\n" 33 | result += "piscsi host: " + str(self.PISCSI_HOST) + "\n" 34 | result += "piscsi port: " + str(self.PISCSI_PORT) + "\n" 35 | result += "transitions: " + str(self.TRANSITIONS) + "\n" 36 | 37 | return result 38 | -------------------------------------------------------------------------------- /python/web/src/static/themes/modern/icons/file-device-attach.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 14 | 17 | 23 | 24 | -------------------------------------------------------------------------------- /cpp/test/ctapdriver_test.cpp: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2022-2023 Uwe Seimet 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #include "mocks.h" 11 | #include 12 | #include "devices/ctapdriver.h" 13 | 14 | TEST(CTapDriverTest, Crc32) 15 | { 16 | array buf; 17 | 18 | buf.fill(0x00); 19 | EXPECT_EQ(0xe3d887bb, CTapDriver::Crc32(span(buf.data(), ETH_FRAME_LEN))); 20 | 21 | buf.fill(0xff); 22 | EXPECT_EQ(0x814765f4, CTapDriver::Crc32(span(buf.data(), ETH_FRAME_LEN))); 23 | 24 | buf.fill(0x10); 25 | EXPECT_EQ(0xb7288Cd3, CTapDriver::Crc32(span(buf.data(), ETH_FRAME_LEN))); 26 | 27 | buf.fill(0x7f); 28 | EXPECT_EQ(0x4b543477, CTapDriver::Crc32(span(buf.data(), ETH_FRAME_LEN))); 29 | 30 | buf.fill(0x80); 31 | EXPECT_EQ(0x29cbd638, CTapDriver::Crc32(span(buf.data(), ETH_FRAME_LEN))); 32 | 33 | for (size_t i = 0; i < buf.size(); i++) { 34 | buf[i] = (uint8_t)i; 35 | } 36 | EXPECT_EQ(0xe7870705, CTapDriver::Crc32(span(buf.data(), ETH_FRAME_LEN))); 37 | 38 | for (size_t i = buf.size() - 1; i > 0; i--) { 39 | buf[i] = (uint8_t)i; 40 | } 41 | EXPECT_EQ(0xe7870705, CTapDriver::Crc32(span(buf.data(), ETH_FRAME_LEN))); 42 | } 43 | -------------------------------------------------------------------------------- /python/web/src/templates/deviceinfo.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

{{ _("Detailed Info for Attached Devices") }}

5 |

6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | {% for device in devices %} 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | {% endfor %} 35 |
{{ _("SCSI ID") }}{{ _("LUN") }}{{ _("Type") }}{{ _("Status") }}{{ _("File") }}{{ _("Parameters") }}{{ _("Vendor") }}{{ _("Product") }}{{ _("Revision") }}{{ _("Block Size") }}{{ _("Image Size") }}
{{ device["id"] }}{{ device["unit"] }}{{ device["device_type"] }}{{ device["status"] }}{{ device["image"] }}{{ device["params"] }}{{ device["vendor"] }}{{ device["product"] }}{{ device["revision"] }}{{ device["block_size"] }}{{ device["size"] }}
36 |

37 | 38 |
39 | {% endblock content %} 40 | -------------------------------------------------------------------------------- /python/web/src/static/themes/modern/icons/file-extract.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 11 | 14 | 15 | -------------------------------------------------------------------------------- /python/common/src/piscsi/return_codes.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module for return codes that are refrenced in the return payloads of the piscsi module. 3 | """ 4 | 5 | 6 | # pylint: disable=too-few-public-methods 7 | class ReturnCodes: 8 | """Class for the return codes used within the piscsi module.""" 9 | 10 | DELETEFILE_SUCCESS = 0 11 | DELETEFILE_FILE_NOT_FOUND = 1 12 | DELETEFILE_UNABLE_TO_DELETE = 2 13 | RENAMEFILE_SUCCESS = 10 14 | RENAMEFILE_UNABLE_TO_MOVE = 11 15 | DOWNLOADFILETOISO_SUCCESS = 20 16 | DOWNLOADTODIR_SUCCESS = 30 17 | WRITEFILE_SUCCESS = 40 18 | WRITEFILE_COULD_NOT_WRITE = 41 19 | WRITEFILE_COULD_NOT_OVERWRITE = 42 20 | READCONFIG_SUCCESS = 50 21 | READCONFIG_COULD_NOT_READ = 51 22 | READCONFIG_INVALID_CONFIG_FILE_FORMAT = 52 23 | READDRIVEPROPS_SUCCESS = 70 24 | READDRIVEPROPS_COULD_NOT_READ = 71 25 | ATTACHIMAGE_COULD_NOT_ATTACH = 80 26 | EXTRACTIMAGE_SUCCESS = 90 27 | EXTRACTIMAGE_NO_FILES_SPECIFIED = 91 28 | EXTRACTIMAGE_NO_FILES_EXTRACTED = 92 29 | EXTRACTIMAGE_COMMAND_ERROR = 93 30 | UNDER_VOLTAGE_DETECTED = 100 31 | ARM_FREQUENCY_CAPPED = 101 32 | CURRENTLY_THROTTLED = 102 33 | SOFT_TEMPERATURE_LIMIT_ACTIVE = 103 34 | UNDER_VOLTAGE_HAS_OCCURRED = 116 35 | ARM_FREQUENCY_CAPPING_HAS_OCCURRED = 117 36 | THROTTLING_HAS_OCCURRED = 118 37 | SOFT_TEMPERATURE_LIMIT_HAS_OCCURRED = 119 38 | -------------------------------------------------------------------------------- /.github/workflows/build_code.yml: -------------------------------------------------------------------------------- 1 | name: Build ARM binaries 2 | 3 | on: 4 | push: 5 | branches: 6 | - develop 7 | - main 8 | paths: 9 | - '.github/workflows/arm_cross_compile.yml' 10 | - '.github/workflows/build_code.yml' 11 | - 'cpp/**' 12 | pull_request: 13 | branches: 14 | - develop 15 | - main 16 | types: 17 | - opened 18 | - synchronize 19 | - reopened 20 | paths: 21 | - '.github/workflows/arm_cross_compile.yml' 22 | - '.github/workflows/build_code.yml' 23 | - 'cpp/**' 24 | 25 | jobs: 26 | fullspec: 27 | uses: PiSCSI/piscsi/.github/workflows/arm_cross_compile.yml@develop 28 | with: 29 | connect-type: "FULLSPEC" 30 | 31 | standard: 32 | uses: PiSCSI/piscsi/.github/workflows/arm_cross_compile.yml@develop 33 | with: 34 | connect-type: "STANDARD" 35 | 36 | aibom: 37 | uses: PiSCSI/piscsi/.github/workflows/arm_cross_compile.yml@develop 38 | with: 39 | connect-type: "AIBOM" 40 | 41 | gamernium: 42 | uses: PiSCSI/piscsi/.github/workflows/arm_cross_compile.yml@develop 43 | with: 44 | connect-type: "GAMERNIUM" 45 | 46 | # The fullspec connection board is the most common 47 | debug-fullspec: 48 | uses: PiSCSI/piscsi/.github/workflows/arm_cross_compile.yml@develop 49 | with: 50 | connect-type: "FULLSPEC" 51 | debug-flag: true 52 | -------------------------------------------------------------------------------- /cpp/hal/gpiobus_factory.cpp: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2022 akuker 7 | // Copyright (C) 2023 Uwe Seimet 8 | // 9 | //--------------------------------------------------------------------------- 10 | 11 | #include 12 | 13 | #include "hal/gpiobus_factory.h" 14 | #include "hal/gpiobus_raspberry.h" 15 | #include "hal/gpiobus_virtual.h" 16 | #include "hal/sbc_version.h" 17 | #include 18 | #include 19 | 20 | using namespace std; 21 | 22 | unique_ptr GPIOBUS_Factory::Create(BUS::mode_e mode) 23 | { 24 | unique_ptr bus; 25 | 26 | try { 27 | SBC_Version::Init(); 28 | if (SBC_Version::IsRaspberryPi()) { 29 | if (getuid()) { 30 | spdlog::error("GPIO bus access requires root permissions. Are you running as root?"); 31 | return nullptr; 32 | } 33 | 34 | bus = make_unique(); 35 | } else { 36 | bus = make_unique(); 37 | } 38 | 39 | if (bus->Init(mode)) { 40 | bus->Reset(); 41 | } 42 | } catch (const invalid_argument& e) { 43 | spdlog::error(string("Exception while trying to initialize GPIO bus: ") + e.what()); 44 | return nullptr; 45 | } 46 | 47 | return bus; 48 | } 49 | -------------------------------------------------------------------------------- /cpp/shared/piscsi_util.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2021-2023 Uwe Seimet 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #pragma once 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | using namespace std; 18 | 19 | namespace piscsi_util 20 | { 21 | // Separator for compound options like ID:LUN 22 | static const char COMPONENT_SEPARATOR = ':'; 23 | 24 | struct StringHash { 25 | using is_transparent = void; 26 | 27 | size_t operator()(string_view sv) const { 28 | hash hasher; 29 | return hasher(sv); 30 | } 31 | }; 32 | 33 | string Join(const auto& collection, const string_view separator = ", ") { 34 | ostringstream s; 35 | 36 | for (const auto& element : collection) { 37 | if (s.tellp()) { 38 | s << separator; 39 | } 40 | 41 | s << element; 42 | } 43 | 44 | return s.str(); 45 | } 46 | 47 | vector Split(const string&, char, int = INT_MAX); 48 | string GetLocale(); 49 | bool GetAsUnsignedInt(const string&, int&); 50 | string ProcessId(const string&, int&, int&); 51 | string Banner(string_view); 52 | 53 | string GetExtensionLowerCase(string_view); 54 | 55 | void LogErrno(const string&); 56 | 57 | void FixCpu(int); 58 | } 59 | -------------------------------------------------------------------------------- /cpp/test/piscsi_exceptions_test.cpp: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2022-2023 Uwe Seimet 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #include 11 | 12 | #include "shared/piscsi_exceptions.h" 13 | 14 | using namespace scsi_defs; 15 | 16 | TEST(PiscsiExceptionsTest, IoException) 17 | { 18 | try { 19 | throw io_exception("msg"); 20 | } 21 | catch(const io_exception& e) { 22 | EXPECT_STREQ("msg", e.what()); 23 | } 24 | } 25 | 26 | TEST(PiscsiExceptionsTest, FileNotFoundException) 27 | { 28 | try { 29 | throw file_not_found_exception("msg"); 30 | } 31 | catch(const file_not_found_exception& e) { 32 | EXPECT_STREQ("msg", e.what()); 33 | } 34 | } 35 | 36 | TEST(PiscsiExceptionsTest, ScsiErrorException) 37 | { 38 | try { 39 | throw scsi_exception(sense_key::unit_attention); 40 | } 41 | catch(const scsi_exception& e) { 42 | EXPECT_EQ(sense_key::unit_attention, e.get_sense_key()); 43 | EXPECT_EQ(asc::no_additional_sense_information, e.get_asc()); 44 | } 45 | 46 | try { 47 | throw scsi_exception(sense_key::illegal_request, asc::lba_out_of_range); 48 | } 49 | catch(const scsi_exception& e) { 50 | EXPECT_EQ(sense_key::illegal_request, e.get_sense_key()); 51 | EXPECT_EQ(asc::lba_out_of_range, e.get_asc()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /cpp/devices/scsihd.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) 7 | // Copyright (C) 2014-2020 GIMONS 8 | // Copyright (C) 2022-2023 Uwe Seimet 9 | // Copyright (C) akuker 10 | // 11 | // Licensed under the BSD 3-Clause License. 12 | // See LICENSE file in the project root folder. 13 | // 14 | //--------------------------------------------------------------------------- 15 | 16 | #pragma once 17 | 18 | #include "shared/scsi.h" 19 | #include "disk.h" 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | class SCSIHD : public Disk 26 | { 27 | const string DEFAULT_PRODUCT = "SCSI HD"; 28 | 29 | public: 30 | 31 | SCSIHD(int, bool, scsi_defs::scsi_level, const unordered_set& = { 512, 1024, 2048, 4096 }); 32 | ~SCSIHD() override = default; 33 | 34 | void FinalizeSetup(off_t); 35 | 36 | void Open() override; 37 | 38 | // Commands 39 | vector InquiryInternal() const override; 40 | void ModeSelect(scsi_defs::scsi_command, cdb_t, span, int) override; 41 | 42 | void AddFormatPage(map>&, bool) const override; 43 | void AddVendorPage(map>&, int, bool) const override; 44 | 45 | private: 46 | 47 | string GetProductData() const; 48 | 49 | scsi_defs::scsi_level scsi_level; 50 | }; 51 | -------------------------------------------------------------------------------- /cpp/hal/systimer.cpp: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Powered by XM6 TypeG Technology. 7 | // Copyright (C) 2016-2020 GIMONS 8 | // Copyright (C) 2022 akuker 9 | // 10 | // [ High resolution timer ] 11 | // 12 | //--------------------------------------------------------------------------- 13 | 14 | #include "hal/systimer.h" 15 | #include "hal/systimer_raspberry.h" 16 | #include 17 | 18 | #include "hal/gpiobus.h" 19 | #include "hal/sbc_version.h" 20 | 21 | bool SysTimer::initialized = false; 22 | bool SysTimer::is_raspberry = false; 23 | 24 | using namespace std; 25 | 26 | unique_ptr SysTimer::systimer_ptr; 27 | 28 | void SysTimer::Init() 29 | { 30 | if (!initialized) { 31 | if (SBC_Version::IsRaspberryPi()) { 32 | systimer_ptr = make_unique(); 33 | is_raspberry = true; 34 | } 35 | systimer_ptr->Init(); 36 | initialized = true; 37 | } 38 | } 39 | 40 | // Get system timer low byte 41 | uint32_t SysTimer::GetTimerLow() 42 | { 43 | return systimer_ptr->GetTimerLow(); 44 | } 45 | 46 | // Sleep for N nanoseconds 47 | void SysTimer::SleepNsec(uint32_t nsec) 48 | { 49 | systimer_ptr->SleepNsec(nsec); 50 | } 51 | 52 | // Sleep for N microseconds 53 | void SysTimer::SleepUsec(uint32_t usec) 54 | { 55 | systimer_ptr->SleepUsec(usec); 56 | } 57 | -------------------------------------------------------------------------------- /cpp/scsimon/sm_core.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2022-2023 Uwe Seimet 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #pragma once 11 | 12 | #include "hal/bus.h" 13 | #include "hal/data_sample.h" 14 | #include 15 | #include 16 | #include 17 | 18 | using namespace std; 19 | 20 | // TODO Make static fields/methods non-static 21 | class ScsiMon 22 | { 23 | public: 24 | ScsiMon() = default; 25 | ~ScsiMon() = default; 26 | 27 | int run(const vector &); 28 | 29 | inline static double ns_per_loop; 30 | 31 | private: 32 | void ParseArguments(const vector &); 33 | void PrintHelpText(const vector &) const; 34 | void Banner() const; 35 | bool Init(); 36 | void Cleanup() const; 37 | void Reset() const; 38 | 39 | static void KillHandler(int); 40 | 41 | static inline atomic running; 42 | 43 | shared_ptr bus; 44 | 45 | uint32_t buff_size = 1000000; 46 | 47 | vector> data_buffer; 48 | 49 | uint32_t data_idx = 0; 50 | 51 | bool print_help = false; 52 | 53 | bool import_data = false; 54 | 55 | string file_base_name = "log"; 56 | string vcd_file_name; 57 | string json_file_name; 58 | string html_file_name; 59 | string input_file_name; 60 | }; 61 | -------------------------------------------------------------------------------- /python/web/tests/api/test_auth.py: -------------------------------------------------------------------------------- 1 | from conftest import STATUS_SUCCESS, STATUS_ERROR, LOGIN_ENDPOINT, LOGOUT_ENDPOINT 2 | 3 | 4 | def test_login_with_valid_credentials(pytestconfig, http_client_unauthenticated): 5 | # Note: This test depends on the piscsi group existing and 'username' a member the group 6 | response = http_client_unauthenticated.post( 7 | LOGIN_ENDPOINT, 8 | data={ 9 | "username": pytestconfig.getoption("piscsi_username"), 10 | "password": pytestconfig.getoption("piscsi_password"), 11 | }, 12 | ) 13 | 14 | response_data = response.json() 15 | 16 | assert response.status_code == 200 17 | assert response_data["status"] == STATUS_SUCCESS 18 | assert "env" in response_data["data"] 19 | 20 | 21 | def test_login_with_invalid_credentials(http_client_unauthenticated): 22 | response = http_client_unauthenticated.post( 23 | LOGIN_ENDPOINT, 24 | data={ 25 | "username": "__INVALID_USER__", 26 | "password": "__INVALID_PASS__", 27 | }, 28 | ) 29 | 30 | response_data = response.json() 31 | 32 | assert response.status_code == 401 33 | assert response_data["status"] == STATUS_ERROR 34 | assert response_data["messages"][0]["message"] == ( 35 | "You must log in with valid credentials for a user in the 'piscsi' group" 36 | ) 37 | 38 | 39 | def test_logout(http_client): 40 | response = http_client.get(LOGOUT_ENDPOINT) 41 | assert response.status_code == 200 42 | -------------------------------------------------------------------------------- /cpp/hal/data_sample.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2022 akuker 7 | // 8 | // [ Logical representation of a single data sample ] 9 | // 10 | //--------------------------------------------------------------------------- 11 | 12 | #pragma once 13 | 14 | #include "shared/scsi.h" 15 | #include 16 | #include 17 | 18 | using namespace scsi_defs; 19 | 20 | class DataSample 21 | { 22 | public: 23 | virtual bool GetBSY() const = 0; 24 | virtual bool GetSEL() const = 0; 25 | virtual bool GetATN() const = 0; 26 | virtual bool GetACK() const = 0; 27 | virtual bool GetRST() const = 0; 28 | virtual bool GetMSG() const = 0; 29 | virtual bool GetCD() const = 0; 30 | virtual bool GetIO() const = 0; 31 | virtual bool GetREQ() const = 0; 32 | virtual bool GetACT() const = 0; 33 | virtual uint8_t GetDAT() const = 0; 34 | 35 | virtual uint32_t GetRawCapture() const = 0; 36 | 37 | phase_t GetPhase() const; 38 | virtual bool GetSignal(int pin) const = 0; 39 | 40 | uint64_t GetTimestamp() const 41 | { 42 | return timestamp; 43 | } 44 | 45 | string GetPhaseStr() const; 46 | 47 | virtual ~DataSample() = default; 48 | 49 | explicit DataSample(uint64_t in_timestamp) : timestamp{in_timestamp} {} 50 | DataSample() = default; 51 | 52 | private: 53 | uint64_t timestamp = 0; 54 | }; 55 | -------------------------------------------------------------------------------- /cpp/devices/mode_page_device.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2022-2023 Uwe Seimet 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #pragma once 11 | 12 | #include "primary_device.h" 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | class ModePageDevice : public PrimaryDevice 19 | { 20 | public: 21 | 22 | using PrimaryDevice::PrimaryDevice; 23 | 24 | bool Init(const param_map&) override; 25 | 26 | virtual void ModeSelect(scsi_defs::scsi_command, cdb_t, span, int); 27 | 28 | protected: 29 | 30 | bool SupportsSaveParameters() const { return supports_save_parameters; } 31 | void SupportsSaveParameters(bool b) { supports_save_parameters = b; } 32 | int AddModePages(cdb_t, vector&, int, int, int) const; 33 | virtual void SetUpModePages(map>&, int, bool) const = 0; 34 | virtual void AddVendorPage(map>&, int, bool) const { 35 | // Nothing to add by default 36 | } 37 | 38 | private: 39 | 40 | bool supports_save_parameters = false; 41 | 42 | virtual int ModeSense6(cdb_t, vector&) const = 0; 43 | virtual int ModeSense10(cdb_t, vector&) const = 0; 44 | 45 | void ModeSense6() const; 46 | void ModeSense10() const; 47 | void ModeSelect6() const; 48 | void ModeSelect10() const; 49 | 50 | void SaveParametersCheck(int) const; 51 | }; 52 | -------------------------------------------------------------------------------- /cpp/devices/cd_track.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) 7 | // Copyright (C) 2014-2020 GIMONS 8 | // Copyright (C) akuker 9 | // 10 | // Licensed under the BSD 3-Clause License. 11 | // See LICENSE file in the project root folder. 12 | // 13 | //--------------------------------------------------------------------------- 14 | 15 | #pragma once 16 | 17 | #include 18 | #include 19 | 20 | using namespace std; 21 | 22 | class CDTrack final 23 | { 24 | public: 25 | 26 | CDTrack() = default; 27 | ~CDTrack() = default; 28 | 29 | void Init(int track, uint32_t first, uint32_t last); 30 | 31 | // Properties 32 | void SetPath(bool, string_view); // Set the path 33 | string GetPath() const; // Get the path 34 | uint32_t GetFirst() const; // Get the start LBA 35 | uint32_t GetLast() const; // Get the last LBA 36 | uint32_t GetBlocks() const; // Get the number of blocks 37 | int GetTrackNo() const; // Get the track number 38 | bool IsValid(uint32_t lba) const; // Is this a valid LBA? 39 | bool IsAudio() const; // Is this an audio track? 40 | 41 | private: 42 | bool valid = false; // Valid track 43 | int track_no = -1; // Track number 44 | uint32_t first_lba = 0; // First LBA 45 | uint32_t last_lba = 0; // Last LBA 46 | bool audio = false; // Audio track flag 47 | 48 | string imgpath; // Image file path 49 | }; 50 | -------------------------------------------------------------------------------- /cpp/devices/scsimo.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) 7 | // Copyright (C) 2014-2020 GIMONS 8 | // Copyright (C) 2022-2023 Uwe Seimet 9 | // Copyright (C) akuker 10 | // 11 | // Licensed under the BSD 3-Clause License. 12 | // See LICENSE file in the project root folder. 13 | // 14 | //--------------------------------------------------------------------------- 15 | 16 | #pragma once 17 | 18 | #include "disk.h" 19 | #include 20 | #include 21 | #include 22 | 23 | using Geometry = pair; 24 | 25 | class SCSIMO : public Disk 26 | { 27 | public: 28 | 29 | explicit SCSIMO(int); 30 | ~SCSIMO() override = default; 31 | 32 | void Open() override; 33 | 34 | vector InquiryInternal() const override; 35 | void ModeSelect(scsi_defs::scsi_command, cdb_t, span, int) override; 36 | 37 | protected: 38 | 39 | void SetUpModePages(map>&, int, bool) const override; 40 | void AddFormatPage(map>&, bool) const override; 41 | void AddVendorPage(map>&, int, bool) const override; 42 | 43 | private: 44 | 45 | void AddOptionPage(map>&, bool) const; 46 | 47 | bool SetGeometryForCapacity(uint64_t); 48 | 49 | // The mapping of supported capacities to block sizes and block counts, empty if there is no capacity restriction 50 | unordered_map geometries; 51 | }; 52 | -------------------------------------------------------------------------------- /python/ctrlboard/README.md: -------------------------------------------------------------------------------- 1 | # PiSCSI Control Board UI 2 | 3 | ## Run as standalone script for development / troubleshooting 4 | 5 | ```bash 6 | # Make a virtual env named venv 7 | $ python3 -m venv venv 8 | # Use that virtual env in this shell 9 | $ source venv/bin/activate 10 | # Install requirements 11 | $ pip3 install -r requirements.txt 12 | $ python3 src/main.py 13 | ``` 14 | 15 | ### Parameters 16 | 17 | The script parameters can be shown with 18 | ``` 19 | python src/main.py --help 20 | ``` 21 | or 22 | ``` 23 | start.sh --help 24 | ``` 25 | 26 | Example: 27 | ``` 28 | $ python3 src/main.py --rotation=0 --transitions=0 29 | ``` 30 | 31 | ## Run the start.sh script standalone 32 | 33 | The start.sh script can also be run standalone, and will handle the venv creation/updating for you. It takes the same command line parameters in the following format: 34 | 35 | ``` 36 | $ ./start.sh --rotation=0 --transitions=0 37 | ``` 38 | ### I2C baudrate and transitions 39 | The available bandwidth for the display through I2C is limited. The I2C baudrate is automatically adjusted in 40 | easy_install.sh and start.sh to maximize the possible baudrate, however, depending on the Raspberry Pi model in use, we found that for some 41 | models enabling the transitions does not work very well. As a result, we have implemented a model detection for 42 | the raspberry pi model and enable transitions only for the following pi models: 43 | - Raspberry Pi 4 models 44 | - Raspberry Pi 3 models 45 | - Raspberry Pi Zero 2 models 46 | 47 | The model detection can be overriden by adding a --transitions parameter to start.sh. 48 | -------------------------------------------------------------------------------- /cpp/test/test_shared.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2022-2023 Uwe Seimet 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #pragma once 11 | 12 | #include "generated/piscsi_interface.pb.h" 13 | #include "shared/scsi.h" 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | using namespace std; 20 | using namespace filesystem; 21 | using namespace piscsi_interface; 22 | 23 | class PrimaryDevice; 24 | class MockAbstractController; 25 | 26 | extern const path test_data_temp_path; 27 | 28 | pair, shared_ptr> CreateDevice(PbDeviceType, const string& = ""); 29 | 30 | pair OpenTempFile(); 31 | path CreateTempFile(int); 32 | path CreateTempFileWithData(span); 33 | 34 | // create a file with the specified data 35 | void CreateTempFileWithData(const string&, vector&); 36 | 37 | void DeleteTempFile(const string&); 38 | 39 | string ReadTempFileToString(const string& filename); 40 | 41 | int GetInt16(const vector&, int); 42 | uint32_t GetInt32(const vector&, int); 43 | 44 | // This class is needed in order to be declared as friend, required to have access to AbstractController::SetCmdByte 45 | class TestInquiry { 46 | public: 47 | static void Inquiry(PbDeviceType, scsi_defs::device_type, scsi_defs::scsi_level, const string&, int, bool, 48 | const string& = ""); 49 | }; 50 | -------------------------------------------------------------------------------- /cpp/shared/protobuf_util.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2021-2023 Uwe Seimet 7 | // 8 | // Helper methods for setting up/evaluating protobuf messages 9 | // 10 | //--------------------------------------------------------------------------- 11 | 12 | #pragma once 13 | 14 | #include 15 | #include 16 | #include 17 | #include "generated/piscsi_interface.pb.h" 18 | 19 | using namespace std; 20 | using namespace piscsi_interface; 21 | 22 | namespace protobuf_util 23 | { 24 | static const char KEY_VALUE_SEPARATOR = '='; 25 | 26 | string GetParam(const auto& item, const string& key) 27 | { 28 | const auto& it = item.params().find(key); 29 | return it != item.params().end() ? it->second : ""; 30 | } 31 | 32 | void SetParam(auto& item, const string& key, string_view value) 33 | { 34 | if (!key.empty() && !value.empty()) { 35 | auto& map = *item.mutable_params(); 36 | map[key] = value; 37 | } 38 | } 39 | 40 | void ParseParameters(PbDeviceDefinition&, const string&); 41 | string SetCommandParams(PbCommand&, const string&); 42 | string SetFromGenericParams(PbCommand&, const string&); 43 | void SetProductData(PbDeviceDefinition&, const string&); 44 | string SetIdAndLun(PbDeviceDefinition&, const string&); 45 | string ListDevices(const vector&); 46 | 47 | void SerializeMessage(int, const google::protobuf::Message&); 48 | void DeserializeMessage(int, google::protobuf::Message&); 49 | size_t ReadBytes(int, span); 50 | } 51 | -------------------------------------------------------------------------------- /cpp/controllers/controller_manager.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2022-2023 Uwe Seimet 7 | // 8 | // Keeps track of and manages the controllers 9 | // 10 | //--------------------------------------------------------------------------- 11 | 12 | #pragma once 13 | 14 | #include "hal/bus.h" 15 | #include "controllers/abstract_controller.h" 16 | #include 17 | #include 18 | #include 19 | 20 | using namespace std; 21 | 22 | class ScsiController; 23 | class PrimaryDevice; 24 | 25 | class ControllerManager 26 | { 27 | public: 28 | 29 | ControllerManager() = default; 30 | ~ControllerManager() = default; 31 | 32 | bool AttachToController(BUS&, int, shared_ptr); 33 | bool DeleteController(const AbstractController&); 34 | void DeleteAllControllers(); 35 | AbstractController::piscsi_shutdown_mode ProcessOnController(int) const; 36 | shared_ptr FindController(int) const; 37 | bool HasController(int) const; 38 | unordered_set> GetAllDevices() const; 39 | bool HasDeviceForIdAndLun(int, int) const; 40 | shared_ptr GetDeviceForIdAndLun(int, int) const; 41 | 42 | static int GetScsiIdMax() { return 8; } 43 | static int GetScsiLunMax() { return 32; } 44 | 45 | private: 46 | 47 | shared_ptr CreateScsiController(BUS&, int) const; 48 | 49 | // Controllers mapped to their device IDs 50 | unordered_map> controllers; 51 | }; 52 | -------------------------------------------------------------------------------- /cpp/devices/device_logger.cpp: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2022-2023 Uwe Seimet 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #include "device_logger.h" 11 | 12 | using namespace std; 13 | using namespace spdlog; 14 | 15 | void DeviceLogger::Trace(const string& message) const 16 | { 17 | Log(level::trace, message); 18 | } 19 | 20 | void DeviceLogger::Debug(const string& message) const 21 | { 22 | Log(level::debug, message); 23 | } 24 | 25 | void DeviceLogger::Info(const string& message) const 26 | { 27 | Log(level::info, message); 28 | } 29 | 30 | void DeviceLogger::Warn(const string& message) const 31 | { 32 | Log(level::warn, message); 33 | } 34 | 35 | void DeviceLogger::Error(const string& message) const 36 | { 37 | Log(level::err, message); 38 | } 39 | 40 | void DeviceLogger::Log(level::level_enum level, const string& message) const 41 | { 42 | if ((log_device_id == -1 || log_device_id == id) && (lun == -1 || log_device_lun == -1 || log_device_lun == lun)) { 43 | if (lun == -1) { 44 | log(level, "(ID " + to_string(id) + ") - " + message); 45 | } 46 | else { 47 | log(level, "(ID:LUN " + to_string(id) + ":" + to_string(lun) + ") - " + message); 48 | } 49 | } 50 | } 51 | 52 | void DeviceLogger::SetIdAndLun(int i, int l) 53 | { 54 | id = i; 55 | lun = l; 56 | } 57 | 58 | void DeviceLogger::SetLogIdAndLun(int i, int l) 59 | { 60 | log_device_id = i; 61 | log_device_lun = l; 62 | } 63 | -------------------------------------------------------------------------------- /python/web/src/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Constant definitions used by other modules 3 | """ 4 | 5 | from os import getenv, getcwd 6 | import piscsi.common_settings 7 | 8 | WEB_DIR = getcwd() 9 | HOME_DIR = "/".join(WEB_DIR.split("/")[0:3]) 10 | 11 | FILE_SERVER_DIR = f"{HOME_DIR}/shared_files" 12 | 13 | MAX_FILE_SIZE = getenv("MAX_FILE_SIZE", str(1024 * 1024 * 1024 * 4)) # 4gb 14 | 15 | # The file name of the default config file that loads when piscsi-web starts 16 | DEFAULT_CONFIG = f"default.{piscsi.common_settings.CONFIG_FILE_SUFFIX}" 17 | # File containing canonical drive properties 18 | DRIVE_PROPERTIES_FILE = WEB_DIR + "/drive_properties.json" 19 | 20 | # The user group that is used for webapp authentication 21 | AUTH_GROUP = "piscsi" 22 | 23 | # The language locales supported by PiSCSI 24 | LANGUAGES = ["en", "de", "sv", "fr", "es", "zh"] 25 | 26 | # Available themes 27 | TEMPLATE_THEMES = ["classic", "modern"] 28 | 29 | # Default theme for modern browsers 30 | TEMPLATE_THEME_DEFAULT = "modern" 31 | 32 | # Fallback theme for older browsers 33 | TEMPLATE_THEME_LEGACY = "classic" 34 | 35 | # Enable throttle notifications 36 | # 37 | # Available modes: 38 | # "0": "Under-voltage detected" 39 | # "1": "Arm frequency capped" 40 | # "2": "Currently throttled" 41 | # "3": "Soft temperature limit active" 42 | # "16": "Under-voltage has occurred" 43 | # "17": "Arm frequency capping has occurred" 44 | # "18": "Throttling has occurred" 45 | # "19": "Soft temperature limit has occurred" 46 | THROTTLE_NOTIFY_MODES = ["0", "16"] 47 | # Include a list of modes to be shown ALL THE TIME to be used for styling 48 | # and formatting. 49 | THROTTLE_TEST_MODES = [] 50 | -------------------------------------------------------------------------------- /python/ctrlboard/src/menu/menu_renderer_config.py: -------------------------------------------------------------------------------- 1 | """Module for configuring menu renderer instances""" 2 | 3 | 4 | # pylint: disable=too-many-instance-attributes, too-few-public-methods 5 | class MenuRendererConfig: 6 | """Class for configuring menu renderer instances. Provides configuration options 7 | such as width, height, i2c address, font, transitions, etc.""" 8 | 9 | _rotation_mapper = {0: 0, 90: 1, 180: 2, 270: 3} 10 | 11 | def __init__(self): 12 | self.width = 128 13 | self.height = 64 14 | self.i2c_address = 0x3C 15 | self.i2c_port = 1 16 | self.display_type = "ssd1306" # luma-oled supported devices, "sh1106", "ssd1306", ... 17 | self.font_path = "../common/resources/DejaVuSansMono-Bold.ttf" 18 | self.font_size = 12 19 | self.row_selection_pixel_extension = 2 20 | self.scroll_behavior = "page" # "extend" or "page" 21 | self.transition = "PushTransition" # "PushTransition" or "None 22 | self.transition_attributes_left = {"direction": "push_left"} 23 | self.transition_attributes_right = {"direction": "push_right"} 24 | self.transition_speed = 10 25 | self.scroll_line = True 26 | self.scroll_delay = 3 27 | self.scroll_line_end_delay = 2 28 | self.screensaver = "menu.blank_screensaver.BlankScreenSaver" 29 | self.screensaver_delay = 25 30 | self.rotation = 0 # 0, 180 31 | 32 | def get_mapped_rotation(self): 33 | """Converts human-readable rotation value to the one expected 34 | by the luma and adafruit libraries""" 35 | return self._rotation_mapper[self.rotation] 36 | -------------------------------------------------------------------------------- /cpp/piscsi/command_context.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2021-2023 Uwe Seimet 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #pragma once 11 | 12 | #include "localizer.h" 13 | #include "generated/piscsi_interface.pb.h" 14 | #include 15 | 16 | using namespace std; 17 | using namespace piscsi_interface; 18 | 19 | class CommandContext 20 | { 21 | 22 | public: 23 | 24 | CommandContext(const PbCommand& cmd, string_view f, string_view l) : command(cmd), default_folder(f), locale(l) {} 25 | explicit CommandContext(int f) : fd(f) {} 26 | ~CommandContext() = default; 27 | 28 | string GetDefaultFolder() const { return default_folder; } 29 | void SetDefaultFolder(string_view f) { default_folder = f; } 30 | bool ReadCommand(); 31 | void WriteResult(const PbResult&) const; 32 | bool WriteSuccessResult(PbResult&) const; 33 | const PbCommand& GetCommand() const { return command; } 34 | 35 | bool ReturnLocalizedError(LocalizationKey, const string& = "", const string& = "", const string& = "") const; 36 | bool ReturnLocalizedError(LocalizationKey, PbErrorCode, const string& = "", const string& = "", const string& = "") const; 37 | bool ReturnSuccessStatus() const; 38 | bool ReturnErrorStatus(const string&) const; 39 | 40 | private: 41 | 42 | bool ReturnStatus(bool, const string&, PbErrorCode, bool) const; 43 | 44 | const Localizer localizer; 45 | 46 | PbCommand command; 47 | 48 | string default_folder; 49 | 50 | string locale; 51 | 52 | int fd = -1; 53 | }; 54 | -------------------------------------------------------------------------------- /cpp/devices/host_services.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2022-2023 Uwe Seimet 7 | // 8 | // Host Services with realtime clock and shutdown support 9 | // 10 | //--------------------------------------------------------------------------- 11 | 12 | #pragma once 13 | 14 | #include "mode_page_device.h" 15 | #include 16 | #include 17 | #include 18 | 19 | class HostServices: public ModePageDevice 20 | { 21 | 22 | public: 23 | 24 | explicit HostServices(int lun) : ModePageDevice(SCHS, lun) {} 25 | ~HostServices() override = default; 26 | 27 | bool Init(const param_map&) override; 28 | 29 | vector InquiryInternal() const override; 30 | void TestUnitReady() override; 31 | 32 | protected: 33 | 34 | void SetUpModePages(map>&, int, bool) const override; 35 | 36 | private: 37 | 38 | using mode_page_datetime = struct __attribute__((packed)) { 39 | // Major and minor version of this data structure (e.g. 1.0) 40 | uint8_t major_version; 41 | uint8_t minor_version; 42 | // Current date and time, with daylight savings time adjustment applied 43 | uint8_t year; // year - 1900 44 | uint8_t month; // 0-11 45 | uint8_t day; // 1-31 46 | uint8_t hour; // 0-23 47 | uint8_t minute; // 0-59 48 | uint8_t second; // 0-59 49 | }; 50 | 51 | void StartStopUnit() const; 52 | int ModeSense6(cdb_t, vector&) const override; 53 | int ModeSense10(cdb_t, vector&) const override; 54 | 55 | void AddRealtimeClockPage(map>&, bool) const; 56 | }; 57 | -------------------------------------------------------------------------------- /cpp/devices/device_factory.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2021-2023 Uwe Seimet 7 | // 8 | // The DeviceFactory creates devices based on their type and the image file extension 9 | // 10 | //--------------------------------------------------------------------------- 11 | 12 | #pragma once 13 | 14 | #include 15 | #include 16 | #include "generated/piscsi_interface.pb.h" 17 | 18 | using namespace std; 19 | using namespace piscsi_interface; 20 | 21 | class PrimaryDevice; 22 | 23 | class DeviceFactory 24 | { 25 | 26 | public: 27 | 28 | DeviceFactory() = default; 29 | ~DeviceFactory() = default; 30 | 31 | shared_ptr CreateDevice(PbDeviceType, int, const string&) const; 32 | PbDeviceType GetTypeForFile(const string&) const; 33 | const auto& GetExtensionMapping() const { return EXTENSION_MAPPING; } 34 | 35 | private: 36 | 37 | const inline static unordered_map> EXTENSION_MAPPING = { 38 | { "hd1", SCHD }, 39 | { "hds", SCHD }, 40 | { "hda", SCHD }, 41 | { "hdn", SCHD }, 42 | { "hdi", SCHD }, 43 | { "nhd", SCHD }, 44 | { "hdr", SCRM }, 45 | { "mos", SCMO }, 46 | { "is1", SCCD }, 47 | { "iso", SCCD }, 48 | { "cdr", SCCD }, 49 | { "toast", SCCD }, 50 | { "tar", SCTP }, 51 | { "tap", SCTP }, 52 | }; 53 | 54 | const inline static unordered_map> DEVICE_MAPPING = { 55 | { "daynaport", SCDP }, 56 | { "printer", SCLP }, 57 | { "services", SCHS } 58 | }; 59 | }; 60 | -------------------------------------------------------------------------------- /cpp/hal/systimer.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Powered by XM6 TypeG Technology. 7 | // Copyright (C) 2016-2020 GIMONS 8 | // Copyright (C) 2022 akuker 9 | // 10 | // [ High resolution timer ] 11 | // 12 | //--------------------------------------------------------------------------- 13 | 14 | #pragma once 15 | 16 | #include 17 | #include 18 | 19 | #include "shared/scsi.h" 20 | 21 | class PlatformSpecificTimer 22 | { 23 | public: 24 | // Default constructor 25 | PlatformSpecificTimer() = default; 26 | // Default destructor 27 | virtual ~PlatformSpecificTimer() = default; 28 | // Initialization 29 | virtual void Init() = 0; 30 | // Get system timer low byte 31 | virtual uint32_t GetTimerLow() = 0; 32 | // Sleep for N nanoseconds 33 | virtual void SleepNsec(uint32_t nsec) = 0; 34 | // Sleep for N microseconds 35 | virtual void SleepUsec(uint32_t usec) = 0; 36 | }; 37 | 38 | //=========================================================================== 39 | // 40 | // System timer 41 | // 42 | //=========================================================================== 43 | class SysTimer 44 | { 45 | public: 46 | static void Init(); 47 | // Get system timer low byte 48 | static uint32_t GetTimerLow(); 49 | // Sleep for N nanoseconds 50 | static void SleepNsec(uint32_t nsec); 51 | // Sleep for N microseconds 52 | static void SleepUsec(uint32_t usec); 53 | 54 | private: 55 | static bool initialized; 56 | static bool is_raspberry; 57 | 58 | static std::unique_ptr systimer_ptr; 59 | }; 60 | -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | backend: 3 | container_name: piscsi_backend 4 | image: piscsi-backend 5 | pull_policy: never 6 | build: 7 | context: .. 8 | dockerfile: docker/backend/Dockerfile 9 | volumes: 10 | - ./volumes/images:/home/pi/images:delegated 11 | - ./volumes/config:/home/pi/.config/piscsi:delegated 12 | ports: 13 | - "127.0.0.1:${BACKEND_PORT:-6868}:6868" 14 | environment: 15 | - BACKEND_PASSWORD=${BACKEND_PASSWORD:-} 16 | init: true 17 | command: [ 18 | "-L", 19 | "${BACKEND_LOG_LEVEL:-trace}", 20 | ] 21 | 22 | web: 23 | container_name: piscsi_web 24 | image: piscsi-web:${OS_VERSION:-bullseye} 25 | pull_policy: never 26 | build: 27 | context: .. 28 | dockerfile: docker/web/Dockerfile 29 | args: 30 | - OS_VERSION=${OS_VERSION:-bullseye} 31 | volumes: 32 | - ./volumes/images:/home/pi/images:delegated 33 | - ./volumes/config:/home/pi/.config/piscsi:delegated 34 | ports: 35 | - "127.0.0.1:${WEB_HTTP_PORT:-8080}:80" 36 | - "127.0.0.1:${WEB_HTTPS_PORT:-8443}:443" 37 | environment: 38 | - BACKEND_PASSWORD=${BACKEND_PASSWORD:-} 39 | - RESET_VENV=${RESET_VENV:-} 40 | init: true 41 | command: [ 42 | "--backend-host=${BACKEND_HOST:-backend}", 43 | "--backend-port=${BACKEND_PORT:-6868}", 44 | "--log-level=${WEB_LOG_LEVEL:-debug}", 45 | "--dev-mode" 46 | ] 47 | 48 | pytest: 49 | container_name: piscsi_pytest 50 | image: piscsi-pytest 51 | pull_policy: never 52 | profiles: 53 | - webui-tests 54 | build: 55 | context: .. 56 | dockerfile: docker/pytest/Dockerfile 57 | working_dir: /src 58 | command: ["-vv"] 59 | -------------------------------------------------------------------------------- /cpp/scsictl/scsictl_display.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2021-2023 Uwe Seimet 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #pragma once 11 | 12 | #include "generated/piscsi_interface.pb.h" 13 | #include 14 | 15 | using namespace std; 16 | using namespace piscsi_interface; 17 | 18 | class ScsictlDisplay 19 | { 20 | public: 21 | 22 | ScsictlDisplay() = default; 23 | ~ScsictlDisplay() = default; 24 | 25 | string DisplayDevicesInfo(const PbDevicesInfo&) const; 26 | string DisplayDeviceInfo(const PbDevice&) const; 27 | string DisplayVersionInfo(const PbVersionInfo&) const; 28 | string DisplayLogLevelInfo(const PbLogLevelInfo&) const; 29 | string DisplayDeviceTypesInfo(const PbDeviceTypesInfo&) const; 30 | string DisplayReservedIdsInfo(const PbReservedIdsInfo&) const; 31 | string DisplayImageFile(const PbImageFile&) const; 32 | string DisplayImageFilesInfo(const PbImageFilesInfo&) const; 33 | string DisplayNetworkInterfaces(const PbNetworkInterfacesInfo&) const; 34 | string DisplayMappingInfo(const PbMappingInfo&) const; 35 | string DisplayStatisticsInfo(const PbStatisticsInfo&) const; 36 | string DisplayOperationInfo(const PbOperationInfo&) const; 37 | 38 | private: 39 | 40 | string DisplayParams(const PbDevice&) const; 41 | string DisplayAttributes(const PbDeviceProperties&) const; 42 | string DisplayDefaultParameters(const PbDeviceProperties&) const; 43 | string DisplayBlockSizes(const PbDeviceProperties&) const; 44 | string DisplayParameters(const PbOperationMetaData&) const; 45 | string DisplayPermittedValues(const PbOperationParameter&) const; 46 | }; 47 | -------------------------------------------------------------------------------- /cpp/hal/sbc_version.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2022 akuker 7 | // 8 | // [ Hardware version detection routines ] 9 | // 10 | //--------------------------------------------------------------------------- 11 | 12 | #pragma once 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | using namespace std; 19 | 20 | //=========================================================================== 21 | // 22 | // Single Board Computer Versions 23 | // 24 | //=========================================================================== 25 | class SBC_Version 26 | { 27 | public: 28 | // Type of Single Board Computer 29 | enum class sbc_version_type : uint8_t { 30 | sbc_unknown = 0, 31 | sbc_raspberry_pi_1, 32 | sbc_raspberry_pi_2_3, 33 | sbc_raspberry_pi_4 34 | }; 35 | 36 | SBC_Version() = delete; 37 | ~SBC_Version() = delete; 38 | 39 | static void Init(); 40 | 41 | static sbc_version_type GetSbcVersion(); 42 | 43 | static bool IsRaspberryPi(); 44 | 45 | static string GetAsString(); 46 | 47 | static uint32_t GetPeripheralAddress(); 48 | 49 | private: 50 | static sbc_version_type sbc_version; 51 | 52 | static const string str_raspberry_pi_1; 53 | static const string str_raspberry_pi_2_3; 54 | static const string str_raspberry_pi_4; 55 | static const string str_unknown_sbc; 56 | 57 | static const map> proc_device_tree_mapping; 58 | 59 | static const string m_device_tree_model_path; 60 | 61 | static uint32_t GetDeviceTreeRanges(const char *filename, uint32_t offset); 62 | }; 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) 4 | Copyright (C) 2014-2020 GIMONS 5 | Copyright (c) 2020-2021 akuker 6 | Copyright (c) PiSCSI project contributors (github.com/PiSCSI/piscsi) 7 | All rights reserved. 8 | 9 | Redistribution and use in source and binary forms, with or without 10 | modification, are permitted provided that the following conditions are met: 11 | 12 | 1. Redistributions of source code must retain the above copyright notice, this 13 | list of conditions and the following disclaimer. 14 | 15 | 2. Redistributions in binary form must reproduce the above copyright notice, 16 | this list of conditions and the following disclaimer in the documentation 17 | and/or other materials provided with the distribution. 18 | 19 | 3. Neither the name of the copyright holder nor the names of its 20 | contributors may be used to endorse or promote products derived from 21 | this software without specific prior written permission. 22 | 23 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 24 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 26 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 27 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 29 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 31 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 32 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | -------------------------------------------------------------------------------- /python/ctrlboard/src/piscsi_menu_controller.py: -------------------------------------------------------------------------------- 1 | """Module implementing the PiSCSI Control Board UI specific menu controller""" 2 | 3 | from ctrlboard_menu_builder import CtrlBoardMenuBuilder 4 | from menu.menu_builder import MenuBuilder 5 | from menu.menu_controller import MenuController 6 | from menu.menu_renderer import MenuRenderer 7 | from menu.timer import Timer 8 | 9 | 10 | class PiscsiMenuController(MenuController): 11 | """Class implementing a PiSCSI Control Board UI specific menu controller""" 12 | 13 | def __init__( 14 | self, 15 | refresh_interval, 16 | menu_builder: MenuBuilder, 17 | menu_renderer=None, 18 | menu_renderer_config=None, 19 | ): 20 | super().__init__(menu_builder, menu_renderer, menu_renderer_config) 21 | self._refresh_interval = refresh_interval 22 | self._menu_renderer: MenuRenderer = menu_renderer 23 | self._scsi_list_refresh_timer_flag = Timer(self._refresh_interval) 24 | 25 | def segue(self, name, context_object=None, transition_attributes=None): 26 | super().segue(name, context_object, transition_attributes) 27 | self._scsi_list_refresh_timer_flag.reset_timer() 28 | 29 | def update(self): 30 | super().update() 31 | 32 | if self.get_active_menu().name == CtrlBoardMenuBuilder.SCSI_ID_MENU: 33 | self._scsi_list_refresh_timer_flag.check_timer() 34 | if self._scsi_list_refresh_timer_flag.enabled is True: 35 | self._scsi_list_refresh_timer_flag.reset_timer() 36 | self.refresh(name=CtrlBoardMenuBuilder.SCSI_ID_MENU) 37 | if self._menu_renderer.screensaver.enabled is False: 38 | self.set_active_menu(CtrlBoardMenuBuilder.SCSI_ID_MENU, display_on_device=False) 39 | -------------------------------------------------------------------------------- /doc/scsimon_man_page.txt: -------------------------------------------------------------------------------- 1 | !! ------ THIS FILE IS AUTO_GENERATED! DO NOT MANUALLY UPDATE!!! 2 | !! ------ The native file is scsimon.1. Re-run 'make docs' after updating 3 | 4 | 5 | scsimon(1) General Commands Manual scsimon(1) 6 | 7 | NAME 8 | scsimon - Acts as a data capture tool for all traffic on the SCSI bus. Data is stored in a Value Change Dump 9 | (VCD) file. 10 | 11 | SYNOPSIS 12 | scsimon 13 | 14 | DESCRIPTION 15 | scsimon monitors all of the traffic on the SCSI bus, using a PiSCSI device. The data is cached in memory while 16 | the tool is running. A circular buffer is used so that only the most recent 1,000,000 transactions are stored. 17 | The tool will continue to run until the user presses CTRL-C, or the process receives a SIGINT signal. 18 | 19 | The logged data is stored in a file called "log.vcd" in the current working directory from where scsimon was 20 | launched. 21 | 22 | Currently, scsimon doesn't accept any arguments. 23 | 24 | To quit scsimon, press Control + C. 25 | 26 | OPTIONS 27 | None 28 | 29 | EXAMPLES 30 | Make sure you've stopped the piscsi service. Then launch scsimon to capture all SCSI traffic available to the 31 | PiSCSI hardware: 32 | scsimon 33 | 34 | If you're trying to capture a specific scenario, you'll want to wait to start scsimon until immediately before 35 | the scenario. 36 | 37 | SEE ALSO 38 | scsictl(1), piscsi(1), scsidump(1) 39 | 40 | Full documentation is available at: 41 | 42 | scsimon(1) 43 | -------------------------------------------------------------------------------- /cpp/piscsi/piscsi_image.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2021-2023 Uwe Seimet 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #pragma once 11 | 12 | #include "generated/piscsi_interface.pb.h" 13 | #include "command_context.h" 14 | #include 15 | #include 16 | 17 | using namespace std; 18 | using namespace filesystem; 19 | using namespace piscsi_interface; 20 | 21 | class PiscsiImage 22 | { 23 | public: 24 | 25 | PiscsiImage(); 26 | ~PiscsiImage() = default; 27 | 28 | void SetDepth(int d) { depth = d; } 29 | int GetDepth() const { return depth; } 30 | string GetDefaultFolder() const { return default_folder; } 31 | string SetDefaultFolder(string_view); 32 | bool CreateImage(const CommandContext&) const; 33 | bool DeleteImage(const CommandContext&) const; 34 | bool RenameImage(const CommandContext&) const; 35 | bool CopyImage(const CommandContext&) const; 36 | bool SetImagePermissions(const CommandContext&) const; 37 | 38 | private: 39 | 40 | bool CheckDepth(string_view) const; 41 | string GetFullName(const string& filename) const { return default_folder + "/" + filename; } 42 | bool CreateImageFolder(const CommandContext&, string_view) const; 43 | static bool IsReservedFile(const CommandContext&, const string&, const string&); 44 | bool ValidateParams(const CommandContext&, const string&, string&, string&) const; 45 | 46 | static bool IsValidSrcFilename(string_view); 47 | static bool IsValidDstFilename(string_view); 48 | static bool ChangeOwner(const CommandContext&, const path&, bool); 49 | static string GetHomeDir(); 50 | static pair GetUidAndGid(); 51 | 52 | string default_folder; 53 | 54 | int depth = 1; 55 | }; 56 | -------------------------------------------------------------------------------- /cpp/devices/scsihd_nec.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) 7 | // Copyright (C) 2014-2020 GIMONS 8 | // Copyright (C) akuker 9 | // 10 | // Licensed under the BSD 3-Clause License. 11 | // See LICENSE file in the project root folder. 12 | // 13 | // [ SCSI NEC Compatible Hard Disk] 14 | // 15 | //--------------------------------------------------------------------------- 16 | 17 | #pragma once 18 | 19 | #include "scsihd.h" 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | using namespace std; 26 | 27 | //=========================================================================== 28 | // 29 | // SCSI hard disk (PC-9801-55 NEC compatible / Anex86 / T98Next) 30 | // 31 | //=========================================================================== 32 | class SCSIHD_NEC : public SCSIHD //NOSONAR The inheritance hierarchy depth is acceptable in this case 33 | { 34 | public: 35 | 36 | explicit SCSIHD_NEC(int lun) : SCSIHD(lun, false, scsi_level::scsi_1_ccs, { 512 }) {} 37 | ~SCSIHD_NEC() override = default; 38 | 39 | void Open() override; 40 | 41 | protected: 42 | 43 | vector InquiryInternal() const override; 44 | 45 | void AddFormatPage(map>&, bool) const override; 46 | void AddDrivePage(map>&, bool) const override; 47 | 48 | private: 49 | 50 | pair SetParameters(span, int); 51 | 52 | static int GetInt16LittleEndian(const uint8_t *); 53 | static int GetInt32LittleEndian(const uint8_t *); 54 | 55 | // Image file offset 56 | off_t image_offset = 0; 57 | 58 | // Geometry data 59 | int cylinders = 0; 60 | int heads = 0; 61 | int sectors = 0; 62 | }; 63 | -------------------------------------------------------------------------------- /cpp/devices/disk_track.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // X68000 EMULATOR "XM6" 4 | // 5 | // Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) 6 | // Copyright (C) 2014-2020 GIMONS 7 | // 8 | // XM6i 9 | // Copyright (C) 2010-2015 isaki@NetBSD.org 10 | // 11 | // Imported sava's Anex86/T98Next image and MO format support patch. 12 | // Comments translated to english by akuker. 13 | // 14 | //--------------------------------------------------------------------------- 15 | 16 | #pragma once 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | using namespace std; 25 | 26 | class DiskTrack 27 | { 28 | struct { 29 | int track; // Track Number 30 | int size; // Sector Size (8=256, 9=512, 10=1024, 11=2048, 12=4096) 31 | int sectors; // Number of sectors(<0x100) 32 | uint32_t length; // Data buffer length 33 | uint8_t *buffer; // Data buffer 34 | bool init; // Is it initilized? 35 | bool changed; // Changed flag 36 | vector changemap; // Changed map 37 | bool raw; // RAW mode flag 38 | off_t imgoffset; // Offset to actual data 39 | } dt = {}; 40 | 41 | public: 42 | 43 | DiskTrack() = default; 44 | ~DiskTrack(); 45 | DiskTrack(DiskTrack&) = delete; 46 | DiskTrack& operator=(const DiskTrack&) = delete; 47 | 48 | private: 49 | 50 | friend class DiskCache; 51 | 52 | void Init(int track, int size, int sectors, bool raw = false, off_t imgoff = 0); 53 | bool Load(const string& path, uint64_t&); 54 | bool Save(const string& path, uint64_t&); 55 | 56 | bool ReadSector(span, int) const; // Sector Read 57 | bool WriteSector(span buf, int); // Sector Write 58 | 59 | int GetTrack() const { return dt.track; } // Get track 60 | }; 61 | -------------------------------------------------------------------------------- /doc/scsiloop_man_page.txt: -------------------------------------------------------------------------------- 1 | !! ------ THIS FILE IS AUTO_GENERATED! DO NOT MANUALLY UPDATE!!! 2 | !! ------ The native file is scsiloop.1. Re-run 'make docs' after updating 3 | 4 | 5 | scsiloop(1) General Commands Manual scsiloop(1) 6 | 7 | NAME 8 | scsiloop - Tool for testing the PiSCSI board with a loopback adapter installed 9 | 10 | SYNOPSIS 11 | scsiloop [-L LOG_LEVEL] 12 | 13 | DESCRIPTION 14 | scsiloop Performs a self-test of the PiSCSI hardware to ensure that the board is functioning properly. In order for this 15 | tool to work, a special loopback cable MUST be attached to the PiSCSI SCSI connector. 16 | 17 | In addition to testing the GPIO signals, scsiloop will perform a self-test of the hardware timers that are built into the 18 | system on a chip (SoC). 19 | 20 | The loopback connections for the DB25 connector are shown here: 21 | 22 | |Pin | Name | Pin | Name | 23 | +----+------+-----+------+ 24 | | 1 | REQ | 13 | DB7 | 25 | | 2 | MSG | 12 | DB6 | 26 | | 3 | I/O | 11 | DB5 | 27 | | 4 | RST | 10 | DB3 | 28 | | 5 | ACK | 8 | DB0 | 29 | | 6 | BSY | 20 | DBP | 30 | | 15 | C/D | 23 | DB4 | 31 | | 17 | ATN | 22 | DB2 | 32 | | 19 | SEL | 21 | DB1 | 33 | 34 | OPTIONS 35 | -L LOG_LEVEL 36 | The piscsi log level (trace, debug, info, warn, err, off). The default log level is 'info'. 37 | 38 | SEE ALSO 39 | scsictl(1), piscsi(1), scsimon(1) 40 | 41 | Full documentation is available at: 42 | 43 | scsiloop(1) 44 | -------------------------------------------------------------------------------- /cpp/scsictl/scsictl_commands.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2021-2023 Uwe Seimet 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #pragma once 11 | 12 | #include "generated/piscsi_interface.pb.h" 13 | #include "scsictl_display.h" 14 | #include 15 | 16 | using namespace piscsi_interface; 17 | 18 | struct sockaddr_in; 19 | 20 | class ScsictlCommands 21 | { 22 | public: 23 | 24 | ScsictlCommands(PbCommand& command, const string& hostname, int port) 25 | : command(command), hostname(hostname), port(port) {} 26 | ~ScsictlCommands() = default; 27 | 28 | bool Execute(string_view, string_view, string_view, string_view, string_view); 29 | 30 | bool CommandDevicesInfo(); 31 | 32 | private: 33 | 34 | bool CommandLogLevel(string_view); 35 | bool CommandReserveIds(string_view); 36 | bool CommandCreateImage(string_view); 37 | bool CommandDeleteImage(string_view); 38 | bool CommandRenameImage(string_view); 39 | bool CommandCopyImage(string_view); 40 | bool CommandDefaultImageFolder(string_view); 41 | bool CommandDeviceInfo(); 42 | bool CommandDeviceTypesInfo(); 43 | bool CommandVersionInfo(); 44 | bool CommandServerInfo(); 45 | bool CommandDefaultImageFilesInfo(); 46 | bool CommandImageFileInfo(string_view); 47 | bool CommandNetworkInterfacesInfo(); 48 | bool CommandLogLevelInfo(); 49 | bool CommandReservedIdsInfo(); 50 | bool CommandMappingInfo(); 51 | bool CommandStatisticsInfo(); 52 | bool CommandOperationInfo(); 53 | bool SendCommand(); 54 | bool EvaluateParams(string_view, const string&, const string&); 55 | 56 | PbCommand& command; 57 | string hostname; 58 | int port; 59 | 60 | PbResult result; 61 | 62 | [[no_unique_address]] const ScsictlDisplay scsictl_display; 63 | }; 64 | -------------------------------------------------------------------------------- /cpp/controllers/phase_handler.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2022-2023 Uwe Seimet 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #pragma once 11 | 12 | #include "shared/scsi.h" 13 | #include "shared/piscsi_exceptions.h" 14 | #include 15 | #include 16 | 17 | using namespace scsi_defs; 18 | 19 | class PhaseHandler 20 | { 21 | phase_t phase = phase_t::busfree; 22 | 23 | public: 24 | 25 | PhaseHandler() = default; 26 | virtual ~PhaseHandler() = default; 27 | 28 | void Init(); 29 | 30 | virtual void BusFree() = 0; 31 | virtual void Selection() = 0; 32 | virtual void Command() = 0; 33 | virtual void Status() = 0; 34 | virtual void DataIn() = 0; 35 | virtual void DataOut() = 0; 36 | virtual void MsgIn() = 0; 37 | virtual void MsgOut() = 0; 38 | 39 | virtual bool Process(int) = 0; 40 | 41 | protected: 42 | 43 | phase_t GetPhase() const { return phase; } 44 | void SetPhase(phase_t p) { phase = p; } 45 | bool IsSelection() const { return phase == phase_t::selection; } 46 | bool IsBusFree() const { return phase == phase_t::busfree; } 47 | bool IsCommand() const { return phase == phase_t::command; } 48 | bool IsStatus() const { return phase == phase_t::status; } 49 | bool IsDataIn() const { return phase == phase_t::datain; } 50 | bool IsDataOut() const { return phase == phase_t::dataout; } 51 | bool IsMsgIn() const { return phase == phase_t::msgin; } 52 | bool IsMsgOut() const { return phase == phase_t::msgout; } 53 | 54 | void ProcessPhase() const 55 | { 56 | try { 57 | phase_executors.at(phase)(); 58 | } 59 | catch(const out_of_range&) { 60 | throw scsi_exception(sense_key::aborted_command); 61 | } 62 | } 63 | 64 | private: 65 | 66 | unordered_map> phase_executors; 67 | }; 68 | -------------------------------------------------------------------------------- /cpp/test/linux_os_stubs.cpp: -------------------------------------------------------------------------------- 1 | 2 | //--------------------------------------------------------------------------- 3 | // 4 | // SCSI Target Emulator PiSCSI 5 | // for Raspberry Pi 6 | // 7 | // Copyright (C) 2022 akuker 8 | // 9 | //--------------------------------------------------------------------------- 10 | 11 | #include "test/linux_os_stubs.h" 12 | #include "test/test_shared.h" 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #ifdef __linux__ 21 | #include 22 | #endif 23 | #include 24 | #include 25 | #include 26 | 27 | using namespace std; 28 | using namespace filesystem; 29 | 30 | extern "C" { 31 | 32 | #ifdef __USE_LARGEFILE64 33 | FILE *__wrap_fopen64(const char *__restrict __filename, const char *__restrict __modes) 34 | #else 35 | FILE *__wrap_fopen(const char *__restrict __filename, const char *__restrict __modes) 36 | #endif 37 | { 38 | path new_filename; 39 | auto in_filename = path(__filename); 40 | bool create_directory = false; 41 | 42 | // If we're trying to open up the device tree soc ranges, 43 | // re-direct it to a temporary local file. 44 | if ((string(__filename) == "/proc/device-tree/soc/ranges") || 45 | (string(__filename).find(".properties") != string::npos)) { 46 | create_directory = true; 47 | new_filename = test_data_temp_path; 48 | if (!in_filename.has_parent_path()) { 49 | new_filename += "/"; 50 | } 51 | new_filename += in_filename; 52 | } else { 53 | new_filename = in_filename; 54 | } 55 | 56 | if (create_directory) { 57 | create_directories(new_filename.parent_path()); 58 | } 59 | #ifdef __USE_LARGEFILE64 60 | return __real_fopen64(new_filename.c_str(), __modes); 61 | #else 62 | return __real_fopen(new_filename.c_str(), __modes); 63 | #endif 64 | } 65 | 66 | } // end extern "C" 67 | -------------------------------------------------------------------------------- /cpp/hal/pin_control.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2022 akuker 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #include 11 | 12 | #pragma once 13 | 14 | // Virtual functions that must be implemented by the derived gpiobus classes 15 | // to control the GPIO pins 16 | class PinControl 17 | { 18 | public: 19 | virtual bool GetBSY() const = 0; 20 | virtual void SetBSY(bool ast) = 0; 21 | 22 | virtual bool GetSEL() const = 0; 23 | virtual void SetSEL(bool ast) = 0; 24 | 25 | virtual bool GetATN() const = 0; 26 | virtual void SetATN(bool ast) = 0; 27 | 28 | virtual bool GetACK() const = 0; 29 | virtual void SetACK(bool ast) = 0; 30 | 31 | virtual bool GetRST() const = 0; 32 | virtual void SetRST(bool ast) = 0; 33 | 34 | virtual bool GetMSG() const = 0; 35 | virtual void SetMSG(bool ast) = 0; 36 | 37 | virtual bool GetCD() const = 0; 38 | virtual void SetCD(bool ast) = 0; 39 | 40 | virtual bool GetIO() = 0; 41 | virtual void SetIO(bool ast) = 0; 42 | 43 | virtual bool GetREQ() const = 0; 44 | virtual void SetREQ(bool ast) = 0; 45 | 46 | virtual bool GetACT() const = 0; 47 | virtual void SetACT(bool ast) = 0; 48 | 49 | virtual uint8_t GetDAT() = 0; 50 | virtual void SetDAT(uint8_t dat) = 0; 51 | 52 | // Set ENB signal 53 | virtual void SetENB(bool ast) = 0; 54 | 55 | // GPIO pin direction setting 56 | virtual void PinConfig(int pin, int mode) = 0; 57 | // GPIO pin pull up/down resistor setting 58 | virtual void PullConfig(int pin, int mode) = 0; 59 | 60 | virtual void SetControl(int pin, bool ast) = 0; 61 | virtual void SetMode(int pin, int mode) = 0; 62 | 63 | PinControl() = default; 64 | virtual ~PinControl() = default; 65 | }; 66 | -------------------------------------------------------------------------------- /cpp/test/scsictl_parser_test.cpp: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2022 Uwe Seimet 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #include 11 | 12 | #include "scsictl/scsictl_parser.h" 13 | 14 | TEST(ScsictlParserTest, ParseOperation) 15 | { 16 | ScsictlParser parser; 17 | 18 | EXPECT_EQ(ATTACH, parser.ParseOperation("A")); 19 | EXPECT_EQ(ATTACH, parser.ParseOperation("a")); 20 | EXPECT_EQ(DETACH, parser.ParseOperation("d")); 21 | EXPECT_EQ(INSERT, parser.ParseOperation("i")); 22 | EXPECT_EQ(EJECT, parser.ParseOperation("e")); 23 | EXPECT_EQ(PROTECT, parser.ParseOperation("p")); 24 | EXPECT_EQ(UNPROTECT, parser.ParseOperation("u")); 25 | EXPECT_EQ(NO_OPERATION, parser.ParseOperation("")); 26 | EXPECT_EQ(NO_OPERATION, parser.ParseOperation("xyz")); 27 | } 28 | 29 | TEST(ScsictlParserTest, ParseType) 30 | { 31 | ScsictlParser parser; 32 | 33 | EXPECT_EQ(SCCD, parser.ParseType("SCCD")); 34 | EXPECT_EQ(SCCD, parser.ParseType("sccd")); 35 | EXPECT_EQ(SCDP, parser.ParseType("scdp")); 36 | EXPECT_EQ(SCHD, parser.ParseType("schd")); 37 | EXPECT_EQ(SCHS, parser.ParseType("schs")); 38 | EXPECT_EQ(SCLP, parser.ParseType("sclp")); 39 | EXPECT_EQ(SCMO, parser.ParseType("scmo")); 40 | EXPECT_EQ(SCRM, parser.ParseType("scrm")); 41 | EXPECT_EQ(SCTP, parser.ParseType("sctp")); 42 | 43 | EXPECT_EQ(SCCD, parser.ParseType("C")); 44 | EXPECT_EQ(SCCD, parser.ParseType("c")); 45 | EXPECT_EQ(SCDP, parser.ParseType("d")); 46 | EXPECT_EQ(SCHD, parser.ParseType("h")); 47 | EXPECT_EQ(SCHS, parser.ParseType("s")); 48 | EXPECT_EQ(SCLP, parser.ParseType("p")); 49 | EXPECT_EQ(SCMO, parser.ParseType("m")); 50 | EXPECT_EQ(SCRM, parser.ParseType("r")); 51 | EXPECT_EQ(SCTP, parser.ParseType("t")); 52 | 53 | EXPECT_EQ(UNDEFINED, parser.ParseType("")); 54 | EXPECT_EQ(UNDEFINED, parser.ParseType("xyz")); 55 | } 56 | -------------------------------------------------------------------------------- /python/ctrlboard/src/ctrlboard_hw/encoder.py: -------------------------------------------------------------------------------- 1 | """Module containing an implementation for reading the rotary encoder directions through 2 | the i2c multiplexer + interrupt""" 3 | 4 | from ctrlboard_hw.hardware_button import HardwareButton 5 | 6 | 7 | class Encoder: 8 | """Class implementing a detection mechanism to detect the rotary encoder directions 9 | through the i2c multiplexer + interrupt""" 10 | 11 | def __init__(self, enc_a: HardwareButton, enc_b: HardwareButton): 12 | self.enc_a = enc_a 13 | self.enc_b = enc_b 14 | self.pos = 0 15 | self.state = 0b00000000 16 | self.direction = 0 17 | 18 | def update(self): 19 | """Updates the internal attributes wrt. to the encoder position and direction.""" 20 | value_enc_a = self.enc_a.state_interrupt 21 | value_enc_b = self.enc_b.state_interrupt 22 | 23 | self.direction = 0 24 | state = self.state & 0b00111111 25 | 26 | if value_enc_a: 27 | state |= 0b01000000 28 | if value_enc_b: 29 | state |= 0b10000000 30 | 31 | # clockwise pattern detection 32 | if ( 33 | state == 0b11010010 34 | or state == 0b11001000 35 | or state == 0b11011000 36 | or state == 0b11010001 37 | or state == 0b11011011 38 | or state == 0b11100000 39 | or state == 0b11001011 40 | ): 41 | self.pos += 1 42 | self.direction = 1 43 | self.state = 0b00000000 44 | return 45 | # counter-clockwise pattern detection 46 | elif ( 47 | state == 0b11000100 48 | or state == 0b11100100 49 | or state == 0b11100001 50 | or state == 0b11000111 51 | or state == 0b11100111 52 | ): 53 | self.pos -= 1 54 | self.direction = -1 55 | self.state = 0b00000000 56 | return 57 | 58 | self.state = state >> 2 59 | -------------------------------------------------------------------------------- /docker/web/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG DEBIAN_FRONTEND=noninteractive 2 | ARG OS_VERSION=bullseye 3 | 4 | FROM "debian:${OS_VERSION}-slim" 5 | 6 | RUN apt-get update \ 7 | && apt-get install -y --no-install-recommends sudo systemd rsyslog procps man-db wget git gcc \ 8 | && apt-get clean \ 9 | && rm -rf /var/lib/apt/lists/* 10 | 11 | RUN groupadd pi \ 12 | && useradd --create-home --shell /bin/bash -g pi pi \ 13 | && echo "pi ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers \ 14 | && echo "pi:piscsi" | chpasswd 15 | 16 | # Make mock commands available when executing sudo 17 | RUN sed -i 's/^Defaults\tsecure_path="\/usr/Defaults\tsecure_path="\/home\/pi\/piscsi\/python\/web\/mock\/bin:\/usr/' /etc/sudoers 18 | 19 | RUN mkdir -p /home/pi/shared_files \ 20 | && mkdir /home/pi/images \ 21 | && mkdir -p /etc/network/interfaces.d \ 22 | && touch /etc/dhcpcd.conf 23 | 24 | USER pi 25 | WORKDIR /home/pi/piscsi 26 | 27 | COPY --chown=pi:pi easyinstall.sh . 28 | COPY --chown=pi:pi os_integration os_integration 29 | COPY --chown=pi:pi proto proto 30 | COPY --chown=pi:pi python/web python/web 31 | COPY --chown=pi:pi python/common python/common 32 | 33 | # Install standalone PiSCSI Web UI 34 | RUN ./easyinstall.sh --run_choice=12 \ 35 | && sudo apt-get remove build-essential --yes \ 36 | && sudo apt autoremove -y \ 37 | && sudo apt-get clean \ 38 | && sudo rm -rf /var/lib/apt/lists/* 39 | 40 | # Enable web UI authentication 41 | RUN ./easyinstall.sh --run_choice=14 42 | 43 | # Setup wired network bridge 44 | RUN ./easyinstall.sh --run_choice=5 --headless 45 | 46 | USER root 47 | WORKDIR /home/pi 48 | RUN pip3 install --no-cache-dir PyYAML watchdog 49 | COPY docker/web/web_start_wrapper.sh /usr/local/bin/web_start_wrapper.sh 50 | RUN chmod +x /usr/local/bin/web_start_wrapper.sh 51 | 52 | EXPOSE 80 443 53 | ENTRYPOINT ["/usr/local/bin/web_start_wrapper.sh"] 54 | 55 | HEALTHCHECK --interval=5m --timeout=3s \ 56 | CMD wget --quiet --server-response http://localhost/healthcheck 2>&1 | grep "200 OK" 57 | -------------------------------------------------------------------------------- /cpp/devices/scsicd.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) 7 | // Copyright (C) 2014-2020 GIMONS 8 | // Copyright (C) akuker 9 | // 10 | // Licensed under the BSD 3-Clause License. 11 | // See LICENSE file in the project root folder. 12 | // 13 | //--------------------------------------------------------------------------- 14 | 15 | #pragma once 16 | 17 | #include "cd_track.h" 18 | #include "disk.h" 19 | #include "interfaces/scsi_mmc_commands.h" 20 | #include 21 | #include 22 | #include 23 | 24 | class SCSICD : public Disk, private ScsiMmcCommands 25 | { 26 | public: 27 | 28 | SCSICD(int, scsi_defs::scsi_level = scsi_level::scsi_2); 29 | ~SCSICD() override = default; 30 | 31 | bool Init(const param_map&) override; 32 | 33 | void Open() override; 34 | 35 | vector InquiryInternal() const override; 36 | int Read(span, uint64_t) override; 37 | 38 | protected: 39 | 40 | void SetUpModePages(map>&, int, bool) const override; 41 | void AddVendorPage(map>&, int, bool) const override; 42 | 43 | private: 44 | 45 | int ReadTocInternal(cdb_t, vector&); 46 | 47 | void AddCDROMPage(map>&, bool) const; 48 | void AddCDDAPage(map>&, bool) const; 49 | scsi_defs::scsi_level scsi_level; 50 | 51 | void OpenIso(); 52 | void OpenPhysical(); 53 | 54 | void CreateDataTrack(); 55 | 56 | void ReadToc() override; 57 | 58 | void LBAtoMSF(uint32_t, uint8_t *) const; // LBA→MSF conversion 59 | 60 | bool rawfile = false; // RAW flag 61 | 62 | // Track management 63 | void ClearTrack(); // Clear the track 64 | int SearchTrack(uint32_t lba) const; // Track search 65 | vector> tracks; // Track opbject references 66 | int dataindex = -1; // Current data track 67 | int audioindex = -1; // Current audio track 68 | }; 69 | -------------------------------------------------------------------------------- /cpp/devices/ctapdriver.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Powered by XM6 TypeG Technology. 7 | // Copyright (C) 2016-2020 GIMONS 8 | // Copyright (C) akuker 9 | // 10 | //--------------------------------------------------------------------------- 11 | 12 | #pragma once 13 | 14 | #include "devices/device.h" 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #ifndef ETH_FRAME_LEN 22 | static const int ETH_FRAME_LEN = 1514; 23 | #endif 24 | #ifndef ETH_FCS_LEN 25 | static const int ETH_FCS_LEN = 4; 26 | #endif 27 | 28 | using namespace std; 29 | 30 | class CTapDriver 31 | { 32 | static const string BRIDGE_NAME; 33 | 34 | const inline static string DEFAULT_IP = "10.10.20.1/24"; //NOSONAR This hardcoded IP address is safe 35 | 36 | public: 37 | 38 | CTapDriver() = default; 39 | ~CTapDriver() = default; 40 | CTapDriver(CTapDriver&) = default; 41 | CTapDriver& operator=(const CTapDriver&) = default; 42 | 43 | bool Init(const param_map&); 44 | void CleanUp() const; 45 | 46 | param_map GetDefaultParams() const; 47 | 48 | void GetMacAddr(uint8_t *) const; 49 | int Receive(uint8_t *) const; 50 | int Send(const uint8_t *, int) const; 51 | bool HasPendingPackets() const; // Check if there are IP packets available 52 | string IpLink(bool) const; // Enable/Disable the piscsi0 interface 53 | void Flush() const; // Purge all of the packets that are waiting to be processed 54 | 55 | static uint32_t Crc32(span); 56 | 57 | private: 58 | 59 | static string SetUpEth0(int, const string&); 60 | static string SetUpNonEth0(int, int, const string&); 61 | static pair ExtractAddressAndMask(const string&); 62 | 63 | array m_MacAddr; // MAC Address 64 | 65 | int m_hTAP = -1; // File handle 66 | 67 | // Prioritized comma-separated list of interfaces to create the bridge for 68 | vector interfaces; 69 | 70 | string inet; 71 | }; 72 | 73 | -------------------------------------------------------------------------------- /cpp/hal/connection_type/connection_aibom.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Powered by XM6 TypeG Technology. 7 | // Copyright (C) 2016-2020 GIMONS 8 | // 9 | //--------------------------------------------------------------------------- 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | // 16 | // RaSCSI Adapter Aibom version 17 | // 18 | 19 | const std::string CONNECT_DESC = "AIBOM PRODUCTS version"; // Startup message 20 | 21 | // Select signal control mode 22 | const static int SIGNAL_CONTROL_MODE = 2; // SCSI positive logic specification 23 | 24 | // Control signal output logic 25 | #define ACT_ON ON // ACTIVE SIGNAL ON 26 | #define ENB_ON ON // ENABLE SIGNAL ON 27 | #define IND_IN OFF // INITIATOR SIGNAL INPUT 28 | #define TAD_IN OFF // TARGET SIGNAL INPUT 29 | #define DTD_IN OFF // DATA SIGNAL INPUT 30 | 31 | // Control signal pin assignment (-1 means no control) 32 | const static int PIN_ACT = 4; // ACTIVE 33 | const static int PIN_ENB = 17; // ENABLE 34 | const static int PIN_IND = 27; // INITIATOR CTRL DIRECTION 35 | const static int PIN_TAD = -1; // TARGET CTRL DIRECTION 36 | const static int PIN_DTD = 18; // DATA DIRECTION 37 | 38 | // SCSI signal pin assignment 39 | const static int PIN_DT0 = 6; // Data 0 40 | const static int PIN_DT1 = 12; // Data 1 41 | const static int PIN_DT2 = 13; // Data 2 42 | const static int PIN_DT3 = 16; // Data 3 43 | const static int PIN_DT4 = 19; // Data 4 44 | const static int PIN_DT5 = 20; // Data 5 45 | const static int PIN_DT6 = 26; // Data 6 46 | const static int PIN_DT7 = 21; // Data 7 47 | const static int PIN_DP = 5; // Data parity 48 | const static int PIN_ATN = 22; // ATN 49 | const static int PIN_RST = 25; // RST 50 | const static int PIN_ACK = 10; // ACK 51 | const static int PIN_REQ = 7; // REQ 52 | const static int PIN_MSG = 9; // MSG 53 | const static int PIN_CD = 11; // CD 54 | const static int PIN_IO = 23; // IO 55 | const static int PIN_BSY = 24; // BSY 56 | const static int PIN_SEL = 8; // SEL 57 | -------------------------------------------------------------------------------- /cpp/hal/connection_type/connection_fullspec.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Powered by XM6 TypeG Technology. 7 | // Copyright (C) 2016-2020 GIMONS 8 | // 9 | //--------------------------------------------------------------------------- 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | // 16 | // PiSCSI standard (SCSI logic, standard pin assignment) 17 | // 18 | 19 | const std::string CONNECT_DESC = "FULLSPEC"; // Startup message 20 | 21 | // Select signal control mode 22 | const static int SIGNAL_CONTROL_MODE = 0; // SCSI logical specification 23 | 24 | // Control signal pin assignment (-1 means no control) 25 | const static int PIN_ACT = 4; // ACTIVE 26 | const static int PIN_ENB = 5; // ENABLE 27 | const static int PIN_IND = 6; // INITIATOR CTRL DIRECTION 28 | const static int PIN_TAD = 7; // TARGET CTRL DIRECTION 29 | const static int PIN_DTD = 8; // DATA DIRECTION 30 | 31 | // Control signal output logic 32 | #define ACT_ON ON // ACTIVE SIGNAL ON 33 | #define ENB_ON ON // ENABLE SIGNAL ON 34 | #define IND_IN OFF // INITIATOR SIGNAL INPUT 35 | #define TAD_IN OFF // TARGET SIGNAL INPUT 36 | #define DTD_IN ON // DATA SIGNAL INPUT 37 | 38 | // SCSI signal pin assignment 39 | const static int PIN_DT0 = 10; // Data 0 40 | const static int PIN_DT1 = 11; // Data 1 41 | const static int PIN_DT2 = 12; // Data 2 42 | const static int PIN_DT3 = 13; // Data 3 43 | const static int PIN_DT4 = 14; // Data 4 44 | const static int PIN_DT5 = 15; // Data 5 45 | const static int PIN_DT6 = 16; // Data 6 46 | const static int PIN_DT7 = 17; // Data 7 47 | const static int PIN_DP = 18; // Data parity 48 | const static int PIN_ATN = 19; // ATN 49 | const static int PIN_RST = 20; // RST 50 | const static int PIN_ACK = 21; // ACK 51 | const static int PIN_REQ = 22; // REQ 52 | const static int PIN_MSG = 23; // MSG 53 | const static int PIN_CD = 24; // CD 54 | const static int PIN_IO = 25; // IO 55 | const static int PIN_BSY = 26; // BSY 56 | const static int PIN_SEL = 27; // SEL 57 | -------------------------------------------------------------------------------- /cpp/hal/connection_type/connection_gamernium.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Powered by XM6 TypeG Technology. 7 | // Copyright (C) 2016-2020 GIMONS 8 | // 9 | //--------------------------------------------------------------------------- 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | // 16 | // RaSCSI Adapter GAMERnium.com version 17 | // 18 | 19 | const std::string CONNECT_DESC = "GAMERnium.com version"; // Startup message 20 | 21 | // Select signal control mode 22 | const static int SIGNAL_CONTROL_MODE = 0; // SCSI logical specification 23 | 24 | // Control signal output logic 25 | #define ACT_ON ON // ACTIVE SIGNAL ON 26 | #define ENB_ON ON // ENABLE SIGNAL ON 27 | #define IND_IN OFF // INITIATOR SIGNAL INPUT 28 | #define TAD_IN OFF // TARGET SIGNAL INPUT 29 | #define DTD_IN ON // DATA SIGNAL INPUT 30 | 31 | // Control signal pin assignment (-1 means no control) 32 | const static int PIN_ACT = 14; // ACTIVE 33 | const static int PIN_ENB = 6; // ENABLE 34 | const static int PIN_IND = 7; // INITIATOR CTRL DIRECTION 35 | const static int PIN_TAD = 8; // TARGET CTRL DIRECTION 36 | const static int PIN_DTD = 5; // DATA DIRECTION 37 | 38 | // SCSI signal pin assignment 39 | const static int PIN_DT0 = 21; // Data 0 40 | const static int PIN_DT1 = 26; // Data 1 41 | const static int PIN_DT2 = 20; // Data 2 42 | const static int PIN_DT3 = 19; // Data 3 43 | const static int PIN_DT4 = 16; // Data 4 44 | const static int PIN_DT5 = 13; // Data 5 45 | const static int PIN_DT6 = 12; // Data 6 46 | const static int PIN_DT7 = 11; // Data 7 47 | const static int PIN_DP = 25; // Data parity 48 | const static int PIN_ATN = 10; // ATN 49 | const static int PIN_RST = 22; // RST 50 | const static int PIN_ACK = 24; // ACK 51 | const static int PIN_REQ = 15; // REQ 52 | const static int PIN_MSG = 17; // MSG 53 | const static int PIN_CD = 18; // CD 54 | const static int PIN_IO = 4; // IO 55 | const static int PIN_BSY = 27; // BSY 56 | const static int PIN_SEL = 23; // SEL 57 | -------------------------------------------------------------------------------- /cpp/hal/connection_type/connection_standard.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Powered by XM6 TypeG Technology. 7 | // Copyright (C) 2016-2020 GIMONS 8 | // 9 | //--------------------------------------------------------------------------- 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | // 16 | // PiSCSI standard (SCSI logic, standard pin assignment) 17 | // 18 | 19 | const std::string CONNECT_DESC = "STANDARD"; // Startup message 20 | 21 | // Select signal control mode 22 | const static int SIGNAL_CONTROL_MODE = 0; // SCSI logical specification 23 | 24 | // Control signal pin assignment (-1 means no control) 25 | const static int PIN_ACT = 4; // ACTIVE 26 | const static int PIN_ENB = 5; // ENABLE 27 | const static int PIN_IND = -1; // INITIATOR CTRL DIRECTION 28 | const static int PIN_TAD = -1; // TARGET CTRL DIRECTION 29 | const static int PIN_DTD = -1; // DATA DIRECTION 30 | 31 | // Control signal output logic 32 | #define ACT_ON ON // ACTIVE SIGNAL ON 33 | #define ENB_ON ON // ENABLE SIGNAL ON 34 | #define IND_IN OFF // INITIATOR SIGNAL INPUT 35 | #define TAD_IN OFF // TARGET SIGNAL INPUT 36 | #define DTD_IN ON // DATA SIGNAL INPUT 37 | 38 | // SCSI signal pin assignment 39 | const static int PIN_DT0 = 10; // Data 0 40 | const static int PIN_DT1 = 11; // Data 1 41 | const static int PIN_DT2 = 12; // Data 2 42 | const static int PIN_DT3 = 13; // Data 3 43 | const static int PIN_DT4 = 14; // Data 4 44 | const static int PIN_DT5 = 15; // Data 5 45 | const static int PIN_DT6 = 16; // Data 6 46 | const static int PIN_DT7 = 17; // Data 7 47 | const static int PIN_DP = 18; // Data parity 48 | const static int PIN_ATN = 19; // ATN 49 | const static int PIN_RST = 20; // RST 50 | const static int PIN_ACK = 21; // ACK 51 | const static int PIN_REQ = 22; // REQ 52 | const static int PIN_MSG = 23; // MSG 53 | const static int PIN_CD = 24; // CD 54 | const static int PIN_IO = 25; // IO 55 | const static int PIN_BSY = 26; // BSY 56 | const static int PIN_SEL = 27; // SEL 57 | -------------------------------------------------------------------------------- /cpp/devices/scsi_printer.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2022-2023 Uwe Seimet 7 | // 8 | // Implementation of a SCSI printer (see SCSI-2 specification for a command description) 9 | // 10 | //--------------------------------------------------------------------------- 11 | #pragma once 12 | 13 | #include "interfaces/scsi_printer_commands.h" 14 | #include "primary_device.h" 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | using namespace std; 21 | 22 | class SCSIPrinter : public PrimaryDevice, private ScsiPrinterCommands 23 | { 24 | uint64_t file_print_count = 0; 25 | uint64_t byte_receive_count = 0; 26 | uint64_t print_error_count = 0; 27 | uint64_t print_warning_count = 0; 28 | 29 | static const int NOT_RESERVED = -2; 30 | 31 | static constexpr const char *PRINTER_FILE_PATTERN = "/piscsi_sclp-XXXXXX"; 32 | 33 | inline static const string FILE_PRINT_COUNT = "file_print_count"; 34 | inline static const string BYTE_RECEIVE_COUNT = "byte_receive_count"; 35 | inline static const string PRINT_ERROR_COUNT = "print_error_count"; 36 | inline static const string PRINT_WARNING_COUNT = "print_warning_count"; 37 | 38 | public: 39 | 40 | explicit SCSIPrinter(int); 41 | ~SCSIPrinter() override = default; 42 | 43 | bool Init(const param_map&) override; 44 | void CleanUp() override; 45 | 46 | param_map GetDefaultParams() const override; 47 | 48 | vector InquiryInternal() const override; 49 | 50 | bool WriteByteSequence(span) override; 51 | 52 | vector GetStatistics() const override; 53 | 54 | private: 55 | 56 | void TestUnitReady() override; 57 | void ReserveUnit() override { PrimaryDevice::ReserveUnit(); } 58 | void ReleaseUnit() override { PrimaryDevice::ReleaseUnit(); } 59 | void SendDiagnostic() override { PrimaryDevice::SendDiagnostic(); } 60 | void Print() override; 61 | void SynchronizeBuffer(); 62 | 63 | string file_template; 64 | 65 | string filename; 66 | 67 | ofstream out; 68 | }; 69 | -------------------------------------------------------------------------------- /cpp/shared/network_util.cpp: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2023 Uwe Seimet 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #include "network_util.h" 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | using namespace std; 21 | 22 | bool network_util::IsInterfaceUp(const string& interface) 23 | { 24 | ifreq ifr = {}; 25 | strncpy(ifr.ifr_name, interface.c_str(), IFNAMSIZ - 1); //NOSONAR Using strncpy is safe 26 | const int fd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP); 27 | 28 | if (!ioctl(fd, SIOCGIFFLAGS, &ifr) && (ifr.ifr_flags & IFF_UP)) { 29 | close(fd); 30 | return true; 31 | } 32 | 33 | close(fd); 34 | return false; 35 | } 36 | 37 | set> network_util::GetNetworkInterfaces() 38 | { 39 | set> network_interfaces; 40 | 41 | #ifdef __linux__ 42 | ifaddrs *addrs; 43 | getifaddrs(&addrs); 44 | ifaddrs *tmp = addrs; 45 | 46 | while (tmp) { 47 | if (const string name = tmp->ifa_name; tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_PACKET && 48 | name != "lo" && name != "piscsi_bridge" && !name.starts_with("dummy") && IsInterfaceUp(name)) { 49 | // Only list interfaces that are up 50 | network_interfaces.insert(name); 51 | } 52 | 53 | tmp = tmp->ifa_next; 54 | } 55 | 56 | freeifaddrs(addrs); 57 | #endif 58 | 59 | return network_interfaces; 60 | } 61 | 62 | bool network_util::ResolveHostName(const string& host, sockaddr_in *addr) 63 | { 64 | addrinfo hints = {}; 65 | hints.ai_family = AF_INET; 66 | hints.ai_socktype = SOCK_STREAM; 67 | 68 | if (addrinfo *result; !getaddrinfo(host.c_str(), nullptr, &hints, &result)) { 69 | *addr = *reinterpret_cast(result->ai_addr); //NOSONAR bit_cast is not supported by the bullseye compiler 70 | freeaddrinfo(result); 71 | return true; 72 | } 73 | 74 | return false; 75 | } 76 | -------------------------------------------------------------------------------- /python/README.md: -------------------------------------------------------------------------------- 1 | # PiSCSI Python Apps 2 | 3 | This directory contains Python-based clients for PiSCSI as well as common 4 | packages that are shared among the clients. 5 | 6 | The following paragraphs in this README contain instructions that are shared 7 | among all Python apps. 8 | 9 | ## Supported Python interpreters 10 | 11 | The policy in this project is to support the Python 3 interpreter that comes 12 | standard with the current stable, as well as the previous stable releases of Debian. 13 | 14 | At the time of writing they are: 15 | - Python 3.11 in [Debian Bookworm](https://packages.debian.org/bookworm/python3) 16 | - Python 3.9 in [Debian Bullseye](https://packages.debian.org/bullseye/python3) 17 | 18 | ## Dependencies 19 | 20 | We use 'pip freeze' to manage explicit Python dependencies in this project. 21 | After adding new or bumping the versions of Python dependencies, 22 | please run the following command in the requisite subdir commit the results: 23 | 24 | ``` 25 | pip freeze -l > requirements.txt 26 | ``` 27 | 28 | ## Static analysis and formatting 29 | 30 | The CI workflow is set up to check code formatting with `black`, 31 | and linting with `flake8`. If non-conformant code is found, the CI job 32 | will fail. 33 | 34 | Before checking in new code, install the development packages and run 35 | these two tools locally. 36 | 37 | ``` 38 | pip install -r web/requirements-dev.txt 39 | ``` 40 | 41 | Note that `black` only works correctly if you run it in the root of the 42 | `python/` dir: 43 | 44 | ``` 45 | cd python 46 | black . 47 | ``` 48 | 49 | Optionally: It is recommended to run pylint against new code to protect against bugs 50 | and keep the code readable and maintainable. 51 | The local pylint configuration lives in .pylintrc. 52 | In order for pylint to recognize venv libraries, the pylint-venv package is required. 53 | 54 | ``` 55 | sudo apt install pylint3 56 | sudo pip install pylint-venv 57 | source venv/bin/activate 58 | pylint3 python_source_file.py 59 | ``` 60 | 61 | Examples: 62 | ``` 63 | # check a single file 64 | pylint web/src/web.py 65 | 66 | # check the python modules 67 | pylint common/src 68 | pylint web/src 69 | pylint oled/src 70 | ``` 71 | -------------------------------------------------------------------------------- /cpp/piscsi/piscsi_core.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2022-2023 Uwe Seimet 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #pragma once 11 | 12 | #include "controllers/controller_manager.h" 13 | #include "piscsi/command_context.h" 14 | #include "piscsi/piscsi_service.h" 15 | #include "piscsi/piscsi_image.h" 16 | #include "piscsi/piscsi_response.h" 17 | #include "piscsi/piscsi_executor.h" 18 | #include "generated/piscsi_interface.pb.h" 19 | #include "spdlog/sinks/stdout_color_sinks.h" 20 | #include 21 | #include 22 | #include 23 | 24 | using namespace std; 25 | 26 | class BUS; 27 | 28 | class Piscsi 29 | { 30 | static const int DEFAULT_PORT = 6868; 31 | 32 | public: 33 | 34 | Piscsi() = default; 35 | ~Piscsi() = default; 36 | 37 | int run(span); 38 | 39 | private: 40 | 41 | void Banner(span) const; 42 | bool InitBus(); 43 | void CleanUp(); 44 | void ReadAccessToken(const path&); 45 | void LogDevices(string_view) const; 46 | static void TerminationHandler(int); 47 | string ParseArguments(span, PbCommand&, int&, string&); 48 | void Process(); 49 | bool IsNotBusy() const; 50 | 51 | bool ShutDown(AbstractController::piscsi_shutdown_mode); 52 | bool ShutDown(const CommandContext&, const string&); 53 | 54 | bool ExecuteCommand(const CommandContext&); 55 | bool ExecuteWithLock(const CommandContext&); 56 | bool HandleDeviceListChange(const CommandContext&, PbOperation) const; 57 | 58 | bool SetLogLevel(const string&) const; 59 | 60 | const shared_ptr logger = spdlog::stdout_color_mt("piscsi stdout logger"); 61 | 62 | static PbDeviceType ParseDeviceType(const string&); 63 | 64 | mutex execution_locker; 65 | 66 | string access_token; 67 | 68 | PiscsiImage piscsi_image; 69 | 70 | [[no_unique_address]] PiscsiResponse response; 71 | 72 | PiscsiService service; 73 | 74 | unique_ptr executor; 75 | 76 | ControllerManager controller_manager; 77 | 78 | unique_ptr bus; 79 | 80 | // Required for the termination handler 81 | static inline Piscsi *instance; 82 | }; 83 | -------------------------------------------------------------------------------- /cpp/hal/systimer_raspberry.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Powered by XM6 TypeG Technology. 7 | // Copyright (C) 2016-2020 GIMONS 8 | // Copyright (C) 2022 akuker 9 | // 10 | // [ High resolution timer ] 11 | // 12 | //--------------------------------------------------------------------------- 13 | 14 | #pragma once 15 | 16 | #include "systimer.h" 17 | #include 18 | 19 | //=========================================================================== 20 | // 21 | // System timer 22 | // 23 | //=========================================================================== 24 | class SysTimer_Raspberry : public PlatformSpecificTimer 25 | { 26 | public: 27 | // Default constructor 28 | SysTimer_Raspberry() = default; 29 | // Default destructor 30 | ~SysTimer_Raspberry() override = default; 31 | // Initialization 32 | void Init() override; 33 | // Get system timer low byte 34 | uint32_t GetTimerLow() override; 35 | // Sleep for N nanoseconds 36 | void SleepNsec(uint32_t nsec) override; 37 | // Sleep for N microseconds 38 | void SleepUsec(uint32_t usec) override; 39 | 40 | private: 41 | // System timer address 42 | static volatile uint32_t *systaddr; 43 | // ARM timer address 44 | static volatile uint32_t *armtaddr; 45 | // Core frequency 46 | static volatile uint32_t corefreq; 47 | 48 | const static int ARMT_LOAD = 0; 49 | const static int ARMT_VALUE = 1; 50 | const static int ARMT_CTRL = 2; 51 | const static int ARMT_CLRIRQ = 3; 52 | const static int ARMT_RAWIRQ = 4; 53 | const static int ARMT_MSKIRQ = 5; 54 | const static int ARMT_RELOAD = 6; 55 | const static int ARMT_PREDIV = 7; 56 | const static int ARMT_FREERUN = 8; 57 | 58 | const static int SYST_CS = 0; 59 | const static int SYST_CLO = 1; 60 | const static int SYST_CHI = 2; 61 | const static int SYST_C0 = 3; 62 | const static int SYST_C1 = 4; 63 | const static int SYST_C2 = 5; 64 | const static int SYST_C3 = 6; 65 | 66 | const static uint32_t SYST_OFFSET = 0x00003000; 67 | const static uint32_t ARMT_OFFSET = 0x0000B400; 68 | }; 69 | -------------------------------------------------------------------------------- /cpp/piscsi/localizer.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2021-2023 Uwe Seimet 7 | // 8 | // Message localization support. Currently only for messages with up to 3 string parameters. 9 | // 10 | //--------------------------------------------------------------------------- 11 | 12 | #pragma once 13 | 14 | #include "shared/piscsi_util.h" 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | using namespace std; 21 | 22 | enum class LocalizationKey { 23 | ERROR_AUTHENTICATION, 24 | ERROR_OPERATION, 25 | ERROR_LOG_LEVEL, 26 | ERROR_MISSING_DEVICE_ID, 27 | ERROR_MISSING_FILENAME, 28 | ERROR_DEVICE_MISSING_FILENAME, 29 | ERROR_IMAGE_IN_USE, 30 | ERROR_IMAGE_FILE_INFO, 31 | ERROR_RESERVED_ID, 32 | ERROR_NON_EXISTING_DEVICE, 33 | ERROR_NON_EXISTING_UNIT, 34 | ERROR_UNKNOWN_DEVICE_TYPE, 35 | ERROR_MISSING_DEVICE_TYPE, 36 | ERROR_DUPLICATE_ID, 37 | ERROR_DETACH, 38 | ERROR_EJECT_REQUIRED, 39 | ERROR_DEVICE_NAME_UPDATE, 40 | ERROR_SHUTDOWN_MODE_MISSING, 41 | ERROR_SHUTDOWN_MODE_INVALID, 42 | ERROR_SHUTDOWN_PERMISSION, 43 | ERROR_FILE_OPEN, 44 | ERROR_BLOCK_SIZE, 45 | ERROR_BLOCK_SIZE_NOT_CONFIGURABLE, 46 | ERROR_SCSI_CONTROLLER, 47 | ERROR_INVALID_ID, 48 | ERROR_INVALID_LUN, 49 | ERROR_LUN0, 50 | ERROR_INITIALIZATION, 51 | ERROR_OPERATION_DENIED_STOPPABLE, 52 | ERROR_OPERATION_DENIED_REMOVABLE, 53 | ERROR_OPERATION_DENIED_PROTECTABLE, 54 | ERROR_OPERATION_DENIED_READY 55 | }; 56 | 57 | class Localizer 58 | { 59 | public: 60 | 61 | Localizer(); 62 | ~Localizer() = default; 63 | 64 | string Localize(LocalizationKey, const string&, const string& = "", const string& = "", const string& = "") const; 65 | 66 | private: 67 | 68 | void Add(LocalizationKey, const string&, string_view); 69 | unordered_map, piscsi_util::StringHash, equal_to<>> localized_messages; 70 | 71 | // Supported locales, always lower case 72 | unordered_set> supported_languages = { "en", "de", "sv", "fr", "es", "zh" }; 73 | 74 | const regex regex1 = regex("%1"); 75 | const regex regex2 = regex("%2"); 76 | const regex regex3 = regex("%3"); 77 | }; 78 | -------------------------------------------------------------------------------- /cpp/scsiloop/scsiloop_gpio.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI for Raspberry Pi 4 | // Loopback tester utility 5 | // 6 | // Copyright (C) 2022 akuker 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include "hal/gpiobus.h" 15 | 16 | using namespace std; 17 | 18 | class ScsiLoop_GPIO 19 | { 20 | public: 21 | ScsiLoop_GPIO(); 22 | ~ScsiLoop_GPIO() = default; 23 | int RunLoopbackTest(vector &error_list); 24 | int RunDataInputTest(vector &error_list); 25 | int RunDataOutputTest(vector &error_list); 26 | void Cleanup(); 27 | 28 | private: 29 | struct loopback_connections_struct { 30 | int this_pin; 31 | int connected_pin; 32 | int dir_ctrl_pin; 33 | }; 34 | using loopback_connection = loopback_connections_struct; 35 | 36 | std::map pin_name_lookup; 37 | std::vector loopback_conn_table; 38 | 39 | void set_dtd_out(); 40 | void set_dtd_in(); 41 | void set_ind_out(); 42 | void set_ind_in(); 43 | void set_tad_out(); 44 | void set_tad_in(); 45 | void set_output_channel(int out_gpio); 46 | void loopback_setup(); 47 | 48 | int test_gpio_pin(loopback_connection &gpio_rec, vector &error_list, bool &loopback_adapter_missing); 49 | 50 | void set_dat_inputs_loop(uint8_t value); 51 | 52 | void dat_input_test_setup(); 53 | void dat_output_test_setup(); 54 | 55 | uint8_t get_dat_outputs_loop(); 56 | 57 | int local_pin_dtd = -1; 58 | int local_pin_tad = -1; 59 | int local_pin_ind = -1; 60 | int local_pin_ack = -1; 61 | int local_pin_sel = -1; 62 | int local_pin_atn = -1; 63 | int local_pin_rst = -1; 64 | int local_pin_cd = -1; 65 | int local_pin_io = -1; 66 | int local_pin_msg = -1; 67 | int local_pin_req = -1; 68 | int local_pin_bsy = -1; 69 | 70 | int local_pin_dt0 = -1; 71 | int local_pin_dt1 = -1; 72 | int local_pin_dt2 = -1; 73 | int local_pin_dt3 = -1; 74 | int local_pin_dt4 = -1; 75 | int local_pin_dt5 = -1; 76 | int local_pin_dt6 = -1; 77 | int local_pin_dt7 = -1; 78 | int local_pin_dp = -1; 79 | 80 | shared_ptr bus; 81 | }; 82 | -------------------------------------------------------------------------------- /cpp/devices/scsi_command_util.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2022-2023 Uwe Seimet 7 | // 8 | // Shared code for SCSI command implementations 9 | // 10 | //--------------------------------------------------------------------------- 11 | 12 | #pragma once 13 | 14 | #include "shared/scsi.h" 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | using namespace std; 22 | 23 | namespace scsi_command_util 24 | { 25 | string ModeSelect(scsi_defs::scsi_command, cdb_t, span, int, int); 26 | void EnrichFormatPage(map>&, bool, int); 27 | void AddAppleVendorModePage(map>&, bool); 28 | 29 | int GetInt16(const auto buf, int offset) 30 | { 31 | assert(buf.size() > static_cast(offset) + 1); 32 | 33 | return (static_cast(buf[offset]) << 8) | buf[offset + 1]; 34 | } 35 | 36 | template 37 | void SetInt16(vector& buf, int offset, int value) 38 | { 39 | assert(buf.size() > static_cast(offset) + 1); 40 | 41 | buf[offset] = static_cast(value >> 8); 42 | buf[offset + 1] = static_cast(value); 43 | } 44 | 45 | template 46 | void SetInt32(vector& buf, int offset, uint32_t value) 47 | { 48 | assert(buf.size() > static_cast(offset) + 3); 49 | 50 | buf[offset] = static_cast(value >> 24); 51 | buf[offset + 1] = static_cast(value >> 16); 52 | buf[offset + 2] = static_cast(value >> 8); 53 | buf[offset + 3] = static_cast(value); 54 | } 55 | template 56 | void SetInt24(vector& buf, int offset, uint32_t value) 57 | { 58 | assert(buf.size() > static_cast(offset) + 3); 59 | 60 | buf[offset + 0] = static_cast(value >> 16); 61 | buf[offset + 1] = static_cast(value >> 8); 62 | buf[offset + 2] = static_cast(value); 63 | } 64 | 65 | inline int GetInt24(const auto buf, int offset) 66 | { 67 | assert(buf.size() > static_cast(offset) + 2); 68 | 69 | return (int(buf[offset]) << 16) | (int(buf[offset + 1]) << 8) | buf[offset + 2]; 70 | } 71 | uint32_t GetInt32(span , int); 72 | uint64_t GetInt64(span, int); 73 | void SetInt64(vector&, int, uint64_t); 74 | } 75 | -------------------------------------------------------------------------------- /cpp/devices/scsi_streamer.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2024 BogDan Vatra 7 | // 8 | //--------------------------------------------------------------------------- 9 | 10 | #pragma once 11 | 12 | #include "storage_device.h" 13 | #include 14 | 15 | class File { 16 | FILE *file = nullptr; 17 | inline auto checkSize(auto size) const { 18 | if (size < 0) 19 | throw scsi_exception(sense_key::medium_error); 20 | return size; 21 | } 22 | 23 | public: 24 | ~File(); 25 | bool open(std::string_view filename); 26 | void rewind(); 27 | size_t read(uint8_t* buff, size_t size, size_t count = 1); 28 | size_t write(const uint8_t *buff, size_t size, size_t count = 1); 29 | void seek(long offset, int origin); 30 | long tell(); 31 | void close(); 32 | }; 33 | 34 | class SCSIST: public StorageDevice 35 | { 36 | public: 37 | SCSIST(int lun); 38 | 39 | public: 40 | bool Init(const param_map &pm) final; 41 | void CleanUp() final; 42 | 43 | private: 44 | void Erase(); 45 | void Read6(); 46 | void ReadBlockLimits() const; 47 | void Rewind(); 48 | void Space(); 49 | void Write6(); 50 | void WriteFilemarks(); 51 | 52 | void LoadUnload(); 53 | void ReadPosition(); 54 | void Verify(); 55 | 56 | int ModeSense6(cdb_t cdb, std::vector &buf) const final; 57 | int ModeSense10(cdb_t, std::vector &) const final; 58 | 59 | // PrimaryDevice interface 60 | protected: 61 | std::vector InquiryInternal() const final; 62 | 63 | // ModePageDevice interface 64 | protected: 65 | void ModeSelect(scsi_defs::scsi_command, cdb_t, span, int) final; 66 | void SetUpModePages(std::map > &, int, bool) const final; 67 | 68 | // StorageDevice interface 69 | private: 70 | void Write(span, uint64_t) final; 71 | int Read(span , uint64_t) final; 72 | 73 | public: 74 | void Open() final; 75 | void AddBlockDescriptorPage(std::map > &, bool) const; 76 | void AddErrorPage(map > &, bool) const; 77 | void AddReconnectPage(map > &, bool) const; 78 | void AddDevicePage(map > &, bool) const; 79 | void AddMediumPartitionPage(map > &, bool) const; 80 | void AddMiscellaneousPage(map > &, bool) const; 81 | bool Eject(bool force) override; 82 | 83 | private: 84 | File file; 85 | }; 86 | -------------------------------------------------------------------------------- /cpp/test/scsidump_test.cpp: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // SCSI Target Emulator PiSCSI 4 | // for Raspberry Pi 5 | // 6 | // Copyright (C) 2022 akuker 7 | // Copyright (C) 2023 Uwe Seimet 8 | // 9 | //--------------------------------------------------------------------------- 10 | 11 | #include 12 | 13 | #include "scsidump/scsidump_core.h" 14 | #include "test/test_shared.h" 15 | 16 | using namespace std; 17 | using namespace filesystem; 18 | 19 | TEST(ScsiDumpTest, GeneratePropertiesFile) 20 | { 21 | // Basic test 22 | auto filename = CreateTempFile(0); 23 | ScsiDump::inquiry_info_t test_data = { 24 | .vendor = "PISCSI", .product = "TEST PRODUCT", .revision = "REV1", .sector_size = 1000, .capacity = 100}; 25 | test_data.GeneratePropertiesFile(filename); 26 | 27 | string expected_str = "{\n" 28 | " \"vendor\": \"PISCSI\",\n" 29 | " \"product\": \"TEST PRODUCT\",\n" 30 | " \"revision\": \"REV1\",\n" 31 | " \"block_size\": \"1000\"\n}" 32 | "\n"; 33 | EXPECT_EQ(expected_str, ReadTempFileToString(filename)); 34 | 35 | // Long string test 36 | filename = CreateTempFile(0); 37 | test_data = {.vendor = "01234567", 38 | .product = "0123456789ABCDEF", 39 | .revision = "0123", 40 | .sector_size = UINT32_MAX, 41 | .capacity = UINT64_MAX}; 42 | test_data.GeneratePropertiesFile(filename); 43 | 44 | expected_str = "{\n" 45 | " \"vendor\": \"01234567\",\n" 46 | " \"product\": \"0123456789ABCDEF\",\n" 47 | " \"revision\": \"0123\",\n" 48 | " \"block_size\": \"4294967295\"\n" 49 | "}\n"; 50 | EXPECT_EQ(expected_str, ReadTempFileToString(filename)); 51 | remove(filename); 52 | 53 | // Empty data test 54 | filename = CreateTempFile(0); 55 | test_data = {.vendor = "", .product = "", .revision = "", .sector_size = 0, .capacity = 0}; 56 | test_data.GeneratePropertiesFile(filename); 57 | 58 | expected_str = "{\n" 59 | " \"vendor\": \"\",\n" 60 | " \"product\": \"\",\n" 61 | " \"revision\": \"\",\n" 62 | " \"block_size\": \"0\"\n" 63 | "}\n"; 64 | EXPECT_EQ(expected_str, ReadTempFileToString(filename)); 65 | remove(filename); 66 | } 67 | --------------------------------------------------------------------------------