Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88e92254e9 | ||
|
|
5725d35e5b | ||
|
|
8c95525793 | ||
|
|
5bba5d3363 | ||
|
|
80b72db676 | ||
|
|
aa0c97353a |
@@ -60,6 +60,8 @@ fn addUserBinary(
|
|||||||
runtime_module: *std.Build.Module,
|
runtime_module: *std.Build.Module,
|
||||||
posix_module: *std.Build.Module,
|
posix_module: *std.Build.Module,
|
||||||
mmio_module: *std.Build.Module,
|
mmio_module: *std.Build.Module,
|
||||||
|
xkeyboard_config_module: *std.Build.Module,
|
||||||
|
acpi_ids_module: *std.Build.Module,
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
root: []const u8,
|
root: []const u8,
|
||||||
) *std.Build.Step.Compile {
|
) *std.Build.Step.Compile {
|
||||||
@@ -81,6 +83,12 @@ fn addUserBinary(
|
|||||||
.{ .name = "posix", .module = posix_module },
|
.{ .name = "posix", .module = posix_module },
|
||||||
// Typed volatile MMIO + memory barriers, for drivers. See library/mmio/.
|
// Typed volatile MMIO + memory barriers, for drivers. See library/mmio/.
|
||||||
.{ .name = "mmio", .module = mmio_module },
|
.{ .name = "mmio", .module = mmio_module },
|
||||||
|
// Keyboard layouts (keycode + modifiers -> keysym/character), available
|
||||||
|
// to any program that wants it. See library/xkeyboard-config/.
|
||||||
|
.{ .name = "xkeyboard-config", .module = xkeyboard_config_module },
|
||||||
|
// ACPI/PnP hardware-ID registry, so drivers name devices
|
||||||
|
// (HardwareId.ps2_keyboard) instead of magic "_HID" strings.
|
||||||
|
.{ .name = "acpi-ids", .module = acpi_ids_module },
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
@@ -224,7 +232,6 @@ pub fn build(b: *std.Build) void {
|
|||||||
.{ .name = "layouts", .module = xkb_layouts_module },
|
.{ .name = "layouts", .module = xkb_layouts_module },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
_ = xkeyboard_config_module;
|
|
||||||
|
|
||||||
// The POSIX / C compatibility layer, a separate library layered strictly over the
|
// The POSIX / C compatibility layer, a separate library layered strictly over the
|
||||||
// runtime (it calls the runtime's IPC/heap, never system calls directly). This is
|
// runtime (it calls the runtime's IPC/heap, never system calls directly). This is
|
||||||
@@ -309,7 +316,7 @@ pub fn build(b: *std.Build) void {
|
|||||||
// Built by the shared user-binary recipe (see addUserBinary): freestanding,
|
// Built by the shared user-binary recipe (see addUserBinary): freestanding,
|
||||||
// linked into the kernel's user region against the `runtime` runtime library, and
|
// linked into the kernel's user region against the `runtime` runtime library, and
|
||||||
// started in ring 3 by the kernel's user-ELF loader.
|
// started in ring 3 by the kernel's user-ELF loader.
|
||||||
const init_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "init", "system/services/init/init.zig");
|
const init_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "init", "system/services/init/init.zig");
|
||||||
const init_install = b.addInstallArtifact(init_exe, .{ .dest_dir = .{ .override = .{ .custom = "system/services" } } });
|
const init_install = b.addInstallArtifact(init_exe, .{ .dest_dir = .{ .override = .{ .custom = "system/services" } } });
|
||||||
b.getInstallStep().dependOn(&init_install.step);
|
b.getInstallStep().dependOn(&init_install.step);
|
||||||
|
|
||||||
@@ -317,21 +324,21 @@ pub fn build(b: *std.Build) void {
|
|||||||
// Each is built by the same user-binary recipe, then packed into one image by
|
// Each is built by the same user-binary recipe, then packed into one image by
|
||||||
// the host-side make-initial-ramdisk tool. The bootloader ferries the image to the kernel,
|
// the host-side make-initial-ramdisk tool. The bootloader ferries the image to the kernel,
|
||||||
// which unpacks it and spawns each program (system/initial-ramdisk.zig).
|
// which unpacks it and spawns each program (system/initial-ramdisk.zig).
|
||||||
const vfs_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "vfs", "system/services/vfs/vfs.zig");
|
const vfs_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "vfs", "system/services/vfs/vfs.zig");
|
||||||
const vfstest_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "vfs-test", "system/services/vfs/vfs-test.zig");
|
const vfstest_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "vfs-test", "system/services/vfs/vfs-test.zig");
|
||||||
const hpet_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "hpet", "system/drivers/hpet/hpet.zig");
|
const hpet_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "hpet", "system/drivers/hpet/hpet.zig");
|
||||||
const bus_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "bus", "system/drivers/bus/bus.zig");
|
const bus_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "bus", "system/drivers/bus/bus.zig");
|
||||||
const ps2_bus_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "bus", "system/drivers/ps2-bus/ps2-bus.zig");
|
const ps2_bus_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "bus", "system/drivers/ps2-bus/ps2-bus.zig");
|
||||||
const ps2_keyboard_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "ps2-keyboard", "system/drivers/ps2-bus/keyboard.zig");
|
const ps2_keyboard_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "ps2-keyboard", "system/drivers/ps2-bus/keyboard.zig");
|
||||||
const ps2_mouse_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "ps2-mouse", "system/drivers/ps2-bus/mouse.zig");
|
const ps2_mouse_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "ps2-mouse", "system/drivers/ps2-bus/mouse.zig");
|
||||||
const device_manager_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "device-manager", "system/services/device-manager/device-manager.zig");
|
const device_manager_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "device-manager", "system/services/device-manager/device-manager.zig");
|
||||||
// The input service and its exercisers: the fan-out server, a hardware-free synthetic
|
// The input service and its exercisers: the fan-out server, a hardware-free synthetic
|
||||||
// source, and a subscriber that doubles as the `input` test's oracle. See docs/input.md.
|
// source, and a subscriber that doubles as the `input` test's oracle. See docs/input.md.
|
||||||
const input_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "input", "system/services/input/input.zig");
|
const input_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "input", "system/services/input/input.zig");
|
||||||
const input_source_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "input-source", "system/services/input-source/input-source.zig");
|
const input_source_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "input-source", "system/services/input-source/input-source.zig");
|
||||||
const input_test_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "input-test", "system/services/input-test/input-test.zig");
|
const input_test_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "input-test", "system/services/input-test/input-test.zig");
|
||||||
const args_echo_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "args-echo", "system/services/args-echo/args-echo.zig");
|
const args_echo_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "args-echo", "system/services/args-echo/args-echo.zig");
|
||||||
const process_test_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "process-test", "system/services/process-test/process-test.zig");
|
const process_test_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "process-test", "system/services/process-test/process-test.zig");
|
||||||
|
|
||||||
// Pack the user binaries into the initial_ramdisk image with the host-side Python tool
|
// Pack the user binaries into the initial_ramdisk image with the host-side Python tool
|
||||||
// (the container format is trivial, and Python sidesteps std API churn). Args:
|
// (the container format is trivial, and Python sidesteps std API churn). Args:
|
||||||
@@ -507,6 +514,8 @@ pub fn build(b: *std.Build) void {
|
|||||||
"system/devices/pci-class.zig", // class/subclass/prog-IF name decoding
|
"system/devices/pci-class.zig", // class/subclass/prog-IF name decoding
|
||||||
"system/devices/acpi-ids.zig", // _HID name decoding
|
"system/devices/acpi-ids.zig", // _HID name decoding
|
||||||
"library/mmio/mmio.zig", // barriers assemble + registers round-trip
|
"library/mmio/mmio.zig", // barriers assemble + registers round-trip
|
||||||
|
"system/drivers/ps2-bus/scancode.zig", // set-2 decode + keyboard state machine
|
||||||
|
"system/drivers/ps2-bus/mouse-packet.zig", // 3-byte mouse packet assembly
|
||||||
}) |root| {
|
}) |root| {
|
||||||
const mod_tests = b.addTest(.{
|
const mod_tests = b.addTest(.{
|
||||||
.root_module = b.createModule(.{
|
.root_module = b.createModule(.{
|
||||||
|
|||||||
+30
-16
@@ -108,22 +108,36 @@ the service delivers to its endpoint, which only the same thread could receive).
|
|||||||
|
|
||||||
## Status and follow-ups
|
## Status and follow-ups
|
||||||
|
|
||||||
- **Synthetic sources, for now.** The `ps2-bus` driver owns PNP0303, which carries *both*
|
- **The keyboard is real.** The `ps2-bus` driver owns PNP0303, which carries *both* the
|
||||||
the 0x60/0x64 ports and IRQ1, so reading real scancodes/packets has to live in the bus,
|
0x60/0x64 ports and IRQ1, so reading the hardware lives in the bus, not in
|
||||||
not in [keyboard.zig](../system/drivers/ps2-bus/keyboard.zig) /
|
[keyboard.zig](../system/drivers/ps2-bus/keyboard.zig): the bus binds IRQ1 and, on each
|
||||||
[mouse.zig](../system/drivers/ps2-bus/mouse.zig). Until that lands, the keyboard driver
|
interrupt, drains port 0x60, routing every byte by the status register's
|
||||||
publishes a synthetic key stream, the mouse driver a synthetic motion/click stream, and
|
auxiliary-output bit to whichever child driver **attached** for that device (an
|
||||||
the hardware-free `input-source` rotates through all three classes (including a synthetic
|
`AttachRequest` to the well-known `ps2_bus` service, carrying the child's endpoint as a
|
||||||
joystick, which has no driver yet) — all via the `input.synthetic*Event` helpers. The
|
capability; the bytes then arrive as asynchronous `ForwardedByte` messages, so the IRQ
|
||||||
fan-out and per-device routing are real; only the bytes are placeholder. **Follow-up:** the
|
path never blocks on a child). The keyboard driver decodes the stream — scancode **set 2**,
|
||||||
bus binds IRQ1/IRQ12, reads port 0x60, and `ps2-library` decodes scan-set-1 →
|
what the keyboard sends with the 8042's legacy translation off, decoded by
|
||||||
keycodes and mouse packets; the drivers publish decoded events.
|
[scancode.zig](../system/drivers/ps2-bus/scancode.zig) into USB HID usage keycodes with
|
||||||
- **Keycode → character** is a keymap, and danos has one:
|
make/break, typematic-repeat, and modifier tracking (host-tested under `zig build test`) —
|
||||||
[`library/xkeyboard-config`](../library/xkeyboard-config/README.md) compiles the X11
|
and publishes real `key_down`/`key_press`/`key_up` events.
|
||||||
xkeyboard-config layouts (us, gb, de, fr, …) to native Zig —
|
- **Keycode → character** is wired in: the keyboard driver fills a `key_press` event's
|
||||||
`xkb.map(layout, keycode, mods)` → keysym + Unicode character. Wiring it in to fill a
|
`character` through [`library/xkeyboard-config`](../library/xkeyboard-config/README.md)
|
||||||
`key_press` event's `character` (in the keyboard driver, or a small keymap service) is the
|
(`xkb.map(layout, keycode, mods)` → keysym + Unicode character), synthesizing the ASCII
|
||||||
natural next step.
|
control characters for Enter/Tab/Backspace/Escape, whose keysyms map to no Unicode. The
|
||||||
|
layout defaults to `us`; the bus can pass another as the driver's argv[2] — the seam for
|
||||||
|
a future settings source.
|
||||||
|
- **The mouse is real too.** IRQ12 is enumerated on the auxiliary device's own ACPI node
|
||||||
|
(PNP0F13), so the bus claims that node alongside the controller and routes both IRQs to
|
||||||
|
its one endpoint, acking whichever line the notification's badge names.
|
||||||
|
[mouse.zig](../system/drivers/ps2-bus/mouse.zig) attaches the way the keyboard does and
|
||||||
|
assembles the forwarded bytes with
|
||||||
|
[mouse-packet.zig](../system/drivers/ps2-bus/mouse-packet.zig) (three-byte stream-mode
|
||||||
|
packets: sync/overflow handling, nine-bit movement, screen-convention `dy` — host-tested
|
||||||
|
under `zig build test`) into `button_down`/`button_up` transitions and `motion` events.
|
||||||
|
**Follow-up:** the IntelliMouse magic-knock for a scroll wheel (four-byte packets) and
|
||||||
|
`scroll` events. The hardware-free `input-source` still rotates through all three classes
|
||||||
|
synthetically (including a joystick, which has no driver yet) via the
|
||||||
|
`input.synthetic*Event` helpers.
|
||||||
- **Drop-oldest under overflow** is a defined loss; the 16-slot ring absorbs normal bursts.
|
- **Drop-oldest under overflow** is a defined loss; the 16-slot ring absorbs normal bursts.
|
||||||
Real backpressure/flow-control is future work.
|
Real backpressure/flow-control is future work.
|
||||||
- **`publish` is unauthenticated** — any process may publish, consistent with the current
|
- **`publish` is unauthenticated** — any process may publish, consistent with the current
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ pub const ProcessDescriptor = extern struct {
|
|||||||
pub const ServiceId = enum(u32) {
|
pub const ServiceId = enum(u32) {
|
||||||
vfs = 1,
|
vfs = 1,
|
||||||
input = 2,
|
input = 2,
|
||||||
|
ps2_bus = 3, // the 8042 owner; child device drivers attach here for raw bytes
|
||||||
_,
|
_,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+115
-45
@@ -1,60 +1,120 @@
|
|||||||
//! ACPI / PnP hardware-ID (`_HID`) names: the flat analog of pci-class.zig for
|
//! ACPI / PnP hardware-ID (`_HID`) names: the flat analog of pci-class.zig for
|
||||||
//! `acpi_device` nodes. Unlike PCI, ACPI has no class/subclass/prog-IF taxonomy — a
|
//! `acpi_device` nodes. Unlike PCI, ACPI has no class/subclass/prog-IF taxonomy — a
|
||||||
//! device's identity *is* its `_HID` string (`PNP0303` simply means "PS/2 keyboard"),
|
//! device's identity *is* its `_HID` string (`PNP0303` simply means "PS/2 keyboard"),
|
||||||
//! so this is a plain id -> description registry rather than a hierarchical decoder.
|
//! so this is a plain id <-> name registry rather than a hierarchical decoder.
|
||||||
//! The well-known PnP/ACPI IDs; vendor-specific ids (e.g. `QEMU0002`, `INTC1234`) have
|
//! The well-known PnP/ACPI IDs; vendor-specific ids (e.g. `QEMU0002`, `INTC1234`) have
|
||||||
//! no standard name and return "". Pure reference data, so it is shared by kernel
|
//! no standard name and decode to nothing. Pure reference data, so it is shared by
|
||||||
//! discovery (the device-tree dump) and any user-space tool.
|
//! kernel discovery (the device-tree dump) and any user-space driver or tool.
|
||||||
|
//!
|
||||||
|
//! Code that means a specific device names the `HardwareId` variant instead of its
|
||||||
|
//! `_HID` string — `HardwareId.ps2_keyboard.hid()` reads without a registry lookup,
|
||||||
|
//! where a bare `"PNP0303"` does not.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
|
/// The common standard PnP/ACPI hardware IDs, as named values. Prefix ranges hint at
|
||||||
|
/// the grouping (PNP03xx keyboards, PNP0Fxx pointing devices, PNP0Cxx ACPI
|
||||||
|
/// power/thermal, PNP0Axx buses), but there is no formal hierarchy — hence a flat
|
||||||
|
/// enum over a flat registry.
|
||||||
|
pub const HardwareId = enum {
|
||||||
|
programmable_interrupt_controller,
|
||||||
|
system_timer,
|
||||||
|
high_precision_event_timer,
|
||||||
|
dma_controller,
|
||||||
|
ps2_keyboard,
|
||||||
|
parallel_port,
|
||||||
|
ecp_parallel_port,
|
||||||
|
serial_port,
|
||||||
|
floppy_disk_controller,
|
||||||
|
system_speaker,
|
||||||
|
pci_bus,
|
||||||
|
generic_container,
|
||||||
|
/// The second id the ACPI spec assigns the same "Generic Container Device" name.
|
||||||
|
generic_container_extended,
|
||||||
|
pci_express_root_bridge,
|
||||||
|
real_time_clock,
|
||||||
|
system_board,
|
||||||
|
motherboard_reserved_resources,
|
||||||
|
math_coprocessor,
|
||||||
|
acpi_system_board,
|
||||||
|
embedded_controller,
|
||||||
|
control_method_battery,
|
||||||
|
fan,
|
||||||
|
power_button,
|
||||||
|
lid,
|
||||||
|
sleep_button,
|
||||||
|
pci_interrupt_link,
|
||||||
|
microsoft_ps2_mouse,
|
||||||
|
ps2_mouse,
|
||||||
|
ac_adapter,
|
||||||
|
processor_device,
|
||||||
|
processor_aggregator,
|
||||||
|
processor_container,
|
||||||
|
|
||||||
const Entry = struct { hid: []const u8, name: []const u8 };
|
const Entry = struct { hid: []const u8, name: []const u8 };
|
||||||
|
|
||||||
/// The common standard PnP/ACPI hardware IDs. Prefix ranges hint at the grouping
|
/// The registry row for this id: its `_HID` string and human-readable name.
|
||||||
/// (PNP03xx keyboards, PNP0Fxx pointing devices, PNP0Cxx ACPI power/thermal,
|
fn entry(self: HardwareId) Entry {
|
||||||
/// PNP0Axx buses), but there is no formal hierarchy — hence a flat table.
|
return switch (self) {
|
||||||
const table = [_]Entry{
|
.programmable_interrupt_controller => .{ .hid = "PNP0000", .name = "Programmable Interrupt Controller (PIC)" },
|
||||||
.{ .hid = "PNP0000", .name = "Programmable Interrupt Controller (PIC)" },
|
.system_timer => .{ .hid = "PNP0100", .name = "System Timer (PIT)" },
|
||||||
.{ .hid = "PNP0100", .name = "System Timer (PIT)" },
|
.high_precision_event_timer => .{ .hid = "PNP0103", .name = "High Precision Event Timer (HPET)" },
|
||||||
.{ .hid = "PNP0103", .name = "High Precision Event Timer (HPET)" },
|
.dma_controller => .{ .hid = "PNP0200", .name = "DMA Controller" },
|
||||||
.{ .hid = "PNP0200", .name = "DMA Controller" },
|
.ps2_keyboard => .{ .hid = "PNP0303", .name = "PS/2 Keyboard" },
|
||||||
.{ .hid = "PNP0303", .name = "PS/2 Keyboard" },
|
.parallel_port => .{ .hid = "PNP0400", .name = "Standard LPT Parallel Port" },
|
||||||
.{ .hid = "PNP0400", .name = "Standard LPT Parallel Port" },
|
.ecp_parallel_port => .{ .hid = "PNP0401", .name = "ECP Parallel Port" },
|
||||||
.{ .hid = "PNP0401", .name = "ECP Parallel Port" },
|
.serial_port => .{ .hid = "PNP0501", .name = "16550A-compatible Serial Port" },
|
||||||
.{ .hid = "PNP0501", .name = "16550A-compatible Serial Port" },
|
.floppy_disk_controller => .{ .hid = "PNP0700", .name = "PC Floppy Disk Controller" },
|
||||||
.{ .hid = "PNP0700", .name = "PC Floppy Disk Controller" },
|
.system_speaker => .{ .hid = "PNP0800", .name = "System Speaker" },
|
||||||
.{ .hid = "PNP0800", .name = "System Speaker" },
|
.pci_bus => .{ .hid = "PNP0A03", .name = "PCI Bus" },
|
||||||
.{ .hid = "PNP0A03", .name = "PCI Bus" },
|
.generic_container => .{ .hid = "PNP0A05", .name = "Generic Container Device" },
|
||||||
.{ .hid = "PNP0A05", .name = "Generic Container Device" },
|
.generic_container_extended => .{ .hid = "PNP0A06", .name = "Generic Container Device" },
|
||||||
.{ .hid = "PNP0A06", .name = "Generic Container Device" },
|
.pci_express_root_bridge => .{ .hid = "PNP0A08", .name = "PCI Express Root Bridge" },
|
||||||
.{ .hid = "PNP0A08", .name = "PCI Express Root Bridge" },
|
.real_time_clock => .{ .hid = "PNP0B00", .name = "Real-Time Clock (RTC)" },
|
||||||
.{ .hid = "PNP0B00", .name = "Real-Time Clock (RTC)" },
|
.system_board => .{ .hid = "PNP0C01", .name = "System Board" },
|
||||||
.{ .hid = "PNP0C01", .name = "System Board" },
|
.motherboard_reserved_resources => .{ .hid = "PNP0C02", .name = "Motherboard Reserved Resources" },
|
||||||
.{ .hid = "PNP0C02", .name = "Motherboard Reserved Resources" },
|
.math_coprocessor => .{ .hid = "PNP0C04", .name = "Math Coprocessor" },
|
||||||
.{ .hid = "PNP0C04", .name = "Math Coprocessor" },
|
.acpi_system_board => .{ .hid = "PNP0C08", .name = "ACPI System Board" },
|
||||||
.{ .hid = "PNP0C08", .name = "ACPI System Board" },
|
.embedded_controller => .{ .hid = "PNP0C09", .name = "ACPI Embedded Controller" },
|
||||||
.{ .hid = "PNP0C09", .name = "ACPI Embedded Controller" },
|
.control_method_battery => .{ .hid = "PNP0C0A", .name = "ACPI Control Method Battery" },
|
||||||
.{ .hid = "PNP0C0A", .name = "ACPI Control Method Battery" },
|
.fan => .{ .hid = "PNP0C0B", .name = "ACPI Fan" },
|
||||||
.{ .hid = "PNP0C0B", .name = "ACPI Fan" },
|
.power_button => .{ .hid = "PNP0C0C", .name = "ACPI Power Button" },
|
||||||
.{ .hid = "PNP0C0C", .name = "ACPI Power Button" },
|
.lid => .{ .hid = "PNP0C0D", .name = "ACPI Lid" },
|
||||||
.{ .hid = "PNP0C0D", .name = "ACPI Lid" },
|
.sleep_button => .{ .hid = "PNP0C0E", .name = "ACPI Sleep Button" },
|
||||||
.{ .hid = "PNP0C0E", .name = "ACPI Sleep Button" },
|
.pci_interrupt_link => .{ .hid = "PNP0C0F", .name = "PCI Interrupt Link Device" },
|
||||||
.{ .hid = "PNP0C0F", .name = "PCI Interrupt Link Device" },
|
.microsoft_ps2_mouse => .{ .hid = "PNP0F03", .name = "Microsoft PS/2 Mouse" },
|
||||||
.{ .hid = "PNP0F03", .name = "Microsoft PS/2 Mouse" },
|
.ps2_mouse => .{ .hid = "PNP0F13", .name = "PS/2 Mouse" },
|
||||||
.{ .hid = "PNP0F13", .name = "PS/2 Mouse" },
|
.ac_adapter => .{ .hid = "ACPI0003", .name = "AC Adapter" },
|
||||||
.{ .hid = "ACPI0003", .name = "AC Adapter" },
|
.processor_device => .{ .hid = "ACPI0007", .name = "Processor Device" },
|
||||||
.{ .hid = "ACPI0007", .name = "Processor Device" },
|
.processor_aggregator => .{ .hid = "ACPI000C", .name = "Processor Aggregator" },
|
||||||
.{ .hid = "ACPI000C", .name = "Processor Aggregator" },
|
.processor_container => .{ .hid = "ACPI0010", .name = "Processor Container" },
|
||||||
.{ .hid = "ACPI0010", .name = "Processor Container" },
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This id's `_HID` string (e.g. `.ps2_keyboard` -> "PNP0303").
|
||||||
|
pub fn hid(self: HardwareId) []const u8 {
|
||||||
|
return self.entry().hid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This id's human-readable name (e.g. `.ps2_keyboard` -> "PS/2 Keyboard").
|
||||||
|
pub fn description(self: HardwareId) []const u8 {
|
||||||
|
return self.entry().name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The named value for a `_HID` string, or null if it is not a known standard
|
||||||
|
/// id (vendor-specific ids are not in the registry).
|
||||||
|
pub fn fromHid(hid_string: []const u8) ?HardwareId {
|
||||||
|
for (std.enums.values(HardwareId)) |id| {
|
||||||
|
if (std.mem.eql(u8, id.hid(), hid_string)) return id;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The human-readable name for a `_HID`, or "" if it is not a known standard id
|
/// The human-readable name for a `_HID` string, or "" if it is not a known standard
|
||||||
/// (vendor-specific ids have no registry name — callers just print the raw HID).
|
/// id (vendor-specific ids have no registry name — callers just print the raw HID).
|
||||||
pub fn description(hid: []const u8) []const u8 {
|
pub fn description(hid: []const u8) []const u8 {
|
||||||
for (table) |entry| {
|
return (HardwareId.fromHid(hid) orelse return "").description();
|
||||||
if (std.mem.eql(u8, entry.hid, hid)) return entry.name;
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test "decodes standard PnP/ACPI ids and leaves the rest alone" {
|
test "decodes standard PnP/ACPI ids and leaves the rest alone" {
|
||||||
@@ -66,3 +126,13 @@ test "decodes standard PnP/ACPI ids and leaves the rest alone" {
|
|||||||
try eq("", description("QEMU0002")); // vendor-specific: no standard name
|
try eq("", description("QEMU0002")); // vendor-specific: no standard name
|
||||||
try eq("", description("")); // no HID at all
|
try eq("", description("")); // no HID at all
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "named values round-trip through their _HID strings" {
|
||||||
|
const testing = std.testing;
|
||||||
|
try testing.expectEqualStrings("PNP0303", HardwareId.ps2_keyboard.hid());
|
||||||
|
try testing.expectEqual(@as(?HardwareId, .ps2_mouse), HardwareId.fromHid("PNP0F13"));
|
||||||
|
try testing.expectEqual(@as(?HardwareId, null), HardwareId.fromHid("QEMU0002"));
|
||||||
|
for (std.enums.values(HardwareId)) |id| {
|
||||||
|
try testing.expectEqual(@as(?HardwareId, id), HardwareId.fromHid(id.hid()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+12
-6
@@ -17,6 +17,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const boot_handoff = @import("boot-handoff");
|
const boot_handoff = @import("boot-handoff");
|
||||||
const abi = @import("abi");
|
const abi = @import("abi");
|
||||||
|
const acpi_ids = @import("acpi-ids");
|
||||||
const parameters = @import("parameters");
|
const parameters = @import("parameters");
|
||||||
const device_model = @import("device-model.zig");
|
const device_model = @import("device-model.zig");
|
||||||
const aml = @import("aml/aml.zig");
|
const aml = @import("aml/aml.zig");
|
||||||
@@ -914,7 +915,14 @@ fn readAdr(node: *aml.Node) ?u32 {
|
|||||||
return @truncate(readIntObj(n.value, &p) orelse return null);
|
return @truncate(readIntObj(n.value, &p) orelse return null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether a namespace device is a PCI(e) host bridge (`PNP0A03` / `PNP0A08`).
|
/// Whether a `_HID` string names a PCI(e) host bridge.
|
||||||
|
fn isPciRootHid(hid: []const u8) bool {
|
||||||
|
const id = acpi_ids.HardwareId.fromHid(hid) orelse return false;
|
||||||
|
return id == .pci_bus or id == .pci_express_root_bridge;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether a namespace device is a PCI(e) host bridge. A packed EISA id is decoded
|
||||||
|
/// to its string form first, so both encodings answer through the one registry.
|
||||||
fn isPciRootNode(node: *aml.Node) bool {
|
fn isPciRootNode(node: *aml.Node) bool {
|
||||||
const hid = aml.Namespace.childOf(node, seg4("_HID")) orelse return false;
|
const hid = aml.Namespace.childOf(node, seg4("_HID")) orelse return false;
|
||||||
if (hid.kind != .name or hid.value.len == 0) return false;
|
if (hid.kind != .name or hid.value.len == 0) return false;
|
||||||
@@ -922,12 +930,10 @@ fn isPciRootNode(node: *aml.Node) bool {
|
|||||||
0x00, 0x01, 0xFF, 0x0A, 0x0B, 0x0C, 0x0E => {
|
0x00, 0x01, 0xFF, 0x0A, 0x0B, 0x0C, 0x0E => {
|
||||||
var p: usize = 0;
|
var p: usize = 0;
|
||||||
const n = readIntObj(hid.value, &p) orelse return false;
|
const n = readIntObj(hid.value, &p) orelse return false;
|
||||||
return n == 0x030AD041 or n == 0x080AD041; // PNP0A03 / PNP0A08
|
var buffer: [8]u8 = undefined;
|
||||||
},
|
return isPciRootHid(eisaIdToStr(@truncate(n), &buffer));
|
||||||
0x0D => {
|
|
||||||
const s = cstr(hid.value[1..]);
|
|
||||||
return std.mem.eql(u8, s, "PNP0A03") or std.mem.eql(u8, s, "PNP0A08");
|
|
||||||
},
|
},
|
||||||
|
0x0D => return isPciRootHid(cstr(hid.value[1..])),
|
||||||
else => return false,
|
else => return false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,76 @@
|
|||||||
//! PS/2 Keyboard Driver
|
//! PS/2 Keyboard Driver
|
||||||
//!
|
//!
|
||||||
//! Spawned by the ps2-bus driver once the controller is initialized and port 1
|
//! Spawned by the ps2-bus driver once the controller is initialized and the port
|
||||||
//! has passed its interface test and device reset. The bus driver hands us our
|
//! has passed its interface test and device reset. The bus driver hands us our
|
||||||
//! device HID as argv[1]; we use it to locate our own device descriptor.
|
//! device HID as argv[1] and, optionally, a layout name (`"us"`, `"gb"`, ...) as
|
||||||
|
//! argv[2].
|
||||||
|
//!
|
||||||
|
//! The 8042's ports (0x60/0x64) and IRQ1 live on the PNP0303 node, which the
|
||||||
|
//! ps2-bus driver exclusively owns — so this driver never touches the hardware.
|
||||||
|
//! Instead it **attaches** to the bus (handing over its endpoint as a capability)
|
||||||
|
//! and receives every scancode byte as a forwarded asynchronous message. Each byte
|
||||||
|
//! feeds the set-2 decoder; a decoded key becomes input-protocol events:
|
||||||
|
//!
|
||||||
|
//! scancode byte -> HID usage keycode -> key_down / key_up
|
||||||
|
//! -> xkeyboard-config -> character -> key_press
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const runtime = @import("runtime");
|
const runtime = @import("runtime");
|
||||||
|
const xkb = @import("xkeyboard-config");
|
||||||
const ps2 = @import("ps2-library.zig");
|
const ps2 = @import("ps2-library.zig");
|
||||||
|
const scancode = @import("scancode.zig");
|
||||||
const device = runtime.device;
|
const device = runtime.device;
|
||||||
|
const ipc = runtime.ipc;
|
||||||
|
const protocol = runtime.input_protocol;
|
||||||
|
|
||||||
fn writeLine(comptime fmt: []const u8, arguments: anytype) void {
|
fn writeLine(comptime fmt: []const u8, arguments: anytype) void {
|
||||||
var line: [128]u8 = undefined;
|
var line: [128]u8 = undefined;
|
||||||
_ = runtime.system.write(std.fmt.bufPrint(&line, fmt, arguments) catch return);
|
_ = runtime.system.write(std.fmt.bufPrint(&line, fmt, arguments) catch return);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main() void {
|
/// Look up the ps2-bus service, retrying while the bus (which spawned us before
|
||||||
const hid = runtime.argument(1);
|
/// registering) is still coming up.
|
||||||
|
fn lookupBus() ?ipc.Handle {
|
||||||
|
var attempts: usize = 0;
|
||||||
|
while (attempts < 100) : (attempts += 1) {
|
||||||
|
if (ipc.lookup(.ps2_bus)) |handle| return handle;
|
||||||
|
runtime.system.sleep(50);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The character a pressed key produces under `modifiers`, or 0 for none. The
|
||||||
|
/// layout lookup answers for printable keys; the keys whose keysym has no Unicode
|
||||||
|
/// mapping but that every consumer still expects as a character (Enter, Tab,
|
||||||
|
/// Backspace, Escape) are given their ASCII control characters here.
|
||||||
|
fn characterFor(layout: *const xkb.Layout, usage: u8, modifiers: scancode.ModifierSnapshot) u32 {
|
||||||
|
const mapping = xkb.map(layout, usage, .{
|
||||||
|
.shift = modifiers.shift,
|
||||||
|
.caps_lock = modifiers.caps_lock,
|
||||||
|
.level3 = modifiers.right_alt,
|
||||||
|
.control = modifiers.control,
|
||||||
|
});
|
||||||
|
if (mapping.character) |character| return character;
|
||||||
|
return switch (@as(protocol.Keycode, @enumFromInt(usage))) {
|
||||||
|
.enter, .keypad_enter => '\n',
|
||||||
|
.tab => '\t',
|
||||||
|
.backspace => 0x08,
|
||||||
|
.escape => 0x1B,
|
||||||
|
else => 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The input protocol's modifier word for a snapshot.
|
||||||
|
fn modifierWord(modifiers: scancode.ModifierSnapshot) u32 {
|
||||||
|
var word: u32 = 0;
|
||||||
|
if (modifiers.shift) word |= protocol.modifier_shift;
|
||||||
|
if (modifiers.control) word |= protocol.modifier_control;
|
||||||
|
if (modifiers.alt) word |= protocol.modifier_alt;
|
||||||
|
return word;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main(init: runtime.process.Init) void {
|
||||||
|
const hid = init.arguments.get(1).?;
|
||||||
if (hid.len == 0) {
|
if (hid.len == 0) {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: no HID argument\n");
|
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: no HID argument\n");
|
||||||
return;
|
return;
|
||||||
@@ -31,25 +86,95 @@ pub fn main() void {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The 8042 ports (0x60/0x64) and this keyboard's IRQ1 both live on the same
|
// The layout is a spawn argument so a later settings source can choose it;
|
||||||
// PNP0303 node, which the ps2-bus driver exclusively owns — so the keyboard is
|
// absent (as today) it defaults to us.
|
||||||
// served through the bus and does not claim the controller itself.
|
const layout_name = init.arguments.get(2) orelse "us";
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: served by ps2-bus (controller owned by bus)\n");
|
const layout = xkb.byName(layout_name) orelse xkb.us;
|
||||||
|
writeLine("system/drivers/ps2-bus/keyboard: layout {s}\n", .{layout.name});
|
||||||
|
|
||||||
// Broadcast keyboard events through the input service so programs can listen for them
|
// Attach to the bus: hand it our endpoint, and it forwards every byte the
|
||||||
// (docs/input.md). Until the bus reads real IRQ1 scancodes and hands them here (a
|
// keyboard sends (it owns the controller; we own the decoding).
|
||||||
// follow-up), we publish the same synthetic stand-in stream the demo source uses — the
|
const bus = lookupBus() orelse {
|
||||||
// fan-out path is real, only the source of the bytes is placeholder.
|
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: ps2-bus service unavailable\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
const endpoint = ipc.createIpcEndpoint() orelse {
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: no endpoint\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
var attach = ps2.AttachRequest{ .device_type = @intFromEnum(ps2.DeviceType.keyboard) };
|
||||||
|
var attach_reply: [@sizeOf(ps2.AttachReply)]u8 = undefined;
|
||||||
|
const attached = ipc.callCap(bus, std.mem.asBytes(&attach), &attach_reply, endpoint) catch {
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: attach call failed\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if (attached.len < @sizeOf(ps2.AttachReply) or
|
||||||
|
std.mem.bytesToValue(ps2.AttachReply, attach_reply[0..@sizeOf(ps2.AttachReply)]).status != @intFromEnum(ps2.AttachStatus.ok))
|
||||||
|
{
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: attach refused\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Broadcast keyboard events through the input service so programs can listen
|
||||||
|
// for them (docs/input.md).
|
||||||
var source = runtime.input.connectSource() orelse {
|
var source = runtime.input.connectSource() orelse {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: input service unavailable\n");
|
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: input service unavailable\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: ok\n");
|
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: ok\n");
|
||||||
|
|
||||||
var step: usize = 0;
|
var decoder = scancode.Decoder{};
|
||||||
while (true) : (step +%= 1) {
|
var state = scancode.KeyboardState{};
|
||||||
_ = source.publishKeyboardEvent(runtime.input.syntheticKeyEvent(step));
|
var receive: [@sizeOf(ps2.ForwardedByte)]u8 = undefined;
|
||||||
runtime.system.sleep(200);
|
while (true) {
|
||||||
|
const got = ipc.replyWait(endpoint, &.{}, &receive, null);
|
||||||
|
if (!got.isMessage() or got.len < @sizeOf(ps2.ForwardedByte)) continue;
|
||||||
|
const forwarded = std.mem.bytesToValue(ps2.ForwardedByte, receive[0..@sizeOf(ps2.ForwardedByte)]);
|
||||||
|
|
||||||
|
const key = decoder.feed(@intCast(forwarded.byte & 0xFF)) orelse continue;
|
||||||
|
const transition = state.apply(key);
|
||||||
|
const modifiers = modifierWord(transition.modifiers);
|
||||||
|
|
||||||
|
switch (transition.action) {
|
||||||
|
.pressed => {
|
||||||
|
_ = source.publishKeyboardEvent(.{
|
||||||
|
.kind = @intFromEnum(protocol.EventKind.key_down),
|
||||||
|
.keycode = key.usage,
|
||||||
|
.character = 0,
|
||||||
|
.modifiers = modifiers,
|
||||||
|
});
|
||||||
|
const character = characterFor(layout, key.usage, transition.modifiers);
|
||||||
|
if (character != 0) {
|
||||||
|
_ = source.publishKeyboardEvent(.{
|
||||||
|
.kind = @intFromEnum(protocol.EventKind.key_press),
|
||||||
|
.keycode = key.usage,
|
||||||
|
.character = character,
|
||||||
|
.modifiers = modifiers,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Typematic repeat: the key did not physically go down again, so no
|
||||||
|
// key_down — but it keeps producing its character.
|
||||||
|
.repeated => {
|
||||||
|
const character = characterFor(layout, key.usage, transition.modifiers);
|
||||||
|
if (character != 0) {
|
||||||
|
_ = source.publishKeyboardEvent(.{
|
||||||
|
.kind = @intFromEnum(protocol.EventKind.key_press),
|
||||||
|
.keycode = key.usage,
|
||||||
|
.character = character,
|
||||||
|
.modifiers = modifiers,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.released => {
|
||||||
|
_ = source.publishKeyboardEvent(.{
|
||||||
|
.kind = @intFromEnum(protocol.EventKind.key_up),
|
||||||
|
.keycode = key.usage,
|
||||||
|
.character = 0,
|
||||||
|
.modifiers = modifiers,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,143 @@
|
|||||||
|
//! PS/2 mouse packet assembly — the byte stream a streaming mouse sends, turned
|
||||||
|
//! into decoded movement/button reports.
|
||||||
|
//!
|
||||||
|
//! A standard PS/2 mouse in stream mode sends three-byte packets:
|
||||||
|
//!
|
||||||
|
//! byte 0: | Y ovf | X ovf | Y sign | X sign | 1 | middle | right | left |
|
||||||
|
//! byte 1: X movement (low eight bits; the sign bit lives in byte 0)
|
||||||
|
//! byte 2: Y movement (likewise)
|
||||||
|
//!
|
||||||
|
//! Movement is nine-bit two's complement, PS/2 convention: positive X right,
|
||||||
|
//! positive Y **up**. The decoded packet converts Y to the screen convention
|
||||||
|
//! (positive down), matching what every consumer of relative motion expects.
|
||||||
|
//! Bit 3 of byte 0 is always set — the resynchronization anchor: a byte at
|
||||||
|
//! packet start with bit 3 clear cannot be a packet header and is dropped.
|
||||||
|
//!
|
||||||
|
//! Everything here is pure (no imports beyond `std`, no IO), so it is
|
||||||
|
//! host-testable: the tests at the bottom run under `zig build test`.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
/// One decoded movement/button report, in screen convention (positive dy down).
|
||||||
|
pub const Packet = struct {
|
||||||
|
left: bool,
|
||||||
|
right: bool,
|
||||||
|
middle: bool,
|
||||||
|
dx: i16,
|
||||||
|
dy: i16,
|
||||||
|
};
|
||||||
|
|
||||||
|
const header_always_set: u8 = 1 << 3;
|
||||||
|
const header_left: u8 = 1 << 0;
|
||||||
|
const header_right: u8 = 1 << 1;
|
||||||
|
const header_middle: u8 = 1 << 2;
|
||||||
|
const header_x_sign: u8 = 1 << 4;
|
||||||
|
const header_y_sign: u8 = 1 << 5;
|
||||||
|
const header_x_overflow: u8 = 1 << 6;
|
||||||
|
const header_y_overflow: u8 = 1 << 7;
|
||||||
|
|
||||||
|
/// Device protocol bytes that can reach the packet stream around bring-up (the
|
||||||
|
/// acknowledge to enable-reporting, a reset's self-test result). Both have bit 3
|
||||||
|
/// set, so the header check alone cannot reject them; they are recognized only
|
||||||
|
/// at packet start, where a real header cannot be one of them in practice.
|
||||||
|
const response_acknowledge: u8 = 0xFA;
|
||||||
|
const response_self_test_passed: u8 = 0xAA;
|
||||||
|
|
||||||
|
/// Accumulates the byte stream into `Packet`s. Feed it every byte the mouse
|
||||||
|
/// sends; the third byte of each well-formed packet returns one.
|
||||||
|
pub const Assembler = struct {
|
||||||
|
bytes: [3]u8 = undefined,
|
||||||
|
count: u8 = 0,
|
||||||
|
|
||||||
|
pub fn feed(self: *Assembler, byte: u8) ?Packet {
|
||||||
|
if (self.count == 0) {
|
||||||
|
// Resynchronize: a packet must start with a plausible header.
|
||||||
|
if (byte & header_always_set == 0) return null;
|
||||||
|
if (byte == response_acknowledge or byte == response_self_test_passed) return null;
|
||||||
|
}
|
||||||
|
self.bytes[self.count] = byte;
|
||||||
|
self.count += 1;
|
||||||
|
if (self.count < 3) return null;
|
||||||
|
self.count = 0;
|
||||||
|
|
||||||
|
const header = self.bytes[0];
|
||||||
|
// An overflowed count is garbage by definition; discard the packet.
|
||||||
|
if (header & (header_x_overflow | header_y_overflow) != 0) return null;
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.left = header & header_left != 0,
|
||||||
|
.right = header & header_right != 0,
|
||||||
|
.middle = header & header_middle != 0,
|
||||||
|
.dx = movement(self.bytes[1], header & header_x_sign != 0),
|
||||||
|
// PS/2 positive Y is up; screen positive Y is down.
|
||||||
|
.dy = -movement(self.bytes[2], header & header_y_sign != 0),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Nine-bit two's complement: the eight movement bits plus the header's sign.
|
||||||
|
fn movement(low: u8, negative: bool) i16 {
|
||||||
|
const value: i16 = low;
|
||||||
|
return if (negative) value - 256 else value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- tests (host-run via `zig build test`) ------------------------------------
|
||||||
|
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
fn feedAll(assembler: *Assembler, bytes: []const u8) ?Packet {
|
||||||
|
var result: ?Packet = null;
|
||||||
|
for (bytes) |byte| {
|
||||||
|
if (assembler.feed(byte)) |packet| result = packet;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
test "plain motion decodes with screen-convention y" {
|
||||||
|
var assembler = Assembler{};
|
||||||
|
const packet = feedAll(&assembler, &.{ 0x08, 5, 3 }).?;
|
||||||
|
try testing.expectEqual(@as(i16, 5), packet.dx);
|
||||||
|
try testing.expectEqual(@as(i16, -3), packet.dy); // PS/2 up 3 -> screen -3
|
||||||
|
try testing.expect(!packet.left and !packet.right and !packet.middle);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "negative movement sign-extends through the header bits" {
|
||||||
|
var assembler = Assembler{};
|
||||||
|
// X sign and Y sign set: dx = 0xFB - 256 = -5, dy raw = 0xFE - 256 = -2 -> screen +2.
|
||||||
|
const packet = feedAll(&assembler, &.{ 0x08 | 0x10 | 0x20, 0xFB, 0xFE }).?;
|
||||||
|
try testing.expectEqual(@as(i16, -5), packet.dx);
|
||||||
|
try testing.expectEqual(@as(i16, 2), packet.dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "buttons decode from the header" {
|
||||||
|
var assembler = Assembler{};
|
||||||
|
const packet = feedAll(&assembler, &.{ 0x08 | 0x01 | 0x02, 0, 0 }).?;
|
||||||
|
try testing.expect(packet.left);
|
||||||
|
try testing.expect(packet.right);
|
||||||
|
try testing.expect(!packet.middle);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "a byte with bit 3 clear at packet start is dropped" {
|
||||||
|
var assembler = Assembler{};
|
||||||
|
// The stray 0x02 cannot be a header; the following packet still decodes.
|
||||||
|
try testing.expectEqual(@as(?Packet, null), assembler.feed(0x02));
|
||||||
|
const packet = feedAll(&assembler, &.{ 0x09, 1, 0 }).?;
|
||||||
|
try testing.expect(packet.left);
|
||||||
|
try testing.expectEqual(@as(i16, 1), packet.dx);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "protocol bytes at packet start are dropped" {
|
||||||
|
var assembler = Assembler{};
|
||||||
|
try testing.expectEqual(@as(?Packet, null), assembler.feed(0xFA)); // enable-reporting ACK
|
||||||
|
try testing.expectEqual(@as(?Packet, null), assembler.feed(0xAA)); // self-test passed
|
||||||
|
const packet = feedAll(&assembler, &.{ 0x08, 7, 0 }).?;
|
||||||
|
try testing.expectEqual(@as(i16, 7), packet.dx);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "an overflowed packet is discarded whole" {
|
||||||
|
var assembler = Assembler{};
|
||||||
|
try testing.expectEqual(@as(?Packet, null), feedAll(&assembler, &.{ 0x08 | 0x40, 0xFF, 0xFF }));
|
||||||
|
// The assembler is back at packet start.
|
||||||
|
const packet = feedAll(&assembler, &.{ 0x08, 1, 1 }).?;
|
||||||
|
try testing.expectEqual(@as(i16, 1), packet.dx);
|
||||||
|
}
|
||||||
@@ -1,21 +1,55 @@
|
|||||||
//! PS/2 Mouse Driver
|
//! PS/2 Mouse Driver
|
||||||
//!
|
//!
|
||||||
//! Spawned by the ps2-bus driver once the controller is initialized and port 2
|
//! Spawned by the ps2-bus driver once the controller is initialized and the port
|
||||||
//! has passed its interface test and device reset. The bus driver hands us our
|
//! has passed its interface test and device reset. The bus driver hands us our
|
||||||
//! device HID as argv[1]; we use it to locate our own device descriptor.
|
//! device HID as argv[1].
|
||||||
|
//!
|
||||||
|
//! Like the keyboard, this driver never touches the hardware: the 8042's ports
|
||||||
|
//! and both port IRQs are owned by the ps2-bus driver (the auxiliary port's
|
||||||
|
//! IRQ12 lives on the PNP0F13 node, which the bus claims alongside the
|
||||||
|
//! controller). The driver **attaches** to the bus and receives every byte the
|
||||||
|
//! mouse sends as a forwarded asynchronous message. The bytes assemble into
|
||||||
|
//! three-byte packets, and each packet becomes input-protocol events:
|
||||||
|
//!
|
||||||
|
//! packet -> button transitions -> button_down / button_up
|
||||||
|
//! -> movement -> motion (dx/dy, screen convention)
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const runtime = @import("runtime");
|
const runtime = @import("runtime");
|
||||||
const ps2 = @import("ps2-library.zig");
|
const ps2 = @import("ps2-library.zig");
|
||||||
|
const mouse_packet = @import("mouse-packet.zig");
|
||||||
const device = runtime.device;
|
const device = runtime.device;
|
||||||
|
const ipc = runtime.ipc;
|
||||||
|
const protocol = runtime.input_protocol;
|
||||||
|
|
||||||
fn writeLine(comptime fmt: []const u8, arguments: anytype) void {
|
fn writeLine(comptime fmt: []const u8, arguments: anytype) void {
|
||||||
var line: [128]u8 = undefined;
|
var line: [128]u8 = undefined;
|
||||||
_ = runtime.system.write(std.fmt.bufPrint(&line, fmt, arguments) catch return);
|
_ = runtime.system.write(std.fmt.bufPrint(&line, fmt, arguments) catch return);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main() void {
|
/// Look up the ps2-bus service, retrying while the bus (which spawned us before
|
||||||
const hid = runtime.argument(1);
|
/// registering) is still coming up.
|
||||||
|
fn lookupBus() ?ipc.Handle {
|
||||||
|
var attempts: usize = 0;
|
||||||
|
while (attempts < 100) : (attempts += 1) {
|
||||||
|
if (ipc.lookup(.ps2_bus)) |handle| return handle;
|
||||||
|
runtime.system.sleep(50);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The protocol's pressed-button bitmask for a packet.
|
||||||
|
fn buttonMask(packet: mouse_packet.Packet) u32 {
|
||||||
|
var mask: u32 = 0;
|
||||||
|
if (packet.left) mask |= protocol.mouse_button_left;
|
||||||
|
if (packet.right) mask |= protocol.mouse_button_right;
|
||||||
|
if (packet.middle) mask |= protocol.mouse_button_middle;
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main(init: runtime.process.Init) void {
|
||||||
|
const hid = init.arguments.get(1).?;
|
||||||
|
|
||||||
if (hid.len == 0) {
|
if (hid.len == 0) {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus/mouse: no HID argument\n");
|
_ = runtime.system.write("system/drivers/ps2-bus/mouse: no HID argument\n");
|
||||||
return;
|
return;
|
||||||
@@ -26,34 +60,81 @@ pub fn main() void {
|
|||||||
_ = runtime.system.write("system/drivers/ps2-bus/mouse: out of memory\n");
|
_ = runtime.system.write("system/drivers/ps2-bus/mouse: out of memory\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
const mouse_device_descriptor = device.findDeviceDescriptorByHid(buffer, hid) orelse {
|
if (device.findDeviceDescriptorByHid(buffer, hid) == null) {
|
||||||
writeLine("system/drivers/ps2-bus/mouse: no device for hid {s}\n", .{hid});
|
writeLine("system/drivers/ps2-bus/mouse: no device for hid {s}\n", .{hid});
|
||||||
return;
|
return;
|
||||||
};
|
}
|
||||||
|
|
||||||
// The mouse's own node (PNP0F13) carries IRQ12 and is not claimed by the bus,
|
// Attach to the bus: hand it our endpoint, and it forwards every byte the
|
||||||
// so this driver takes exclusive ownership of it. Port IO still goes through
|
// mouse sends (it owns the controller; we own the decoding).
|
||||||
// the bus, which owns the shared 8042 ports.
|
const bus = lookupBus() orelse {
|
||||||
if (!device.claim(mouse_device_descriptor.id)) {
|
_ = runtime.system.write("system/drivers/ps2-bus/mouse: ps2-bus service unavailable\n");
|
||||||
writeLine("system/drivers/ps2-bus/mouse: unable to claim device for hid {s}\n", .{hid});
|
return;
|
||||||
|
};
|
||||||
|
const endpoint = ipc.createIpcEndpoint() orelse {
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus/mouse: no endpoint\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
var attach = ps2.AttachRequest{ .device_type = @intFromEnum(ps2.DeviceType.mouse) };
|
||||||
|
var attach_reply: [@sizeOf(ps2.AttachReply)]u8 = undefined;
|
||||||
|
const attached = ipc.callCap(bus, std.mem.asBytes(&attach), &attach_reply, endpoint) catch {
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus/mouse: attach call failed\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if (attached.len < @sizeOf(ps2.AttachReply) or
|
||||||
|
std.mem.bytesToValue(ps2.AttachReply, attach_reply[0..@sizeOf(ps2.AttachReply)]).status != @intFromEnum(ps2.AttachStatus.ok))
|
||||||
|
{
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus/mouse: attach refused\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
writeLine("system/drivers/ps2-bus/mouse: claimed device for hid {s}\n", .{hid});
|
|
||||||
|
|
||||||
// Broadcast mouse events through the input service so programs can listen for them
|
// Broadcast mouse events through the input service so programs can listen
|
||||||
// (docs/input.md). As with the keyboard, decoding real PS/2 mouse packets is a
|
// for them (docs/input.md).
|
||||||
// follow-up; for now we publish the synthetic stand-in stream. The fan-out path is
|
|
||||||
// real, only the source of the movement is placeholder.
|
|
||||||
var source = runtime.input.connectSource() orelse {
|
var source = runtime.input.connectSource() orelse {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus/mouse: input service unavailable\n");
|
_ = runtime.system.write("system/drivers/ps2-bus/mouse: input service unavailable\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus/mouse: ok\n");
|
_ = runtime.system.write("system/drivers/ps2-bus/mouse: ok\n");
|
||||||
|
|
||||||
var step: usize = 0;
|
var assembler = mouse_packet.Assembler{};
|
||||||
while (true) : (step +%= 1) {
|
var buttons: u32 = 0;
|
||||||
_ = source.publishMouseEvent(runtime.input.syntheticMouseEvent(step));
|
var receive: [@sizeOf(ps2.ForwardedByte)]u8 = undefined;
|
||||||
runtime.system.sleep(200);
|
while (true) {
|
||||||
|
const got = ipc.replyWait(endpoint, &.{}, &receive, null);
|
||||||
|
if (!got.isMessage() or got.len < @sizeOf(ps2.ForwardedByte)) continue;
|
||||||
|
const forwarded = std.mem.bytesToValue(ps2.ForwardedByte, receive[0..@sizeOf(ps2.ForwardedByte)]);
|
||||||
|
|
||||||
|
const packet = assembler.feed(@intCast(forwarded.byte & 0xFF)) orelse continue;
|
||||||
|
const new_buttons = buttonMask(packet);
|
||||||
|
|
||||||
|
// A button transition per changed button, carrying the new whole mask.
|
||||||
|
const changed = buttons ^ new_buttons;
|
||||||
|
for ([_]u32{ protocol.mouse_button_left, protocol.mouse_button_right, protocol.mouse_button_middle }) |button| {
|
||||||
|
if (changed & button == 0) continue;
|
||||||
|
const kind: protocol.MouseEventKind = if (new_buttons & button != 0) .button_down else .button_up;
|
||||||
|
_ = source.publishMouseEvent(.{
|
||||||
|
.kind = @intFromEnum(kind),
|
||||||
|
.button = button,
|
||||||
|
.dx = 0,
|
||||||
|
.dy = 0,
|
||||||
|
.scroll_x = 0,
|
||||||
|
.scroll_y = 0,
|
||||||
|
.buttons = new_buttons,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
buttons = new_buttons;
|
||||||
|
|
||||||
|
if (packet.dx != 0 or packet.dy != 0) {
|
||||||
|
_ = source.publishMouseEvent(.{
|
||||||
|
.kind = @intFromEnum(protocol.MouseEventKind.motion),
|
||||||
|
.button = 0,
|
||||||
|
.dx = packet.dx,
|
||||||
|
.dy = packet.dy,
|
||||||
|
.scroll_x = 0,
|
||||||
|
.scroll_y = 0,
|
||||||
|
.buttons = new_buttons,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,10 @@
|
|||||||
//! - irq 0xc len 0x1
|
//! - irq 0xc len 0x1
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const runtime = @import("runtime");
|
const runtime = @import("runtime");
|
||||||
|
const acpi_ids = @import("acpi-ids");
|
||||||
const ps2 = @import("ps2-library.zig");
|
const ps2 = @import("ps2-library.zig");
|
||||||
const device = runtime.device;
|
const device = runtime.device;
|
||||||
|
const ipc = runtime.ipc;
|
||||||
|
|
||||||
/// Format one whole log line and emit it in a single `debug_write`, so output
|
/// Format one whole log line and emit it in a single `debug_write`, so output
|
||||||
/// from the child drivers (which run concurrently) can never interleave with it.
|
/// from the child drivers (which run concurrently) can never interleave with it.
|
||||||
@@ -23,22 +25,68 @@ fn writeLine(comptime fmt: []const u8, arguments: anytype) void {
|
|||||||
|
|
||||||
/// Ask the device on `port` what it is, then spawn the matching driver from the
|
/// Ask the device on `port` what it is, then spawn the matching driver from the
|
||||||
/// initial-ramdisk, handing it the device's HID as argv[1]. The driver is chosen
|
/// initial-ramdisk, handing it the device's HID as argv[1]. The driver is chosen
|
||||||
/// from what the device reports, not from the port number.
|
/// from what the device reports, not from the port number. Returns the identified
|
||||||
fn spawnIdentifiedDriver(controller: ps2.Controller, port: ps2.Port) void {
|
/// type so the forwarding loop can route that port's bytes to the driver once it
|
||||||
|
/// attaches, or null if nothing was spawned.
|
||||||
|
fn spawnIdentifiedDriver(controller: ps2.Controller, port: ps2.Port) ?ps2.DeviceType {
|
||||||
const device_type = controller.identifyDevice(port) orelse {
|
const device_type = controller.identifyDevice(port) orelse {
|
||||||
writeLine("system/drivers/ps2-bus: identify timed out on port {s}\n", .{@tagName(port)});
|
writeLine("system/drivers/ps2-bus: identify timed out on port {s}\n", .{@tagName(port)});
|
||||||
return;
|
return null;
|
||||||
};
|
};
|
||||||
const driver_name = device_type.driverName() orelse {
|
const driver_name = device_type.driverName() orelse {
|
||||||
writeLine("system/drivers/ps2-bus: unrecognized device on port {s}\n", .{@tagName(port)});
|
writeLine("system/drivers/ps2-bus: unrecognized device on port {s}\n", .{@tagName(port)});
|
||||||
return;
|
return null;
|
||||||
};
|
};
|
||||||
const hid = device_type.hid() orelse "";
|
const hid = device_type.hid() orelse "";
|
||||||
if (runtime.system.spawnWithArguments(driver_name, &.{hid}) != null) {
|
if (runtime.system.spawnWithArguments(driver_name, &.{hid}) != null) {
|
||||||
writeLine("system/drivers/ps2-bus: port {s} is a {s}, spawned {s}\n", .{ @tagName(port), hid, driver_name });
|
writeLine("system/drivers/ps2-bus: port {s} is a {s}, spawned {s}\n", .{ @tagName(port), hid, driver_name });
|
||||||
} else {
|
return device_type;
|
||||||
writeLine("system/drivers/ps2-bus: failed to spawn {s}\n", .{driver_name});
|
|
||||||
}
|
}
|
||||||
|
writeLine("system/drivers/ps2-bus: failed to spawn {s}\n", .{driver_name});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resource index of the controller's IRQ (IRQ1) on the PNP0303 descriptor, found
|
||||||
|
/// the way the ports are found in `Controller.init`.
|
||||||
|
fn findInterruptResourceIndex(descriptor: device.DeviceDescriptor) ?u64 {
|
||||||
|
for (0..descriptor.resource_count) |index| {
|
||||||
|
if (descriptor.resources[index].kind == @intFromEnum(device.ResourceKind.irq)) return index;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Forwarding endpoints of the attached child drivers, indexed by `ps2.Port`.
|
||||||
|
/// Written when a child's `AttachRequest` arrives, read on every forwarded byte.
|
||||||
|
var port_endpoints = [_]?ipc.Handle{ null, null };
|
||||||
|
|
||||||
|
/// Which device type each port identified as, so an attaching child (which knows
|
||||||
|
/// its type, not its port) can be matched to the right port's byte stream.
|
||||||
|
var port_device_types = [_]?ps2.DeviceType{ null, null };
|
||||||
|
|
||||||
|
/// Handle a child driver's `AttachRequest`: record the endpoint capability it
|
||||||
|
/// passed as the forwarding target for the port whose device matches its type.
|
||||||
|
/// Writes an `AttachReply` into `out` and returns its length.
|
||||||
|
fn handleAttach(message: []const u8, got: ipc.Received, out: []u8) usize {
|
||||||
|
const reply = struct {
|
||||||
|
fn write(buffer: []u8, status: ps2.AttachStatus) usize {
|
||||||
|
const header = ps2.AttachReply{ .status = @intFromEnum(status) };
|
||||||
|
@memcpy(buffer[0..@sizeOf(ps2.AttachReply)], std.mem.asBytes(&header));
|
||||||
|
return @sizeOf(ps2.AttachReply);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (message.len < @sizeOf(ps2.AttachRequest)) return reply.write(out, .invalid_request);
|
||||||
|
const request = std.mem.bytesToValue(ps2.AttachRequest, message[0..@sizeOf(ps2.AttachRequest)]);
|
||||||
|
const endpoint = got.cap orelse return reply.write(out, .missing_endpoint);
|
||||||
|
|
||||||
|
for (&port_device_types, 0..) |maybe_type, port_index| {
|
||||||
|
const device_type = maybe_type orelse continue;
|
||||||
|
if (@intFromEnum(device_type) != request.device_type) continue;
|
||||||
|
port_endpoints[port_index] = endpoint;
|
||||||
|
writeLine("system/drivers/ps2-bus: {s} driver attached\n", .{@tagName(device_type)});
|
||||||
|
return reply.write(out, .ok);
|
||||||
|
}
|
||||||
|
return reply.write(out, .no_such_device);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main() void {
|
pub fn main() void {
|
||||||
@@ -48,10 +96,12 @@ pub fn main() void {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var has_two_channels = false;
|
var has_two_channels = false;
|
||||||
|
var maybe_controller: ?ps2.Controller = null;
|
||||||
|
var maybe_interrupt_index: ?u64 = null;
|
||||||
// The 8042's IO ports (0x60/0x64) are enumerated under the keyboard ACPI node
|
// The 8042's IO ports (0x60/0x64) are enumerated under the keyboard ACPI node
|
||||||
// (PNP0303), so we init the controller from that descriptor — but which device
|
// (PNP0303), so we init the controller from that descriptor — but which device
|
||||||
// is on which port is decided later by identify, not by this HID.
|
// is on which port is decided later by identify, not by this HID.
|
||||||
const maybe_controller_device_descriptor = device.findDeviceDescriptorByHid(buffer, "PNP0303");
|
const maybe_controller_device_descriptor = device.findDeviceDescriptorByHid(buffer, acpi_ids.HardwareId.ps2_keyboard.hid());
|
||||||
if (maybe_controller_device_descriptor) |controller_device_descriptor| {
|
if (maybe_controller_device_descriptor) |controller_device_descriptor| {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus: found PS/2 controller\n");
|
_ = runtime.system.write("system/drivers/ps2-bus: found PS/2 controller\n");
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus: initializing controller\n");
|
_ = runtime.system.write("system/drivers/ps2-bus: initializing controller\n");
|
||||||
@@ -65,6 +115,8 @@ pub fn main() void {
|
|||||||
_ = runtime.system.write("system/drivers/ps2-bus: controller is missing its IO ports\n");
|
_ = runtime.system.write("system/drivers/ps2-bus: controller is missing its IO ports\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
maybe_controller = controller;
|
||||||
|
maybe_interrupt_index = findInterruptResourceIndex(controller_device_descriptor);
|
||||||
|
|
||||||
controller.disablePort(.One);
|
controller.disablePort(.One);
|
||||||
controller.disablePort(.Two);
|
controller.disablePort(.Two);
|
||||||
@@ -126,18 +178,12 @@ pub fn main() void {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// enable the working ports and, via a read-modify-write, their interrupts
|
// Enable the working ports. Their interrupts stay off until IRQ1 is bound
|
||||||
|
// below — reset and identify use polled reads, which must never race the
|
||||||
|
// interrupt-driven drain loop for bytes.
|
||||||
controller.enablePort(.One);
|
controller.enablePort(.One);
|
||||||
if (port_two_works) controller.enablePort(.Two);
|
if (port_two_works) controller.enablePort(.Two);
|
||||||
|
|
||||||
var configuration = controller.readConfigurationByte() orelse {
|
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus: controller configuration timed out\n");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
if (port_one_works) configuration |= ps2.Port.One.interruptBit();
|
|
||||||
if (port_two_works) configuration |= ps2.Port.Two.interruptBit();
|
|
||||||
_ = controller.writeConfigurationByte(configuration);
|
|
||||||
|
|
||||||
// reset each working device; a failing device is logged but does not
|
// reset each working device; a failing device is logged but does not
|
||||||
// abort bring-up of the other one
|
// abort bring-up of the other one
|
||||||
if (port_one_works) {
|
if (port_one_works) {
|
||||||
@@ -158,15 +204,109 @@ pub fn main() void {
|
|||||||
// Identify the device on each working port and hand it off to the driver
|
// Identify the device on each working port and hand it off to the driver
|
||||||
// that matches what it reported — a port is not assumed to be a keyboard
|
// that matches what it reported — a port is not assumed to be a keyboard
|
||||||
// or a mouse by its number.
|
// or a mouse by its number.
|
||||||
if (port_one_works) spawnIdentifiedDriver(controller, .One);
|
if (port_one_works) port_device_types[@intFromEnum(ps2.Port.One)] = spawnIdentifiedDriver(controller, .One);
|
||||||
if (port_two_works) spawnIdentifiedDriver(controller, .Two);
|
if (port_two_works) port_device_types[@intFromEnum(ps2.Port.Two)] = spawnIdentifiedDriver(controller, .Two);
|
||||||
} else {
|
} else {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus: no PS/2 controller found\n");
|
_ = runtime.system.write("system/drivers/ps2-bus: no PS/2 controller found\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const controller = maybe_controller.?;
|
||||||
|
const interrupt_index = maybe_interrupt_index orelse {
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus: controller is missing its IRQ\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The endpoint the child drivers attach to and IRQ1 wakes. Registered under a
|
||||||
|
// well-known id so the children can find it, the way input subscribers find
|
||||||
|
// the input service.
|
||||||
|
const endpoint = ipc.createIpcEndpoint() orelse {
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus: no endpoint\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if (!ipc.register(.ps2_bus, endpoint)) {
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus: register failed\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// From here on, only the interrupt path reads the data port. Drop anything a
|
||||||
|
// device sent between enable-scanning and now, bind the IRQs, and only then
|
||||||
|
// let the controller raise them — an interrupt with nobody bound is lost.
|
||||||
|
controller.drainOutputBuffer();
|
||||||
|
if (!device.irqBind(controller.device_id, interrupt_index, endpoint)) {
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus: irq_bind failed\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Port 2's interrupt (IRQ12) is enumerated on the auxiliary device's own ACPI
|
||||||
|
// node (PNP0F13), not on the controller's — so if port 2 carries a device,
|
||||||
|
// claim that node too and route its IRQ to the same endpoint. The IRQ belongs
|
||||||
|
// to the *port*, whatever device identify found on it.
|
||||||
|
var maybe_auxiliary_interrupt: ?struct { device_id: u64, interrupt_index: u64, gsi: u64 } = null;
|
||||||
|
if (port_device_types[@intFromEnum(ps2.Port.Two)] != null) {
|
||||||
|
if (device.findDeviceDescriptorByHid(buffer, acpi_ids.HardwareId.ps2_mouse.hid())) |descriptor| {
|
||||||
|
if (findInterruptResourceIndex(descriptor)) |auxiliary_index| {
|
||||||
|
if (device.claim(descriptor.id) and device.irqBind(descriptor.id, auxiliary_index, endpoint)) {
|
||||||
|
maybe_auxiliary_interrupt = .{
|
||||||
|
.device_id = descriptor.id,
|
||||||
|
.interrupt_index = auxiliary_index,
|
||||||
|
.gsi = descriptor.resources[auxiliary_index].start,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus: auxiliary irq_bind failed\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var configuration = controller.readConfigurationByte() orelse {
|
||||||
|
_ = runtime.system.write("system/drivers/ps2-bus: controller configuration timed out\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if (port_device_types[@intFromEnum(ps2.Port.One)] != null) configuration |= ps2.Port.One.interruptBit();
|
||||||
|
if (maybe_auxiliary_interrupt != null) configuration |= ps2.Port.Two.interruptBit();
|
||||||
|
_ = controller.writeConfigurationByte(configuration);
|
||||||
|
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus: ok\n");
|
_ = runtime.system.write("system/drivers/ps2-bus: ok\n");
|
||||||
while (true) runtime.system.sleep(1000);
|
|
||||||
|
// The forwarding loop: an IRQ1 notification drains the output buffer, routing
|
||||||
|
// each byte to the attached driver of the port it came from; a client message
|
||||||
|
// is a child driver's AttachRequest.
|
||||||
|
var reply_buffer: [@sizeOf(ps2.AttachReply)]u8 = undefined;
|
||||||
|
var reply_len: usize = 0;
|
||||||
|
var receive: [@sizeOf(ps2.AttachRequest)]u8 = undefined;
|
||||||
|
while (true) {
|
||||||
|
const got = ipc.replyWait(endpoint, reply_buffer[0..reply_len], &receive, null);
|
||||||
|
if (got.isNotification()) {
|
||||||
|
reply_len = 0;
|
||||||
|
if (got.isMessage() or got.isChildExit()) continue; // nothing sends us these
|
||||||
|
while (true) {
|
||||||
|
const current_status = ps2.status(controller.device_id, controller.status_index);
|
||||||
|
if (current_status & ps2.status_output_buffer_full == 0) break;
|
||||||
|
const byte = device.ioRead(controller.device_id, controller.data_index, 0, 1) orelse break;
|
||||||
|
const port: ps2.Port = if (current_status & ps2.status_auxiliary_output != 0) .Two else .One;
|
||||||
|
if (port_endpoints[@intFromEnum(port)]) |child| {
|
||||||
|
const forwarded = ps2.ForwardedByte{ .port = @intFromEnum(port), .byte = byte };
|
||||||
|
_ = ipc.send(child, std.mem.asBytes(&forwarded));
|
||||||
|
}
|
||||||
|
// An unattached port's byte is dropped — e.g. a keystroke before
|
||||||
|
// the keyboard driver has attached.
|
||||||
|
}
|
||||||
|
// Re-arm the line that woke us: the notification badge carries the
|
||||||
|
// GSI, and IRQ1 and IRQ12 are acked through different device claims.
|
||||||
|
if (maybe_auxiliary_interrupt) |auxiliary| {
|
||||||
|
if (got.source() == auxiliary.gsi) {
|
||||||
|
_ = device.irqAck(auxiliary.device_id, auxiliary.interrupt_index);
|
||||||
|
} else {
|
||||||
|
_ = device.irqAck(controller.device_id, interrupt_index);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_ = device.irqAck(controller.device_id, interrupt_index);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
reply_len = handleAttach(receive[0..got.len], got, &reply_buffer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const panic = runtime.panic;
|
pub const panic = runtime.panic;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
//! shared definitions between the different PS/2 drivers
|
//! shared definitions between the different PS/2 drivers
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const runtime = @import("runtime");
|
const runtime = @import("runtime");
|
||||||
|
const acpi_ids = @import("acpi-ids");
|
||||||
const device = runtime.device;
|
const device = runtime.device;
|
||||||
const system = runtime.system;
|
const system = runtime.system;
|
||||||
|
|
||||||
@@ -49,12 +50,15 @@ pub const cmd_write_second_port_output: u8 = 0xD3; // write next data byte to th
|
|||||||
pub const cmd_write_second_port_input: u8 = 0xD4; // write next data byte to the second port input buffer (to the mouse)
|
pub const cmd_write_second_port_input: u8 = 0xD4; // write next data byte to the second port input buffer (to the mouse)
|
||||||
pub const cmd_pulse_system_reset: u8 = 0xFE; // pulse output line 0 low: resets the CPU
|
pub const cmd_pulse_system_reset: u8 = 0xFE; // pulse output line 0 low: resets the CPU
|
||||||
|
|
||||||
/// PS/2 status register bits (read from the status port, 0x64). Bits 4 and 5
|
/// PS/2 status register bits (read from the status port, 0x64). Bit 4 is
|
||||||
/// are chipset-specific and intentionally omitted.
|
/// chipset-specific and intentionally omitted.
|
||||||
pub const status_output_buffer_full: u8 = 1 << 0; // 1 = a byte is waiting to be read from the data port
|
pub const status_output_buffer_full: u8 = 1 << 0; // 1 = a byte is waiting to be read from the data port
|
||||||
pub const status_input_buffer_full: u8 = 1 << 1; // 1 = the controller has not yet consumed the last write
|
pub const status_input_buffer_full: u8 = 1 << 1; // 1 = the controller has not yet consumed the last write
|
||||||
pub const status_system_flag: u8 = 1 << 2; // set once the controller passes POST
|
pub const status_system_flag: u8 = 1 << 2; // set once the controller passes POST
|
||||||
pub const status_command_or_data: u8 = 1 << 3; // 1 = last write was a command, 0 = data
|
pub const status_command_or_data: u8 = 1 << 3; // 1 = last write was a command, 0 = data
|
||||||
|
/// Chipset-specific in the original spec, universal in practice on dual-channel
|
||||||
|
/// controllers: set = the waiting byte came from the second port (the mouse).
|
||||||
|
pub const status_auxiliary_output: u8 = 1 << 5;
|
||||||
pub const status_timeout_error: u8 = 1 << 6; // 1 = time-out error
|
pub const status_timeout_error: u8 = 1 << 6; // 1 = time-out error
|
||||||
pub const status_parity_error: u8 = 1 << 7; // 1 = parity error
|
pub const status_parity_error: u8 = 1 << 7; // 1 = parity error
|
||||||
|
|
||||||
@@ -237,11 +241,12 @@ pub const Port = enum(u2) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// The kind of device attached to a port, as reported by the device itself in
|
/// The kind of device attached to a port, as reported by the device itself in
|
||||||
/// response to the identify command — not assumed from the port number.
|
/// response to the identify command — not assumed from the port number. Fixed
|
||||||
pub const DeviceType = enum {
|
/// `u32` values because the type also travels in an `AttachRequest`.
|
||||||
keyboard,
|
pub const DeviceType = enum(u32) {
|
||||||
mouse,
|
keyboard = 0,
|
||||||
unknown,
|
mouse = 1,
|
||||||
|
unknown = 2,
|
||||||
|
|
||||||
/// Initial-ramdisk name of the driver that serves this device type, or null
|
/// Initial-ramdisk name of the driver that serves this device type, or null
|
||||||
/// if we could not classify it.
|
/// if we could not classify it.
|
||||||
@@ -257,13 +262,55 @@ pub const DeviceType = enum {
|
|||||||
/// its command-line argument, or null if we could not classify it.
|
/// its command-line argument, or null if we could not classify it.
|
||||||
pub fn hid(self: DeviceType) ?[]const u8 {
|
pub fn hid(self: DeviceType) ?[]const u8 {
|
||||||
return switch (self) {
|
return switch (self) {
|
||||||
.keyboard => "PNP0303",
|
.keyboard => acpi_ids.HardwareId.ps2_keyboard.hid(),
|
||||||
.mouse => "PNP0F13",
|
.mouse => acpi_ids.HardwareId.ps2_mouse.hid(),
|
||||||
.unknown => null,
|
.unknown => null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// --- the bus <-> child-driver forwarding protocol -----------------------------
|
||||||
|
//
|
||||||
|
// The 8042's ports and IRQ1 live on the PNP0303 node that only the ps2-bus driver
|
||||||
|
// claims, so the child device drivers (ps2-keyboard, ps2-mouse) cannot read port
|
||||||
|
// 0x60 themselves. Instead each child **attaches**: it calls the bus's well-known
|
||||||
|
// `ps2_bus` endpoint with an `AttachRequest`, handing over its own endpoint as the
|
||||||
|
// call's capability. From then on the bus forwards every byte the device sends as
|
||||||
|
// a `ForwardedByte` via the asynchronous `ipc.send` — the IRQ path in the bus can
|
||||||
|
// never block on a slow child, and the child never touches the controller.
|
||||||
|
|
||||||
|
/// A child driver registering for its device's bytes. `device_type` is a
|
||||||
|
/// `DeviceType` value; the child's receive endpoint travels as the call's
|
||||||
|
/// capability (`send_cap`).
|
||||||
|
pub const AttachRequest = extern struct {
|
||||||
|
device_type: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// How the bus answered an `AttachRequest` (`AttachReply.status`).
|
||||||
|
pub const AttachStatus = enum(i32) {
|
||||||
|
ok = 0,
|
||||||
|
/// The request was malformed (too short to be an `AttachRequest`).
|
||||||
|
invalid_request = -1,
|
||||||
|
/// The call carried no endpoint capability to forward to.
|
||||||
|
missing_endpoint = -2,
|
||||||
|
/// No port identified a device of the requested type.
|
||||||
|
no_such_device = -3,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Reply to an `AttachRequest`. `status` is an `AttachStatus` value.
|
||||||
|
pub const AttachReply = extern struct {
|
||||||
|
status: i32,
|
||||||
|
_padding: u32 = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// One raw byte read from the data port, forwarded to the attached child whose
|
||||||
|
/// port it came from (routed by the status register's auxiliary-output bit).
|
||||||
|
pub const ForwardedByte = extern struct {
|
||||||
|
/// The `Port` the byte came from, as `@intFromEnum`.
|
||||||
|
port: u32,
|
||||||
|
byte: u32,
|
||||||
|
};
|
||||||
|
|
||||||
/// A single PS/2 (8042) controller. Construct one with `Controller.init` and
|
/// A single PS/2 (8042) controller. Construct one with `Controller.init` and
|
||||||
/// drive the controller through its methods; there is only ever one 8042 per
|
/// drive the controller through its methods; there is only ever one 8042 per
|
||||||
/// machine, but holding the resolved resource indices in an instance keeps the
|
/// machine, but holding the resolved resource indices in an instance keeps the
|
||||||
|
|||||||
@@ -0,0 +1,389 @@
|
|||||||
|
//! PS/2 scancode set 2 → USB HID usage decoding, plus the keyboard state a driver
|
||||||
|
//! needs on top of it (pressed keys, modifier tracking, caps-lock toggle).
|
||||||
|
//!
|
||||||
|
//! Set 2 is what a keyboard sends when the 8042's legacy set-1 translation is off —
|
||||||
|
//! which is how ps2-bus.zig deliberately configures the controller. A key's **make**
|
||||||
|
//! code is one byte (two with an `E0` prefix for the "extended" keys added after the
|
||||||
|
//! original AT layout); its **break** code is the same code behind an `F0` prefix.
|
||||||
|
//! Pause alone is an eight-byte `E1` sequence with no break.
|
||||||
|
//!
|
||||||
|
//! The output vocabulary is USB HID keyboard-page usages (a=4, enter=40, ...), the
|
||||||
|
//! same numbering the input protocol's `Keycode` and the xkeyboard-config layout
|
||||||
|
//! tables use — so a decoded usage indexes a layout directly.
|
||||||
|
//!
|
||||||
|
//! Everything here is pure (no imports beyond `std`, no IO), so it is host-testable:
|
||||||
|
//! the tests at the bottom run under `zig build test`.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
// --- USB HID usages the state machine itself needs to recognize --------------
|
||||||
|
|
||||||
|
pub const usage_caps_lock: u8 = 0x39;
|
||||||
|
pub const usage_left_control: u8 = 0xE0;
|
||||||
|
pub const usage_left_shift: u8 = 0xE1;
|
||||||
|
pub const usage_left_alt: u8 = 0xE2;
|
||||||
|
pub const usage_right_control: u8 = 0xE4;
|
||||||
|
pub const usage_right_shift: u8 = 0xE5;
|
||||||
|
pub const usage_right_alt: u8 = 0xE6; // AltGr — selects XKB level 3
|
||||||
|
|
||||||
|
// --- scancode set 2 → HID usage tables ---------------------------------------
|
||||||
|
|
||||||
|
/// Single-byte (non-`E0`) make codes. Zero means "no key" — protocol bytes (ACK,
|
||||||
|
/// BAT results) and reserved codes land there and decode to nothing.
|
||||||
|
pub const set2_base: [256]u8 = blk: {
|
||||||
|
var table = [_]u8{0} ** 256;
|
||||||
|
// function row
|
||||||
|
table[0x01] = 0x42; // F9
|
||||||
|
table[0x03] = 0x3E; // F5
|
||||||
|
table[0x04] = 0x3C; // F3
|
||||||
|
table[0x05] = 0x3A; // F1
|
||||||
|
table[0x06] = 0x3B; // F2
|
||||||
|
table[0x07] = 0x45; // F12
|
||||||
|
table[0x09] = 0x43; // F10
|
||||||
|
table[0x0A] = 0x41; // F8
|
||||||
|
table[0x0B] = 0x3F; // F6
|
||||||
|
table[0x0C] = 0x3D; // F4
|
||||||
|
table[0x78] = 0x44; // F11
|
||||||
|
table[0x83] = 0x40; // F7
|
||||||
|
// letters
|
||||||
|
table[0x1C] = 0x04; // A
|
||||||
|
table[0x32] = 0x05; // B
|
||||||
|
table[0x21] = 0x06; // C
|
||||||
|
table[0x23] = 0x07; // D
|
||||||
|
table[0x24] = 0x08; // E
|
||||||
|
table[0x2B] = 0x09; // F
|
||||||
|
table[0x34] = 0x0A; // G
|
||||||
|
table[0x33] = 0x0B; // H
|
||||||
|
table[0x43] = 0x0C; // I
|
||||||
|
table[0x3B] = 0x0D; // J
|
||||||
|
table[0x42] = 0x0E; // K
|
||||||
|
table[0x4B] = 0x0F; // L
|
||||||
|
table[0x3A] = 0x10; // M
|
||||||
|
table[0x31] = 0x11; // N
|
||||||
|
table[0x44] = 0x12; // O
|
||||||
|
table[0x4D] = 0x13; // P
|
||||||
|
table[0x15] = 0x14; // Q
|
||||||
|
table[0x2D] = 0x15; // R
|
||||||
|
table[0x1B] = 0x16; // S
|
||||||
|
table[0x2C] = 0x17; // T
|
||||||
|
table[0x3C] = 0x18; // U
|
||||||
|
table[0x2A] = 0x19; // V
|
||||||
|
table[0x1D] = 0x1A; // W
|
||||||
|
table[0x22] = 0x1B; // X
|
||||||
|
table[0x35] = 0x1C; // Y
|
||||||
|
table[0x1A] = 0x1D; // Z
|
||||||
|
// digit row
|
||||||
|
table[0x16] = 0x1E; // 1
|
||||||
|
table[0x1E] = 0x1F; // 2
|
||||||
|
table[0x26] = 0x20; // 3
|
||||||
|
table[0x25] = 0x21; // 4
|
||||||
|
table[0x2E] = 0x22; // 5
|
||||||
|
table[0x36] = 0x23; // 6
|
||||||
|
table[0x3D] = 0x24; // 7
|
||||||
|
table[0x3E] = 0x25; // 8
|
||||||
|
table[0x46] = 0x26; // 9
|
||||||
|
table[0x45] = 0x27; // 0
|
||||||
|
// control and whitespace
|
||||||
|
table[0x5A] = 0x28; // Enter
|
||||||
|
table[0x76] = 0x29; // Escape
|
||||||
|
table[0x66] = 0x2A; // Backspace
|
||||||
|
table[0x0D] = 0x2B; // Tab
|
||||||
|
table[0x29] = 0x2C; // Space
|
||||||
|
// punctuation
|
||||||
|
table[0x4E] = 0x2D; // - _
|
||||||
|
table[0x55] = 0x2E; // = +
|
||||||
|
table[0x54] = 0x2F; // [ {
|
||||||
|
table[0x5B] = 0x30; // ] }
|
||||||
|
table[0x5D] = 0x31; // \ | (non-US hash on ISO boards, same position)
|
||||||
|
table[0x4C] = 0x33; // ; :
|
||||||
|
table[0x52] = 0x34; // ' "
|
||||||
|
table[0x0E] = 0x35; // ` ~
|
||||||
|
table[0x41] = 0x36; // , <
|
||||||
|
table[0x49] = 0x37; // . >
|
||||||
|
table[0x4A] = 0x38; // / ?
|
||||||
|
table[0x61] = 0x64; // non-US backslash (the extra ISO key between shift and Z)
|
||||||
|
// locks
|
||||||
|
table[0x58] = usage_caps_lock;
|
||||||
|
table[0x77] = 0x53; // Num Lock
|
||||||
|
table[0x7E] = 0x47; // Scroll Lock
|
||||||
|
// keypad
|
||||||
|
table[0x7C] = 0x55; // keypad *
|
||||||
|
table[0x7B] = 0x56; // keypad -
|
||||||
|
table[0x79] = 0x57; // keypad +
|
||||||
|
table[0x69] = 0x59; // keypad 1
|
||||||
|
table[0x72] = 0x5A; // keypad 2
|
||||||
|
table[0x7A] = 0x5B; // keypad 3
|
||||||
|
table[0x6B] = 0x5C; // keypad 4
|
||||||
|
table[0x73] = 0x5D; // keypad 5
|
||||||
|
table[0x74] = 0x5E; // keypad 6
|
||||||
|
table[0x6C] = 0x5F; // keypad 7
|
||||||
|
table[0x75] = 0x60; // keypad 8
|
||||||
|
table[0x7D] = 0x61; // keypad 9
|
||||||
|
table[0x70] = 0x62; // keypad 0
|
||||||
|
table[0x71] = 0x63; // keypad .
|
||||||
|
// modifiers
|
||||||
|
table[0x14] = usage_left_control;
|
||||||
|
table[0x12] = usage_left_shift;
|
||||||
|
table[0x11] = usage_left_alt;
|
||||||
|
table[0x59] = usage_right_shift;
|
||||||
|
break :blk table;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// `E0`-prefixed make codes. `E0 12` is the "fake shift" the keyboard wraps around
|
||||||
|
/// Print Screen and navigation keys when a real shift is involved; it maps to zero
|
||||||
|
/// here, so it decodes to nothing and only the real key comes through.
|
||||||
|
pub const set2_extended: [256]u8 = blk: {
|
||||||
|
var table = [_]u8{0} ** 256;
|
||||||
|
table[0x11] = usage_right_alt;
|
||||||
|
table[0x14] = usage_right_control;
|
||||||
|
table[0x1F] = 0xE3; // left GUI
|
||||||
|
table[0x27] = 0xE7; // right GUI
|
||||||
|
table[0x2F] = 0x65; // application (menu)
|
||||||
|
table[0x7C] = 0x46; // Print Screen (arrives as E0 12 E0 7C; the E0 12 decodes to nothing)
|
||||||
|
table[0x4A] = 0x54; // keypad /
|
||||||
|
table[0x5A] = 0x58; // keypad Enter
|
||||||
|
table[0x70] = 0x49; // Insert
|
||||||
|
table[0x6C] = 0x4A; // Home
|
||||||
|
table[0x7D] = 0x4B; // Page Up
|
||||||
|
table[0x71] = 0x4C; // Delete
|
||||||
|
table[0x69] = 0x4D; // End
|
||||||
|
table[0x7A] = 0x4E; // Page Down
|
||||||
|
table[0x74] = 0x4F; // right arrow
|
||||||
|
table[0x6B] = 0x50; // left arrow
|
||||||
|
table[0x72] = 0x51; // down arrow
|
||||||
|
table[0x75] = 0x52; // up arrow
|
||||||
|
break :blk table;
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- the byte-stream decoder --------------------------------------------------
|
||||||
|
|
||||||
|
/// One decoded key transition: which key (as a USB HID usage) and whether this is
|
||||||
|
/// a make (press or typematic repeat) or a break (release).
|
||||||
|
pub const DecodedKey = struct {
|
||||||
|
usage: u8,
|
||||||
|
make: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Turns the raw set-2 byte stream into `DecodedKey`s. Feed it every byte the
|
||||||
|
/// keyboard sends; most bytes complete a key and return one, prefix bytes return
|
||||||
|
/// null and arm the state machine for the next byte.
|
||||||
|
pub const Decoder = struct {
|
||||||
|
const State = enum {
|
||||||
|
idle,
|
||||||
|
extended, // saw E0
|
||||||
|
break_prefix, // saw F0
|
||||||
|
extended_break, // saw E0 F0
|
||||||
|
pause_skip, // inside the 8-byte E1 Pause sequence
|
||||||
|
};
|
||||||
|
|
||||||
|
state: State = .idle,
|
||||||
|
/// Bytes still to swallow in `pause_skip`.
|
||||||
|
skip: u8 = 0,
|
||||||
|
|
||||||
|
/// The whole Pause make sequence is `E1 14 77 E1 F0 14 F0 77` — seven bytes
|
||||||
|
/// after the leading `E1`, and no break sequence ever follows.
|
||||||
|
const pause_bytes_after_e1: u8 = 7;
|
||||||
|
|
||||||
|
pub fn feed(self: *Decoder, byte: u8) ?DecodedKey {
|
||||||
|
switch (self.state) {
|
||||||
|
.idle => switch (byte) {
|
||||||
|
0xE0 => self.state = .extended,
|
||||||
|
0xF0 => self.state = .break_prefix,
|
||||||
|
0xE1 => {
|
||||||
|
self.state = .pause_skip;
|
||||||
|
self.skip = pause_bytes_after_e1;
|
||||||
|
},
|
||||||
|
// Anything else is a make code — or a protocol byte (0xFA ACK,
|
||||||
|
// 0xAA BAT-passed, 0xEE echo, ...), which the tables map to zero.
|
||||||
|
else => return decoded(set2_base[byte], true),
|
||||||
|
},
|
||||||
|
.extended => switch (byte) {
|
||||||
|
0xF0 => self.state = .extended_break,
|
||||||
|
else => {
|
||||||
|
self.state = .idle;
|
||||||
|
return decoded(set2_extended[byte], true);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.break_prefix => {
|
||||||
|
self.state = .idle;
|
||||||
|
return decoded(set2_base[byte], false);
|
||||||
|
},
|
||||||
|
.extended_break => {
|
||||||
|
self.state = .idle;
|
||||||
|
return decoded(set2_extended[byte], false);
|
||||||
|
},
|
||||||
|
.pause_skip => {
|
||||||
|
self.skip -= 1;
|
||||||
|
if (self.skip == 0) self.state = .idle;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decoded(usage: u8, make: bool) ?DecodedKey {
|
||||||
|
if (usage == 0) return null; // unmapped or a protocol byte
|
||||||
|
return .{ .usage = usage, .make = make };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- driver-side keyboard state -----------------------------------------------
|
||||||
|
|
||||||
|
/// What a key transition did, plus the modifier state to stamp on the resulting
|
||||||
|
/// events (snapshotted after the transition was applied).
|
||||||
|
pub const Transition = struct {
|
||||||
|
pub const Action = enum {
|
||||||
|
pressed, // physical make of a key that was up
|
||||||
|
repeated, // typematic make of a key already down — no new key_down
|
||||||
|
released, // physical break
|
||||||
|
};
|
||||||
|
action: Action,
|
||||||
|
modifiers: ModifierSnapshot,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The modifier state at one instant, in both vocabularies a driver needs: the
|
||||||
|
/// input protocol's coarse bits (shift/control/alt) and the level-selection
|
||||||
|
/// inputs xkeyboard-config takes (shift, caps_lock, AltGr as level3).
|
||||||
|
pub const ModifierSnapshot = struct {
|
||||||
|
shift: bool, // either shift held
|
||||||
|
control: bool, // either control held
|
||||||
|
alt: bool, // either alt held (including AltGr)
|
||||||
|
right_alt: bool, // AltGr specifically — the XKB level-3 selector
|
||||||
|
caps_lock: bool, // the toggle, not the key
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Tracks which keys are physically down and the caps-lock toggle, and classifies
|
||||||
|
/// each decoded transition. Pure state — no IO — so repeat detection and modifier
|
||||||
|
/// snapshots are host-testable.
|
||||||
|
pub const KeyboardState = struct {
|
||||||
|
/// One bit per HID usage: set while the key is physically down.
|
||||||
|
pressed: [32]u8 = [_]u8{0} ** 32,
|
||||||
|
caps_lock: bool = false,
|
||||||
|
|
||||||
|
pub fn apply(self: *KeyboardState, key: DecodedKey) Transition {
|
||||||
|
const already_down = self.isPressed(key.usage);
|
||||||
|
if (key.make) {
|
||||||
|
if (!already_down) {
|
||||||
|
self.setPressed(key.usage, true);
|
||||||
|
if (key.usage == usage_caps_lock) self.caps_lock = !self.caps_lock;
|
||||||
|
}
|
||||||
|
return .{
|
||||||
|
.action = if (already_down) .repeated else .pressed,
|
||||||
|
.modifiers = self.snapshot(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
self.setPressed(key.usage, false);
|
||||||
|
return .{ .action = .released, .modifiers = self.snapshot() };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isPressed(self: *const KeyboardState, usage: u8) bool {
|
||||||
|
return self.pressed[usage / 8] & (@as(u8, 1) << @intCast(usage % 8)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setPressed(self: *KeyboardState, usage: u8, down: bool) void {
|
||||||
|
const bit = @as(u8, 1) << @intCast(usage % 8);
|
||||||
|
if (down) {
|
||||||
|
self.pressed[usage / 8] |= bit;
|
||||||
|
} else {
|
||||||
|
self.pressed[usage / 8] &= ~bit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn snapshot(self: *const KeyboardState) ModifierSnapshot {
|
||||||
|
const right_alt = self.isPressed(usage_right_alt);
|
||||||
|
return .{
|
||||||
|
.shift = self.isPressed(usage_left_shift) or self.isPressed(usage_right_shift),
|
||||||
|
.control = self.isPressed(usage_left_control) or self.isPressed(usage_right_control),
|
||||||
|
.alt = self.isPressed(usage_left_alt) or right_alt,
|
||||||
|
.right_alt = right_alt,
|
||||||
|
.caps_lock = self.caps_lock,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- tests (host-run via `zig build test`) ------------------------------------
|
||||||
|
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
/// Feed `bytes` and return the single DecodedKey they should produce (fails the
|
||||||
|
/// test if they produce none or more than one).
|
||||||
|
fn feedOne(decoder: *Decoder, bytes: []const u8) !DecodedKey {
|
||||||
|
var result: ?DecodedKey = null;
|
||||||
|
for (bytes) |byte| {
|
||||||
|
if (decoder.feed(byte)) |key| {
|
||||||
|
try testing.expect(result == null);
|
||||||
|
result = key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result orelse error.TestExpectedResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn feedNone(decoder: *Decoder, bytes: []const u8) !void {
|
||||||
|
for (bytes) |byte| try testing.expectEqual(@as(?DecodedKey, null), decoder.feed(byte));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "base make and break: A" {
|
||||||
|
var decoder = Decoder{};
|
||||||
|
try testing.expectEqual(DecodedKey{ .usage = 0x04, .make = true }, try feedOne(&decoder, &.{0x1C}));
|
||||||
|
try testing.expectEqual(DecodedKey{ .usage = 0x04, .make = false }, try feedOne(&decoder, &.{ 0xF0, 0x1C }));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "extended make and break: right arrow" {
|
||||||
|
var decoder = Decoder{};
|
||||||
|
try testing.expectEqual(DecodedKey{ .usage = 0x4F, .make = true }, try feedOne(&decoder, &.{ 0xE0, 0x74 }));
|
||||||
|
try testing.expectEqual(DecodedKey{ .usage = 0x4F, .make = false }, try feedOne(&decoder, &.{ 0xE0, 0xF0, 0x74 }));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "pause: the E1 sequence is consumed silently" {
|
||||||
|
var decoder = Decoder{};
|
||||||
|
try feedNone(&decoder, &.{ 0xE1, 0x14, 0x77, 0xE1, 0xF0, 0x14, 0xF0, 0x77 });
|
||||||
|
// The decoder is back in idle: an ordinary key still decodes.
|
||||||
|
try testing.expectEqual(DecodedKey{ .usage = 0x04, .make = true }, try feedOne(&decoder, &.{0x1C}));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "protocol bytes decode to nothing" {
|
||||||
|
var decoder = Decoder{};
|
||||||
|
try feedNone(&decoder, &.{ 0xFA, 0xAA, 0xEE }); // ACK, BAT-passed, echo
|
||||||
|
}
|
||||||
|
|
||||||
|
test "print screen: the fake-shift E0 12 decodes to nothing" {
|
||||||
|
var decoder = Decoder{};
|
||||||
|
try feedNone(&decoder, &.{ 0xE0, 0x12 });
|
||||||
|
try testing.expectEqual(DecodedKey{ .usage = 0x46, .make = true }, try feedOne(&decoder, &.{ 0xE0, 0x7C }));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "typematic repeat is classified, not re-pressed" {
|
||||||
|
var state = KeyboardState{};
|
||||||
|
const a = DecodedKey{ .usage = 0x04, .make = true };
|
||||||
|
try testing.expectEqual(Transition.Action.pressed, state.apply(a).action);
|
||||||
|
try testing.expectEqual(Transition.Action.repeated, state.apply(a).action);
|
||||||
|
try testing.expectEqual(Transition.Action.repeated, state.apply(a).action);
|
||||||
|
try testing.expectEqual(Transition.Action.released, state.apply(.{ .usage = 0x04, .make = false }).action);
|
||||||
|
try testing.expectEqual(Transition.Action.pressed, state.apply(a).action);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "shift held shows in the snapshot of other keys" {
|
||||||
|
var state = KeyboardState{};
|
||||||
|
_ = state.apply(.{ .usage = usage_left_shift, .make = true });
|
||||||
|
const transition = state.apply(.{ .usage = 0x04, .make = true });
|
||||||
|
try testing.expect(transition.modifiers.shift);
|
||||||
|
try testing.expect(!transition.modifiers.control);
|
||||||
|
_ = state.apply(.{ .usage = usage_left_shift, .make = false });
|
||||||
|
_ = state.apply(.{ .usage = 0x04, .make = false });
|
||||||
|
try testing.expect(!state.apply(.{ .usage = 0x04, .make = true }).modifiers.shift);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "right alt reports both alt and the level-3 selector" {
|
||||||
|
var state = KeyboardState{};
|
||||||
|
_ = state.apply(.{ .usage = usage_right_alt, .make = true });
|
||||||
|
const transition = state.apply(.{ .usage = 0x04, .make = true });
|
||||||
|
try testing.expect(transition.modifiers.alt);
|
||||||
|
try testing.expect(transition.modifiers.right_alt);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "caps lock toggles on make, not on repeat or break" {
|
||||||
|
var state = KeyboardState{};
|
||||||
|
try testing.expect(state.apply(.{ .usage = usage_caps_lock, .make = true }).modifiers.caps_lock);
|
||||||
|
try testing.expect(state.apply(.{ .usage = usage_caps_lock, .make = true }).modifiers.caps_lock); // repeat
|
||||||
|
try testing.expect(state.apply(.{ .usage = usage_caps_lock, .make = false }).modifiers.caps_lock);
|
||||||
|
try testing.expect(!state.apply(.{ .usage = usage_caps_lock, .make = true }).modifiers.caps_lock); // second press: off
|
||||||
|
}
|
||||||
@@ -745,7 +745,6 @@ fn systemDebugWrite(state: *architecture.CpuState) void {
|
|||||||
write_len = len;
|
write_len = len;
|
||||||
write_from_user = architecture.fromUser(state);
|
write_from_user = architecture.fromUser(state);
|
||||||
write_count += 1;
|
write_count += 1;
|
||||||
if (write_at_line_start) log.write("DANOS-INIT: ");
|
|
||||||
log.write(source[0..len]);
|
log.write(source[0..len]);
|
||||||
if (len != 0) write_at_line_start = source[len - 1] == '\n';
|
if (len != 0) write_at_line_start = source[len - 1] == '\n';
|
||||||
architecture.setSystemCallResult(state, len);
|
architecture.setSystemCallResult(state, len);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
//! that redundancy so the manager is the sole owner of driver spawning.)
|
//! that redundancy so the manager is the sole owner of driver spawning.)
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const runtime = @import("runtime");
|
const runtime = @import("runtime");
|
||||||
|
const acpi_ids = @import("acpi-ids");
|
||||||
const device = runtime.device;
|
const device = runtime.device;
|
||||||
const system = runtime.system;
|
const system = runtime.system;
|
||||||
|
|
||||||
@@ -34,9 +35,11 @@ fn driverFor(d: device.DeviceDescriptor) ?[]const u8 {
|
|||||||
if (d.class == @intFromEnum(device.DeviceClass.timer)) return "hpet";
|
if (d.class == @intFromEnum(device.DeviceClass.timer)) return "hpet";
|
||||||
// detect device via hid
|
// detect device via hid
|
||||||
const hid = d.hid[0..@intCast(d.hid_len)];
|
const hid = d.hid[0..@intCast(d.hid_len)];
|
||||||
if (std.mem.eql(u8, hid, "PNP0303") or std.mem.eql(u8, hid, "PNP0F13")) return "ps2-bus";
|
const id = acpi_ids.HardwareId.fromHid(hid) orelse return null;
|
||||||
|
return switch (id) {
|
||||||
return null;
|
.ps2_keyboard, .ps2_mouse => "ps2-bus",
|
||||||
|
else => null,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -73,16 +73,125 @@ pub const modifier_shift: u32 = 1 << 0;
|
|||||||
pub const modifier_control: u32 = 1 << 1;
|
pub const modifier_control: u32 = 1 << 1;
|
||||||
pub const modifier_alt: u32 = 1 << 2;
|
pub const modifier_alt: u32 = 1 << 2;
|
||||||
|
|
||||||
/// A minimal danos-native keycode namespace — enough for the synthetic source and to show
|
/// The danos-native keycode namespace: USB HID keyboard-page usages (page 0x07), the
|
||||||
/// the shape. A real set (USB HID usage-style) fills in with the scancode decoder.
|
/// numbering the PS/2 scancode decoder emits and the xkeyboard-config layout tables are
|
||||||
|
/// indexed by. Non-exhaustive, so an unnamed usage still travels as a valid value.
|
||||||
pub const Keycode = enum(u32) {
|
pub const Keycode = enum(u32) {
|
||||||
unknown = 0,
|
unknown = 0,
|
||||||
a = 4, // deliberately USB-HID-usage-aligned so a real decoder can extend this
|
// letters
|
||||||
|
a = 4,
|
||||||
b = 5,
|
b = 5,
|
||||||
c = 6,
|
c = 6,
|
||||||
d = 7,
|
d = 7,
|
||||||
e = 8,
|
e = 8,
|
||||||
|
f = 9,
|
||||||
|
g = 10,
|
||||||
|
h = 11,
|
||||||
|
i = 12,
|
||||||
|
j = 13,
|
||||||
|
k = 14,
|
||||||
|
l = 15,
|
||||||
|
m = 16,
|
||||||
|
n = 17,
|
||||||
|
o = 18,
|
||||||
|
p = 19,
|
||||||
|
q = 20,
|
||||||
|
r = 21,
|
||||||
|
s = 22,
|
||||||
|
t = 23,
|
||||||
|
u = 24,
|
||||||
|
v = 25,
|
||||||
|
w = 26,
|
||||||
|
x = 27,
|
||||||
|
y = 28,
|
||||||
|
z = 29,
|
||||||
|
// digit row
|
||||||
|
one = 30,
|
||||||
|
two = 31,
|
||||||
|
three = 32,
|
||||||
|
four = 33,
|
||||||
|
five = 34,
|
||||||
|
six = 35,
|
||||||
|
seven = 36,
|
||||||
|
eight = 37,
|
||||||
|
nine = 38,
|
||||||
|
zero = 39,
|
||||||
|
// control and whitespace
|
||||||
enter = 40,
|
enter = 40,
|
||||||
|
escape = 41,
|
||||||
|
backspace = 42,
|
||||||
|
tab = 43,
|
||||||
|
spacebar = 44,
|
||||||
|
// punctuation
|
||||||
|
minus = 45,
|
||||||
|
equal = 46,
|
||||||
|
left_bracket = 47,
|
||||||
|
right_bracket = 48,
|
||||||
|
backslash = 49,
|
||||||
|
non_us_hash = 50,
|
||||||
|
semicolon = 51,
|
||||||
|
apostrophe = 52,
|
||||||
|
grave = 53,
|
||||||
|
comma = 54,
|
||||||
|
period = 55,
|
||||||
|
slash = 56,
|
||||||
|
caps_lock = 57,
|
||||||
|
// function row
|
||||||
|
f1 = 58,
|
||||||
|
f2 = 59,
|
||||||
|
f3 = 60,
|
||||||
|
f4 = 61,
|
||||||
|
f5 = 62,
|
||||||
|
f6 = 63,
|
||||||
|
f7 = 64,
|
||||||
|
f8 = 65,
|
||||||
|
f9 = 66,
|
||||||
|
f10 = 67,
|
||||||
|
f11 = 68,
|
||||||
|
f12 = 69,
|
||||||
|
print_screen = 70,
|
||||||
|
scroll_lock = 71,
|
||||||
|
pause = 72,
|
||||||
|
// navigation
|
||||||
|
insert = 73,
|
||||||
|
home = 74,
|
||||||
|
page_up = 75,
|
||||||
|
delete = 76,
|
||||||
|
end = 77,
|
||||||
|
page_down = 78,
|
||||||
|
right_arrow = 79,
|
||||||
|
left_arrow = 80,
|
||||||
|
down_arrow = 81,
|
||||||
|
up_arrow = 82,
|
||||||
|
// keypad
|
||||||
|
num_lock = 83,
|
||||||
|
keypad_slash = 84,
|
||||||
|
keypad_asterisk = 85,
|
||||||
|
keypad_minus = 86,
|
||||||
|
keypad_plus = 87,
|
||||||
|
keypad_enter = 88,
|
||||||
|
keypad_one = 89,
|
||||||
|
keypad_two = 90,
|
||||||
|
keypad_three = 91,
|
||||||
|
keypad_four = 92,
|
||||||
|
keypad_five = 93,
|
||||||
|
keypad_six = 94,
|
||||||
|
keypad_seven = 95,
|
||||||
|
keypad_eight = 96,
|
||||||
|
keypad_nine = 97,
|
||||||
|
keypad_zero = 98,
|
||||||
|
keypad_period = 99,
|
||||||
|
non_us_backslash = 100,
|
||||||
|
application = 101,
|
||||||
|
// modifiers
|
||||||
|
left_control = 224,
|
||||||
|
left_shift = 225,
|
||||||
|
left_alt = 226,
|
||||||
|
left_gui = 227,
|
||||||
|
right_control = 228,
|
||||||
|
right_shift = 229,
|
||||||
|
right_alt = 230,
|
||||||
|
right_gui = 231,
|
||||||
_,
|
_,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user