RE: ikvmserver startup, VCD module, vmass module, globals

Document initialization flow, kernel module internals, and remaining
undocumented functions. Key findings:

- main() is minimal: CreateServerSocket -> MainAcceptLoop, with
  C++ global constructors handling DeviceManager/session list init
- DeviceManager construction (0xb63c): allocates three sub-devices
- Shared memory init (0xdc10): GlobalVar, NVRAM, PS segments
- vcddev.ko: NO ioctl interface — VCD registers accessed directly
  via /dev/mem mmap at 0xF0028000. Module only handles interrupt-
  based capture completion (IRQ 22, major 25)
- ikvm_vmass.ko: USB VID=0x0EA0 PID=0x2168, Bulk-Only Mass Storage,
  3 ioctls (register/disconnect/connect), dual-LUN support,
  userspace reads CBW and writes responses via char device
- ~30 new functions documented: thread management, session cleanup,
  bandwidth throttling (select-based sleep), VCD utilities,
  message queue helpers
- 5 new global variable addresses (session count, shared memory
  pointers, connection ref count, listen socket)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Davíð Steinn Geirsson 2026-03-12 13:14:06 +00:00
parent 9e78e481aa
commit aa041556b7

View file

@ -33,9 +33,14 @@ the root filesystem. Web content at `original_binaries/server/B80000/`.
| 0x0000a9dc | SIGUSR1 handler (session kill) | Reads `/tmp/killsess`: -1=all, -2=reset, else by username |
| 0x0000acbc | Main accept loop | Spawns per-connection threads |
| 0x0000aea8 | Global init (device manager) | Creates DeviceManager at DAT_0001e560 |
| 0x0000b10c | CancelThread(pthread_t*) | pthread_cancel + pthread_detach |
| 0x0000b140 | SetCancelState | PTHREAD_CANCEL_ENABLE + ASYNCHRONOUS |
| 0x0000b160 | Init recursive mutex | pthread_mutexattr_settype(RECURSIVE) |
| 0x0000b1b0 | TriggerPortChange | Writes "-1" to /tmp/killsess, sends SIGUSR1 to self |
| 0x0000b254 | Read IKVM port from PS | Default 5900 if empty |
| 0x0000b2ec | Install SIGUSR2 handler | sigaction(SIGUSR2, handler) |
| 0x0000b2bc | Install SIGUSR1 handler | sigaction(10=SIGUSR1, handler) |
| 0x0000b2e8 | (nop) | Empty function, called before signal setup |
| 0x0000b2ec | Install SIGUSR2 handler | sigaction(12=SIGUSR2, handler) |
| 0x0000b318 | SaveMouseMode(mode) | Writes mode to /nv/MouseMode |
| 0x0000b63c | DeviceManager constructor | Allocates VCD, Keyboard, Mouse sub-objects |
| 0x0000b7b4 | DeviceManager::~dtor | |
@ -79,6 +84,8 @@ the root filesystem. Web content at `original_binaries/server/B80000/`.
| 0x0000ce5c | InitConnectionDescriptor | Stores socket fd, DeviceManager ptr, clears state |
| 0x0000ce74 | NotifyPeersSessionEvent | Sends cmd to peers with ≥ this session's privilege |
| 0x0000cf58 | NotifyPeersStatusUpdate | Sends own status to peers with ≥ own privilege |
| 0x0000d0c8 | CleanupRFBProtocol(rfb_proto) | Removes session from list, notifies peers, deletes Auth/Stream |
| 0x0000d194 | CleanupRFBProtocol2(rfb_proto) | Variant cleanup: removes from list, notifies, destroys Stream |
| 0x0000d050 | SendMessageToSession | Finds session by thread ID, sends mq message |
| 0x0000d260 | EnforceMaxSessions | Kills oldest session if >4 active sessions |
| 0x0000d334 | Per-session handler (main thread fn) | InitHandShake → Authenticate → ProtocolHandler |
@ -86,15 +93,23 @@ the root filesystem. Web content at `original_binaries/server/B80000/`.
| 0x0000d608 | CleanupSessionMQ(session_ctx) | Deletes priv info, closes/unlinks mq |
| 0x0000d6e0 | ReceiveFromMQ(session_ctx, buf) | mq_receive into buffer |
| 0x0000d740 | IsKbdMouseExclusive | Returns 0 if any session has kbd/mouse perm |
| 0x0000d800 | GetAcceptThreadId | Finds own session node by pthread_self(), returns thread_id at +0x0c |
| 0x0000d810 | FindSessionNode(thread_id) | Iterates linked list for matching thread ID |
| 0x0000d870 | GetSessionInfo(out, thread_id) | Copies 0x48-byte session info from linked list |
| 0x0000d8e8 | BroadcastMessage(session, target_tid, msg) | Looks up target, sends via mq_send |
| 0x0000d950 | KillSessionByThreadId(list, tid) | Gets session info, sends mq msg, signals thread |
| 0x0000d9d0 | InitSessionContext(ctx, accept_cb, tid) | Allocs 0x164-byte priv struct, creates mq, adds to list |
| 0x0000daa8 | InitSessionContext2(ctx, param, tid) | Variant of InitSessionContext used in accept loop |
| 0x0000dba0 | InitSessionList (global ctor) | Initializes DAT_0001e58c/DAT_0001e590 circular list |
| 0x0000dbd8 | FreeLinkedList2 | Walks list, operator_delete each node |
| 0x0000dc04 | (nop) | Empty function |
| 0x0000dc10 | SharedMemoryInit | Attaches GlobalVar/NVRAM/PS shm, registers PID |
| 0x0000dd28 | SetPermissions(username, priv, level) | Sets 4 permission bytes based on IPMI privilege |
| 0x0000ddf4 | GetVideoPermission(priv_info) | Returns byte at +0x155 |
| 0x0000de04 | GetKbdMousePermission(priv_info) | Returns byte at +0x156 |
| 0x0000de14 | GetKickPermission(priv_info) | Returns byte at +0x157 |
| 0x0000de24 | GetPowerPermission(priv_info) | Returns byte at +0x158 |
| 0x0000de30 | DecrementSessionCount(username) | Decrements /tmp/sess_<user> counter, logs to SEL |
| 0x0000e1b4 | LoadViewerLang(priv_info) | Reads /nv/IKVMViewerLang for user, stores +0x15c/+0x160 |
| 0x0000e2f0 | AuthenticateFromWebSession | Reads /tmp/sess_<user>, SSO auth, tracks session count |
| 0x0000e638 | AuthBackend | Tries web session SSO first, falls back to UtilAuthUser |
@ -141,10 +156,13 @@ the root filesystem. Web content at `original_binaries/server/B80000/`.
| 0x0000fb70 | KeepAlive broadcast to peers | Sends power status change to all sessions via mq |
| 0x000108b8 | **ProtocolHandler** (main dispatch) | Queue processing → read type → permission check → dispatch |
| 0x0001069c | ProcessCommandQueue | Drains mq + pending list, sends type 0x39 or throws disconnect |
| 0x00010b34 | ReceiveAndEnqueueMQ | mq_receive, enqueue to pending list or throw exception |
| 0x00010bec | FreeLinkedList3 | Walks list, operator_delete each node (9 refs) |
| 0x00010244 | RFBProtocol constructor | 0x28-byte object with vtable at 0x14b00 |
| 0x00010e7c | Auth constructor | Sets vtable to 0x14c64 (Auth base) |
| 0x00010fa8 | AESCrypto constructor | Calls aes_gen_tables, sets vtable to 0x14cb4 |
| 0x000110c0 | AES CBC multi-block wrapper | param2: 0=encrypt, 1=decrypt; param5=block_count |
| 0x00011020 | AES multi-block ECB (unused) | Encrypt/decrypt without CBC, unreferenced |
| 0x00011268 | **AES-128-CBC decrypt** (hardcoded key) | Key at 0x14cf0, IV at 0x14ce0 |
| 0x000112d8 | AESCrypto::Decrypt (vtable thunk) | Calls 0x11268, returns 1 |
| 0x000112e8 | **AES-128-CBC encrypt** (hardcoded key) | Same key/IV |
@ -165,6 +183,8 @@ the root filesystem. Web content at `original_binaries/server/B80000/`.
| 0x00011aec | VCD: ResetCapture | Triggers capture cycle via reg+0x18 |
| 0x00011bfc | VCD: Init | Opens /dev/vcd, validates hardware, configures registers |
| 0x00011da0 | VCD: SetFrameBufferAddr | Writes address to reg+0x00, offset to reg+0x04 |
| 0x00011df0 | VCD: VerifyLineStride (unused) | Writes reg+0x08, verifies read-back |
| 0x00011e64 | VCD: VerifyCaptureWindow (unused) | Writes reg+0x10, verifies [10:0] and [26:16] |
| 0x00011ed8 | VCD: SetSourceSelect | Selects video input (0-4) via reg+0x14 |
| 0x00011f6c | VCD: SetPixelFormat | 0=2bpp(RGB555), 1=1bpp(8bit), 2=1bpp |
| 0x000120d4 | VCD: WaitForCaptureDone | select()+read() on /dev/vcd fd, 100ms timeout |
@ -177,6 +197,8 @@ the root filesystem. Web content at `original_binaries/server/B80000/`.
| 0x00012808 | VCD: GetResolutionInfoPtr | Returns DAT_0001e594 (resolution struct) |
| 0x00012830 | VCD: GetLineStride | Returns low 16 bits of reg+0x08 |
| 0x000127ec | VCD: IsDisplayOff | True if DAT_f0030300 == 0x86 |
| 0x0001221c | VCD: SetColorLookup (unused) | Palette→RGB555 conversion, writes reg+0x24 |
| 0x00012794 | VCD: PrintStatus (unused/debug) | Prints vcd_stat register value |
| 0x00012a6c | VCD: OpenDevice | open("/dev/vcd", O_RDONLY), singleton |
| 0x00012dc8 | StreamReadBytes(stream, buf, len) | |
| 0x00012e18 | StreamRead8(stream) → u8 | |
@ -191,13 +213,33 @@ the root filesystem. Web content at `original_binaries/server/B80000/`.
| 0x0001308c | StreamWriteZeros(stream, count) | Alloc+memset+write+free |
| 0x000130f4 | ByteSwap16 (for decrypted AES data) | |
| 0x00013128 | Set bandwidth limits on stream | |
| 0x00013180 | InstallCrashSignalHandlers | Installs SIG_IGN for SIGPIPE, handlers for 15 signals |
| 0x00012c90 | Stream constructor (0x610 bytes) | |
| 0x00012d88 | Stream::Disconnect(stream) | Calls transport vtable dtor via +0x60c |
| 0x00012da8 | Stream::Connect(stream) | Calls transport vtable connect via +0x60c |
| 0x000132f0 | Socket recv wrapper | select() + recv, 60s timeout |
| 0x00013464 | Socket send wrapper | select() + send, 60s timeout, MSG_NOSIGNAL |
| 0x00013628 | Accept wrapper | select() + accept |
| 0x000136cc | Socket setup (bind/listen/connect) | SO_SNDBUF/SO_RCVBUF = 0x40000 (256KB) |
| 0x000135dc | (unused socket utility) | Socket-related helper, 0 references |
| 0x00013944 | Connection establishment | getaddrinfo + server/client mode |
| 0x000139f4 | CloseConnection(stream) | shutdown+close socket, manages ref count at DAT_0001e5a8 |
| 0x00013d70 | TimeDiffMicros | Computes time difference in microseconds |
| 0x00013db0 | MicrosToTimeval | Converts microseconds to {tv_sec, tv_usec} |
| 0x00013dec | BandwidthCounterReset | Zeros counter fields |
| 0x00013e00 | BandwidthCounterInit(obj, limit, period) | Initializes bandwidth counter object |
| 0x00013e1c | BandwidthCounterUpdate | Updates byte/time counters |
| 0x00013e38 | BandwidthCounterApply | Used by stream write to enforce limits |
| 0x00013f38 | BandwidthThrottle(counter, bytes) | Sleeps via select() to enforce bandwidth limit |
| 0x0000a2d4 | **main()** | Calls CreateServerSocket then MainAcceptLoop |
| 0x0000a2ec | SharedMemoryInit (thunk to 0xdc10) | GlobalVar/NVRAM/PS shared memory attach, registers PID |
| 0x0000abdc | KillAllSessionsInner | Iterates pending list, cancels threads, frees resources |
| 0x0000af04 | FreeLinkedList | Walks std::list, operator_delete on each node |
| 0x0000af30 | RemoveListNode | std::_List_node_base::unhook + operator_delete |
| 0x0000af54 | SpawnDetachedThread | pthread_create + pthread_detach wrapper |
| 0x0000af7c | (nop) | Empty function |
| 0x0000af84 | InstallSignalHandler(signum, handler) | sigaction wrapper (no SA_RESTART) |
| 0x0000afac | ThreadExit | pthread_exit(NULL) |
| 0x0000afb8 | CreateMessageQueue | mq_open(name, O_CREAT\|O_RDWR\|O_EXCL, 0666) |
| 0x0000b04c | DestroyMessageQueue | mq_close + mq_unlink |
| 0x0000b074 | MQSend | mq_send wrapper |
@ -222,9 +264,11 @@ the root filesystem. Web content at `original_binaries/server/B80000/`.
| | | First 16 bytes used: `2b 7e 15 16 28 ae d2 a6 ab f7 15 88 09 cf 4f 3c` |
| 0x00014e40 | 0x18 | Stream vtable |
| 0x0001d524 | var | BMP file header template (for /tmp/Snapshot.bmp) |
| 0x0001e4ac | 0x04 | `ikvmSessCount` — current IKVM session count |
| 0x0001e4b0 | 0x04 | VCD register block pointer (mmap'd hardware) |
| 0x0001e4e8 | 0x04 | VCD file descriptor (/dev/vcd) |
| 0x0001e4f0 | 0x04 | Global shared memory pointer (GlobalVar_Shm_AT) |
| 0x0001e4f0 | 0x04 | `at_p_St_GlobalVar` — GlobalVar shared memory pointer |
| 0x0001e4f4 | 0x04 | `at_p_St_PS` — Platform-Specific shared memory pointer |
| 0x0001e560 | 0x18 | **DeviceManager** global instance (6 words) |
| 0x0001e578 | 0x08 | Accept loop: pending connection linked list (std::list) |
| 0x0001e580 | 0x04 | Global: server socket object pointer |
@ -234,6 +278,8 @@ the root filesystem. Web content at `original_binaries/server/B80000/`.
| 0x0001e59c | 0x04 | VCD initialized flag (singleton guard) |
| 0x0001e5a0 | 0x04 | VCD hardware revision (from 0xF0010000 >> 24) |
| 0x0001e5a4 | 0x04 | VCD device opened flag (singleton guard) |
| 0x0001e5a8 | 0x04 | Server connection reference count |
| 0x0001e5ac | 0x04 | Server listen socket file descriptor |
#### ikvmserver RTTI Strings
@ -373,6 +419,104 @@ the root filesystem. Web content at `original_binaries/server/B80000/`.
| +0x58 | (additional session info, copy of SessionContext mq attrs) |
| +0x5c | PrivilegeInfo* pointer (copy of SessionContext+0x4c) |
#### Startup and Initialization
##### Global Constructors (before main)
C++ global constructors run before `main()`, registered via `__cxa_atexit`:
1. **FUN_0000caf0** — Initializes `std::ios_base::Init` at DAT_0001e584 (C++ iostream)
2. **FUN_0000dba0** — Initializes the session linked list at DAT_0001e58c/0x1e590 as an empty circular list
3. **FUN_0000aea8** — Initializes the pending connection list at DAT_0001e578/0x1e57c as an empty circular list, then constructs the global DeviceManager at DAT_0001e560 (calls FUN_0000b63c)
##### DeviceManager Construction (FUN_0000b63c)
The DeviceManager constructor at 0xb63c allocates and initializes three sub-objects:
1. `operator_new(0x84)`**VideoCaptureDevice** (FUN_0000bd08): opens `/dev/vcd`, initializes VCD hardware, allocates frame buffer
2. `operator_new(0x1c)`**KeyboardDevice** (FUN_0000cc08): opens `/dev/keyboard`
3. `operator_new(0x1c)`**MouseDevice** (FUN_0000e81c): opens `/dev/mouse`
Sets DeviceManager+0x01 = 640 (width), DeviceManager+0x02 = 480 (height).
##### main() Function (0xa2d4)
The main function is minimal (5 ARM instructions):
```c
int main(int argc, char **argv) {
CreateServerSocket(); // FUN_0000a430
MainAcceptLoop(argc); // FUN_0000acbc (never returns)
return 0;
}
```
No command-line arguments are processed — `argc` is passed through to the accept loop but only used as a parameter slot for `SpawnDetachedThread`.
##### Shared Memory Initialization (FUN_0000dc10 / thunk at 0xa2ec)
Called from `CreateServerSocket` (FUN_0000a430) via `thunk_FUN_0000dc10`. Attaches three shared memory segments:
1. `GlobalVar_Shm_AT(&at_p_St_GlobalVar)` — global variables shared between all BMC processes
2. `NVRAM_Shm_AT()` — persistent NVRAM storage
3. `PS_Shm_AT(&at_p_St_PS)` — platform-specific configuration (contains IKVM port at +0x1b42)
On success, registers this process's PID in the global variable table:
- Stores PID at `GlobalVar[0x24d4 + counter*4]` (process registry array)
- Increments counter at `GlobalVar[0x2534]`
- Also stores PID at `GlobalVar[0x2724]` (ikvmserver-specific PID slot)
On failure at any step, prints to stderr and returns without initializing.
##### CreateServerSocket (FUN_0000a430)
Allocates a 4-byte server socket object (via `operator_new(4)`), calls `SharedMemoryInit`, and stores the result at DAT_0001e580 (the global server socket pointer). Uses singleton pattern — only allocates if DAT_0001e580 is NULL.
##### Main Accept Loop (FUN_0000acbc)
The accept loop performs initialization steps then enters an infinite loop:
1. **Read IKVM port**: Calls FUN_0000b254 which reads `PS_Shm[0x1b42]` as a 16-bit port number. Returns 5900 if the value is 0.
2. **Install signal handlers**:
- FUN_0000b2ec: `sigaction(SIGUSR2=12, FUN_0000a2f0)` — preview capture handler
- FUN_0000b2bc: `sigaction(SIGUSR1=10, FUN_0000a9dc)` — session kill handler
- Both handlers use `SA_NODEFER` flag (0x40000000)
3. **Accept loop** (infinite `do-while`):
- Allocates a connection descriptor (`operator_new(0x20)`) with a pthread mutex
- Allocates a connection info struct (`operator_new(0x14)`)
- Calls `InitConnectionDescriptor` (FUN_0000ce5c): stores socket fd and DeviceManager pointer
- Spawns a detached accept thread via `SpawnDetachedThread` (entry at LAB_0000a620)
- **Polls** in a loop with 1-second sleep until the accept thread sets the ready flag (descriptor+0x04 != 0)
- Prints "GetVaildSession=true" [sic] and unlocks the descriptor mutex
The accept thread (at LAB_0000a620) calls the socket accept wrapper, and on success, the per-session handler (FUN_0000d334) proceeds with the RFB handshake.
##### Signal Handlers
**SIGUSR2 — Preview Capture (FUN_0000a2f0)**:
Creates a non-detached thread (via `pthread_create`) running FUN_0000a33c. The thread:
1. Calls `DeviceManager::CaptureFrame` with incremental=-1 (forced full capture)
2. If capture data length > 0: calls `DeviceManager::SaveSnapshot` → writes `/tmp/Snapshot.bmp`
3. If no video signal: `cp -f /web/images/blank.bmp /tmp/Snapshot.bmp`
4. Calls `DeviceManager::ReleaseCapture` and `DeviceManager::UnlockCapture`
**SIGUSR1 — Session Kill (FUN_0000a9dc)**:
Reads `/tmp/killsess` file, dispatches based on content:
- `"-1"``KillAllSessions` (FUN_0000a4fc): sends cmd=6 "Kill Session Due to Change Port" to all sessions (only if >1 active)
- `"-2"``ResetIPHandler` (FUN_0000a9c0): calls cleanup (FUN_0000a998), then `exit(0)`
- Otherwise → username match: iterates session list, compares with PrivilegeInfo+0x40, sends cmd=3 "Kill Session Due to Web Logout" to matching session
- Deletes `/tmp/killsess` after processing
**ResetIPHandler (FUN_0000a9c0)**:
Prints "ResetIP_Handler", calls FUN_0000a998 which kills all sessions in the pending connection list and frees the server socket object at DAT_0001e580, then calls `exit(0)`.
##### Signal Handler Setup Details
Two patterns for signal installation:
- FUN_0000b2bc/FUN_0000b2ec: Uses `sigaction` with `sa_flags = SA_NODEFER` (0x40000000). Used for SIGUSR1/SIGUSR2.
- FUN_0000af84: Uses `sigaction` with `sa_flags = 0`. General-purpose signal installer.
- FUN_0000b2e8: No-op, called between signal setup calls.
#### Video Capture and Encoding Pipeline
##### Architecture Overview
@ -1737,20 +1881,84 @@ Total: 2 bytes.
#### vcddev.ko — Video Capture Device
- **Path**: `original_binaries/server/180000/bin/module/vcddev.ko`
- **Ghidra name**: `/180000/bin/module/vcddev.ko-179d04`
- **Author**: Winbond Limited (Nuvoton)
- **Device**: `/dev/vcd` (major 25)
- **Source**: `vcddev.c`
- **Device**: `/dev/vcd` (major 25, char device)
- **IRQ**: 22 (0x16)
- **NOT stripped** — full symbol table available
| Symbol | Purpose |
|--------|---------|
| `vcd_open` | Open /dev/vcd |
| `vcd_release` | Close /dev/vcd |
| `vcd_read` | Read captured video frame (272 bytes) |
| `vcd_poll` | Poll for capture completion |
| `vcd_mmap` | Memory-map video buffer |
| `vcddev_interrupt` | Hardware IRQ handler (88 bytes) |
| `captureDone` | Capture completion flag |
| `invalidateCache` | Cache invalidation flag |
##### Module Init (init_module at 0x10280)
1. **Hardware detection**: Writes 0xFFFFFFFF to `vcd+0x08` and `vcd+0x0c`, reads back. Expected values: `0xFE00FE00` (-0x1ff0200) and `0x07FF07FF`. If mismatch, prints "VCD: hardware not present" and returns `-ENODEV`.
2. **Register char device**: `register_chrdev(25, "vcd", &vcddev_fops)`.
3. **Request IRQ**: `request_irq(22, vcddev_interrupt, SA_SHIRQ, "vcd", NULL)`.
##### File Operations
The module uses a two-level fops structure:
**vcddev_fops** (outer, at 0x10804) — registered with the kernel:
- `open``vcddev_open` (0x101e0): validates minor number, redirects to `vcd_fops` open
**vcd_fops** (inner, at 0x1087c) — the actual operations:
- `open``vcd_open` (0x10000): returns 0 (no-op)
- `release``vcd_release` (0x10014): clears `vcd+0x20` (interrupt mask register), returns 0
- `read``vcd_read` (0x1003c): blocking read for capture completion
- `poll``vcd_poll` (0x1014c): poll for capture done (returns POLLIN|POLLRDNORM)
- `mmap``vcd_mmap` (0x101cc): returns 0 (stub — the actual mmap is done in userspace via `/dev/mem`)
##### vcd_read (0x1003c)
Blocks until capture is done:
1. If `O_NONBLOCK` is set and `captureDone == 0`: returns `-EAGAIN`
2. Otherwise, enters a wait loop on `vcd_queue` (wait queue):
- Calls `prepare_to_wait(&vcd_queue, &wait, TASK_INTERRUPTIBLE)`
- If `captureDone != 0`: breaks
- If signal pending: returns `-ERESTARTSYS`
- Calls `schedule_timeout(50)` (50 jiffies = ~500ms at HZ=100)
3. Clears `captureDone = 0`
4. Writes 0 to the output buffer (1 byte), returns 50 (success)
##### vcd_poll (0x1014c)
1. If `invalidateCache != 0`: clears it
2. If `captureDone == 0`: writes 9 to `vcd+0x20` (enables capture-done interrupt bits 0 and 3)
3. Calls `poll_wait(filp, &vcd_queue, table)`
4. If `captureDone != 0`: returns `POLLIN | POLLRDNORM` (0x41), sets `invalidateCache = 1`
5. Otherwise: returns 0 (not ready)
##### Interrupt Handler (vcddev_interrupt at 0x10228)
Checks VCD status register `vcd+0x1c`. If bits 0 or 3 are set (capture complete or valid capture):
1. Clears interrupt mask: writes 0 to `vcd+0x20`
2. Sets `captureDone = 1`
3. Calls `__wake_up(&vcd_queue, TASK_NORMAL, 1, NULL)` to wake blocked readers
Returns `IRQ_HANDLED` (1) always.
##### Global Variables
| Symbol | Address | Purpose |
|--------|---------|---------|
| `vcd` | 0x108f0 | VCD device structure (base: physical 0xF0028000) |
| `vcd_queue` | 0x108f4 | Wait queue head for capture completion |
| `invalidateCache` | 0x108fc | Flag: 1 when cache needs invalidation after poll |
| `captureDone` | 0x10a40 | Flag: 1 when capture is complete, cleared by read |
##### Hardware Register Access Model
The `vcd` structure at 0x108f0 holds the VCD hardware base address (0xF0028000 physical). The kernel module does NOT provide an ioctl interface — all VCD register manipulation is done directly from userspace by the ikvmserver process via `/dev/mem` mmap. The kernel module's role is limited to:
1. Interrupt handling — detecting capture completion via IRQ 22
2. Signaling completion — via wait queue to the userspace poll/read interface
The ikvmserver VCD functions at 0x11368-0x12a6c directly read/write the memory-mapped VCD register block (pointed to by DAT_0001e4b0 in the ikvmserver address space, mapped via `/dev/mem`).
##### Module Cleanup (cleanup_module at 0x10374)
1. Clears interrupt mask: writes 0 to `vcd+0x20`
2. Frees IRQ 22
3. Unregisters char device major 25
#### usb_hid.ko — Virtual USB HID
- **Path**: `original_binaries/server/180000/bin/module/usb_hid.ko`
@ -1790,8 +1998,164 @@ Total: 2 bytes.
#### ikvm_vmass.ko — Virtual USB Mass Storage
- **Path**: `original_binaries/server/180000/bin/module/ikvm_vmass.ko`
- **Ghidra name**: `/180000/bin/module/ikvm_vmass.ko-070d10`
- **Source**: `wpcm450_vmass.c`
- **NOT stripped** (369 symbols)
- **USB Vendor/Product**: ATEN 0x0EA0 / 0x2168
- **USB Device Class**: Miscellaneous (0xEF), Interface Association Descriptor (subclass 0x02, protocol 0x01)
##### Architecture Overview
The module implements a USB 2.0 device controller driver for the WPCM450's on-chip USB hardware. It presents a USB Mass Storage (Bulk-Only) device to the host, with SCSI command handling delegated to userspace (the virtual media daemon). The module handles:
1. USB device enumeration (Chapter 9 control transfers)
2. Bulk endpoint data transfer (EP1 OUT for CBW, EP2 IN for data/CSW)
3. DMA buffer management
4. Character device interface for userspace communication
##### Module Init (wpcm450_USB20_init_module at 0x12600)
1. **DMA allocation**: `dma_alloc_coherent(NULL, 0x152000, &USB20_PhyAddr, GFP_KERNEL)` — allocates 1.3MB coherent DMA buffer for USB transfer descriptors and data
2. **Char device registration**: Allocates 4 minor numbers (using `alloc_chrdev_region` or `register_chrdev_region`)
3. **Device initialization**: Allocates `wpcm450_devices20` struct (0x280 bytes), initializes 5 cdev entries
4. **Per-device setup**: For each of 5 devices (minors 0-4):
- Allocates a 0x5000-byte (20KB) non-cached SCSI buffer at device+0x178
- Initializes cdev with `wpcm450_usb20_fops`
- Sets EP max packet size: 2 (initially), later 0x80 (128 bytes)
5. **Wait queue**: Initializes `delaywaitq` for startup delay
##### Ioctl Interface (wpcm450_usb20_ioctl at 0x120e8)
| Ioctl Code | Direction | Purpose |
|-----------|-----------|---------|
| 0xC0085501 | Read/Write | **Register device / Set descriptors** — main setup ioctl |
| 0x40085502 | Write | **Disconnect USB** — unbinds gadget, shuts down USB controller |
| 0x40085503 | Write | **Connect USB** — starts USB device controller |
Ioctl magic number: `0x55` ('U'), size field: 8 bytes.
**Ioctl 0xC0085501 — Register Device / Set Descriptors**:
Copies a 0x20-byte control struct from userspace. The first byte determines behavior:
- **byte[0] == 2**: Calls `RegisterDevice()` to allocate a device slot (max 2 slots, returns 0 or 1). Increments `at_b_USB_count`.
- **byte[0] < 2**: Copies 0x520 bytes of USB descriptor data from a userspace pointer (at control+0x10) into `at_a_vuDevRespData`. Allocates a 0xA0000-byte (640KB) SCSI data buffer at device+0x78. If this is the second device (`at_b_USB_count == 1`), calls `ikvm_vmass_gadget_register_driver` to bind the USB gadget driver.
The 0x520-byte descriptor data (`at_a_vuDevRespData` at 0x1629c) contains the complete USB device, configuration, interface, and endpoint descriptors that will be presented to the host. The userspace daemon constructs these descriptors to define the virtual CD-ROM device identity.
**Ioctl 0x40085502 — USB Disconnect**:
Unbinds the gadget driver, shuts down the USB controller, resets all global state. Wakes all wait queues with error code 0xF1.
**Ioctl 0x40085503 — USB Connect**:
Starts the USB device controller via `_usb20_device_run`. Increments `g_init_cnt`.
##### Data Flow: Network → USB
The virtual media daemon (userspace) communicates with the kernel module via read/write on the char device:
**Read (wpcm450_usb20_read at 0x11928)** — receives SCSI CBW from host:
1. If `TEST_ENABLED == 0`: returns 0 (USB not active)
2. Startup delay: waits on `delaywaitq` for up to 5 seconds (50 ticks) on first call
3. Reads 8-byte header from userspace (device index + flags)
4. Calls `_usb20_device_recv_data` to initiate a USB OUT transfer on the bulk endpoint
5. Waits on the device's OUT wait queue until `at_b_usb_out_ack` is set by the ISR
6. If 0xF1 (disconnect): returns 0
7. Otherwise: copies the received USB data to the userspace buffer (8-byte header + payload)
**Write (wpcm450_usb20_write at 0x11cd4)** — sends SCSI response data to host:
1. Reads 8-byte header from userspace (device index, endpoint index)
2. Copies payload data into the device's DMA buffer (device+0x78)
3. If payload is exactly 13 bytes (0x0D): this is a CSW (Command Status Wrapper). The first byte is set to 0x55 ('U' — CSW signature), the device state is set to 2 (ready for new CBW), and a semaphore is released.
4. Calls `_usb20_device_send_data` to transmit data on the bulk IN endpoint
5. Waits on the device's IN wait queue until `at_b_usb_in_ack` is set
6. Supports multi-packet transfers: large SCSI IN data is accumulated in the buffer via `at_dw_SCSIInCont`/`at_dw_SCSIInLen` counters, sent in max-packet-size chunks
##### SCSI Command Parsing (Endpoint Service, 0x11158)
The `wpcm450_USB20_service_ep1` function (EP1 completion callback) parses incoming CBW packets:
1. If received length == 31 (0x1F): this is a CBW (USB Mass Storage Bulk-Only Transport command wrapper)
- Sets device state to 0 (processing command)
- Reads direction flag from CBW byte 12 (bit 7: 0=OUT, 1=IN)
- Reads SCSI opcode from CBW byte 15
- Dispatches based on opcode:
- **0x28 (READ_10)**, **0xBE (READ_CD)**: Sets `at_dw_SCSIInLen` from CBW transfer length (data IN to host)
- **0x20 (proprietary?)**, **0x2A (WRITE_10)**: Sets `at_dw_SCSIOutLen` from CBW transfer length (data OUT from host)
- **All others**: Sets both lengths to 0 (no data phase)
2. If received length != 31 but < max packet size: short packet, sets state to 1 (data received)
3. Wakes the OUT wait queue and sets ack flags
The second device (EP4/EP5 pair) mirrors this logic in `wpcm450_USB20_service_ep4` and `wpcm450_USB20_service_ep5`.
##### USB Gadget Driver Registration
`ikvm_vmass_gadget_register_driver` (0x10000) validates the driver struct and calls the bind callback. The bind function (`wpcm450_USB20_udc_bind` at 0x100cc):
1. Initializes the USB 2.0 device controller: `_usb20_device_init(2, &handle, 8)` — 2 endpoints, 8 config slots
2. Registers endpoint service callbacks:
- EP0: `wpcm450_USB20_service_ep0` (control/enumeration)
- Service 0x10: `wpcm450_USB20_reset_ep0` (bus reset)
- Service 0x15: `wpcm450_udc_mstorage_service_speed` (speed change)
- EP1 (bulk OUT): `wpcm450_USB20_service_ep1` (CBW receive, device 0)
- EP2 (bulk IN): `wpcm450_USB20_service_ep2` (data/CSW send, device 0)
- EP4 (bulk OUT): `wpcm450_USB20_service_ep4` (CBW receive, device 1)
- EP5 (bulk IN): `wpcm450_USB20_service_ep5` (data/CSW send, device 1)
3. Flushes ARM926 caches
##### USB Enumeration (EP0 Service, 0x10fb4)
Standard USB Chapter 9 control transfer handling. Dispatches on `bmRequestType` type field:
- **Standard (0x00)**: Full Chapter 9 implementation: GetStatus, ClearFeature, SetFeature, SetAddress, GetDescriptor, SetDescriptor, GetConfig, SetConfig, GetInterface, SetInterface, SynchFrame
- **Class (0x20)**: `USB20_ch9Class` — handles mass storage class requests (e.g., Bulk-Only Mass Storage Reset = 0xFE). Responds with 0-length data.
- **Vendor (0x40)**: Silently ignored
##### Speed Negotiation (0x117f8)
`wpcm450_udc_mstorage_service_speed` adjusts endpoint max packet sizes:
- **Full-speed (1)**: 64 bytes per packet (0x40)
- **High-speed (2)**: 512 bytes per packet (0x200)
Updates both the hardware endpoint configuration and the descriptor data in `at_a_vuDevRespData`.
##### Dual-LUN Support
The module supports up to 2 LUNs (logical units), each with its own:
- Bulk OUT endpoint (EP1 for LUN 0, EP4 for LUN 1)
- Bulk IN endpoint (EP2 for LUN 0, EP5 for LUN 1)
- 640KB SCSI data buffer (device+0x78)
- Independent SCSI length tracking (`at_dw_SCSIInLen[0..1]`, `at_dw_SCSIOutLen[0..1]`)
When 2 devices are registered (`at_b_USB_count == 2`), `update_descriptor` (0x120ac) patches the configuration descriptor to add the second interface.
##### Key Global Variables
| Symbol | Address | Purpose |
|--------|---------|---------|
| `wpcm450_devices20` | 0x16ae0 | Pointer to device struct array (0x80 bytes per device, 5 entries) |
| `at_a_vuDevRespData` | 0x1629c | USB descriptor data (0x520 bytes, set by ioctl from userspace) |
| `at_dw_SCSIOutLen` | 0x16a40 | SCSI OUT (host→device) transfer length per LUN (4 bytes × 4) |
| `at_dw_SCSIInLen` | 0x16a50 | SCSI IN (device→host) transfer length per LUN (4 bytes × 4) |
| `at_dw_SCSIInCont` | 0x16a60 | SCSI IN bytes accumulated so far per LUN |
| `at_b_usb_out_ack` | 0x16a78 | USB OUT transfer completion flags (per LUN) |
| `at_b_usb_in_ack` | 0x16a7c | USB IN transfer completion flags (per LUN) |
| `at_b_USB_count` | 0x16a30 | Number of registered USB devices (0-2) |
| `at_b_DevicesBitMap` | 0x16a31 | Bitmap of allocated device slots |
| `USB20_LogAddr` | 0x16a34 | DMA buffer logical (kernel virtual) address |
| `USB20_PhyAddr` | 0x16a38 | DMA buffer physical address |
| `at_speed` | 0x167bc | USB speed: 1=full, 2=high |
| `TEST_ENABLED` | 0x16a24 | USB active flag (set by connect ioctl) |
| `g_init_cnt` | 0x16a3c | USB controller initialization count |
| `g_usb_init_done` | 0x16c5c | USB initialization complete flag |
| `delay_flag` | 0x16a20 | Startup delay completion flag |
| `USB_Reset_Flag` | 0x16a32 | Set on USB bus reset, cleared on next read/write |
##### Module Cleanup (wpcm450_USB20_cleanup_module at 0x1246c)
1. Sets all ack flags to 0xF1 (error/disconnect sentinel)
2. Wakes all wait queues (causes blocked reads/writes to fail)
3. If USB was initialized: unbinds gadget, shuts down controller
4. Deletes all 5 cdev entries
5. Frees device struct
6. Unregisters char device region
7. Frees DMA buffer: `dma_free_coherent(NULL, 0x152000, OrigLogAddr, OrigPhyAddr)`
### Shared Libraries (ikvmserver dependencies)