summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMaxim Devaev <[email protected]>2025-01-11 21:22:17 +0200
committerMaxim Devaev <[email protected]>2025-01-11 21:22:17 +0200
commit3cf543a13eab752ffe3c99f0d03f2b0bde4bbbae (patch)
tree5fd69e733a986c81f81e5e2f6ee09c11cc2f65cb
parent4d89d6b222635fc99a9b28b45d5bfe7e086f54bc (diff)
switch binary
-rw-r--r--PKGBUILD3
-rw-r--r--kvmd/apps/swctl/__init__.py167
-rw-r--r--kvmd/apps/swctl/__main__.py24
-rwxr-xr-xsetup.py1
-rw-r--r--switch/LICENSE15
-rw-r--r--switch/Makefile8
-rw-r--r--switch/mnt/README1
-rw-r--r--switch/switch.uf2bin0 -> 192512 bytes
-rw-r--r--testenv/Dockerfile1
9 files changed, 219 insertions, 1 deletions
diff --git a/PKGBUILD b/PKGBUILD
index 553cf073..cde06c16 100644
--- a/PKGBUILD
+++ b/PKGBUILD
@@ -79,6 +79,7 @@ depends=(
python-mako
python-luma-oled
python-pyusb
+ python-pyudev
"libgpiod>=2.1"
freetype2
"v4l-utils>=1.22.1-1"
@@ -167,7 +168,7 @@ package_kvmd() {
install -DTm644 configs/os/tmpfiles.conf "$pkgdir/usr/lib/tmpfiles.d/kvmd.conf"
mkdir -p "$pkgdir/usr/share/kvmd"
- cp -r {hid,web,extras,contrib/keymaps} "$pkgdir/usr/share/kvmd"
+ cp -r {switch,hid,web,extras,contrib/keymaps} "$pkgdir/usr/share/kvmd"
find "$pkgdir/usr/share/kvmd/web" -name '*.pug' -exec rm -f '{}' \;
local _cfg_default="$pkgdir/usr/share/kvmd/configs.default"
diff --git a/kvmd/apps/swctl/__init__.py b/kvmd/apps/swctl/__init__.py
new file mode 100644
index 00000000..d5915d74
--- /dev/null
+++ b/kvmd/apps/swctl/__init__.py
@@ -0,0 +1,167 @@
+# ========================================================================== #
+# #
+# KVMD - The main PiKVM daemon. #
+# #
+# Copyright (C) 2018-2024 Maxim Devaev <[email protected]> #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <https://www.gnu.org/licenses/>. #
+# #
+# ========================================================================== #
+
+
+import os
+import argparse
+import pprint
+import time
+
+import pyudev
+
+from ..kvmd.switch.device import Device
+from ..kvmd.switch.proto import Edid
+
+
+# =====
+def _find_serial_device() -> str:
+ ctx = pyudev.Context()
+ for device in ctx.list_devices(subsystem="tty"):
+ if (
+ str(device.properties.get("ID_VENDOR_ID")).upper() == "2E8A"
+ and str(device.properties.get("ID_MODEL_ID")).upper() == "1080"
+ ):
+ path = device.properties["DEVNAME"]
+ assert path.startswith("/dev/")
+ return path
+ return ""
+
+
+def _wait_boot_device() -> str:
+ stop_ts = time.time() + 5
+ ctx = pyudev.Context()
+ while time.time() < stop_ts:
+ for device in ctx.list_devices(subsystem="block", DEVTYPE="partition"):
+ if (
+ str(device.properties.get("ID_VENDOR_ID")).upper() == "2E8A"
+ and str(device.properties.get("ID_MODEL_ID")).upper() == "0003"
+ ):
+ path = device.properties["DEVNAME"]
+ assert path.startswith("/dev/")
+ return path
+ time.sleep(0.2)
+ return ""
+
+
+def _create_edid(arg: str) -> Edid:
+ if arg == "@":
+ return Edid.from_data("Empty", None)
+ with open(arg) as file:
+ return Edid.from_data(os.path.basename(arg), file.read())
+
+
+# =====
+def main() -> None: # pylint: disable=too-many-statements
+ parser = argparse.ArgumentParser()
+ parser.add_argument("-d", "--device", default="")
+ parser.set_defaults(cmd="")
+ subs = parser.add_subparsers()
+
+ def add_command(name: str) -> argparse.ArgumentParser:
+ cmd = subs.add_parser(name)
+ cmd.set_defaults(cmd=name)
+ return cmd
+
+ add_command("poll")
+
+ add_command("state")
+
+ cmd = add_command("bootloader")
+ cmd.add_argument("unit", type=int)
+
+ cmd = add_command("reboot")
+ cmd.add_argument("unit", type=int)
+
+ cmd = add_command("switch")
+ cmd.add_argument("unit", type=int)
+ cmd.add_argument("port", type=int, choices=list(range(5)))
+
+ cmd = add_command("beacon")
+ cmd.add_argument("unit", type=int)
+ cmd.add_argument("port", type=int, choices=list(range(6)))
+ cmd.add_argument("on", choices=["on", "off"])
+
+ add_command("leds")
+
+ cmd = add_command("click")
+ cmd.add_argument("button", choices=["power", "reset"])
+ cmd.add_argument("unit", type=int)
+ cmd.add_argument("port", type=int, choices=list(range(4)))
+ cmd.add_argument("delay_ms", type=int)
+
+ cmd = add_command("set-edid")
+ cmd.add_argument("unit", type=int)
+ cmd.add_argument("port", type=int, choices=list(range(4)))
+ cmd.add_argument("edid", type=_create_edid)
+
+ opts = parser.parse_args()
+
+ if not opts.device:
+ opts.device = _find_serial_device()
+
+ if opts.cmd == "bootloader" and opts.unit == 0:
+ if opts.device:
+ with Device(opts.device) as device:
+ device.request_reboot(opts.unit, bootloader=True)
+ found = _wait_boot_device()
+ if found:
+ print(found)
+ raise SystemExit()
+ raise SystemExit("Error: No switch found")
+
+ if not opts.device:
+ raise SystemExit("Error: No switch found")
+
+ with Device(opts.device) as device:
+ wait_rid: (int | None) = None
+ match opts.cmd:
+ case "poll":
+ device.request_state()
+ device.request_atx_leds()
+ case "state":
+ wait_rid = device.request_state()
+ case "bootloader" | "reboot":
+ device.request_reboot(opts.unit, (opts.cmd == "bootloader"))
+ raise SystemExit()
+ case "switch":
+ wait_rid = device.request_switch(opts.unit, opts.port)
+ case "leds":
+ wait_rid = device.request_atx_leds()
+ case "click":
+ match opts.button:
+ case "power":
+ wait_rid = device.request_atx_cp(opts.unit, opts.port, opts.delay_ms)
+ case "reset":
+ wait_rid = device.request_atx_cr(opts.unit, opts.port, opts.delay_ms)
+ case "beacon":
+ wait_rid = device.request_beacon(opts.unit, opts.port, (opts.on == "on"))
+ case "set-edid":
+ wait_rid = device.request_set_edid(opts.unit, opts.port, opts.edid)
+
+ error_ts = time.monotonic() + 1
+ while True:
+ for resp in device.read_all():
+ pprint.pprint((int(time.time()), resp))
+ print()
+ if resp.header.rid == wait_rid:
+ raise SystemExit()
+ if wait_rid is not None and time.monotonic() > error_ts:
+ raise SystemExit("No answer from unit")
diff --git a/kvmd/apps/swctl/__main__.py b/kvmd/apps/swctl/__main__.py
new file mode 100644
index 00000000..4827fc49
--- /dev/null
+++ b/kvmd/apps/swctl/__main__.py
@@ -0,0 +1,24 @@
+# ========================================================================== #
+# #
+# KVMD - The main PiKVM daemon. #
+# #
+# Copyright (C) 2018-2024 Maxim Devaev <[email protected]> #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <https://www.gnu.org/licenses/>. #
+# #
+# ========================================================================== #
+
+
+from . import main
+main()
diff --git a/setup.py b/setup.py
index 344f5444..c97cdab5 100755
--- a/setup.py
+++ b/setup.py
@@ -94,6 +94,7 @@ def main() -> None:
"kvmd.apps.otgnet",
"kvmd.apps.otgmsd",
"kvmd.apps.otgconf",
+ "kvmd.apps.swctl",
"kvmd.apps.htpasswd",
"kvmd.apps.totp",
"kvmd.apps.edidconf",
diff --git a/switch/LICENSE b/switch/LICENSE
new file mode 100644
index 00000000..5c574c88
--- /dev/null
+++ b/switch/LICENSE
@@ -0,0 +1,15 @@
+The PiKVM Switch Firmware
+Copyright (C) 2024-2025
+
+This software is distributed in binary form and is allowed for run only on original PiKVM Switch hardware.
+
+Modifications are not allowed.
+
+One day we will publish the source code, but not today.
+
+=====
+Includes other software related under other licenses:
+- MIT: TinyUSB - Copyright (c) 2018, hathach (tinyusb.org).
+- MIT: Pico-PIO-USB - Copyright (c) 2021 sekigon-gonnoc.
+- BSD: Pico-SDK - Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
+- BSD: FatFS - Copyright (C) 20xx, ChaN, all right reserved.
diff --git a/switch/Makefile b/switch/Makefile
new file mode 100644
index 00000000..4988126a
--- /dev/null
+++ b/switch/Makefile
@@ -0,0 +1,8 @@
+all:
+ @echo "Run 'make install'"
+
+upload: install
+install:
+ mount `python -m kvmd.apps.swctl bootloader 0` mnt
+ cp switch.uf2 mnt
+ umount mnt
diff --git a/switch/mnt/README b/switch/mnt/README
new file mode 100644
index 00000000..588522aa
--- /dev/null
+++ b/switch/mnt/README
@@ -0,0 +1 @@
+This is a mount point for the switch.
diff --git a/switch/switch.uf2 b/switch/switch.uf2
new file mode 100644
index 00000000..c3f37491
--- /dev/null
+++ b/switch/switch.uf2
Binary files differ
diff --git a/testenv/Dockerfile b/testenv/Dockerfile
index 6373345f..f907bb3d 100644
--- a/testenv/Dockerfile
+++ b/testenv/Dockerfile
@@ -49,6 +49,7 @@ RUN pacman --noconfirm --ask=4 -Syy \
python-pyotp \
python-qrcode \
python-pyserial \
+ python-pyudev \
python-setproctitle \
python-psutil \
python-netifaces \