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:
parent
9e78e481aa
commit
aa041556b7
1 changed files with 377 additions and 13 deletions
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue