From c27b8909dc425b4c06a12264de77877419a13497 Mon Sep 17 00:00:00 2001 From: Devaev Maxim Date: Mon, 19 Oct 2020 23:31:46 +0300 Subject: [PATCH 01/16] spi --- .gitignore | 1 + PKGBUILD | 1 + hid/Makefile | 13 ++++ hid/avrdude-rpi.conf | 7 ++ hid/avrdude.py | 53 +++++++++++++ hid/lib/.gitignore | 0 hid/patch.py | 34 +++++--- hid/platformio.ini | 180 ++++++++++++++++++++++++++++++++----------- hid/src/main.cpp | 2 +- 9 files changed, 234 insertions(+), 57 deletions(-) create mode 100644 hid/avrdude-rpi.conf create mode 100644 hid/avrdude.py create mode 100644 hid/lib/.gitignore diff --git a/.gitignore b/.gitignore index 2ed535c9..6ad7745d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +/hid/.platformio/ /hid/.pio/ /pkg/ /src/ diff --git a/PKGBUILD b/PKGBUILD index 306f1b51..95ae4549 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -56,6 +56,7 @@ depends=( nginx-mainline openssl platformio + avrdude-svn make patch sudo diff --git a/hid/Makefile b/hid/Makefile index 0d07db84..81fe129d 100644 --- a/hid/Makefile +++ b/hid/Makefile @@ -4,6 +4,12 @@ ps2: make _build E=ps2 mixed: make _build E=mixed +usb-spi: + make _build E=usb_spi +ps2-spi: + make _build E=ps2_spi +mixed-spi: + make _build E=mixed_spi _build: rm -f .current platformio run --environment $(E) @@ -15,11 +21,18 @@ upload: platformio run --environment $(shell cat .current) --target upload +bootloader-spi: install-bootloader-spi +install-bootloader-spi: upload-bootloader-spi +upload-bootloader-spi: + platformio run --environment bootloader_spi --target bootloader + + update: platformio platform update clean-all: clean + rm -rf .platformio clean: rm -rf .pio .current diff --git a/hid/avrdude-rpi.conf b/hid/avrdude-rpi.conf new file mode 100644 index 00000000..8a6f5460 --- /dev/null +++ b/hid/avrdude-rpi.conf @@ -0,0 +1,7 @@ +programmer + id = "rpi"; + desc = "RPi SPI programmer"; + type = "linuxspi"; + reset = 25; + baudrate = 400000; +; diff --git a/hid/avrdude.py b/hid/avrdude.py new file mode 100644 index 00000000..1eef53b4 --- /dev/null +++ b/hid/avrdude.py @@ -0,0 +1,53 @@ +# https://docs.platformio.org/en/latest/projectconf/advanced_scripting.html + +from os import rename +from os import symlink +from os.path import exists +from os.path import join + +import platform + +Import("env") + + +# ===== +def _get_tool_path() -> str: + path = env.PioPlatform().get_package_dir("tool-avrdude") + assert exists(path) + return path + + +def _fix_ld_arm() -> None: + tool_path = _get_tool_path() + flag_path = join(tool_path, ".fix-ld-arm.done") + + if not exists(flag_path): + def patch(*_, **__) -> None: + symlink("/usr/lib/libtinfo.so.6", join(tool_path, "libtinfo.so.5")) + open(flag_path, "w").close() + + env.Execute(patch) + + +def _replace_to_system(new_path: str) -> None: + tool_path = _get_tool_path() + flag_path = join(tool_path, ".replace-to-system.done") + + if not exists(flag_path): + def patch(*_, **__) -> None: + old_path = join(tool_path, "avrdude") + bak_path = join(tool_path, "_avrdude_bak") + rename(old_path, bak_path) + symlink(new_path, old_path) + open(flag_path, "w").close() + + env.Execute(patch) + + +# ===== +if "arm" in platform.machine(): + _fix_ld_arm() + +_path = "/usr/bin/avrdude" +if exists(_path): + _replace_to_system(_path) diff --git a/hid/lib/.gitignore b/hid/lib/.gitignore new file mode 100644 index 00000000..e69de29b diff --git a/hid/patch.py b/hid/patch.py index 100c0d5b..7b88eeb1 100644 --- a/hid/patch.py +++ b/hid/patch.py @@ -1,20 +1,32 @@ -from os.path import join +# https://docs.platformio.org/en/latest/projectconf/advanced_scripting.html + from os.path import exists +from os.path import join +from os.path import basename + +from typing import Dict Import("env") # ===== -deps_path = env.get("PROJECT_LIBDEPS_DIR", env.get("PROJECTLIBDEPS_DIR")) -assert deps_path, deps_path -env_path = join(deps_path, env["PIOENV"]) -flag_path = join(env_path, ".patched") +def _get_libs() -> Dict[str, str]: + return { + builder.name: builder.path + for builder in env.GetLibBuilders() + } -if not exists(flag_path): - env.Execute(f"patch -p1 -d {join(env_path, 'HID-Project')} < {join('patches', 'absmouse.patch')}") - def touch_flag(*_, **__) -> None: - with open(flag_path, "w") as flag_file: - pass +def _patch_lib(lib_path: str, patch_path: str) -> None: + assert exists(lib_path) + flag_path: str = join(lib_path, f".{basename(patch_path)}.done") + if not exists(flag_path): + env.Execute(f"patch -p1 -d {lib_path} < {patch_path}") + env.Execute(lambda *_, **__: open(flag_path, "w").close()) - env.Execute(touch_flag) + +# ===== +_libs = _get_libs() +assert "TimerOne" in _libs # Just checking +if "HID-Project" in _libs: + _patch_lib(_libs["HID-Project"], "patches/absmouse.patch") diff --git a/hid/platformio.ini b/hid/platformio.ini index c6c7c34b..1bf05d03 100644 --- a/hid/platformio.ini +++ b/hid/platformio.ini @@ -1,59 +1,149 @@ -; PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; http://docs.platformio.org/page/projectconf.html +# http://docs.platformio.org/page/projectconf.html +[platformio] +core_dir = ./.platformio/ -[common] +[env] +platform = atmelavr +board = micro +framework = arduino +extra_scripts = + pre:avrdude.py + post:patch.py +platform_packages = + tool-avrdude + +[_parts_common] lib_deps = TimerOne@1.1 + +[_parts_usb_kbd] +lib_deps = + HID-Project@2.6.1 +build_flags = + -DHID_USB_KBD + +[_parts_usb_mouse] +lib_deps = + HID-Project@2.6.1 +build_flags = + -DHID_USB_MOUSE + +[_parts_ps2_kbd] +lib_deps = + git+https://github.com/Harvie/ps2dev#v0.0.3 +build_flags = + -DHID_PS2_KBD + -DPS2_KBD_CLOCK_PIN=7 + -DPS2_KBD_DATA_PIN=5 + +[_usb] +lib_deps = + ${_parts_common.lib_deps} + ${_parts_usb_kbd.lib_deps} +# ${_parts_usb_mouse.lib_deps} +build_flags = + ${_parts_usb_kbd.build_flags} + ${_parts_usb_mouse.build_flags} + +[_ps2] +lib_deps = + ${_parts_common.lib_deps} + ${_parts_ps2_kbd.lib_deps} +build_flags = + ${_parts_ps2_kbd.build_flags} + +[_mixed] +lib_deps = + ${_parts_common.lib_deps} + ${_parts_ps2_kbd.lib_deps} + ${_parts_usb_mouse.lib_deps} +build_flags = + ${_parts_ps2_kbd.build_flags} + ${_parts_usb_mouse.build_flags} + + +# ===== Serial ===== +[_cmd_serial] build_flags = -DCMD_SERIAL=Serial1 + -DCMD_SERIAL_SPEED=115200 +upload_port = /dev/ttyACM0 [env:usb] -platform = atmelavr -board = micro -framework = arduino -upload_port = /dev/ttyACM0 -lib_deps = - ${common.lib_deps} - HID-Project@2.6.1 +extends = + _usb + _cmd_serial build_flags = - ${common.build_flags} - -DHID_USB_KBD - -DHID_USB_MOUSE -extra_scripts = post:patch.py + ${_usb.build_flags} + ${_cmd_serial.build_flags} [env:ps2] -platform = atmelavr -board = micro -framework = arduino -upload_port = /dev/ttyACM0 -lib_deps = - ${common.lib_deps} - git+https://github.com/Harvie/ps2dev#v0.0.3 +extends = + _ps2 + _cmd_serial build_flags = - ${common.build_flags} - -DHID_PS2_KBD - -DPS2_KBD_CLOCK_PIN=7 - -DPS2_KBD_DATA_PIN=5 + ${_ps2.build_flags} + ${_cmd_serial.build_flags} [env:mixed] -platform = atmelavr -board = micro -framework = arduino -upload_port = /dev/ttyACM0 -lib_deps = - ${common.lib_deps} - HID-Project@2.6.1 - git+https://github.com/Harvie/ps2dev#v0.0.3 +extends = + _mixed + _cmd_serial build_flags = - ${common.build_flags} - -DHID_PS2_KBD - -DHID_USB_MOUSE - -DPS2_KBD_CLOCK_PIN=7 - -DPS2_KBD_DATA_PIN=5 + ${_mixed.build_flags} + ${_cmd_serial.build_flags} + + +# ===== RPi SPI ===== +[env:bootloader_spi] +upload_protocol = rpi +upload_flags = + -C + +avrdude-rpi.conf + -P + /dev/spidev0.0:/dev/gpiochip0 +extra_scripts = + pre:avrdude.py + +[_cmd_spi] +build_flags = + -DCMD_SERIAL=Serial1 + -DCMD_SERIAL_SPEED=115200 +# -DCMD_SPI +upload_protocol = custom +upload_flags = + -C + $PROJECT_PACKAGES_DIR/tool-avrdude/avrdude.conf + -C + +avrdude-rpi.conf + -P + /dev/spidev0.0:/dev/gpiochip0 + -c + rpi + -p + $BOARD_MCU +upload_command = avrdude $UPLOAD_FLAGS -U flash:w:$SOURCE:i + +[env:usb_spi] +extends = + _usb + _cmd_spi +build_flags = + ${_usb.build_flags} + ${_cmd_spi.build_flags} + +[env:ps2_spi] +extends = + _ps2 + _cmd_spi +build_flags = + ${_ps2.build_flags} + ${_cmd_spi.build_flags} + +[env:mixed_spi] +extends = + _mixed + _cmd_spi +build_flags = + ${_mixed.build_flags} + ${_cmd_spi.build_flags} diff --git a/hid/src/main.cpp b/hid/src/main.cpp index cd3c245a..29982c8d 100644 --- a/hid/src/main.cpp +++ b/hid/src/main.cpp @@ -34,7 +34,7 @@ // #define CMD_SERIAL Serial1 -#define CMD_SERIAL_SPEED 115200 +// #define CMD_SERIAL_SPEED 115200 #define CMD_RECV_TIMEOUT 100000 #define PROTO_MAGIC 0x33 From dc0340583ecd2f76939edfc85cf69417743c088d Mon Sep 17 00:00:00 2001 From: Devaev Maxim Date: Wed, 28 Oct 2020 21:19:19 +0300 Subject: [PATCH 02/16] splitting serial --- .../hid/{serial.py => serial/__init__.py} | 77 +++++-------------- kvmd/plugins/hid/serial/gpio.py | 71 +++++++++++++++++ setup.py | 1 + 3 files changed, 90 insertions(+), 59 deletions(-) rename kvmd/plugins/hid/{serial.py => serial/__init__.py} (88%) create mode 100644 kvmd/plugins/hid/serial/gpio.py diff --git a/kvmd/plugins/hid/serial.py b/kvmd/plugins/hid/serial/__init__.py similarity index 88% rename from kvmd/plugins/hid/serial.py rename to kvmd/plugins/hid/serial/__init__.py index a45cd89a..2b1c0af6 100644 --- a/kvmd/plugins/hid/serial.py +++ b/kvmd/plugins/hid/serial/__init__.py @@ -33,33 +33,31 @@ from typing import List from typing import Dict from typing import Iterable from typing import AsyncGenerator -from typing import Optional -import gpiod import serial -from ...logging import get_logger +from ....logging import get_logger -from ...keyboard.mappings import KEYMAP +from ....keyboard.mappings import KEYMAP -from ... import env -from ... import tools -from ... import aiotools -from ... import aiomulti -from ... import aioproc -from ... import aiogp +from .... import tools +from .... import aiotools +from .... import aiomulti +from .... import aioproc -from ...yamlconf import Option +from ....yamlconf import Option -from ...validators.basic import valid_bool -from ...validators.basic import valid_int_f0 -from ...validators.basic import valid_int_f1 -from ...validators.basic import valid_float_f01 -from ...validators.os import valid_abs_path -from ...validators.hw import valid_tty_speed -from ...validators.hw import valid_gpio_pin_optional +from ....validators.basic import valid_bool +from ....validators.basic import valid_int_f0 +from ....validators.basic import valid_int_f1 +from ....validators.basic import valid_float_f01 +from ....validators.os import valid_abs_path +from ....validators.hw import valid_tty_speed +from ....validators.hw import valid_gpio_pin_optional -from . import BaseHid +from .. import BaseHid + +from .gpio import Gpio # ===== @@ -156,45 +154,6 @@ class _MouseWheelEvent(_BaseEvent): return struct.pack(">Bxbxx", 0x14, self.delta_y) -class _Gpio: - def __init__(self, reset_pin: int, reset_delay: float) -> None: - self.__reset_pin = reset_pin - self.__reset_delay = reset_delay - - self.__chip: Optional[gpiod.Chip] = None - self.__reset_line: Optional[gpiod.Line] = None - self.__reset_wip = False - - def open(self) -> None: - if self.__reset_pin >= 0: - assert self.__chip is None - assert self.__reset_line is None - self.__chip = gpiod.Chip(env.GPIO_DEVICE_PATH) - self.__reset_line = self.__chip.get_line(self.__reset_pin) - self.__reset_line.request("kvmd::hid-serial::reset", gpiod.LINE_REQ_DIR_OUT, default_vals=[0]) - - def close(self) -> None: - if self.__chip: - try: - self.__chip.close() - except Exception: - pass - - @aiotools.atomic - async def reset(self) -> None: - if self.__reset_pin >= 0: - assert self.__reset_line - if not self.__reset_wip: - self.__reset_wip = True - try: - await aiogp.pulse(self.__reset_line, self.__reset_delay, 1) - finally: - self.__reset_wip = False - get_logger(0).info("Reset HID performed") - else: - get_logger(0).info("Another reset HID in progress") - - # ===== class Plugin(BaseHid, multiprocessing.Process): # pylint: disable=too-many-instance-attributes def __init__( # pylint: disable=too-many-arguments,super-init-not-called @@ -223,7 +182,7 @@ class Plugin(BaseHid, multiprocessing.Process): # pylint: disable=too-many-inst self.__errors_threshold = errors_threshold self.__noop = noop - self.__gpio = _Gpio(reset_pin, reset_delay) + self.__gpio = Gpio(reset_pin, reset_delay) self.__events_queue: "multiprocessing.Queue[_BaseEvent]" = multiprocessing.Queue() diff --git a/kvmd/plugins/hid/serial/gpio.py b/kvmd/plugins/hid/serial/gpio.py new file mode 100644 index 00000000..a3e4018b --- /dev/null +++ b/kvmd/plugins/hid/serial/gpio.py @@ -0,0 +1,71 @@ +# ========================================================================== # +# # +# KVMD - The main Pi-KVM daemon. # +# # +# Copyright (C) 2018 Maxim Devaev # +# # +# 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 . # +# # +# ========================================================================== # + + +from typing import Optional + +import gpiod + +from ....logging import get_logger + +from .... import env +from .... import aiotools +from .... import aiogp + + +# ===== +class Gpio: + def __init__(self, reset_pin: int, reset_delay: float) -> None: + self.__reset_pin = reset_pin + self.__reset_delay = reset_delay + + self.__chip: Optional[gpiod.Chip] = None + self.__reset_line: Optional[gpiod.Line] = None + self.__reset_wip = False + + def open(self) -> None: + if self.__reset_pin >= 0: + assert self.__chip is None + assert self.__reset_line is None + self.__chip = gpiod.Chip(env.GPIO_DEVICE_PATH) + self.__reset_line = self.__chip.get_line(self.__reset_pin) + self.__reset_line.request("kvmd::hid-serial::reset", gpiod.LINE_REQ_DIR_OUT, default_vals=[0]) + + def close(self) -> None: + if self.__chip: + try: + self.__chip.close() + except Exception: + pass + + @aiotools.atomic + async def reset(self) -> None: + if self.__reset_pin >= 0: + assert self.__reset_line + if not self.__reset_wip: + self.__reset_wip = True + try: + await aiogp.pulse(self.__reset_line, self.__reset_delay, 1) + finally: + self.__reset_wip = False + get_logger(0).info("Reset HID performed") + else: + get_logger(0).info("Another reset HID in progress") diff --git a/setup.py b/setup.py index d1bdf816..852ca706 100755 --- a/setup.py +++ b/setup.py @@ -83,6 +83,7 @@ def main() -> None: "kvmd.plugins", "kvmd.plugins.auth", "kvmd.plugins.hid", + "kvmd.plugins.hid.serial", "kvmd.plugins.hid.otg", "kvmd.plugins.hid.bt", "kvmd.plugins.atx", From 08b96b7ada3363f599c9f58d091d9a06d54c2031 Mon Sep 17 00:00:00 2001 From: Devaev Maxim Date: Wed, 28 Oct 2020 22:29:27 +0300 Subject: [PATCH 03/16] serial phy layer --- kvmd/plugins/__init__.py | 2 + kvmd/plugins/hid/{serial => _mcu}/__init__.py | 89 +++++++++-------- kvmd/plugins/hid/{serial => _mcu}/gpio.py | 2 +- kvmd/plugins/hid/serial.py | 98 +++++++++++++++++++ setup.py | 2 +- 5 files changed, 146 insertions(+), 47 deletions(-) rename kvmd/plugins/hid/{serial => _mcu}/__init__.py (84%) rename kvmd/plugins/hid/{serial => _mcu}/gpio.py (96%) create mode 100644 kvmd/plugins/hid/serial.py diff --git a/kvmd/plugins/__init__.py b/kvmd/plugins/__init__.py index ebdeaf0b..ea25bcfc 100644 --- a/kvmd/plugins/__init__.py +++ b/kvmd/plugins/__init__.py @@ -52,6 +52,8 @@ class BasePlugin: def get_plugin_class(sub: str, name: str) -> Type[BasePlugin]: assert sub assert name + if name.startswith("_"): + raise UnknownPluginError(f"Unknown plugin '{sub}/{name}'") try: module = importlib.import_module(f"kvmd.plugins.{sub}.{name}") except ModuleNotFoundError: diff --git a/kvmd/plugins/hid/serial/__init__.py b/kvmd/plugins/hid/_mcu/__init__.py similarity index 84% rename from kvmd/plugins/hid/serial/__init__.py rename to kvmd/plugins/hid/_mcu/__init__.py index 2b1c0af6..bc978499 100644 --- a/kvmd/plugins/hid/serial/__init__.py +++ b/kvmd/plugins/hid/_mcu/__init__.py @@ -23,19 +23,18 @@ import os import multiprocessing import dataclasses +import contextlib import queue import struct -import errno import time from typing import Tuple from typing import List from typing import Dict from typing import Iterable +from typing import Generator from typing import AsyncGenerator -import serial - from ....logging import get_logger from ....keyboard.mappings import KEYMAP @@ -51,8 +50,6 @@ from ....validators.basic import valid_bool from ....validators.basic import valid_int_f0 from ....validators.basic import valid_int_f1 from ....validators.basic import valid_float_f01 -from ....validators.os import valid_abs_path -from ....validators.hw import valid_tty_speed from ....validators.hw import valid_gpio_pin_optional from .. import BaseHid @@ -155,15 +152,28 @@ class _MouseWheelEvent(_BaseEvent): # ===== -class Plugin(BaseHid, multiprocessing.Process): # pylint: disable=too-many-instance-attributes +class BasePhyConnection: + def send(self, request: bytes, receive: int) -> bytes: + raise NotImplementedError + + +class BasePhy: + def has_device(self) -> bool: + raise NotImplementedError + + @contextlib.contextmanager + def connected(self) -> Generator[BasePhyConnection, None, None]: + raise NotImplementedError + + +class BaseMcuHid(BaseHid, multiprocessing.Process): # pylint: disable=too-many-instance-attributes def __init__( # pylint: disable=too-many-arguments,super-init-not-called self, + phy: BasePhy, + reset_pin: int, reset_delay: float, - device_path: str, - speed: int, - read_timeout: float, read_retries: int, common_retries: int, retries_delay: float, @@ -173,15 +183,13 @@ class Plugin(BaseHid, multiprocessing.Process): # pylint: disable=too-many-inst multiprocessing.Process.__init__(self, daemon=True) - self.__device_path = device_path - self.__speed = speed - self.__read_timeout = read_timeout self.__read_retries = read_retries self.__common_retries = common_retries self.__retries_delay = retries_delay self.__errors_threshold = errors_threshold self.__noop = noop + self.__phy = phy self.__gpio = Gpio(reset_pin, reset_delay) self.__events_queue: "multiprocessing.Queue[_BaseEvent]" = multiprocessing.Queue() @@ -202,9 +210,6 @@ class Plugin(BaseHid, multiprocessing.Process): # pylint: disable=too-many-inst "reset_pin": Option(-1, type=valid_gpio_pin_optional), "reset_delay": Option(0.1, type=valid_float_f01), - "device": Option("", type=valid_abs_path, unpack_as="device_path"), - "speed": Option(115200, type=valid_tty_speed), - "read_timeout": Option(2.0, type=valid_float_f01), "read_retries": Option(10, type=valid_int_f1), "common_retries": Option(100, type=valid_int_f1), "retries_delay": Option(0.1, type=valid_float_f01), @@ -254,11 +259,11 @@ class Plugin(BaseHid, multiprocessing.Process): # pylint: disable=too-many-inst self.__stop_event.set() if self.exitcode is not None: self.join() - if os.path.exists(self.__device_path): + if self.__phy.has_device(): get_logger().info("Clearing HID events ...") try: - with self.__get_serial() as tty: - self.__process_command(tty, b"\x10\x00\x00\x00\x00") + with self.__phy.connected() as conn: + self.__process_command(conn, b"\x10\x00\x00\x00\x00") except Exception: logger.exception("Can't clear HID events") finally: @@ -299,31 +304,28 @@ class Plugin(BaseHid, multiprocessing.Process): # pylint: disable=too-many-inst while not self.__stop_event.is_set(): try: - with self.__get_serial() as tty: - while not (self.__stop_event.is_set() and self.__events_queue.qsize() == 0): - try: - event = self.__events_queue.get(timeout=0.1) - except queue.Empty: - self.__process_command(tty, b"\x01\x00\x00\x00\x00") # Ping - else: - if not self.__process_command(tty, event.make_command()): - self.clear_events() - - except Exception as err: - self.clear_events() - if isinstance(err, serial.SerialException) and err.errno == errno.ENOENT: # pylint: disable=no-member - logger.error("Missing HID serial device: %s", self.__device_path) + if self.__phy.has_device(): + with self.__phy.connected() as conn: + while not (self.__stop_event.is_set() and self.__events_queue.qsize() == 0): + try: + event = self.__events_queue.get(timeout=0.1) + except queue.Empty: + self.__process_command(conn, b"\x01\x00\x00\x00\x00") # Ping + else: + if not self.__process_command(conn, event.make_command()): + self.clear_events() else: - logger.exception("Unexpected HID error") + logger.error("Missing HID device") + time.sleep(1) + except Exception: + self.clear_events() + logger.exception("Unexpected HID error") time.sleep(1) - def __get_serial(self) -> serial.Serial: - return serial.Serial(self.__device_path, self.__speed, timeout=self.__read_timeout) + def __process_command(self, conn: BasePhyConnection, command: bytes) -> bool: + return self.__process_request(conn, self.__make_request(command)) - def __process_command(self, tty: serial.Serial, command: bytes) -> bool: - return self.__process_request(tty, self.__make_request(command)) - - def __process_request(self, tty: serial.Serial, request: bytes) -> bool: # pylint: disable=too-many-branches + def __process_request(self, conn: BasePhyConnection, request: bytes) -> bool: # pylint: disable=too-many-branches logger = get_logger() error_messages: List[str] = [] live_log_errors = False @@ -333,7 +335,7 @@ class Plugin(BaseHid, multiprocessing.Process): # pylint: disable=too-many-inst error_retval = False while common_retries and read_retries: - response = self.__send_request(tty, request) + response = self.__send_request(conn, request) try: if len(response) < 4: read_retries -= 1 @@ -392,12 +394,9 @@ class Plugin(BaseHid, multiprocessing.Process): # pylint: disable=too-many-inst logger.error("Can't process HID request due many errors: %r", request) return error_retval - def __send_request(self, tty: serial.Serial, request: bytes) -> bytes: + def __send_request(self, conn: BasePhyConnection, request: bytes) -> bytes: if not self.__noop: - if tty.in_waiting: - tty.read(tty.in_waiting) - assert tty.write(request) == len(request) - response = tty.read(4) + response = conn.send(request, 4) else: response = b"\x33\x20" # Magic + OK response += struct.pack(">H", self.__make_crc16(response)) diff --git a/kvmd/plugins/hid/serial/gpio.py b/kvmd/plugins/hid/_mcu/gpio.py similarity index 96% rename from kvmd/plugins/hid/serial/gpio.py rename to kvmd/plugins/hid/_mcu/gpio.py index a3e4018b..830819f0 100644 --- a/kvmd/plugins/hid/serial/gpio.py +++ b/kvmd/plugins/hid/_mcu/gpio.py @@ -47,7 +47,7 @@ class Gpio: assert self.__reset_line is None self.__chip = gpiod.Chip(env.GPIO_DEVICE_PATH) self.__reset_line = self.__chip.get_line(self.__reset_pin) - self.__reset_line.request("kvmd::hid-serial::reset", gpiod.LINE_REQ_DIR_OUT, default_vals=[0]) + self.__reset_line.request("kvmd::hid-mcu::reset", gpiod.LINE_REQ_DIR_OUT, default_vals=[0]) def close(self) -> None: if self.__chip: diff --git a/kvmd/plugins/hid/serial.py b/kvmd/plugins/hid/serial.py new file mode 100644 index 00000000..d5de1cc7 --- /dev/null +++ b/kvmd/plugins/hid/serial.py @@ -0,0 +1,98 @@ +# ========================================================================== # +# # +# KVMD - The main Pi-KVM daemon. # +# # +# Copyright (C) 2018 Maxim Devaev # +# # +# 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 . # +# # +# ========================================================================== # + + +import os +import contextlib + +from typing import Dict +from typing import Generator +from typing import Any + +import serial + +from ...yamlconf import Option + +from ...validators.basic import valid_float_f01 +from ...validators.os import valid_abs_path +from ...validators.hw import valid_tty_speed + +from ._mcu import BasePhyConnection +from ._mcu import BasePhy +from ._mcu import BaseMcuHid + + +# ===== +class _SerialPhyConnection(BasePhyConnection): + def __init__(self, tty: serial.Serial) -> None: + self.__tty = tty + + def send(self, request: bytes, receive: int) -> bytes: + if self.__tty.in_waiting: + self.__tty.read_all() + assert self.__tty.write(request) == len(request) + return self.__tty.read(receive) + + +class _SerialPhy(BasePhy): + def __init__( + self, + device_path: str, + speed: int, + read_timeout: float, + ) -> None: + + self.__device_path = device_path + self.__speed = speed + self.__read_timeout = read_timeout + + def has_device(self) -> bool: + return os.path.exists(self.__device_path) + + @contextlib.contextmanager + def connected(self) -> Generator[_SerialPhyConnection, None, None]: # type: ignore + with serial.Serial(self.__device_path, self.__speed, timeout=self.__read_timeout) as tty: + yield _SerialPhyConnection(tty) + + +# ===== +class Plugin(BaseMcuHid): + def __init__( + self, + device_path: str, + speed: int, + read_timeout: float, + **kwargs: Any, + ) -> None: + + super().__init__( + phy=_SerialPhy(device_path, speed, read_timeout), + **kwargs, + ) + + @classmethod + def get_plugin_options(cls) -> Dict: + return { + "device": Option("", type=valid_abs_path, unpack_as="device_path"), + "speed": Option(115200, type=valid_tty_speed), + "read_timeout": Option(2.0, type=valid_float_f01), + **BaseMcuHid.get_plugin_options(), + } diff --git a/setup.py b/setup.py index 852ca706..669dd004 100755 --- a/setup.py +++ b/setup.py @@ -83,7 +83,7 @@ def main() -> None: "kvmd.plugins", "kvmd.plugins.auth", "kvmd.plugins.hid", - "kvmd.plugins.hid.serial", + "kvmd.plugins.hid._mcu", "kvmd.plugins.hid.otg", "kvmd.plugins.hid.bt", "kvmd.plugins.atx", From e54449fd8ef0ea98aea881d25c9bb134a3bc2e11 Mon Sep 17 00:00:00 2001 From: Devaev Maxim Date: Thu, 29 Oct 2020 02:29:40 +0300 Subject: [PATCH 04/16] renamed serial keymap codes to mcu --- genmap.py | 6 +- hid/src/ps2/keymap.h.mako | 4 +- hid/src/usb/keymap.h.mako | 4 +- keymap.csv | 2 +- kvmd/keyboard/mappings.py | 216 +++++++++++++------------- kvmd/keyboard/mappings.py.mako | 8 +- kvmd/plugins/hid/_mcu/__init__.py | 2 +- testenv/linters/vulture-wl.py | 2 +- testenv/tests/keyboard/test_keymap.py | 2 +- 9 files changed, 123 insertions(+), 123 deletions(-) diff --git a/genmap.py b/genmap.py index da68726f..6c818e21 100755 --- a/genmap.py +++ b/genmap.py @@ -61,7 +61,7 @@ class _X11Key: @dataclasses.dataclass(frozen=True) class _KeyMapping: web_name: str - serial_code: int + mcu_code: int arduino_name: str otg_key: _OtgKey ps2_key: _Ps2Key @@ -115,7 +115,7 @@ def _read_keymap_csv(path: str) -> List[_KeyMapping]: if len(row) >= 6: keymap.append(_KeyMapping( web_name=row["web_name"], - serial_code=int(row["serial_code"]), + mcu_code=int(row["mcu_code"]), arduino_name=row["arduino_name"], otg_key=_parse_otg_key(row["otg_key"]), ps2_key=_parse_ps2_key(row["ps2_key"]), @@ -144,7 +144,7 @@ def main() -> None: # Fields list: # - Web - # - Serial code + # - MCU code # - Arduino name # - OTG code (^ for mod) # - PS/2 key diff --git a/hid/src/ps2/keymap.h.mako b/hid/src/ps2/keymap.h.mako index 70da2f36..cec7cb8a 100644 --- a/hid/src/ps2/keymap.h.mako +++ b/hid/src/ps2/keymap.h.mako @@ -39,8 +39,8 @@ INLINE void keymapPs2(uint8_t code, Ps2KeyType *ps2_type, uint8_t *ps2_code) { *ps2_code = 0; switch (code) { -% for km in sorted(keymap, key=operator.attrgetter("serial_code")): - case ${km.serial_code}: *ps2_type = PS2_KEY_TYPE_${km.ps2_key.type.upper()}; *ps2_code = ${km.ps2_key.code}; return; // ${km.arduino_name} +% for km in sorted(keymap, key=operator.attrgetter("mcu_code")): + case ${km.mcu_code}: *ps2_type = PS2_KEY_TYPE_${km.ps2_key.type.upper()}; *ps2_code = ${km.ps2_key.code}; return; // ${km.arduino_name} % endfor } } diff --git a/hid/src/usb/keymap.h.mako b/hid/src/usb/keymap.h.mako index 5df3a7f5..8cd94fd9 100644 --- a/hid/src/usb/keymap.h.mako +++ b/hid/src/usb/keymap.h.mako @@ -29,8 +29,8 @@ <%! import operator %> INLINE KeyboardKeycode keymapUsb(uint8_t code) { switch (code) { -% for km in sorted(keymap, key=operator.attrgetter("serial_code")): - case ${km.serial_code}: return ${km.arduino_name}; +% for km in sorted(keymap, key=operator.attrgetter("mcu_code")): + case ${km.mcu_code}: return ${km.arduino_name}; % endfor default: return KEY_ERROR_UNDEFINED; } diff --git a/keymap.csv b/keymap.csv index 138e5e3f..eba07c64 100644 --- a/keymap.csv +++ b/keymap.csv @@ -1,4 +1,4 @@ -web_name,serial_code,arduino_name,otg_key,ps2_key,at1_code,x11_names +web_name,mcu_code,arduino_name,otg_key,ps2_key,at1_code,x11_names KeyA,1,KEY_A,0x04,reg:0x1c,0x1e,"^XK_A,XK_a" KeyB,2,KEY_B,0x05,reg:0x32,0x30,"^XK_B,XK_b" KeyC,3,KEY_C,0x06,reg:0x21,0x2e,"^XK_C,XK_c" diff --git a/kvmd/keyboard/mappings.py b/kvmd/keyboard/mappings.py index 0185a0bc..a841e8b8 100644 --- a/kvmd/keyboard/mappings.py +++ b/kvmd/keyboard/mappings.py @@ -27,7 +27,7 @@ from typing import Dict # ===== @dataclasses.dataclass(frozen=True) -class SerialKey: +class McuKey: code: int @@ -39,117 +39,117 @@ class OtgKey: @dataclasses.dataclass(frozen=True) class Key: - serial: SerialKey + mcu: McuKey otg: OtgKey KEYMAP: Dict[str, Key] = { - "KeyA": Key(serial=SerialKey(code=1), otg=OtgKey(code=4, is_modifier=False)), - "KeyB": Key(serial=SerialKey(code=2), otg=OtgKey(code=5, is_modifier=False)), - "KeyC": Key(serial=SerialKey(code=3), otg=OtgKey(code=6, is_modifier=False)), - "KeyD": Key(serial=SerialKey(code=4), otg=OtgKey(code=7, is_modifier=False)), - "KeyE": Key(serial=SerialKey(code=5), otg=OtgKey(code=8, is_modifier=False)), - "KeyF": Key(serial=SerialKey(code=6), otg=OtgKey(code=9, is_modifier=False)), - "KeyG": Key(serial=SerialKey(code=7), otg=OtgKey(code=10, is_modifier=False)), - "KeyH": Key(serial=SerialKey(code=8), otg=OtgKey(code=11, is_modifier=False)), - "KeyI": Key(serial=SerialKey(code=9), otg=OtgKey(code=12, is_modifier=False)), - "KeyJ": Key(serial=SerialKey(code=10), otg=OtgKey(code=13, is_modifier=False)), - "KeyK": Key(serial=SerialKey(code=11), otg=OtgKey(code=14, is_modifier=False)), - "KeyL": Key(serial=SerialKey(code=12), otg=OtgKey(code=15, is_modifier=False)), - "KeyM": Key(serial=SerialKey(code=13), otg=OtgKey(code=16, is_modifier=False)), - "KeyN": Key(serial=SerialKey(code=14), otg=OtgKey(code=17, is_modifier=False)), - "KeyO": Key(serial=SerialKey(code=15), otg=OtgKey(code=18, is_modifier=False)), - "KeyP": Key(serial=SerialKey(code=16), otg=OtgKey(code=19, is_modifier=False)), - "KeyQ": Key(serial=SerialKey(code=17), otg=OtgKey(code=20, is_modifier=False)), - "KeyR": Key(serial=SerialKey(code=18), otg=OtgKey(code=21, is_modifier=False)), - "KeyS": Key(serial=SerialKey(code=19), otg=OtgKey(code=22, is_modifier=False)), - "KeyT": Key(serial=SerialKey(code=20), otg=OtgKey(code=23, is_modifier=False)), - "KeyU": Key(serial=SerialKey(code=21), otg=OtgKey(code=24, is_modifier=False)), - "KeyV": Key(serial=SerialKey(code=22), otg=OtgKey(code=25, is_modifier=False)), - "KeyW": Key(serial=SerialKey(code=23), otg=OtgKey(code=26, is_modifier=False)), - "KeyX": Key(serial=SerialKey(code=24), otg=OtgKey(code=27, is_modifier=False)), - "KeyY": Key(serial=SerialKey(code=25), otg=OtgKey(code=28, is_modifier=False)), - "KeyZ": Key(serial=SerialKey(code=26), otg=OtgKey(code=29, is_modifier=False)), - "Digit1": Key(serial=SerialKey(code=27), otg=OtgKey(code=30, is_modifier=False)), - "Digit2": Key(serial=SerialKey(code=28), otg=OtgKey(code=31, is_modifier=False)), - "Digit3": Key(serial=SerialKey(code=29), otg=OtgKey(code=32, is_modifier=False)), - "Digit4": Key(serial=SerialKey(code=30), otg=OtgKey(code=33, is_modifier=False)), - "Digit5": Key(serial=SerialKey(code=31), otg=OtgKey(code=34, is_modifier=False)), - "Digit6": Key(serial=SerialKey(code=32), otg=OtgKey(code=35, is_modifier=False)), - "Digit7": Key(serial=SerialKey(code=33), otg=OtgKey(code=36, is_modifier=False)), - "Digit8": Key(serial=SerialKey(code=34), otg=OtgKey(code=37, is_modifier=False)), - "Digit9": Key(serial=SerialKey(code=35), otg=OtgKey(code=38, is_modifier=False)), - "Digit0": Key(serial=SerialKey(code=36), otg=OtgKey(code=39, is_modifier=False)), - "Enter": Key(serial=SerialKey(code=37), otg=OtgKey(code=40, is_modifier=False)), - "Escape": Key(serial=SerialKey(code=38), otg=OtgKey(code=41, is_modifier=False)), - "Backspace": Key(serial=SerialKey(code=39), otg=OtgKey(code=42, is_modifier=False)), - "Tab": Key(serial=SerialKey(code=40), otg=OtgKey(code=43, is_modifier=False)), - "Space": Key(serial=SerialKey(code=41), otg=OtgKey(code=44, is_modifier=False)), - "Minus": Key(serial=SerialKey(code=42), otg=OtgKey(code=45, is_modifier=False)), - "Equal": Key(serial=SerialKey(code=43), otg=OtgKey(code=46, is_modifier=False)), - "BracketLeft": Key(serial=SerialKey(code=44), otg=OtgKey(code=47, is_modifier=False)), - "BracketRight": Key(serial=SerialKey(code=45), otg=OtgKey(code=48, is_modifier=False)), - "Backslash": Key(serial=SerialKey(code=46), otg=OtgKey(code=49, is_modifier=False)), - "Semicolon": Key(serial=SerialKey(code=47), otg=OtgKey(code=51, is_modifier=False)), - "Quote": Key(serial=SerialKey(code=48), otg=OtgKey(code=52, is_modifier=False)), - "Backquote": Key(serial=SerialKey(code=49), otg=OtgKey(code=53, is_modifier=False)), - "Comma": Key(serial=SerialKey(code=50), otg=OtgKey(code=54, is_modifier=False)), - "Period": Key(serial=SerialKey(code=51), otg=OtgKey(code=55, is_modifier=False)), - "Slash": Key(serial=SerialKey(code=52), otg=OtgKey(code=56, is_modifier=False)), - "CapsLock": Key(serial=SerialKey(code=53), otg=OtgKey(code=57, is_modifier=False)), - "F1": Key(serial=SerialKey(code=54), otg=OtgKey(code=58, is_modifier=False)), - "F2": Key(serial=SerialKey(code=55), otg=OtgKey(code=59, is_modifier=False)), - "F3": Key(serial=SerialKey(code=56), otg=OtgKey(code=60, is_modifier=False)), - "F4": Key(serial=SerialKey(code=57), otg=OtgKey(code=61, is_modifier=False)), - "F5": Key(serial=SerialKey(code=58), otg=OtgKey(code=62, is_modifier=False)), - "F6": Key(serial=SerialKey(code=59), otg=OtgKey(code=63, is_modifier=False)), - "F7": Key(serial=SerialKey(code=60), otg=OtgKey(code=64, is_modifier=False)), - "F8": Key(serial=SerialKey(code=61), otg=OtgKey(code=65, is_modifier=False)), - "F9": Key(serial=SerialKey(code=62), otg=OtgKey(code=66, is_modifier=False)), - "F10": Key(serial=SerialKey(code=63), otg=OtgKey(code=67, is_modifier=False)), - "F11": Key(serial=SerialKey(code=64), otg=OtgKey(code=68, is_modifier=False)), - "F12": Key(serial=SerialKey(code=65), otg=OtgKey(code=69, is_modifier=False)), - "PrintScreen": Key(serial=SerialKey(code=66), otg=OtgKey(code=70, is_modifier=False)), - "Insert": Key(serial=SerialKey(code=67), otg=OtgKey(code=73, is_modifier=False)), - "Home": Key(serial=SerialKey(code=68), otg=OtgKey(code=74, is_modifier=False)), - "PageUp": Key(serial=SerialKey(code=69), otg=OtgKey(code=75, is_modifier=False)), - "Delete": Key(serial=SerialKey(code=70), otg=OtgKey(code=76, is_modifier=False)), - "End": Key(serial=SerialKey(code=71), otg=OtgKey(code=77, is_modifier=False)), - "PageDown": Key(serial=SerialKey(code=72), otg=OtgKey(code=78, is_modifier=False)), - "ArrowRight": Key(serial=SerialKey(code=73), otg=OtgKey(code=79, is_modifier=False)), - "ArrowLeft": Key(serial=SerialKey(code=74), otg=OtgKey(code=80, is_modifier=False)), - "ArrowDown": Key(serial=SerialKey(code=75), otg=OtgKey(code=81, is_modifier=False)), - "ArrowUp": Key(serial=SerialKey(code=76), otg=OtgKey(code=82, is_modifier=False)), - "ControlLeft": Key(serial=SerialKey(code=77), otg=OtgKey(code=1, is_modifier=True)), - "ShiftLeft": Key(serial=SerialKey(code=78), otg=OtgKey(code=2, is_modifier=True)), - "AltLeft": Key(serial=SerialKey(code=79), otg=OtgKey(code=4, is_modifier=True)), - "MetaLeft": Key(serial=SerialKey(code=80), otg=OtgKey(code=8, is_modifier=True)), - "ControlRight": Key(serial=SerialKey(code=81), otg=OtgKey(code=16, is_modifier=True)), - "ShiftRight": Key(serial=SerialKey(code=82), otg=OtgKey(code=32, is_modifier=True)), - "AltRight": Key(serial=SerialKey(code=83), otg=OtgKey(code=64, is_modifier=True)), - "MetaRight": Key(serial=SerialKey(code=84), otg=OtgKey(code=128, is_modifier=True)), - "Pause": Key(serial=SerialKey(code=85), otg=OtgKey(code=72, is_modifier=False)), - "ScrollLock": Key(serial=SerialKey(code=86), otg=OtgKey(code=71, is_modifier=False)), - "NumLock": Key(serial=SerialKey(code=87), otg=OtgKey(code=83, is_modifier=False)), - "ContextMenu": Key(serial=SerialKey(code=88), otg=OtgKey(code=101, is_modifier=False)), - "NumpadDivide": Key(serial=SerialKey(code=89), otg=OtgKey(code=84, is_modifier=False)), - "NumpadMultiply": Key(serial=SerialKey(code=90), otg=OtgKey(code=85, is_modifier=False)), - "NumpadSubtract": Key(serial=SerialKey(code=91), otg=OtgKey(code=86, is_modifier=False)), - "NumpadAdd": Key(serial=SerialKey(code=92), otg=OtgKey(code=87, is_modifier=False)), - "NumpadEnter": Key(serial=SerialKey(code=93), otg=OtgKey(code=88, is_modifier=False)), - "Numpad1": Key(serial=SerialKey(code=94), otg=OtgKey(code=89, is_modifier=False)), - "Numpad2": Key(serial=SerialKey(code=95), otg=OtgKey(code=90, is_modifier=False)), - "Numpad3": Key(serial=SerialKey(code=96), otg=OtgKey(code=91, is_modifier=False)), - "Numpad4": Key(serial=SerialKey(code=97), otg=OtgKey(code=92, is_modifier=False)), - "Numpad5": Key(serial=SerialKey(code=98), otg=OtgKey(code=93, is_modifier=False)), - "Numpad6": Key(serial=SerialKey(code=99), otg=OtgKey(code=94, is_modifier=False)), - "Numpad7": Key(serial=SerialKey(code=100), otg=OtgKey(code=95, is_modifier=False)), - "Numpad8": Key(serial=SerialKey(code=101), otg=OtgKey(code=96, is_modifier=False)), - "Numpad9": Key(serial=SerialKey(code=102), otg=OtgKey(code=97, is_modifier=False)), - "Numpad0": Key(serial=SerialKey(code=103), otg=OtgKey(code=98, is_modifier=False)), - "NumpadDecimal": Key(serial=SerialKey(code=104), otg=OtgKey(code=99, is_modifier=False)), - "Power": Key(serial=SerialKey(code=105), otg=OtgKey(code=102, is_modifier=False)), - "IntlBackslash": Key(serial=SerialKey(code=106), otg=OtgKey(code=100, is_modifier=False)), + "KeyA": Key(mcu=McuKey(code=1), otg=OtgKey(code=4, is_modifier=False)), + "KeyB": Key(mcu=McuKey(code=2), otg=OtgKey(code=5, is_modifier=False)), + "KeyC": Key(mcu=McuKey(code=3), otg=OtgKey(code=6, is_modifier=False)), + "KeyD": Key(mcu=McuKey(code=4), otg=OtgKey(code=7, is_modifier=False)), + "KeyE": Key(mcu=McuKey(code=5), otg=OtgKey(code=8, is_modifier=False)), + "KeyF": Key(mcu=McuKey(code=6), otg=OtgKey(code=9, is_modifier=False)), + "KeyG": Key(mcu=McuKey(code=7), otg=OtgKey(code=10, is_modifier=False)), + "KeyH": Key(mcu=McuKey(code=8), otg=OtgKey(code=11, is_modifier=False)), + "KeyI": Key(mcu=McuKey(code=9), otg=OtgKey(code=12, is_modifier=False)), + "KeyJ": Key(mcu=McuKey(code=10), otg=OtgKey(code=13, is_modifier=False)), + "KeyK": Key(mcu=McuKey(code=11), otg=OtgKey(code=14, is_modifier=False)), + "KeyL": Key(mcu=McuKey(code=12), otg=OtgKey(code=15, is_modifier=False)), + "KeyM": Key(mcu=McuKey(code=13), otg=OtgKey(code=16, is_modifier=False)), + "KeyN": Key(mcu=McuKey(code=14), otg=OtgKey(code=17, is_modifier=False)), + "KeyO": Key(mcu=McuKey(code=15), otg=OtgKey(code=18, is_modifier=False)), + "KeyP": Key(mcu=McuKey(code=16), otg=OtgKey(code=19, is_modifier=False)), + "KeyQ": Key(mcu=McuKey(code=17), otg=OtgKey(code=20, is_modifier=False)), + "KeyR": Key(mcu=McuKey(code=18), otg=OtgKey(code=21, is_modifier=False)), + "KeyS": Key(mcu=McuKey(code=19), otg=OtgKey(code=22, is_modifier=False)), + "KeyT": Key(mcu=McuKey(code=20), otg=OtgKey(code=23, is_modifier=False)), + "KeyU": Key(mcu=McuKey(code=21), otg=OtgKey(code=24, is_modifier=False)), + "KeyV": Key(mcu=McuKey(code=22), otg=OtgKey(code=25, is_modifier=False)), + "KeyW": Key(mcu=McuKey(code=23), otg=OtgKey(code=26, is_modifier=False)), + "KeyX": Key(mcu=McuKey(code=24), otg=OtgKey(code=27, is_modifier=False)), + "KeyY": Key(mcu=McuKey(code=25), otg=OtgKey(code=28, is_modifier=False)), + "KeyZ": Key(mcu=McuKey(code=26), otg=OtgKey(code=29, is_modifier=False)), + "Digit1": Key(mcu=McuKey(code=27), otg=OtgKey(code=30, is_modifier=False)), + "Digit2": Key(mcu=McuKey(code=28), otg=OtgKey(code=31, is_modifier=False)), + "Digit3": Key(mcu=McuKey(code=29), otg=OtgKey(code=32, is_modifier=False)), + "Digit4": Key(mcu=McuKey(code=30), otg=OtgKey(code=33, is_modifier=False)), + "Digit5": Key(mcu=McuKey(code=31), otg=OtgKey(code=34, is_modifier=False)), + "Digit6": Key(mcu=McuKey(code=32), otg=OtgKey(code=35, is_modifier=False)), + "Digit7": Key(mcu=McuKey(code=33), otg=OtgKey(code=36, is_modifier=False)), + "Digit8": Key(mcu=McuKey(code=34), otg=OtgKey(code=37, is_modifier=False)), + "Digit9": Key(mcu=McuKey(code=35), otg=OtgKey(code=38, is_modifier=False)), + "Digit0": Key(mcu=McuKey(code=36), otg=OtgKey(code=39, is_modifier=False)), + "Enter": Key(mcu=McuKey(code=37), otg=OtgKey(code=40, is_modifier=False)), + "Escape": Key(mcu=McuKey(code=38), otg=OtgKey(code=41, is_modifier=False)), + "Backspace": Key(mcu=McuKey(code=39), otg=OtgKey(code=42, is_modifier=False)), + "Tab": Key(mcu=McuKey(code=40), otg=OtgKey(code=43, is_modifier=False)), + "Space": Key(mcu=McuKey(code=41), otg=OtgKey(code=44, is_modifier=False)), + "Minus": Key(mcu=McuKey(code=42), otg=OtgKey(code=45, is_modifier=False)), + "Equal": Key(mcu=McuKey(code=43), otg=OtgKey(code=46, is_modifier=False)), + "BracketLeft": Key(mcu=McuKey(code=44), otg=OtgKey(code=47, is_modifier=False)), + "BracketRight": Key(mcu=McuKey(code=45), otg=OtgKey(code=48, is_modifier=False)), + "Backslash": Key(mcu=McuKey(code=46), otg=OtgKey(code=49, is_modifier=False)), + "Semicolon": Key(mcu=McuKey(code=47), otg=OtgKey(code=51, is_modifier=False)), + "Quote": Key(mcu=McuKey(code=48), otg=OtgKey(code=52, is_modifier=False)), + "Backquote": Key(mcu=McuKey(code=49), otg=OtgKey(code=53, is_modifier=False)), + "Comma": Key(mcu=McuKey(code=50), otg=OtgKey(code=54, is_modifier=False)), + "Period": Key(mcu=McuKey(code=51), otg=OtgKey(code=55, is_modifier=False)), + "Slash": Key(mcu=McuKey(code=52), otg=OtgKey(code=56, is_modifier=False)), + "CapsLock": Key(mcu=McuKey(code=53), otg=OtgKey(code=57, is_modifier=False)), + "F1": Key(mcu=McuKey(code=54), otg=OtgKey(code=58, is_modifier=False)), + "F2": Key(mcu=McuKey(code=55), otg=OtgKey(code=59, is_modifier=False)), + "F3": Key(mcu=McuKey(code=56), otg=OtgKey(code=60, is_modifier=False)), + "F4": Key(mcu=McuKey(code=57), otg=OtgKey(code=61, is_modifier=False)), + "F5": Key(mcu=McuKey(code=58), otg=OtgKey(code=62, is_modifier=False)), + "F6": Key(mcu=McuKey(code=59), otg=OtgKey(code=63, is_modifier=False)), + "F7": Key(mcu=McuKey(code=60), otg=OtgKey(code=64, is_modifier=False)), + "F8": Key(mcu=McuKey(code=61), otg=OtgKey(code=65, is_modifier=False)), + "F9": Key(mcu=McuKey(code=62), otg=OtgKey(code=66, is_modifier=False)), + "F10": Key(mcu=McuKey(code=63), otg=OtgKey(code=67, is_modifier=False)), + "F11": Key(mcu=McuKey(code=64), otg=OtgKey(code=68, is_modifier=False)), + "F12": Key(mcu=McuKey(code=65), otg=OtgKey(code=69, is_modifier=False)), + "PrintScreen": Key(mcu=McuKey(code=66), otg=OtgKey(code=70, is_modifier=False)), + "Insert": Key(mcu=McuKey(code=67), otg=OtgKey(code=73, is_modifier=False)), + "Home": Key(mcu=McuKey(code=68), otg=OtgKey(code=74, is_modifier=False)), + "PageUp": Key(mcu=McuKey(code=69), otg=OtgKey(code=75, is_modifier=False)), + "Delete": Key(mcu=McuKey(code=70), otg=OtgKey(code=76, is_modifier=False)), + "End": Key(mcu=McuKey(code=71), otg=OtgKey(code=77, is_modifier=False)), + "PageDown": Key(mcu=McuKey(code=72), otg=OtgKey(code=78, is_modifier=False)), + "ArrowRight": Key(mcu=McuKey(code=73), otg=OtgKey(code=79, is_modifier=False)), + "ArrowLeft": Key(mcu=McuKey(code=74), otg=OtgKey(code=80, is_modifier=False)), + "ArrowDown": Key(mcu=McuKey(code=75), otg=OtgKey(code=81, is_modifier=False)), + "ArrowUp": Key(mcu=McuKey(code=76), otg=OtgKey(code=82, is_modifier=False)), + "ControlLeft": Key(mcu=McuKey(code=77), otg=OtgKey(code=1, is_modifier=True)), + "ShiftLeft": Key(mcu=McuKey(code=78), otg=OtgKey(code=2, is_modifier=True)), + "AltLeft": Key(mcu=McuKey(code=79), otg=OtgKey(code=4, is_modifier=True)), + "MetaLeft": Key(mcu=McuKey(code=80), otg=OtgKey(code=8, is_modifier=True)), + "ControlRight": Key(mcu=McuKey(code=81), otg=OtgKey(code=16, is_modifier=True)), + "ShiftRight": Key(mcu=McuKey(code=82), otg=OtgKey(code=32, is_modifier=True)), + "AltRight": Key(mcu=McuKey(code=83), otg=OtgKey(code=64, is_modifier=True)), + "MetaRight": Key(mcu=McuKey(code=84), otg=OtgKey(code=128, is_modifier=True)), + "Pause": Key(mcu=McuKey(code=85), otg=OtgKey(code=72, is_modifier=False)), + "ScrollLock": Key(mcu=McuKey(code=86), otg=OtgKey(code=71, is_modifier=False)), + "NumLock": Key(mcu=McuKey(code=87), otg=OtgKey(code=83, is_modifier=False)), + "ContextMenu": Key(mcu=McuKey(code=88), otg=OtgKey(code=101, is_modifier=False)), + "NumpadDivide": Key(mcu=McuKey(code=89), otg=OtgKey(code=84, is_modifier=False)), + "NumpadMultiply": Key(mcu=McuKey(code=90), otg=OtgKey(code=85, is_modifier=False)), + "NumpadSubtract": Key(mcu=McuKey(code=91), otg=OtgKey(code=86, is_modifier=False)), + "NumpadAdd": Key(mcu=McuKey(code=92), otg=OtgKey(code=87, is_modifier=False)), + "NumpadEnter": Key(mcu=McuKey(code=93), otg=OtgKey(code=88, is_modifier=False)), + "Numpad1": Key(mcu=McuKey(code=94), otg=OtgKey(code=89, is_modifier=False)), + "Numpad2": Key(mcu=McuKey(code=95), otg=OtgKey(code=90, is_modifier=False)), + "Numpad3": Key(mcu=McuKey(code=96), otg=OtgKey(code=91, is_modifier=False)), + "Numpad4": Key(mcu=McuKey(code=97), otg=OtgKey(code=92, is_modifier=False)), + "Numpad5": Key(mcu=McuKey(code=98), otg=OtgKey(code=93, is_modifier=False)), + "Numpad6": Key(mcu=McuKey(code=99), otg=OtgKey(code=94, is_modifier=False)), + "Numpad7": Key(mcu=McuKey(code=100), otg=OtgKey(code=95, is_modifier=False)), + "Numpad8": Key(mcu=McuKey(code=101), otg=OtgKey(code=96, is_modifier=False)), + "Numpad9": Key(mcu=McuKey(code=102), otg=OtgKey(code=97, is_modifier=False)), + "Numpad0": Key(mcu=McuKey(code=103), otg=OtgKey(code=98, is_modifier=False)), + "NumpadDecimal": Key(mcu=McuKey(code=104), otg=OtgKey(code=99, is_modifier=False)), + "Power": Key(mcu=McuKey(code=105), otg=OtgKey(code=102, is_modifier=False)), + "IntlBackslash": Key(mcu=McuKey(code=106), otg=OtgKey(code=100, is_modifier=False)), } diff --git a/kvmd/keyboard/mappings.py.mako b/kvmd/keyboard/mappings.py.mako index 399ed75e..5f0fc9ad 100644 --- a/kvmd/keyboard/mappings.py.mako +++ b/kvmd/keyboard/mappings.py.mako @@ -27,7 +27,7 @@ from typing import Dict # ===== @dataclasses.dataclass(frozen=True) -class SerialKey: +class McuKey: code: int @@ -39,13 +39,13 @@ class OtgKey: @dataclasses.dataclass(frozen=True) class Key: - serial: SerialKey + mcu: McuKey otg: OtgKey <%! import operator %> KEYMAP: Dict[str, Key] = { -% for km in sorted(keymap, key=operator.attrgetter("serial_code")): - "${km.web_name}": Key(serial=SerialKey(code=${km.serial_code}), otg=OtgKey(code=${km.otg_key.code}, is_modifier=${km.otg_key.is_modifier})), +% for km in sorted(keymap, key=operator.attrgetter("mcu_code")): + "${km.web_name}": Key(mcu=McuKey(code=${km.mcu_code}), otg=OtgKey(code=${km.otg_key.code}, is_modifier=${km.otg_key.is_modifier})), % endfor } diff --git a/kvmd/plugins/hid/_mcu/__init__.py b/kvmd/plugins/hid/_mcu/__init__.py index bc978499..71981e0d 100644 --- a/kvmd/plugins/hid/_mcu/__init__.py +++ b/kvmd/plugins/hid/_mcu/__init__.py @@ -93,7 +93,7 @@ class _KeyEvent(_BaseEvent): assert self.name in KEYMAP def make_command(self) -> bytes: - code = KEYMAP[self.name].serial.code + code = KEYMAP[self.name].mcu.code return struct.pack(">BBBxx", 0x11, code, int(self.state)) diff --git a/testenv/linters/vulture-wl.py b/testenv/linters/vulture-wl.py index 608f0aab..8d353b08 100644 --- a/testenv/linters/vulture-wl.py +++ b/testenv/linters/vulture-wl.py @@ -21,7 +21,7 @@ IpmiServer.handle_raw_request _AtxApiPart.switch_power _KeyMapping.web_name -_KeyMapping.serial_code +_KeyMapping.mcu_code _KeyMapping.arduino_name _KeyMapping.otg_key _KeyMapping.ps2_key diff --git a/testenv/tests/keyboard/test_keymap.py b/testenv/tests/keyboard/test_keymap.py index 9a397c6e..ad26d780 100644 --- a/testenv/tests/keyboard/test_keymap.py +++ b/testenv/tests/keyboard/test_keymap.py @@ -27,7 +27,7 @@ from kvmd.keyboard.mappings import KEYMAP # ===== def test_ok__keymap() -> None: - assert KEYMAP["KeyA"].serial.code == 1 + assert KEYMAP["KeyA"].mcu.code == 1 def test_fail__keymap() -> None: From aaef672ac2ce5dd2cee17ad571e6cab35f284ee4 Mon Sep 17 00:00:00 2001 From: Devaev Maxim Date: Thu, 29 Oct 2020 07:03:59 +0300 Subject: [PATCH 05/16] kvmd spi driver --- PKGBUILD | 1 + kvmd/plugins/hid/spi.py | 154 ++++++++++++++++++++++++++++++++++ testenv/linters/vulture-wl.py | 2 + testenv/requirements.txt | 1 + 4 files changed, 158 insertions(+) create mode 100644 kvmd/plugins/hid/spi.py diff --git a/PKGBUILD b/PKGBUILD index 95ae4549..1b27d224 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -40,6 +40,7 @@ depends=( python-aiofiles python-passlib python-pyserial + python-spidev python-setproctitle python-psutil python-systemd diff --git a/kvmd/plugins/hid/spi.py b/kvmd/plugins/hid/spi.py new file mode 100644 index 00000000..77d2f34c --- /dev/null +++ b/kvmd/plugins/hid/spi.py @@ -0,0 +1,154 @@ +# ========================================================================== # +# # +# KVMD - The main Pi-KVM daemon. # +# # +# Copyright (C) 2018 Maxim Devaev # +# # +# 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 . # +# # +# ========================================================================== # + + +import os +import contextlib +import time + +from typing import List +from typing import Dict +from typing import Generator +from typing import Any + +import spidev + +from ...yamlconf import Option + +from ...validators.basic import valid_int_f0 +from ...validators.basic import valid_int_f1 +from ...validators.basic import valid_float_f0 +from ...validators.basic import valid_float_f01 + +from ._mcu import BasePhyConnection +from ._mcu import BasePhy +from ._mcu import BaseMcuHid + + +# ===== +class SpiPhyError(Exception): + pass + + +class _SpiPhyConnection(BasePhyConnection): + def __init__( + self, + spi: spidev.SpiDev, + read_timeout: float, + read_delay: float, + ) -> None: + + self.__spi = spi + self.__read_timeout = read_timeout + self.__read_delay = read_delay + + def send(self, request: bytes, receive: int) -> bytes: + assert 0 < receive <= len(request) + + dummy = b"\x00" * len(request) + deadline_ts = time.time() + self.__read_timeout + while time.time() < deadline_ts: + garbage = bytes(self.__spi.xfer(dummy)) + if garbage == dummy: + break + else: + raise SpiPhyError("Timeout reached while reading a garbage") + + self.__spi.xfer(request) + + response: List[int] = [] + dummy = b"\x00" * receive + deadline_ts = time.time() + self.__read_timeout + found = False + while time.time() < deadline_ts: + if not found: + time.sleep(self.__read_delay) + for byte in self.__spi.xfer(dummy): + if not found: + if byte == 0: + continue + found = True + response.append(byte) + if len(response) >= receive: + break + if len(response) >= receive: + break + else: + raise SpiPhyError("Timeout reached while responce waiting") + + assert len(response) == receive + return bytes(response) + + +class _SpiPhy(BasePhy): + def __init__( + self, + bus: int, + chip: int, + max_freq: int, + read_timeout: float, + read_delay: float, + ) -> None: + + self.__bus = bus + self.__chip = chip + self.__max_freq = max_freq + self.__read_timeout = read_timeout + self.__read_delay = read_delay + + def has_device(self) -> bool: + return os.path.exists(f"/dev/spidev{self.__bus}.{self.__chip}") + + @contextlib.contextmanager + def connected(self) -> Generator[_SpiPhyConnection, None, None]: # type: ignore + with contextlib.closing(spidev.SpiDev(self.__bus, self.__chip)) as spi: + spi.mode = 0 + spi.max_speed_hz = self.__max_freq + yield _SpiPhyConnection(spi, self.__read_timeout, self.__read_delay) + + +# ===== +class Plugin(BaseMcuHid): + def __init__( + self, + bus: int, + chip: int, + max_freq: int, + read_timeout: float, + read_delay: float, + **kwargs: Any, + ) -> None: + + super().__init__( + phy=_SpiPhy(bus, chip, max_freq, read_timeout, read_delay), + **kwargs, + ) + + @classmethod + def get_plugin_options(cls) -> Dict: + return { + "bus": Option(0, type=valid_int_f0), + "chip": Option(0, type=valid_int_f0), + "max_freq": Option(1000000, type=valid_int_f1), + "read_timeout": Option(2.0, type=valid_float_f01), + "read_delay": Option(0.001, type=valid_float_f0), + **BaseMcuHid.get_plugin_options(), + } diff --git a/testenv/linters/vulture-wl.py b/testenv/linters/vulture-wl.py index 8d353b08..75240082 100644 --- a/testenv/linters/vulture-wl.py +++ b/testenv/linters/vulture-wl.py @@ -18,6 +18,8 @@ InotifyMask.UNMOUNT IpmiServer.handle_raw_request +SpiDev.max_speed_hz + _AtxApiPart.switch_power _KeyMapping.web_name diff --git a/testenv/requirements.txt b/testenv/requirements.txt index bde383b7..b157fea5 100644 --- a/testenv/requirements.txt +++ b/testenv/requirements.txt @@ -1 +1,2 @@ pyghmi +spidev From a5dbc1adeaa0d790206580d5f94c7503808ea1f0 Mon Sep 17 00:00:00 2001 From: Devaev Maxim Date: Thu, 29 Oct 2020 13:51:58 +0300 Subject: [PATCH 06/16] refactoring --- kvmd/plugins/hid/_mcu/__init__.py | 4 ++-- kvmd/plugins/hid/serial.py | 7 ++++--- kvmd/plugins/hid/spi.py | 33 ++++++++++++++++--------------- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/kvmd/plugins/hid/_mcu/__init__.py b/kvmd/plugins/hid/_mcu/__init__.py index 71981e0d..f762567b 100644 --- a/kvmd/plugins/hid/_mcu/__init__.py +++ b/kvmd/plugins/hid/_mcu/__init__.py @@ -153,7 +153,7 @@ class _MouseWheelEvent(_BaseEvent): # ===== class BasePhyConnection: - def send(self, request: bytes, receive: int) -> bytes: + def send(self, request: bytes) -> bytes: raise NotImplementedError @@ -396,7 +396,7 @@ class BaseMcuHid(BaseHid, multiprocessing.Process): # pylint: disable=too-many- def __send_request(self, conn: BasePhyConnection, request: bytes) -> bytes: if not self.__noop: - response = conn.send(request, 4) + response = conn.send(request) else: response = b"\x33\x20" # Magic + OK response += struct.pack(">H", self.__make_crc16(response)) diff --git a/kvmd/plugins/hid/serial.py b/kvmd/plugins/hid/serial.py index d5de1cc7..9bfafc69 100644 --- a/kvmd/plugins/hid/serial.py +++ b/kvmd/plugins/hid/serial.py @@ -45,11 +45,12 @@ class _SerialPhyConnection(BasePhyConnection): def __init__(self, tty: serial.Serial) -> None: self.__tty = tty - def send(self, request: bytes, receive: int) -> bytes: + def send(self, request: bytes) -> bytes: + assert len(request) == 8 if self.__tty.in_waiting: self.__tty.read_all() - assert self.__tty.write(request) == len(request) - return self.__tty.read(receive) + assert self.__tty.write(request) == 8 + return self.__tty.read(4) class _SerialPhy(BasePhy): diff --git a/kvmd/plugins/hid/spi.py b/kvmd/plugins/hid/spi.py index 77d2f34c..6020e04e 100644 --- a/kvmd/plugins/hid/spi.py +++ b/kvmd/plugins/hid/spi.py @@ -31,6 +31,8 @@ from typing import Any import spidev +from ...logging import get_logger + from ...yamlconf import Option from ...validators.basic import valid_int_f0 @@ -44,10 +46,6 @@ from ._mcu import BaseMcuHid # ===== -class SpiPhyError(Exception): - pass - - class _SpiPhyConnection(BasePhyConnection): def __init__( self, @@ -60,41 +58,44 @@ class _SpiPhyConnection(BasePhyConnection): self.__read_timeout = read_timeout self.__read_delay = read_delay - def send(self, request: bytes, receive: int) -> bytes: - assert 0 < receive <= len(request) + self.__empty8 = b"\x00" * 8 + self.__empty4 = b"\x00" * 4 + + def send(self, request: bytes) -> bytes: + assert len(request) == 8 - dummy = b"\x00" * len(request) deadline_ts = time.time() + self.__read_timeout while time.time() < deadline_ts: - garbage = bytes(self.__spi.xfer(dummy)) - if garbage == dummy: + garbage = bytes(self.__spi.xfer(self.__empty8)) + if garbage == self.__empty8: break else: - raise SpiPhyError("Timeout reached while reading a garbage") + get_logger(0).error("SPI timeout reached while reading the a garbage") + return b"" self.__spi.xfer(request) response: List[int] = [] - dummy = b"\x00" * receive deadline_ts = time.time() + self.__read_timeout found = False while time.time() < deadline_ts: if not found: time.sleep(self.__read_delay) - for byte in self.__spi.xfer(dummy): + for byte in self.__spi.xfer(self.__empty4): if not found: if byte == 0: continue found = True response.append(byte) - if len(response) >= receive: + if len(response) >= 4: break - if len(response) >= receive: + if len(response) >= 4: break else: - raise SpiPhyError("Timeout reached while responce waiting") + get_logger(0).error("SPI timeout reached while responce waiting") + return b"" - assert len(response) == receive + assert len(response) == 4 return bytes(response) From 578a290cc2b1c22718f56e5e4d3dc8885677b445 Mon Sep 17 00:00:00 2001 From: Devaev Maxim Date: Thu, 29 Oct 2020 15:39:05 +0300 Subject: [PATCH 07/16] added kvmd to spi group --- configs/os/sysusers.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/configs/os/sysusers.conf b/configs/os/sysusers.conf index 84c465bd..ac6dee3d 100644 --- a/configs/os/sysusers.conf +++ b/configs/os/sysusers.conf @@ -10,6 +10,7 @@ u kvmd-nginx - "Pi-KVM - HTTP entrypoint" - m kvmd gpio m kvmd uucp +m kvmd spi m kvmd systemd-journal m kvmd-ipmi kvmd From a3707d047fddd5f0c923422468be29ddb345f989 Mon Sep 17 00:00:00 2001 From: Devaev Maxim Date: Fri, 30 Oct 2020 15:07:36 +0300 Subject: [PATCH 08/16] refactoring --- hid/src/main.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/hid/src/main.cpp b/hid/src/main.cpp index 29982c8d..b4960cb5 100644 --- a/hid/src/main.cpp +++ b/hid/src/main.cpp @@ -104,13 +104,17 @@ INLINE uint8_t cmdMouseButtonEvent(const uint8_t *buffer) { // 2 bytes uint8_t main_state = buffer[0]; uint8_t extra_state = buffer[1]; +# define MOUSE_PAIR(_state, _button) \ + _state & PROTO_CMD_MOUSE_BUTTON_##_button##_SELECT, \ + _state & PROTO_CMD_MOUSE_BUTTON_##_button##_STATE hid_mouse.sendMouseButtons( - main_state & PROTO_CMD_MOUSE_BUTTON_LEFT_SELECT, main_state & PROTO_CMD_MOUSE_BUTTON_LEFT_STATE, - main_state & PROTO_CMD_MOUSE_BUTTON_RIGHT_SELECT, main_state & PROTO_CMD_MOUSE_BUTTON_RIGHT_STATE, - main_state & PROTO_CMD_MOUSE_BUTTON_MIDDLE_SELECT, main_state & PROTO_CMD_MOUSE_BUTTON_MIDDLE_STATE, - extra_state & PROTO_CMD_MOUSE_BUTTON_EXTRA_UP_SELECT, extra_state & PROTO_CMD_MOUSE_BUTTON_EXTRA_UP_STATE, - extra_state & PROTO_CMD_MOUSE_BUTTON_EXTRA_DOWN_SELECT, extra_state & PROTO_CMD_MOUSE_BUTTON_EXTRA_DOWN_STATE + MOUSE_PAIR(main_state, LEFT), + MOUSE_PAIR(main_state, RIGHT), + MOUSE_PAIR(main_state, MIDDLE), + MOUSE_PAIR(extra_state, EXTRA_UP), + MOUSE_PAIR(extra_state, EXTRA_DOWN) ); +# undef MOUSE_PAIR # endif return PROTO_RESP_OK; } From e07cdd60f393eb17184afe4ea1730f3c5745ea6f Mon Sep 17 00:00:00 2001 From: Devaev Maxim Date: Tue, 3 Nov 2020 10:15:54 +0300 Subject: [PATCH 09/16] spi firmware --- hid/patch.py | 1 - hid/platformio.ini | 11 +- hid/src/inline.h | 26 ---- hid/src/main.cpp | 212 +++++++++++++++++------------- hid/src/proto.h | 76 +++++++++++ hid/src/ps2/hid.h | 8 +- hid/src/ps2/keymap.h | 4 +- hid/src/ps2/keymap.h.mako | 4 +- hid/src/usb/hid.h | 18 ++- hid/src/usb/keymap.h | 4 +- hid/src/usb/keymap.h.mako | 4 +- kvmd/aiogp.py | 6 +- kvmd/plugins/hid/_mcu/__init__.py | 8 +- kvmd/plugins/hid/_mcu/gpio.py | 13 +- kvmd/plugins/hid/spi.py | 54 ++++---- 15 files changed, 254 insertions(+), 195 deletions(-) delete mode 100644 hid/src/inline.h create mode 100644 hid/src/proto.h diff --git a/hid/patch.py b/hid/patch.py index 7b88eeb1..c05ed262 100644 --- a/hid/patch.py +++ b/hid/patch.py @@ -27,6 +27,5 @@ def _patch_lib(lib_path: str, patch_path: str) -> None: # ===== _libs = _get_libs() -assert "TimerOne" in _libs # Just checking if "HID-Project" in _libs: _patch_lib(_libs["HID-Project"], "patches/absmouse.patch") diff --git a/hid/platformio.ini b/hid/platformio.ini index 1bf05d03..8b49e0e5 100644 --- a/hid/platformio.ini +++ b/hid/platformio.ini @@ -12,10 +12,6 @@ extra_scripts = platform_packages = tool-avrdude -[_parts_common] -lib_deps = - TimerOne@1.1 - [_parts_usb_kbd] lib_deps = HID-Project@2.6.1 @@ -38,7 +34,6 @@ build_flags = [_usb] lib_deps = - ${_parts_common.lib_deps} ${_parts_usb_kbd.lib_deps} # ${_parts_usb_mouse.lib_deps} build_flags = @@ -47,14 +42,12 @@ build_flags = [_ps2] lib_deps = - ${_parts_common.lib_deps} ${_parts_ps2_kbd.lib_deps} build_flags = ${_parts_ps2_kbd.build_flags} [_mixed] lib_deps = - ${_parts_common.lib_deps} ${_parts_ps2_kbd.lib_deps} ${_parts_usb_mouse.lib_deps} build_flags = @@ -107,9 +100,7 @@ extra_scripts = [_cmd_spi] build_flags = - -DCMD_SERIAL=Serial1 - -DCMD_SERIAL_SPEED=115200 -# -DCMD_SPI + -DCMD_SPI upload_protocol = custom upload_flags = -C diff --git a/hid/src/inline.h b/hid/src/inline.h deleted file mode 100644 index e4204750..00000000 --- a/hid/src/inline.h +++ /dev/null @@ -1,26 +0,0 @@ -/***************************************************************************** -# # -# KVMD - The main Pi-KVM daemon. # -# # -# Copyright (C) 2018 Maxim Devaev # -# # -# 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 . # -# # -*****************************************************************************/ - - -#pragma once - - -#define INLINE inline __attribute__((always_inline)) diff --git a/hid/src/main.cpp b/hid/src/main.cpp index b4960cb5..0da6884b 100644 --- a/hid/src/main.cpp +++ b/hid/src/main.cpp @@ -20,10 +20,17 @@ *****************************************************************************/ -#include -#include +#if !(defined(CMD_SERIAL) || defined(CMD_SPI)) +# error CMD phy is not defined +#endif -#include "inline.h" + +#include +#ifdef CMD_SPI +# include +#endif + +#include "proto.h" #if defined(HID_USB_KBD) || defined(HID_USB_MOUSE) # include "usb/hid.h" @@ -35,41 +42,9 @@ // #define CMD_SERIAL Serial1 // #define CMD_SERIAL_SPEED 115200 -#define CMD_RECV_TIMEOUT 100000 - -#define PROTO_MAGIC 0x33 -#define PROTO_CRC_POLINOM 0xA001 - -#define PROTO_RESP_OK 0x20 -#define PROTO_RESP_NONE 0x24 -#define PROTO_RESP_CRC_ERROR 0x40 -#define PROTO_RESP_INVALID_ERROR 0x45 -#define PROTO_RESP_TIMEOUT_ERROR 0x48 - -#define PROTO_RESP_PONG_PREFIX 0x80 -#define PROTO_RESP_PONG_CAPS 0b00000001 -#define PROTO_RESP_PONG_SCROLL 0b00000010 -#define PROTO_RESP_PONG_NUM 0b00000100 - -#define PROTO_CMD_PING 0x01 -#define PROTO_CMD_REPEAT 0x02 -#define PROTO_CMD_RESET_HID 0x10 -#define PROTO_CMD_KEY_EVENT 0x11 -#define PROTO_CMD_MOUSE_BUTTON_EVENT 0x13 // Legacy sequence -#define PROTO_CMD_MOUSE_MOVE_EVENT 0x12 -#define PROTO_CMD_MOUSE_WHEEL_EVENT 0x14 - -#define PROTO_CMD_MOUSE_BUTTON_LEFT_SELECT 0b10000000 -#define PROTO_CMD_MOUSE_BUTTON_LEFT_STATE 0b00001000 -#define PROTO_CMD_MOUSE_BUTTON_RIGHT_SELECT 0b01000000 -#define PROTO_CMD_MOUSE_BUTTON_RIGHT_STATE 0b00000100 -#define PROTO_CMD_MOUSE_BUTTON_MIDDLE_SELECT 0b00100000 -#define PROTO_CMD_MOUSE_BUTTON_MIDDLE_STATE 0b00000010 - -#define PROTO_CMD_MOUSE_BUTTON_EXTRA_UP_SELECT 0b10000000 -#define PROTO_CMD_MOUSE_BUTTON_EXTRA_UP_STATE 0b00001000 -#define PROTO_CMD_MOUSE_BUTTON_EXTRA_DOWN_SELECT 0b01000000 -#define PROTO_CMD_MOUSE_BUTTON_EXTRA_DOWN_STATE 0b00000100 +// -- OR -- +// #define CMD_SPI +#define CMD_TIMEOUT 100000 // ----------------------------------------------------------------------------- @@ -84,7 +59,7 @@ // ----------------------------------------------------------------------------- -INLINE uint8_t cmdResetHid(const uint8_t *buffer) { // 0 bytes +uint8_t cmdResetHid(const uint8_t *buffer) { // 0 bytes # ifdef HID_USB_KBD hid_kbd.reset(); # endif @@ -94,12 +69,12 @@ INLINE uint8_t cmdResetHid(const uint8_t *buffer) { // 0 bytes return PROTO_RESP_OK; } -INLINE uint8_t cmdKeyEvent(const uint8_t *buffer) { // 2 bytes +uint8_t cmdKeyEvent(const uint8_t *buffer) { // 2 bytes hid_kbd.sendKey(buffer[0], buffer[1]); return PROTO_RESP_OK; } -INLINE uint8_t cmdMouseButtonEvent(const uint8_t *buffer) { // 2 bytes +uint8_t cmdMouseButtonEvent(const uint8_t *buffer) { // 2 bytes # ifdef HID_USB_MOUSE uint8_t main_state = buffer[0]; uint8_t extra_state = buffer[1]; @@ -119,7 +94,7 @@ INLINE uint8_t cmdMouseButtonEvent(const uint8_t *buffer) { // 2 bytes return PROTO_RESP_OK; } -INLINE uint8_t cmdMouseMoveEvent(const uint8_t *buffer) { // 4 bytes +uint8_t cmdMouseMoveEvent(const uint8_t *buffer) { // 4 bytes # ifdef HID_USB_MOUSE int x = (int)buffer[0] << 8; x |= (int)buffer[1]; @@ -134,14 +109,14 @@ INLINE uint8_t cmdMouseMoveEvent(const uint8_t *buffer) { // 4 bytes return PROTO_RESP_OK; } -INLINE uint8_t cmdMouseWheelEvent(const uint8_t *buffer) { // 2 bytes +uint8_t cmdMouseWheelEvent(const uint8_t *buffer) { // 2 bytes # ifdef HID_USB_MOUSE hid_mouse.sendMouseWheel(buffer[1]); // Y only, X is not supported # endif return PROTO_RESP_OK; } -INLINE uint8_t cmdPongLeds(const uint8_t *buffer) { // 0 bytes +uint8_t cmdPongLeds(const uint8_t *buffer) { // 0 bytes return ((uint8_t) PROTO_RESP_PONG_PREFIX) | hid_kbd.getLedsAs( PROTO_RESP_PONG_CAPS, PROTO_RESP_PONG_SCROLL, @@ -149,40 +124,86 @@ INLINE uint8_t cmdPongLeds(const uint8_t *buffer) { // 0 bytes ); } +uint8_t handleCmdBuffer(const uint8_t *buffer) { // 8 bytes + uint16_t crc = (uint16_t)buffer[6] << 8; + crc |= (uint16_t)buffer[7]; -// ----------------------------------------------------------------------------- -INLINE uint16_t makeCrc16(const uint8_t *buffer, unsigned length) { - uint16_t crc = 0xFFFF; - - for (unsigned byte_count = 0; byte_count < length; ++byte_count) { - crc = crc ^ buffer[byte_count]; - for (unsigned bit_count = 0; bit_count < 8; ++bit_count) { - if ((crc & 0x0001) == 0) { - crc = crc >> 1; - } else { - crc = crc >> 1; - crc = crc ^ PROTO_CRC_POLINOM; - } + if (protoCrc16(buffer, 6) == crc) { +# define HANDLE(_handler) { return _handler(buffer + 2); } + switch (buffer[1]) { + case PROTO_CMD_RESET_HID: HANDLE(cmdResetHid); + case PROTO_CMD_KEY_EVENT: HANDLE(cmdKeyEvent); + case PROTO_CMD_MOUSE_BUTTON_EVENT: HANDLE(cmdMouseButtonEvent); + case PROTO_CMD_MOUSE_MOVE_EVENT: HANDLE(cmdMouseMoveEvent); + case PROTO_CMD_MOUSE_WHEEL_EVENT: HANDLE(cmdMouseWheelEvent); + case PROTO_CMD_PING: HANDLE(cmdPongLeds); + case PROTO_CMD_REPEAT: return 0; + default: return PROTO_RESP_INVALID_ERROR; } +# undef HANDLE } - return crc; + return PROTO_RESP_CRC_ERROR; } // ----------------------------------------------------------------------------- -volatile bool cmd_recv_timed_out = false; +#ifdef CMD_SPI +volatile uint8_t spi_in[8] = {0}; +volatile uint8_t spi_in_index = 0; +volatile uint8_t spi_in_read = 0; // Вычитанное spiRead() -INLINE void recvTimerStop(bool flag) { - Timer1.stop(); - cmd_recv_timed_out = flag; +volatile uint8_t spi_out[4] = {0}; +volatile uint8_t spi_out_index = 0; + +uint8_t spiAvailable() { + return spi_in_index - spi_in_read; } -INLINE void resetCmdRecvTimeout() { - recvTimerStop(false); - Timer1.initialize(CMD_RECV_TIMEOUT); +uint8_t spiRead() { + uint8_t value = 0; + if (spi_in_read < 8) { + value = spi_in[spi_in_read]; + ++spi_in_read; + } + return value; } -INLINE void sendCmdResponse(uint8_t code=0) { +void spiWrite(const uint8_t *buffer) { + if (spi_out[0] == 0) { + spi_out[3] = buffer[3]; + spi_out[2] = buffer[2]; + spi_out[1] = buffer[1]; + spi_out[0] = buffer[0]; // Меджик разрешает начать ответ + } +} + +void spiReadReset() { + spi_in_index = 0; + spi_in_read = 0; +} + +ISR(SPI_STC_vect) { + if (spi_in_index < 8) { + spi_in[spi_in_index] = SPDR; + ++spi_in_index; + SPDR = 0; + } else if (spi_out[0] && spi_out_index < 4) { + SPDR = spi_out[spi_out_index]; + ++spi_out_index; + if (spi_out_index == 4) { + spiReadReset(); + spi_out[0] = 0; + spi_out_index = 0; + } + } else { + SPDR = 0; + } +} +#endif + + +// ----------------------------------------------------------------------------- +void sendCmdResponse(uint8_t code) { static uint8_t prev_code = PROTO_RESP_NONE; if (code == 0) { code = prev_code; // Repeat the last code @@ -193,16 +214,23 @@ INLINE void sendCmdResponse(uint8_t code=0) { uint8_t buffer[4]; buffer[0] = PROTO_MAGIC; buffer[1] = code; - uint16_t crc = makeCrc16(buffer, 2); + uint16_t crc = protoCrc16(buffer, 2); buffer[2] = (uint8_t)(crc >> 8); buffer[3] = (uint8_t)(crc & 0xFF); - recvTimerStop(false); +# ifdef CMD_SERIAL CMD_SERIAL.write(buffer, 4); +# elif defined(CMD_SPI) + spiWrite(buffer); +# endif } -void intRecvTimedOut() { - recvTimerStop(true); +bool isCmdTimedOut(unsigned long last) { + unsigned long now = micros(); + return ( + (now >= last && now - last > CMD_TIMEOUT) + || (now < last && ((unsigned long)-1) - last + now > CMD_TIMEOUT) + ); } void setup() { @@ -211,48 +239,44 @@ void setup() { hid_mouse.begin(); # endif - Timer1.attachInterrupt(intRecvTimedOut); +# ifdef CMD_SERIAL CMD_SERIAL.begin(CMD_SERIAL_SPEED); +# elif defined(CMD_SPI) + pinMode(MISO, OUTPUT); + SPCR = (1 << SPE) | (1 << SPIE); // Slave, SPI En, IRQ En +# endif } void loop() { + unsigned long last = micros(); uint8_t buffer[8]; - unsigned index = 0; + uint8_t index = 0; while (true) { # ifdef HID_PS2_KBD hid_kbd.periodic(); # endif +# ifdef CMD_SERIAL if (CMD_SERIAL.available() > 0) { buffer[index] = (uint8_t)CMD_SERIAL.read(); +# elif defined(CMD_SPI) + if (spiAvailable() > 0) { + buffer[index] = spiRead(); +# endif if (index == 7) { - uint16_t crc = (uint16_t)buffer[6] << 8; - crc |= (uint16_t)buffer[7]; - - if (makeCrc16(buffer, 6) == crc) { -# define HANDLE(_handler) { sendCmdResponse(_handler(buffer + 2)); break; } - switch (buffer[1]) { - case PROTO_CMD_RESET_HID: HANDLE(cmdResetHid); - case PROTO_CMD_KEY_EVENT: HANDLE(cmdKeyEvent); - case PROTO_CMD_MOUSE_BUTTON_EVENT: HANDLE(cmdMouseButtonEvent); - case PROTO_CMD_MOUSE_MOVE_EVENT: HANDLE(cmdMouseMoveEvent); - case PROTO_CMD_MOUSE_WHEEL_EVENT: HANDLE(cmdMouseWheelEvent); - case PROTO_CMD_PING: HANDLE(cmdPongLeds); - case PROTO_CMD_REPEAT: sendCmdResponse(); break; - default: sendCmdResponse(PROTO_RESP_INVALID_ERROR); break; - } -# undef HANDLE - } else { - sendCmdResponse(PROTO_RESP_CRC_ERROR); - } + sendCmdResponse(handleCmdBuffer(buffer)); index = 0; } else { - resetCmdRecvTimeout(); - index += 1; + last = micros(); + ++index; } - } else if (index > 0 && cmd_recv_timed_out) { + } else if (index > 0 && isCmdTimedOut(last)) { +# ifdef CMD_SERIAL sendCmdResponse(PROTO_RESP_TIMEOUT_ERROR); +# elif defined(CMD_SPI) + spiReadReset(); +# endif index = 0; } } diff --git a/hid/src/proto.h b/hid/src/proto.h new file mode 100644 index 00000000..36502d4f --- /dev/null +++ b/hid/src/proto.h @@ -0,0 +1,76 @@ +/***************************************************************************** +# # +# KVMD - The main Pi-KVM daemon. # +# # +# Copyright (C) 2018 Maxim Devaev # +# # +# 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 . # +# # +*****************************************************************************/ + + +#pragma once + + +#define PROTO_MAGIC 0x33 +#define PROTO_CRC_POLINOM 0xA001 + +#define PROTO_RESP_OK 0x20 +#define PROTO_RESP_NONE 0x24 +#define PROTO_RESP_CRC_ERROR 0x40 +#define PROTO_RESP_INVALID_ERROR 0x45 +#define PROTO_RESP_TIMEOUT_ERROR 0x48 + +#define PROTO_RESP_PONG_PREFIX 0x80 +#define PROTO_RESP_PONG_CAPS 0b00000001 +#define PROTO_RESP_PONG_SCROLL 0b00000010 +#define PROTO_RESP_PONG_NUM 0b00000100 + +#define PROTO_CMD_PING 0x01 +#define PROTO_CMD_REPEAT 0x02 +#define PROTO_CMD_RESET_HID 0x10 +#define PROTO_CMD_KEY_EVENT 0x11 +#define PROTO_CMD_MOUSE_BUTTON_EVENT 0x13 // Legacy sequence +#define PROTO_CMD_MOUSE_MOVE_EVENT 0x12 +#define PROTO_CMD_MOUSE_WHEEL_EVENT 0x14 + +#define PROTO_CMD_MOUSE_BUTTON_LEFT_SELECT 0b10000000 +#define PROTO_CMD_MOUSE_BUTTON_LEFT_STATE 0b00001000 +#define PROTO_CMD_MOUSE_BUTTON_RIGHT_SELECT 0b01000000 +#define PROTO_CMD_MOUSE_BUTTON_RIGHT_STATE 0b00000100 +#define PROTO_CMD_MOUSE_BUTTON_MIDDLE_SELECT 0b00100000 +#define PROTO_CMD_MOUSE_BUTTON_MIDDLE_STATE 0b00000010 + +#define PROTO_CMD_MOUSE_BUTTON_EXTRA_UP_SELECT 0b10000000 +#define PROTO_CMD_MOUSE_BUTTON_EXTRA_UP_STATE 0b00001000 +#define PROTO_CMD_MOUSE_BUTTON_EXTRA_DOWN_SELECT 0b01000000 +#define PROTO_CMD_MOUSE_BUTTON_EXTRA_DOWN_STATE 0b00000100 + + +uint16_t protoCrc16(const uint8_t *buffer, unsigned length) { + uint16_t crc = 0xFFFF; + + for (unsigned byte_count = 0; byte_count < length; ++byte_count) { + crc = crc ^ buffer[byte_count]; + for (unsigned bit_count = 0; bit_count < 8; ++bit_count) { + if ((crc & 0x0001) == 0) { + crc = crc >> 1; + } else { + crc = crc >> 1; + crc = crc ^ PROTO_CRC_POLINOM; + } + } + } + return crc; +} diff --git a/hid/src/ps2/hid.h b/hid/src/ps2/hid.h index d12a16e4..eca2bf4b 100644 --- a/hid/src/ps2/hid.h +++ b/hid/src/ps2/hid.h @@ -25,8 +25,6 @@ #include #include -#include "../inline.h" - #include "keymap.h" // #define PS2_KBD_CLOCK_PIN 7 @@ -43,11 +41,11 @@ class Ps2HidKeyboard { _dev.keyboard_init(); } - INLINE void periodic() { + void periodic() { _dev.keyboard_handle(&_leds); } - INLINE void sendKey(uint8_t code, bool state) { + void sendKey(uint8_t code, bool state) { Ps2KeyType ps2_type; uint8_t ps2_code; @@ -76,7 +74,7 @@ class Ps2HidKeyboard { } } - INLINE uint8_t getLedsAs(uint8_t caps, uint8_t scroll, uint8_t num) { + uint8_t getLedsAs(uint8_t caps, uint8_t scroll, uint8_t num) { uint8_t result = 0; periodic(); diff --git a/hid/src/ps2/keymap.h b/hid/src/ps2/keymap.h index 2e484cac..933f6ee5 100644 --- a/hid/src/ps2/keymap.h +++ b/hid/src/ps2/keymap.h @@ -22,8 +22,6 @@ #pragma once -#include "../inline.h" - enum Ps2KeyType : uint8_t { PS2_KEY_TYPE_UNKNOWN = 0, @@ -34,7 +32,7 @@ enum Ps2KeyType : uint8_t { }; -INLINE void keymapPs2(uint8_t code, Ps2KeyType *ps2_type, uint8_t *ps2_code) { +void keymapPs2(uint8_t code, Ps2KeyType *ps2_type, uint8_t *ps2_code) { *ps2_type = PS2_KEY_TYPE_UNKNOWN; *ps2_code = 0; diff --git a/hid/src/ps2/keymap.h.mako b/hid/src/ps2/keymap.h.mako index cec7cb8a..bedb5fcc 100644 --- a/hid/src/ps2/keymap.h.mako +++ b/hid/src/ps2/keymap.h.mako @@ -22,8 +22,6 @@ #pragma once -#include "../inline.h" - enum Ps2KeyType : uint8_t { PS2_KEY_TYPE_UNKNOWN = 0, @@ -34,7 +32,7 @@ enum Ps2KeyType : uint8_t { }; <%! import operator %> -INLINE void keymapPs2(uint8_t code, Ps2KeyType *ps2_type, uint8_t *ps2_code) { +void keymapPs2(uint8_t code, Ps2KeyType *ps2_type, uint8_t *ps2_code) { *ps2_type = PS2_KEY_TYPE_UNKNOWN; *ps2_code = 0; diff --git a/hid/src/usb/hid.h b/hid/src/usb/hid.h index b4523a6b..6b729323 100644 --- a/hid/src/usb/hid.h +++ b/hid/src/usb/hid.h @@ -24,8 +24,6 @@ #include -#include "../inline.h" - #include "keymap.h" @@ -38,11 +36,11 @@ class UsbHidKeyboard { BootKeyboard.begin(); } - INLINE void reset() { + void reset() { BootKeyboard.releaseAll(); } - INLINE void sendKey(uint8_t code, bool state) { + void sendKey(uint8_t code, bool state) { KeyboardKeycode usb_code = keymapUsb(code); if (usb_code != KEY_ERROR_UNDEFINED) { if (state) BootKeyboard.press(usb_code); @@ -50,7 +48,7 @@ class UsbHidKeyboard { } } - INLINE uint8_t getLedsAs(uint8_t caps, uint8_t scroll, uint8_t num) { + uint8_t getLedsAs(uint8_t caps, uint8_t scroll, uint8_t num) { uint8_t leds = BootKeyboard.getLeds(); uint8_t result = 0; @@ -69,11 +67,11 @@ class UsbHidMouse { SingleAbsoluteMouse.begin(); } - INLINE void reset() { + void reset() { SingleAbsoluteMouse.releaseAll(); } - INLINE void sendMouseButtons( + void sendMouseButtons( bool left_select, bool left_state, bool right_select, bool right_state, bool middle_select, bool middle_state, @@ -87,17 +85,17 @@ class UsbHidMouse { if (down_select) _sendMouseButton(MOUSE_NEXT, down_state); } - INLINE void sendMouseMove(int x, int y) { + void sendMouseMove(int x, int y) { SingleAbsoluteMouse.moveTo(x, y); } - INLINE void sendMouseWheel(int delta_y) { + void sendMouseWheel(int delta_y) { // delta_x is not supported by hid-project now SingleAbsoluteMouse.move(0, 0, delta_y); } private: - INLINE void _sendMouseButton(uint8_t button, bool state) { + void _sendMouseButton(uint8_t button, bool state) { if (state) SingleAbsoluteMouse.press(button); else SingleAbsoluteMouse.release(button); } diff --git a/hid/src/usb/keymap.h b/hid/src/usb/keymap.h index 35df2be4..1564b1f0 100644 --- a/hid/src/usb/keymap.h +++ b/hid/src/usb/keymap.h @@ -24,10 +24,8 @@ #include -#include "../inline.h" - -INLINE KeyboardKeycode keymapUsb(uint8_t code) { +KeyboardKeycode keymapUsb(uint8_t code) { switch (code) { case 1: return KEY_A; case 2: return KEY_B; diff --git a/hid/src/usb/keymap.h.mako b/hid/src/usb/keymap.h.mako index 8cd94fd9..a93d91a4 100644 --- a/hid/src/usb/keymap.h.mako +++ b/hid/src/usb/keymap.h.mako @@ -24,10 +24,8 @@ #include -#include "../inline.h" - <%! import operator %> -INLINE KeyboardKeycode keymapUsb(uint8_t code) { +KeyboardKeycode keymapUsb(uint8_t code) { switch (code) { % for km in sorted(keymap, key=operator.attrgetter("mcu_code")): case ${km.mcu_code}: return ${km.arduino_name}; diff --git a/kvmd/aiogp.py b/kvmd/aiogp.py index ba406e8d..46252914 100644 --- a/kvmd/aiogp.py +++ b/kvmd/aiogp.py @@ -34,12 +34,12 @@ from . import aiotools # ===== -async def pulse(line: gpiod.Line, delay: float, final: float) -> None: +async def pulse(line: gpiod.Line, delay: float, final: float, inverted: bool=False) -> None: try: - line.set_value(1) + line.set_value(int(not inverted)) await asyncio.sleep(delay) finally: - line.set_value(0) + line.set_value(int(inverted)) await asyncio.sleep(final) diff --git a/kvmd/plugins/hid/_mcu/__init__.py b/kvmd/plugins/hid/_mcu/__init__.py index f762567b..3f925cd8 100644 --- a/kvmd/plugins/hid/_mcu/__init__.py +++ b/kvmd/plugins/hid/_mcu/__init__.py @@ -172,6 +172,7 @@ class BaseMcuHid(BaseHid, multiprocessing.Process): # pylint: disable=too-many- phy: BasePhy, reset_pin: int, + reset_inverted: bool, reset_delay: float, read_retries: int, @@ -190,7 +191,7 @@ class BaseMcuHid(BaseHid, multiprocessing.Process): # pylint: disable=too-many- self.__noop = noop self.__phy = phy - self.__gpio = Gpio(reset_pin, reset_delay) + self.__gpio = Gpio(reset_pin, reset_inverted, reset_delay) self.__events_queue: "multiprocessing.Queue[_BaseEvent]" = multiprocessing.Queue() @@ -207,8 +208,9 @@ class BaseMcuHid(BaseHid, multiprocessing.Process): # pylint: disable=too-many- @classmethod def get_plugin_options(cls) -> Dict: return { - "reset_pin": Option(-1, type=valid_gpio_pin_optional), - "reset_delay": Option(0.1, type=valid_float_f01), + "reset_pin": Option(-1, type=valid_gpio_pin_optional), + "reset_inverted": Option(False, type=valid_bool), + "reset_delay": Option(0.1, type=valid_float_f01), "read_retries": Option(10, type=valid_int_f1), "common_retries": Option(100, type=valid_int_f1), diff --git a/kvmd/plugins/hid/_mcu/gpio.py b/kvmd/plugins/hid/_mcu/gpio.py index 830819f0..87f4b547 100644 --- a/kvmd/plugins/hid/_mcu/gpio.py +++ b/kvmd/plugins/hid/_mcu/gpio.py @@ -33,8 +33,15 @@ from .... import aiogp # ===== class Gpio: - def __init__(self, reset_pin: int, reset_delay: float) -> None: + def __init__( + self, + reset_pin: int, + reset_inverted: bool, + reset_delay: float, + ) -> None: + self.__reset_pin = reset_pin + self.__reset_inverted = reset_inverted self.__reset_delay = reset_delay self.__chip: Optional[gpiod.Chip] = None @@ -47,7 +54,7 @@ class Gpio: assert self.__reset_line is None self.__chip = gpiod.Chip(env.GPIO_DEVICE_PATH) self.__reset_line = self.__chip.get_line(self.__reset_pin) - self.__reset_line.request("kvmd::hid-mcu::reset", gpiod.LINE_REQ_DIR_OUT, default_vals=[0]) + self.__reset_line.request("kvmd::hid-mcu::reset", gpiod.LINE_REQ_DIR_OUT, default_vals=[int(self.__reset_inverted)]) def close(self) -> None: if self.__chip: @@ -63,7 +70,7 @@ class Gpio: if not self.__reset_wip: self.__reset_wip = True try: - await aiogp.pulse(self.__reset_line, self.__reset_delay, 1) + await aiogp.pulse(self.__reset_line, self.__reset_delay, 1, self.__reset_inverted) finally: self.__reset_wip = False get_logger(0).info("Reset HID performed") diff --git a/kvmd/plugins/hid/spi.py b/kvmd/plugins/hid/spi.py index 6020e04e..d71a4094 100644 --- a/kvmd/plugins/hid/spi.py +++ b/kvmd/plugins/hid/spi.py @@ -27,6 +27,7 @@ import time from typing import List from typing import Dict from typing import Generator +from typing import Callable from typing import Any import spidev @@ -49,31 +50,18 @@ from ._mcu import BaseMcuHid class _SpiPhyConnection(BasePhyConnection): def __init__( self, - spi: spidev.SpiDev, + xfer: Callable[[bytes], bytes], read_timeout: float, read_delay: float, ) -> None: - self.__spi = spi + self.__xfer = xfer self.__read_timeout = read_timeout self.__read_delay = read_delay - self.__empty8 = b"\x00" * 8 - self.__empty4 = b"\x00" * 4 - def send(self, request: bytes) -> bytes: assert len(request) == 8 - - deadline_ts = time.time() + self.__read_timeout - while time.time() < deadline_ts: - garbage = bytes(self.__spi.xfer(self.__empty8)) - if garbage == self.__empty8: - break - else: - get_logger(0).error("SPI timeout reached while reading the a garbage") - return b"" - - self.__spi.xfer(request) + self.__xfer(request) response: List[int] = [] deadline_ts = time.time() + self.__read_timeout @@ -81,21 +69,19 @@ class _SpiPhyConnection(BasePhyConnection): while time.time() < deadline_ts: if not found: time.sleep(self.__read_delay) - for byte in self.__spi.xfer(self.__empty4): + for byte in self.__xfer(b"\x00" * (4 - len(response))): if not found: if byte == 0: continue found = True response.append(byte) - if len(response) >= 4: + if len(response) == 4: break - if len(response) >= 4: + if len(response) == 4: break else: get_logger(0).error("SPI timeout reached while responce waiting") return b"" - - assert len(response) == 4 return bytes(response) @@ -105,6 +91,7 @@ class _SpiPhy(BasePhy): bus: int, chip: int, max_freq: int, + block_usec: int, read_timeout: float, read_delay: float, ) -> None: @@ -112,6 +99,7 @@ class _SpiPhy(BasePhy): self.__bus = bus self.__chip = chip self.__max_freq = max_freq + self.__block_usec = block_usec self.__read_timeout = read_timeout self.__read_delay = read_delay @@ -123,7 +111,15 @@ class _SpiPhy(BasePhy): with contextlib.closing(spidev.SpiDev(self.__bus, self.__chip)) as spi: spi.mode = 0 spi.max_speed_hz = self.__max_freq - yield _SpiPhyConnection(spi, self.__read_timeout, self.__read_delay) + + def xfer(data: bytes) -> bytes: + return spi.xfer(data, self.__max_freq, self.__block_usec) + + yield _SpiPhyConnection( + xfer=xfer, + read_timeout=self.__read_timeout, + read_delay=self.__read_delay, + ) # ===== @@ -133,23 +129,25 @@ class Plugin(BaseMcuHid): bus: int, chip: int, max_freq: int, + block_usec: int, read_timeout: float, read_delay: float, **kwargs: Any, ) -> None: super().__init__( - phy=_SpiPhy(bus, chip, max_freq, read_timeout, read_delay), + phy=_SpiPhy(bus, chip, max_freq, block_usec, read_timeout, read_delay), **kwargs, ) @classmethod def get_plugin_options(cls) -> Dict: return { - "bus": Option(0, type=valid_int_f0), - "chip": Option(0, type=valid_int_f0), - "max_freq": Option(1000000, type=valid_int_f1), - "read_timeout": Option(2.0, type=valid_float_f01), - "read_delay": Option(0.001, type=valid_float_f0), + "bus": Option(0, type=valid_int_f0), + "chip": Option(0, type=valid_int_f0), + "max_freq": Option(400000, type=valid_int_f1), + "block_usec": Option(1, type=valid_int_f0), + "read_timeout": Option(2.0, type=valid_float_f01), + "read_delay": Option(0.001, type=valid_float_f0), **BaseMcuHid.get_plugin_options(), } From a8a075c203e2a458ff51ce0d4152ab09a4710d65 Mon Sep 17 00:00:00 2001 From: Devaev Maxim Date: Tue, 10 Nov 2020 13:38:55 +0300 Subject: [PATCH 10/16] another try --- hid/platformio.ini | 1 + hid/src/main.cpp | 115 ++++++++++++++++++---------------- kvmd/plugins/hid/spi.py | 61 +++++++++--------- testenv/linters/vulture-wl.py | 1 + 4 files changed, 93 insertions(+), 85 deletions(-) diff --git a/hid/platformio.ini b/hid/platformio.ini index 8b49e0e5..3414d1d7 100644 --- a/hid/platformio.ini +++ b/hid/platformio.ini @@ -60,6 +60,7 @@ build_flags = build_flags = -DCMD_SERIAL=Serial1 -DCMD_SERIAL_SPEED=115200 + -DCMD_SERIAL_TIMEOUT=100000 upload_port = /dev/ttyACM0 [env:usb] diff --git a/hid/src/main.cpp b/hid/src/main.cpp index 0da6884b..3d0eb618 100644 --- a/hid/src/main.cpp +++ b/hid/src/main.cpp @@ -40,11 +40,11 @@ #endif -// #define CMD_SERIAL Serial1 -// #define CMD_SERIAL_SPEED 115200 +// #define CMD_SERIAL Serial1 +// #define CMD_SERIAL_SPEED 115200 +// #define CMD_SERIAL_TIMEOUT 100000 // -- OR -- // #define CMD_SPI -#define CMD_TIMEOUT 100000 // ----------------------------------------------------------------------------- @@ -150,52 +150,49 @@ uint8_t handleCmdBuffer(const uint8_t *buffer) { // 8 bytes #ifdef CMD_SPI volatile uint8_t spi_in[8] = {0}; volatile uint8_t spi_in_index = 0; -volatile uint8_t spi_in_read = 0; // Вычитанное spiRead() volatile uint8_t spi_out[4] = {0}; volatile uint8_t spi_out_index = 0; -uint8_t spiAvailable() { - return spi_in_index - spi_in_read; -} - -uint8_t spiRead() { - uint8_t value = 0; - if (spi_in_read < 8) { - value = spi_in[spi_in_read]; - ++spi_in_read; - } - return value; +bool spiReady() { + return (!spi_out[0] && spi_in_index == 8); } void spiWrite(const uint8_t *buffer) { - if (spi_out[0] == 0) { - spi_out[3] = buffer[3]; - spi_out[2] = buffer[2]; - spi_out[1] = buffer[1]; - spi_out[0] = buffer[0]; // Меджик разрешает начать ответ - } -} - -void spiReadReset() { - spi_in_index = 0; - spi_in_read = 0; + spi_out[3] = buffer[3]; + spi_out[2] = buffer[2]; + spi_out[1] = buffer[1]; + spi_out[0] = buffer[0]; // Меджик разрешает начать ответ +// digitalWrite(5, 1); } ISR(SPI_STC_vect) { - if (spi_in_index < 8) { - spi_in[spi_in_index] = SPDR; - ++spi_in_index; - SPDR = 0; - } else if (spi_out[0] && spi_out_index < 4) { + uint8_t in = SPDR; + if (spi_out[0] && spi_out_index < 4) { +// digitalWrite(4, !digitalRead(4)); SPDR = spi_out[spi_out_index]; - ++spi_out_index; - if (spi_out_index == 4) { - spiReadReset(); - spi_out[0] = 0; - spi_out_index = 0; + bool err = (SPSR & (1 << WCOL)); + if (!err) { + ++spi_out_index; + if (spi_out_index == 4) { + spi_out_index = 0; + spi_in_index = 0; + spi_out[0] = 0; +// digitalWrite(5, 0); + } } } else { + static bool receiving = false; + if (!receiving && in == PROTO_MAGIC) { + receiving = true; + } + if (receiving && spi_in_index < 8) { + spi_in[spi_in_index] = in; + ++spi_in_index; + } + if (spi_in_index == 8) { + receiving = false; + } SPDR = 0; } } @@ -225,20 +222,17 @@ void sendCmdResponse(uint8_t code) { # endif } -bool isCmdTimedOut(unsigned long last) { - unsigned long now = micros(); - return ( - (now >= last && now - last > CMD_TIMEOUT) - || (now < last && ((unsigned long)-1) - last + now > CMD_TIMEOUT) - ); -} - void setup() { hid_kbd.begin(); # ifdef HID_USB_MOUSE hid_mouse.begin(); # endif + pinMode(3, OUTPUT); + pinMode(4, OUTPUT); + pinMode(5, OUTPUT); + pinMode(6, OUTPUT); + # ifdef CMD_SERIAL CMD_SERIAL.begin(CMD_SERIAL_SPEED); # elif defined(CMD_SPI) @@ -248,9 +242,11 @@ void setup() { } void loop() { +# ifdef CMD_SERIAL unsigned long last = micros(); uint8_t buffer[8]; uint8_t index = 0; +# endif while (true) { # ifdef HID_PS2_KBD @@ -260,10 +256,6 @@ void loop() { # ifdef CMD_SERIAL if (CMD_SERIAL.available() > 0) { buffer[index] = (uint8_t)CMD_SERIAL.read(); -# elif defined(CMD_SPI) - if (spiAvailable() > 0) { - buffer[index] = spiRead(); -# endif if (index == 7) { sendCmdResponse(handleCmdBuffer(buffer)); index = 0; @@ -271,13 +263,26 @@ void loop() { last = micros(); ++index; } - } else if (index > 0 && isCmdTimedOut(last)) { -# ifdef CMD_SERIAL - sendCmdResponse(PROTO_RESP_TIMEOUT_ERROR); -# elif defined(CMD_SPI) - spiReadReset(); -# endif - index = 0; + } else if (index > 0) { + unsigned long now = micros(); + if ( + (now >= last && now - last > CMD_SERIAL_TIMEOUT) + || (now < last && ((unsigned long)-1) - last + now > CMD_SERIAL_TIMEOUT) + ) { + sendCmdResponse(PROTO_RESP_TIMEOUT_ERROR); + index = 0; + } } +# elif defined(CMD_SPI) + if (SPSR & (1 << WCOL)) { + digitalWrite(3, HIGH); + uint8_t _ = SPDR; + delay(1); + digitalWrite(3, LOW); + } + if (spiReady()) { + sendCmdResponse(handleCmdBuffer(spi_in)); + } +# endif } } diff --git a/kvmd/plugins/hid/spi.py b/kvmd/plugins/hid/spi.py index d71a4094..3dcd0dab 100644 --- a/kvmd/plugins/hid/spi.py +++ b/kvmd/plugins/hid/spi.py @@ -36,9 +36,9 @@ from ...logging import get_logger from ...yamlconf import Option +from ...validators.basic import valid_bool from ...validators.basic import valid_int_f0 from ...validators.basic import valid_int_f1 -from ...validators.basic import valid_float_f0 from ...validators.basic import valid_float_f01 from ._mcu import BasePhyConnection @@ -52,26 +52,33 @@ class _SpiPhyConnection(BasePhyConnection): self, xfer: Callable[[bytes], bytes], read_timeout: float, - read_delay: float, ) -> None: self.__xfer = xfer self.__read_timeout = read_timeout - self.__read_delay = read_delay def send(self, request: bytes) -> bytes: assert len(request) == 8 + assert request[0] == 0x33 + + deadline_ts = time.time() + self.__read_timeout + dummy = b"\x00" * 8 + while time.time() < deadline_ts: + if bytes(self.__xfer(dummy)) == dummy: + break + else: + get_logger(0).error("SPI timeout reached while garbage reading") + return b"" + self.__xfer(request) response: List[int] = [] deadline_ts = time.time() + self.__read_timeout found = False while time.time() < deadline_ts: - if not found: - time.sleep(self.__read_delay) for byte in self.__xfer(b"\x00" * (4 - len(response))): if not found: - if byte == 0: + if byte != 0x33: continue found = True response.append(byte) @@ -90,18 +97,18 @@ class _SpiPhy(BasePhy): self, bus: int, chip: int, + cs: bool, max_freq: int, block_usec: int, read_timeout: float, - read_delay: float, ) -> None: self.__bus = bus self.__chip = chip + self.__cs = cs self.__max_freq = max_freq self.__block_usec = block_usec self.__read_timeout = read_timeout - self.__read_delay = read_delay def has_device(self) -> bool: return os.path.exists(f"/dev/spidev{self.__bus}.{self.__chip}") @@ -110,6 +117,7 @@ class _SpiPhy(BasePhy): def connected(self) -> Generator[_SpiPhyConnection, None, None]: # type: ignore with contextlib.closing(spidev.SpiDev(self.__bus, self.__chip)) as spi: spi.mode = 0 + spi.no_cs = (not self.__cs) spi.max_speed_hz = self.__max_freq def xfer(data: bytes) -> bytes: @@ -118,36 +126,29 @@ class _SpiPhy(BasePhy): yield _SpiPhyConnection( xfer=xfer, read_timeout=self.__read_timeout, - read_delay=self.__read_delay, ) # ===== class Plugin(BaseMcuHid): - def __init__( - self, - bus: int, - chip: int, - max_freq: int, - block_usec: int, - read_timeout: float, - read_delay: float, - **kwargs: Any, - ) -> None: - - super().__init__( - phy=_SpiPhy(bus, chip, max_freq, block_usec, read_timeout, read_delay), - **kwargs, - ) + def __init__(self, **kwargs: Any) -> None: + phy_kwargs: Dict = {key: kwargs.pop(key) for key in self.__get_phy_options()} + super().__init__(phy=_SpiPhy(**phy_kwargs), **kwargs) @classmethod def get_plugin_options(cls) -> Dict: return { - "bus": Option(0, type=valid_int_f0), - "chip": Option(0, type=valid_int_f0), - "max_freq": Option(400000, type=valid_int_f1), - "block_usec": Option(1, type=valid_int_f0), - "read_timeout": Option(2.0, type=valid_float_f01), - "read_delay": Option(0.001, type=valid_float_f0), + **cls.__get_phy_options(), **BaseMcuHid.get_plugin_options(), } + + @classmethod + def __get_phy_options(cls) -> Dict: + return { + "bus": Option(0, type=valid_int_f0), + "chip": Option(0, type=valid_int_f0), + "cs": Option(False, type=valid_bool), + "max_freq": Option(200000, type=valid_int_f1), + "block_usec": Option(1, type=valid_int_f0), + "read_timeout": Option(0.5, type=valid_float_f01), + } diff --git a/testenv/linters/vulture-wl.py b/testenv/linters/vulture-wl.py index 75240082..62752f43 100644 --- a/testenv/linters/vulture-wl.py +++ b/testenv/linters/vulture-wl.py @@ -18,6 +18,7 @@ InotifyMask.UNMOUNT IpmiServer.handle_raw_request +SpiDev.no_cs SpiDev.max_speed_hz _AtxApiPart.switch_power From fef625aee52015dfdca0a96c31eced033663cb97 Mon Sep 17 00:00:00 2001 From: Devaev Maxim Date: Wed, 11 Nov 2020 12:56:34 +0300 Subject: [PATCH 11/16] refactoring --- kvmd/plugins/hid/serial.py | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/kvmd/plugins/hid/serial.py b/kvmd/plugins/hid/serial.py index 9bfafc69..dee992b6 100644 --- a/kvmd/plugins/hid/serial.py +++ b/kvmd/plugins/hid/serial.py @@ -47,6 +47,7 @@ class _SerialPhyConnection(BasePhyConnection): def send(self, request: bytes) -> bytes: assert len(request) == 8 + assert request[0] == 0x33 if self.__tty.in_waiting: self.__tty.read_all() assert self.__tty.write(request) == 8 @@ -76,24 +77,21 @@ class _SerialPhy(BasePhy): # ===== class Plugin(BaseMcuHid): - def __init__( - self, - device_path: str, - speed: int, - read_timeout: float, - **kwargs: Any, - ) -> None: - - super().__init__( - phy=_SerialPhy(device_path, speed, read_timeout), - **kwargs, - ) + def __init__(self, **kwargs: Any) -> None: + phy_kwargs: Dict = {key: kwargs.pop(key) for key in self.__get_phy_options()} + super().__init__(phy=_SerialPhy(**phy_kwargs), **kwargs) @classmethod def get_plugin_options(cls) -> Dict: + return { + **cls.__get_phy_options(), + **BaseMcuHid.get_plugin_options(), + } + + @classmethod + def __get_phy_options(cls) -> Dict: return { "device": Option("", type=valid_abs_path, unpack_as="device_path"), "speed": Option(115200, type=valid_tty_speed), "read_timeout": Option(2.0, type=valid_float_f01), - **BaseMcuHid.get_plugin_options(), } From 55a682803965af9bc4712cb7485a706bd2b25832 Mon Sep 17 00:00:00 2001 From: Devaev Maxim Date: Wed, 11 Nov 2020 21:32:22 +0300 Subject: [PATCH 12/16] disable serial port for spi --- hid/avrdude.py | 1 + hid/patch.py | 19 ++++++++++++++----- hid/patches/serial.patch | 24 ++++++++++++++++++++++++ hid/platformio.ini | 1 + 4 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 hid/patches/serial.patch diff --git a/hid/avrdude.py b/hid/avrdude.py index 1eef53b4..3ae227f9 100644 --- a/hid/avrdude.py +++ b/hid/avrdude.py @@ -1,5 +1,6 @@ # https://docs.platformio.org/en/latest/projectconf/advanced_scripting.html + from os import rename from os import symlink from os.path import exists diff --git a/hid/patch.py b/hid/patch.py index c05ed262..58462bc1 100644 --- a/hid/patch.py +++ b/hid/patch.py @@ -1,5 +1,6 @@ # https://docs.platformio.org/en/latest/projectconf/advanced_scripting.html + from os.path import exists from os.path import join from os.path import basename @@ -10,6 +11,12 @@ Import("env") # ===== +def _get_pkg_path(name: str) -> str: + path = env.PioPlatform().get_package_dir(name) + assert exists(path) + return path + + def _get_libs() -> Dict[str, str]: return { builder.name: builder.path @@ -17,15 +24,17 @@ def _get_libs() -> Dict[str, str]: } -def _patch_lib(lib_path: str, patch_path: str) -> None: - assert exists(lib_path) - flag_path: str = join(lib_path, f".{basename(patch_path)}.done") +def _patch(path: str, patch_path: str) -> None: + assert exists(path) + flag_path: str = join(path, f".{basename(patch_path)}.done") if not exists(flag_path): - env.Execute(f"patch -p1 -d {lib_path} < {patch_path}") + env.Execute(f"patch -p1 -d {path} < {patch_path}") env.Execute(lambda *_, **__: open(flag_path, "w").close()) # ===== +_patch(_get_pkg_path("framework-arduino-avr"), "patches/serial.patch") + _libs = _get_libs() if "HID-Project" in _libs: - _patch_lib(_libs["HID-Project"], "patches/absmouse.patch") + _patch(_libs["HID-Project"], "patches/absmouse.patch") diff --git a/hid/patches/serial.patch b/hid/patches/serial.patch new file mode 100644 index 00000000..16ba3811 --- /dev/null +++ b/hid/patches/serial.patch @@ -0,0 +1,24 @@ +https://github.com/arduino/Arduino/issues/6387 +--- a/cores/arduino/USBCore.cpp 2019-09-20 15:48:38.000000000 +0300 ++++ b/cores/arduino/USBCore.cpp 2020-11-11 19:56:49.233690476 +0300 +@@ -375,8 +375,10 @@ + { + u8 i = setup.wIndex; + ++#ifndef NO_SERIAL + if (CDC_ACM_INTERFACE == i) + return CDC_Setup(setup); ++#endif + + #ifdef PLUGGABLE_USB_ENABLED + return PluggableUSB().setup(setup); +@@ -466,7 +468,9 @@ + { + u8 interfaces = 0; + ++#ifndef NO_SERIAL + CDC_GetInterface(&interfaces); ++#endif + + #ifdef PLUGGABLE_USB_ENABLED + PluggableUSB().getInterface(&interfaces); diff --git a/hid/platformio.ini b/hid/platformio.ini index 3414d1d7..a24c4aca 100644 --- a/hid/platformio.ini +++ b/hid/platformio.ini @@ -102,6 +102,7 @@ extra_scripts = [_cmd_spi] build_flags = -DCMD_SPI + -DNO_SERIAL upload_protocol = custom upload_flags = -C From 0140cba0dcc983e7649f407059636d26727f110f Mon Sep 17 00:00:00 2001 From: Devaev Maxim Date: Thu, 12 Nov 2020 18:33:35 +0300 Subject: [PATCH 13/16] software cs --- hid/src/main.cpp | 7 ++-- kvmd/plugins/hid/spi.py | 62 ++++++++++++++++++++++++++--------- testenv/linters/vulture-wl.py | 1 + 3 files changed, 50 insertions(+), 20 deletions(-) diff --git a/hid/src/main.cpp b/hid/src/main.cpp index 3d0eb618..9111474d 100644 --- a/hid/src/main.cpp +++ b/hid/src/main.cpp @@ -171,8 +171,7 @@ ISR(SPI_STC_vect) { if (spi_out[0] && spi_out_index < 4) { // digitalWrite(4, !digitalRead(4)); SPDR = spi_out[spi_out_index]; - bool err = (SPSR & (1 << WCOL)); - if (!err) { + if (!(SPSR & (1 << WCOL))) { ++spi_out_index; if (spi_out_index == 4) { spi_out_index = 0; @@ -274,12 +273,12 @@ void loop() { } } # elif defined(CMD_SPI) - if (SPSR & (1 << WCOL)) { + /*if (SPSR & (1 << WCOL)) { digitalWrite(3, HIGH); uint8_t _ = SPDR; delay(1); digitalWrite(3, LOW); - } + }*/ if (spiReady()) { sendCmdResponse(handleCmdBuffer(spi_in)); } diff --git a/kvmd/plugins/hid/spi.py b/kvmd/plugins/hid/spi.py index 3dcd0dab..cf48f803 100644 --- a/kvmd/plugins/hid/spi.py +++ b/kvmd/plugins/hid/spi.py @@ -28,9 +28,11 @@ from typing import List from typing import Dict from typing import Generator from typing import Callable +from typing import Optional from typing import Any import spidev +import gpiod from ...logging import get_logger @@ -40,6 +42,9 @@ from ...validators.basic import valid_bool from ...validators.basic import valid_int_f0 from ...validators.basic import valid_int_f1 from ...validators.basic import valid_float_f01 +from ...validators.hw import valid_gpio_pin_optional + +from ... import env from ._mcu import BasePhyConnection from ._mcu import BasePhy @@ -92,12 +97,14 @@ class _SpiPhyConnection(BasePhyConnection): return bytes(response) -class _SpiPhy(BasePhy): +class _SpiPhy(BasePhy): # pylint: disable=too-many-instance-attributes def __init__( self, bus: int, chip: int, - cs: bool, + hw_cs: bool, + sw_cs_pin: int, + cs_high: bool, max_freq: int, block_usec: int, read_timeout: float, @@ -105,7 +112,9 @@ class _SpiPhy(BasePhy): self.__bus = bus self.__chip = chip - self.__cs = cs + self.__hw_cs = hw_cs + self.__sw_cs_pin = sw_cs_pin + self.__cs_high = cs_high self.__max_freq = max_freq self.__block_usec = block_usec self.__read_timeout = read_timeout @@ -115,18 +124,37 @@ class _SpiPhy(BasePhy): @contextlib.contextmanager def connected(self) -> Generator[_SpiPhyConnection, None, None]: # type: ignore - with contextlib.closing(spidev.SpiDev(self.__bus, self.__chip)) as spi: - spi.mode = 0 - spi.no_cs = (not self.__cs) - spi.max_speed_hz = self.__max_freq + with self.__sw_cs_connected() as sw_cs_line: + with contextlib.closing(spidev.SpiDev(self.__bus, self.__chip)) as spi: + spi.mode = 0 + spi.no_cs = (not self.__hw_cs) + if self.__hw_cs: + spi.cshigh = self.__cs_high + spi.max_speed_hz = self.__max_freq - def xfer(data: bytes) -> bytes: - return spi.xfer(data, self.__max_freq, self.__block_usec) + def xfer(data: bytes) -> bytes: + try: + if sw_cs_line is not None: + sw_cs_line.set_value(int(self.__cs_high)) + return spi.xfer(data, self.__max_freq, self.__block_usec) + finally: + if sw_cs_line is not None: + sw_cs_line.set_value(int(not self.__cs_high)) - yield _SpiPhyConnection( - xfer=xfer, - read_timeout=self.__read_timeout, - ) + yield _SpiPhyConnection( + xfer=xfer, + read_timeout=self.__read_timeout, + ) + + @contextlib.contextmanager + def __sw_cs_connected(self) -> Generator[Optional[gpiod.Line], None, None]: + if self.__sw_cs_pin > 0: + with contextlib.closing(gpiod.Chip(env.GPIO_DEVICE_PATH)) as chip: + line = chip.get_line(self.__sw_cs_pin) + line.request("kvmd::hid-mcu::sw_cs", gpiod.LINE_REQ_DIR_OUT, default_vals=[int(not self.__cs_high)]) + yield line + else: + yield None # ===== @@ -145,9 +173,11 @@ class Plugin(BaseMcuHid): @classmethod def __get_phy_options(cls) -> Dict: return { - "bus": Option(0, type=valid_int_f0), - "chip": Option(0, type=valid_int_f0), - "cs": Option(False, type=valid_bool), + "bus": Option(-1, type=valid_int_f0), + "chip": Option(-1, type=valid_int_f0), + "hw_cs": Option(False, type=valid_bool), + "sw_cs_pin": Option(-1, type=valid_gpio_pin_optional), + "cs_high": Option(False, type=valid_bool), "max_freq": Option(200000, type=valid_int_f1), "block_usec": Option(1, type=valid_int_f0), "read_timeout": Option(0.5, type=valid_float_f01), diff --git a/testenv/linters/vulture-wl.py b/testenv/linters/vulture-wl.py index 62752f43..c83dc38e 100644 --- a/testenv/linters/vulture-wl.py +++ b/testenv/linters/vulture-wl.py @@ -19,6 +19,7 @@ InotifyMask.UNMOUNT IpmiServer.handle_raw_request SpiDev.no_cs +SpiDev.cshigh SpiDev.max_speed_hz _AtxApiPart.switch_power From c144f41c1d45f05e76ab95d591d9f47ff59e5a6e Mon Sep 17 00:00:00 2001 From: Devaev Maxim Date: Thu, 12 Nov 2020 20:35:47 +0300 Subject: [PATCH 14/16] removed cshigh option --- kvmd/plugins/hid/spi.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/kvmd/plugins/hid/spi.py b/kvmd/plugins/hid/spi.py index cf48f803..7eaa447f 100644 --- a/kvmd/plugins/hid/spi.py +++ b/kvmd/plugins/hid/spi.py @@ -104,7 +104,6 @@ class _SpiPhy(BasePhy): # pylint: disable=too-many-instance-attributes chip: int, hw_cs: bool, sw_cs_pin: int, - cs_high: bool, max_freq: int, block_usec: int, read_timeout: float, @@ -114,7 +113,6 @@ class _SpiPhy(BasePhy): # pylint: disable=too-many-instance-attributes self.__chip = chip self.__hw_cs = hw_cs self.__sw_cs_pin = sw_cs_pin - self.__cs_high = cs_high self.__max_freq = max_freq self.__block_usec = block_usec self.__read_timeout = read_timeout @@ -128,18 +126,16 @@ class _SpiPhy(BasePhy): # pylint: disable=too-many-instance-attributes with contextlib.closing(spidev.SpiDev(self.__bus, self.__chip)) as spi: spi.mode = 0 spi.no_cs = (not self.__hw_cs) - if self.__hw_cs: - spi.cshigh = self.__cs_high spi.max_speed_hz = self.__max_freq def xfer(data: bytes) -> bytes: try: if sw_cs_line is not None: - sw_cs_line.set_value(int(self.__cs_high)) + sw_cs_line.set_value(0) return spi.xfer(data, self.__max_freq, self.__block_usec) finally: if sw_cs_line is not None: - sw_cs_line.set_value(int(not self.__cs_high)) + sw_cs_line.set_value(1) yield _SpiPhyConnection( xfer=xfer, @@ -151,7 +147,7 @@ class _SpiPhy(BasePhy): # pylint: disable=too-many-instance-attributes if self.__sw_cs_pin > 0: with contextlib.closing(gpiod.Chip(env.GPIO_DEVICE_PATH)) as chip: line = chip.get_line(self.__sw_cs_pin) - line.request("kvmd::hid-mcu::sw_cs", gpiod.LINE_REQ_DIR_OUT, default_vals=[int(not self.__cs_high)]) + line.request("kvmd::hid-mcu::sw_cs", gpiod.LINE_REQ_DIR_OUT, default_vals=[1]) yield line else: yield None @@ -177,7 +173,6 @@ class Plugin(BaseMcuHid): "chip": Option(-1, type=valid_int_f0), "hw_cs": Option(False, type=valid_bool), "sw_cs_pin": Option(-1, type=valid_gpio_pin_optional), - "cs_high": Option(False, type=valid_bool), "max_freq": Option(200000, type=valid_int_f1), "block_usec": Option(1, type=valid_int_f0), "read_timeout": Option(0.5, type=valid_float_f01), From 3d75acf7521a5780b63f3cab7dcfd5a4e778392d Mon Sep 17 00:00:00 2001 From: Devaev Maxim Date: Thu, 12 Nov 2020 20:39:19 +0300 Subject: [PATCH 15/16] cleanup --- hid/src/main.cpp | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/hid/src/main.cpp b/hid/src/main.cpp index 9111474d..4eeb9838 100644 --- a/hid/src/main.cpp +++ b/hid/src/main.cpp @@ -163,13 +163,11 @@ void spiWrite(const uint8_t *buffer) { spi_out[2] = buffer[2]; spi_out[1] = buffer[1]; spi_out[0] = buffer[0]; // Меджик разрешает начать ответ -// digitalWrite(5, 1); } ISR(SPI_STC_vect) { uint8_t in = SPDR; if (spi_out[0] && spi_out_index < 4) { -// digitalWrite(4, !digitalRead(4)); SPDR = spi_out[spi_out_index]; if (!(SPSR & (1 << WCOL))) { ++spi_out_index; @@ -177,7 +175,6 @@ ISR(SPI_STC_vect) { spi_out_index = 0; spi_in_index = 0; spi_out[0] = 0; -// digitalWrite(5, 0); } } } else { @@ -227,11 +224,6 @@ void setup() { hid_mouse.begin(); # endif - pinMode(3, OUTPUT); - pinMode(4, OUTPUT); - pinMode(5, OUTPUT); - pinMode(6, OUTPUT); - # ifdef CMD_SERIAL CMD_SERIAL.begin(CMD_SERIAL_SPEED); # elif defined(CMD_SPI) @@ -273,12 +265,6 @@ void loop() { } } # elif defined(CMD_SPI) - /*if (SPSR & (1 << WCOL)) { - digitalWrite(3, HIGH); - uint8_t _ = SPDR; - delay(1); - digitalWrite(3, LOW); - }*/ if (spiReady()) { sendCmdResponse(handleCmdBuffer(spi_in)); } From 0984f0cb36c5881669010c62e986cc8bc7fe019a Mon Sep 17 00:00:00 2001 From: Devaev Maxim Date: Thu, 12 Nov 2020 20:49:33 +0300 Subject: [PATCH 16/16] fixed xfer answer --- kvmd/plugins/hid/spi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kvmd/plugins/hid/spi.py b/kvmd/plugins/hid/spi.py index 7eaa447f..8af03483 100644 --- a/kvmd/plugins/hid/spi.py +++ b/kvmd/plugins/hid/spi.py @@ -81,7 +81,7 @@ class _SpiPhyConnection(BasePhyConnection): deadline_ts = time.time() + self.__read_timeout found = False while time.time() < deadline_ts: - for byte in self.__xfer(b"\x00" * (4 - len(response))): + for byte in self.__xfer(b"\x00" * (5 - len(response))): if not found: if byte != 0x33: continue