IPC service stack

How a service request travels from a client, through the kernel, to a server and back. Two paths: the production kernel channel path and the in-process loopback for host tests.

Production path (kernel channel)

sequenceDiagram
    participant C as Client<br/>(event loop)
    participant CT as AsyncChannelTransport<br/>(util/ipc)
    participant AT as AsyncTransaction<br/>(util/ipc)
    participant K as pw_kernel<br/>(channel)
    participant S as Server process
    participant D as dispatch()<br/>(pldm/server)
    participant H as FdIpcHandler

    Note over C,H: Request

    C->>CT: start(req: &[u8])
    CT->>CT: copy req into 'static send buf
    CT->>AT: start(send, send_len, recv)
    AT->>K: channel_async_transact(send_ptr, recv_ptr)
    K-->>C: returns immediately

    Note over C: event loop parks on<br/>WaitGroup (READABLE)

    K->>S: delivers request bytes
    S->>D: dispatch(request, response)
    D->>H: accept_offer() / query_status() / ...
    H-->>D: Ok(()) or Err(ResponseCode)
    D->>D: encode response frame
    D-->>S: response length
    S->>K: channel_respond(response)

    Note over C,H: Response

    K-->>C: READABLE signal fires
    C->>CT: poll(resp: &mut [u8])
    CT->>AT: try_recv()
    AT->>K: channel_async_transact_complete()
    K-->>AT: Ok(len)
    AT-->>CT: Completion { len, send, recv }
    CT->>CT: copy recv[..len] into resp
    CT-->>C: Ok(Some(len))
    C->>C: decode resp[..len]

Loopback path (host tests)

A loopback answers on the first poll, so it cannot test a client's not-ready path. Delayed wraps any transport and returns Ok(None) for a set number of polls before forwarding, which is how a host test reaches that path.

sequenceDiagram
    participant C as Client<br/>(test code)
    participant L as Loopback<br/>(util/service)
    participant D as dispatch()<br/>(pldm/server)
    participant H as FdIpcHandler

    C->>L: start(req: &[u8])
    L->>D: dispatch(req, held_buf)
    D->>H: accept_offer() / query_status() / ...
    H-->>D: Ok(()) or Err(ResponseCode)
    D-->>L: response length
    Note over L: response stored in held_buf,<br/>ready immediately

    C->>L: poll(resp: &mut [u8])
    L->>L: copy held_buf[..len] into resp
    L-->>C: Ok(Some(len))
    C->>C: decode resp[..len]

Layer map

Client process Kernel Server process Traits (util/service) start/poll/cancel implements owns 'static bufs unsafe syscalls delivers frames request bytes implements decoded opcodes Service client code AsyncChannelTransport AsyncTransaction pw_kernel channel Server main loop service dispatch handler (e.g. FdIpcHandler) AsyncTransport trait Dispatch trait

Buffer copies

The async path copies twice per direction:

StepWhatWhere
1req slice into 'static send bufAsyncChannelTransport::start()
2send buf to server processkernel internal
3server response to 'static recv bufkernel internal
4recv buf into caller's resp sliceAsyncChannelTransport::poll()

Copies 1 and 4 exist because the kernel needs 'static buffers (it holds raw pointers for the duration of the transaction), but callers work with ordinary stack slices. The loopback skips steps 2 and 3 since everything runs in one process.