[lib][spi][srv] Split TIPC functions into a separate library

This these TIPC functions can be built/linked to separately from the
rest of SPI server functionality.

Bug: 118762930
Change-Id: I5577a77ab376263d79c40f8cefce468c2a2d3f1f
diff --git a/lib/spi/srv/common/include/lib/spi/srv/common/common.h b/lib/spi/srv/common/include/lib/spi/srv/common/common.h
new file mode 100644
index 0000000..43a6b62
--- /dev/null
+++ b/lib/spi/srv/common/include/lib/spi/srv/common/common.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <interface/spi/spi.h>
+#include <lib/spi/common/utils.h>
+#include <lk/compiler.h>
+
+__BEGIN_CDECLS
+
+/**
+ * struct spi_dev_ctx - opaque SPI device context structure
+ */
+struct spi_dev_ctx;
+
+/**
+ * spi_batch_state - tracks state associated with SPI batch being processed
+ * @cs:       CS state resulting from the SPI batch
+ * @num_cmds: number of commands successfully processed. Also corresponds to the
+ *            index of the failed command if an error occurred.
+ */
+struct spi_batch_state {
+    bool cs;
+    size_t num_cmds;
+};
+
+/**
+ * spi_srv_handle_batch() - handle batch of SPI requests
+ * @spi:       handle to SPI device
+ * @mb:        memory buffer containing the batch of SPI requests
+ * @batch_req: metadata about the batch of SPI requests
+ * @state:     keeps track of device state as the batch is being processed
+ *
+ * Return: 0 on success, negative error code otherwise
+ */
+int spi_srv_handle_batch(struct spi_dev_ctx* spi,
+                         struct mem_buf* mb,
+                         struct spi_batch_req* batch_req,
+                         struct spi_batch_state* state);
+
+__END_CDECLS
diff --git a/lib/spi/srv/include/lib/spi/srv/srv.h b/lib/spi/srv/include/lib/spi/srv/srv.h
index 04f1441..18b519b 100644
--- a/lib/spi/srv/include/lib/spi/srv/srv.h
+++ b/lib/spi/srv/include/lib/spi/srv/srv.h
@@ -16,29 +16,5 @@
 
 #pragma once
 
-#include <lib/tipc/tipc_srv.h>
-#include <lk/compiler.h>
-
-__BEGIN_CDECLS
-
-/**
- * add_spi_service() - Add new SPI service to service set
- * @hset:      pointer to handle set to add service to
- * @ports:     an array of &struct tipc_port describing ports for this
- *             service
- * @num_ports: number of ports in array pointed by @ports
- *
- * Caller must provide a pointer to &struct spi_dev_ctx as port-specific private
- * data in @priv field of &struct tipc_port.
- *
- * This routine can be called multiple times to register multiple services.
- *
- * Each port in @ports may have at most one active connection.
- *
- * Return: 0 on success, negative error code otherwise
- */
-int add_spi_service(struct tipc_hset* hset,
-                    const struct tipc_port* ports,
-                    size_t num_ports);
-
-__END_CDECLS
+/* Expose tipc.h functions to clients of lib/spi/srv */
+#include <lib/spi/srv/tipc/tipc.h>
diff --git a/lib/spi/srv/rules.mk b/lib/spi/srv/rules.mk
index 346a66b..7ff178b 100644
--- a/lib/spi/srv/rules.mk
+++ b/lib/spi/srv/rules.mk
@@ -22,6 +22,9 @@
 MODULE_DEPS += \
     trusty/user/base/interface/spi \
     trusty/user/base/lib/spi/common \
-    trusty/user/base/lib/tipc \
+    trusty/user/base/lib/spi/srv/tipc \
+
+MODULE_INCLUDES += \
+    trusty/user/base/lib/spi/srv/common/include \
 
 include make/module.mk
diff --git a/lib/spi/srv/srv.c b/lib/spi/srv/srv.c
index 10ff45b..3486d0a 100644
--- a/lib/spi/srv/srv.c
+++ b/lib/spi/srv/srv.c
@@ -16,142 +16,14 @@
 
 #include <interface/spi/spi.h>
 #include <lib/spi/common/utils.h>
+#include <lib/spi/srv/common/common.h>
 #include <lib/spi/srv/dev.h>
-#include <lib/spi/srv/srv.h>
-#include <lib/tipc/tipc_srv.h>
 #include <lk/compiler.h>
-#include <stdlib.h>
-#include <sys/mman.h>
 #include <uapi/err.h>
-#include <uapi/mm.h>
 
 #define TLOG_TAG "spi-srv"
 #include <trusty_log.h>
 
-/**
- * chan_ctx - per-connection SPI data
- * @shm:        state of memory region shared with SPI server
- * @shm_handle: handle to shared memory region
- * @cs:         tracks CS state of the underlying SPI device
- *              true - asserted, false - deasserted
- */
-struct chan_ctx {
-    struct mem_buf shm;
-    handle_t shm_handle;
-    bool cs;
-};
-
-static inline bool shm_is_mapped(struct chan_ctx* ctx) {
-    return ctx->shm.buf && ctx->shm_handle != INVALID_IPC_HANDLE;
-}
-
-static inline void shm_unmap(struct chan_ctx* ctx) {
-    if (shm_is_mapped(ctx)) {
-        munmap(ctx->shm.buf, ctx->shm.capacity);
-        mb_destroy(&ctx->shm);
-        close(ctx->shm_handle);
-        ctx->shm_handle = INVALID_IPC_HANDLE;
-    }
-}
-
-union spi_msg_req_args {
-    struct spi_shm_map_req shm;
-    struct spi_batch_req batch;
-};
-
-static size_t get_spi_msg_size(struct spi_msg_req* req) {
-    size_t msg_size = sizeof(struct spi_msg_req);
-    switch (req->cmd & SPI_CMD_OP_MASK) {
-    case SPI_CMD_MSG_OP_SHM_MAP:
-        msg_size += sizeof(struct spi_shm_map_req);
-        break;
-
-    case SPI_CMD_MSG_OP_BATCH_EXEC:
-        msg_size += sizeof(struct spi_batch_req);
-        break;
-    }
-    return msg_size;
-}
-
-static int recv_msg(handle_t chan,
-                    struct spi_msg_req* req,
-                    union spi_msg_req_args* args,
-                    handle_t* h) {
-    int rc;
-    struct ipc_msg_info msg_inf;
-    size_t num_handles = h ? 1 : 0;
-
-    rc = get_msg(chan, &msg_inf);
-    if (rc != NO_ERROR) {
-        TLOGE("failed (%d) to get_msg()\n", rc);
-        return rc;
-    }
-
-    struct iovec iovs[2] = {
-            {
-                    .iov_base = req,
-                    .iov_len = sizeof(*req),
-            },
-            {
-                    .iov_base = args,
-                    .iov_len = sizeof(*args),
-            },
-    };
-    struct ipc_msg msg = {
-            .iov = iovs,
-            .num_iov = countof(iovs),
-            .handles = h,
-            .num_handles = num_handles,
-    };
-    rc = read_msg(chan, msg_inf.id, 0, &msg);
-    if (rc != (int)get_spi_msg_size(req)) {
-        TLOGE("failed (%d) to read_msg()\n", rc);
-        put_msg(chan, msg_inf.id);
-        return rc;
-    }
-
-    put_msg(chan, msg_inf.id);
-    return NO_ERROR;
-}
-
-static int handle_msg_shm_map_req(handle_t chan,
-                                  struct chan_ctx* ctx,
-                                  struct spi_shm_map_req* shm_req,
-                                  handle_t shm_handle) {
-    int rc = NO_ERROR;
-    struct spi_msg_resp resp;
-    void* shm_base;
-
-    shm_unmap(ctx);
-
-    shm_base = mmap(0, shm_req->len, MMAP_FLAG_PROT_READ | MMAP_FLAG_PROT_WRITE,
-                    0, shm_handle, 0);
-    if (!shm_base) {
-        TLOGE("failed to map shared memory\n");
-        rc = ERR_GENERIC;
-        goto err_mmap;
-    }
-
-    resp.status = translate_lk_err(rc);
-    rc = tipc_send1(chan, &resp, sizeof(resp));
-    if (rc < 0 || (size_t)rc != sizeof(resp)) {
-        TLOGE("failed (%d) to send SPI response\n", rc);
-        if (rc >= 0) {
-            rc = ERR_BAD_LEN;
-        }
-        goto err_resp;
-    }
-
-    mb_init(&ctx->shm, shm_base, shm_req->len, SPI_CMD_SHM_ALIGN);
-    ctx->shm_handle = shm_handle;
-    return NO_ERROR;
-
-err_resp:
-    munmap(shm_base, shm_req->len);
-err_mmap:
-    return rc;
-}
-
 static int handle_xfer_args(struct spi_dev_ctx* spi, struct mem_buf* shm) {
     int rc;
     struct spi_xfer_args* xfer_args;
@@ -206,17 +78,6 @@
     return rc;
 }
 
-/**
- * spi_batch_state - tracks state associated with SPI batch being processed
- * @cs:       CS state resulting from the SPI batch
- * @num_cmds: number of commands successfully processed. Also corresponds to the
- *            index of the failed command if an error occurred.
- */
-struct spi_batch_state {
-    bool cs;
-    size_t num_cmds;
-};
-
 static int unpack_shm(struct spi_dev_ctx* spi,
                       struct mem_buf* shm,
                       size_t len,
@@ -289,10 +150,10 @@
     return NO_ERROR;
 }
 
-static int handle_shm_batch_req(struct spi_dev_ctx* spi,
-                                struct mem_buf* shm,
-                                struct spi_batch_req* batch_req,
-                                struct spi_batch_state* state) {
+int spi_srv_handle_batch(struct spi_dev_ctx* spi,
+                         struct mem_buf* shm,
+                         struct spi_batch_req* batch_req,
+                         struct spi_batch_state* state) {
     int rc = NO_ERROR;
 
     if (batch_req->len > shm->capacity) {
@@ -349,163 +210,3 @@
     spi_seq_abort(spi);
     return rc;
 }
-
-static int handle_msg_batch_req(handle_t chan,
-                                struct spi_dev_ctx* spi,
-                                struct chan_ctx* ctx,
-                                struct spi_batch_req* batch_req) {
-    int rc;
-    struct spi_msg_resp resp;
-    struct spi_batch_resp batch_resp;
-    struct spi_batch_state state;
-
-    if (!shm_is_mapped(ctx)) {
-        return ERR_BAD_STATE;
-    }
-
-    state.cs = ctx->cs;
-    state.num_cmds = 0;
-    rc = handle_shm_batch_req(spi, &ctx->shm, batch_req, &state);
-    if (rc == NO_ERROR) {
-        ctx->cs = state.cs;
-    }
-
-    resp.cmd = SPI_CMD_MSG_OP_BATCH_EXEC | SPI_CMD_RESP_BIT;
-    resp.status = translate_lk_err(rc);
-    batch_resp.len = mb_curr_pos(&ctx->shm);
-    batch_resp.failed = (rc != NO_ERROR) ? (uint32_t)state.num_cmds : 0;
-
-    rc = tipc_send2(chan, &resp, sizeof(resp), &batch_resp, sizeof(batch_resp));
-    if (rc < 0 || (size_t)rc != sizeof(resp) + sizeof(batch_resp)) {
-        TLOGE("failed (%d) to send batch response\n", rc);
-        if (rc >= 0) {
-            rc = ERR_BAD_LEN;
-        }
-        return rc;
-    }
-
-    return NO_ERROR;
-}
-
-static int on_connect(const struct tipc_port* port,
-                      handle_t chan,
-                      const struct uuid* peer,
-                      void** ctx_p) {
-    struct chan_ctx* ctx = calloc(1, sizeof(struct chan_ctx));
-    if (!ctx) {
-        TLOGE("failed to allocate channel context\n");
-        return ERR_NO_MEMORY;
-    }
-
-    ctx->shm_handle = INVALID_IPC_HANDLE;
-
-    *ctx_p = ctx;
-    return NO_ERROR;
-}
-
-static int on_message(const struct tipc_port* port,
-                      handle_t chan,
-                      void* chan_ctx) {
-    int rc;
-    struct spi_msg_req req;
-    union spi_msg_req_args args;
-    struct spi_dev_ctx* spi = (struct spi_dev_ctx*)port->priv;
-    struct chan_ctx* ctx = (struct chan_ctx*)chan_ctx;
-    handle_t h;
-
-    rc = recv_msg(chan, &req, &args, &h);
-    if (rc != NO_ERROR) {
-        TLOGE("failed (%d) to receive SPI message, closing connection\n", rc);
-        return rc;
-    }
-
-    switch (req.cmd & SPI_CMD_OP_MASK) {
-    case SPI_CMD_MSG_OP_SHM_MAP:
-        rc = handle_msg_shm_map_req(chan, ctx, &args.shm, h);
-        break;
-
-    case SPI_CMD_MSG_OP_BATCH_EXEC:
-        rc = handle_msg_batch_req(chan, spi, ctx, &args.batch);
-        break;
-
-    default:
-        TLOGE("cmd 0x%x: unknown command\n", req.cmd);
-        rc = ERR_CMD_UNKNOWN;
-    }
-
-    if (rc != NO_ERROR) {
-        TLOGE("failed (%d) to handle SPI message, closing connection\n", rc);
-        return rc;
-    }
-
-    return NO_ERROR;
-}
-
-static void on_disconnect(const struct tipc_port* port,
-                          handle_t chan,
-                          void* _ctx) {
-    int rc;
-    struct spi_dev_ctx* spi = (struct spi_dev_ctx*)port->priv;
-    struct chan_ctx* ctx = (struct chan_ctx*)_ctx;
-    struct spi_shm_hdr hdr;
-    struct mem_buf mb;
-    struct spi_batch_req req;
-    struct spi_batch_state state;
-
-    /* make sure CS is deasserted */
-    if (!ctx->cs) {
-        return;
-    }
-
-    /* Construct a batch with a single deassert command to recover CS state */
-    hdr.cmd = SPI_CMD_SHM_OP_CS_DEASSERT;
-
-    /* Deassert commands are header only */
-    mb_init(&mb, &hdr, sizeof(hdr), SPI_CMD_SHM_ALIGN);
-    mb_resize(&mb, sizeof(hdr));
-
-    req.len = sizeof(hdr);
-    req.num_cmds = 1;
-
-    state.cs = true;
-    state.num_cmds = 0;
-
-    rc = handle_shm_batch_req(spi, &mb, &req, &state);
-    /* CS state will be out of sync. This is an unrecoverable error. */
-    assert(rc == NO_ERROR);
-
-    ctx->cs = false;
-}
-
-static void on_channel_cleanup(void* _ctx) {
-    struct chan_ctx* ctx = (struct chan_ctx*)_ctx;
-    assert(!ctx->cs);
-    shm_unmap(ctx);
-    free(ctx);
-}
-
-static const struct tipc_srv_ops spi_dev_ops = {
-        .on_connect = on_connect,
-        .on_message = on_message,
-        .on_disconnect = on_disconnect,
-        .on_channel_cleanup = on_channel_cleanup,
-};
-
-int add_spi_service(struct tipc_hset* hset,
-                    const struct tipc_port* ports,
-                    size_t num_ports) {
-    int rc;
-
-    for (size_t i = 0; i < num_ports; i++) {
-        if (!ports[i].priv) {
-            return ERR_INVALID_ARGS;
-        }
-
-        rc = tipc_add_service(hset, &ports[i], 1, 1, &spi_dev_ops);
-        if (rc != NO_ERROR) {
-            return rc;
-        }
-    }
-
-    return NO_ERROR;
-}
diff --git a/lib/spi/srv/tipc/include/lib/spi/srv/tipc/tipc.h b/lib/spi/srv/tipc/include/lib/spi/srv/tipc/tipc.h
new file mode 100644
index 0000000..04f1441
--- /dev/null
+++ b/lib/spi/srv/tipc/include/lib/spi/srv/tipc/tipc.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <lib/tipc/tipc_srv.h>
+#include <lk/compiler.h>
+
+__BEGIN_CDECLS
+
+/**
+ * add_spi_service() - Add new SPI service to service set
+ * @hset:      pointer to handle set to add service to
+ * @ports:     an array of &struct tipc_port describing ports for this
+ *             service
+ * @num_ports: number of ports in array pointed by @ports
+ *
+ * Caller must provide a pointer to &struct spi_dev_ctx as port-specific private
+ * data in @priv field of &struct tipc_port.
+ *
+ * This routine can be called multiple times to register multiple services.
+ *
+ * Each port in @ports may have at most one active connection.
+ *
+ * Return: 0 on success, negative error code otherwise
+ */
+int add_spi_service(struct tipc_hset* hset,
+                    const struct tipc_port* ports,
+                    size_t num_ports);
+
+__END_CDECLS
diff --git a/lib/spi/srv/tipc/rules.mk b/lib/spi/srv/tipc/rules.mk
new file mode 100644
index 0000000..544a6c4
--- /dev/null
+++ b/lib/spi/srv/tipc/rules.mk
@@ -0,0 +1,30 @@
+# Copyright (C) 2020 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS += \
+    $(LOCAL_DIR)/tipc.c \
+
+MODULE_DEPS += \
+    trusty/user/base/interface/spi \
+    trusty/user/base/lib/spi/common \
+    trusty/user/base/lib/tipc \
+
+MODULE_INCLUDES += \
+    trusty/user/base/lib/spi/srv/common/include \
+
+include make/module.mk
diff --git a/lib/spi/srv/tipc/tipc.c b/lib/spi/srv/tipc/tipc.c
new file mode 100644
index 0000000..a434f72
--- /dev/null
+++ b/lib/spi/srv/tipc/tipc.c
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <interface/spi/spi.h>
+#include <lib/spi/common/utils.h>
+#include <lib/spi/srv/common/common.h>
+#include <lib/spi/srv/tipc/tipc.h>
+#include <lib/tipc/tipc_srv.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <uapi/err.h>
+#include <uapi/mm.h>
+
+#define TLOG_TAG "spi-srv-tipc"
+#include <trusty_log.h>
+
+/**
+ * chan_ctx - per-connection SPI data
+ * @shm:        state of memory region shared with SPI server
+ * @shm_handle: handle to shared memory region
+ * @cs:         tracks CS state of the underlying SPI device
+ *              true - asserted, false - deasserted
+ */
+struct chan_ctx {
+    struct mem_buf shm;
+    handle_t shm_handle;
+    bool cs;
+};
+
+static inline bool shm_is_mapped(struct chan_ctx* ctx) {
+    return ctx->shm.buf && ctx->shm_handle != INVALID_IPC_HANDLE;
+}
+
+static inline void shm_unmap(struct chan_ctx* ctx) {
+    if (shm_is_mapped(ctx)) {
+        munmap(ctx->shm.buf, ctx->shm.capacity);
+        mb_destroy(&ctx->shm);
+        close(ctx->shm_handle);
+        ctx->shm_handle = INVALID_IPC_HANDLE;
+    }
+}
+
+union spi_msg_req_args {
+    struct spi_shm_map_req shm;
+    struct spi_batch_req batch;
+};
+
+static size_t get_spi_msg_size(struct spi_msg_req* req) {
+    size_t msg_size = sizeof(struct spi_msg_req);
+    switch (req->cmd & SPI_CMD_OP_MASK) {
+    case SPI_CMD_MSG_OP_SHM_MAP:
+        msg_size += sizeof(struct spi_shm_map_req);
+        break;
+
+    case SPI_CMD_MSG_OP_BATCH_EXEC:
+        msg_size += sizeof(struct spi_batch_req);
+        break;
+    }
+    return msg_size;
+}
+
+static int recv_msg(handle_t chan,
+                    struct spi_msg_req* req,
+                    union spi_msg_req_args* args,
+                    handle_t* h) {
+    int rc;
+    struct ipc_msg_info msg_inf;
+    size_t num_handles = h ? 1 : 0;
+
+    rc = get_msg(chan, &msg_inf);
+    if (rc != NO_ERROR) {
+        TLOGE("failed (%d) to get_msg()\n", rc);
+        return rc;
+    }
+
+    struct iovec iovs[2] = {
+            {
+                    .iov_base = req,
+                    .iov_len = sizeof(*req),
+            },
+            {
+                    .iov_base = args,
+                    .iov_len = sizeof(*args),
+            },
+    };
+    struct ipc_msg msg = {
+            .iov = iovs,
+            .num_iov = countof(iovs),
+            .handles = h,
+            .num_handles = num_handles,
+    };
+    rc = read_msg(chan, msg_inf.id, 0, &msg);
+    if (rc != (int)get_spi_msg_size(req)) {
+        TLOGE("failed (%d) to read_msg()\n", rc);
+        put_msg(chan, msg_inf.id);
+        return rc;
+    }
+
+    put_msg(chan, msg_inf.id);
+    return NO_ERROR;
+}
+
+static int handle_msg_shm_map_req(handle_t chan,
+                                  struct chan_ctx* ctx,
+                                  struct spi_shm_map_req* shm_req,
+                                  handle_t shm_handle) {
+    int rc = NO_ERROR;
+    struct spi_msg_resp resp;
+    void* shm_base;
+
+    shm_unmap(ctx);
+
+    shm_base = mmap(0, shm_req->len, MMAP_FLAG_PROT_READ | MMAP_FLAG_PROT_WRITE,
+                    0, shm_handle, 0);
+    if (!shm_base) {
+        TLOGE("failed to map shared memory\n");
+        rc = ERR_GENERIC;
+        goto err_mmap;
+    }
+
+    resp.status = translate_lk_err(rc);
+    rc = tipc_send1(chan, &resp, sizeof(resp));
+    if (rc < 0 || (size_t)rc != sizeof(resp)) {
+        TLOGE("failed (%d) to send SPI response\n", rc);
+        if (rc >= 0) {
+            rc = ERR_BAD_LEN;
+        }
+        goto err_resp;
+    }
+
+    mb_init(&ctx->shm, shm_base, shm_req->len, SPI_CMD_SHM_ALIGN);
+    ctx->shm_handle = shm_handle;
+    return NO_ERROR;
+
+err_resp:
+    munmap(shm_base, shm_req->len);
+err_mmap:
+    return rc;
+}
+
+static int handle_msg_batch_req(handle_t chan,
+                                struct spi_dev_ctx* spi,
+                                struct chan_ctx* ctx,
+                                struct spi_batch_req* batch_req) {
+    int rc;
+    struct spi_msg_resp resp;
+    struct spi_batch_resp batch_resp;
+    struct spi_batch_state state;
+
+    if (!shm_is_mapped(ctx)) {
+        return ERR_BAD_STATE;
+    }
+
+    state.cs = ctx->cs;
+    state.num_cmds = 0;
+    rc = spi_srv_handle_batch(spi, &ctx->shm, batch_req, &state);
+    if (rc == NO_ERROR) {
+        ctx->cs = state.cs;
+    }
+
+    resp.cmd = SPI_CMD_MSG_OP_BATCH_EXEC | SPI_CMD_RESP_BIT;
+    resp.status = translate_lk_err(rc);
+    batch_resp.len = mb_curr_pos(&ctx->shm);
+    batch_resp.failed = (rc != NO_ERROR) ? (uint32_t)state.num_cmds : 0;
+
+    rc = tipc_send2(chan, &resp, sizeof(resp), &batch_resp, sizeof(batch_resp));
+    if (rc < 0 || (size_t)rc != sizeof(resp) + sizeof(batch_resp)) {
+        TLOGE("failed (%d) to send batch response\n", rc);
+        if (rc >= 0) {
+            rc = ERR_BAD_LEN;
+        }
+        return rc;
+    }
+
+    return NO_ERROR;
+}
+
+static int on_connect(const struct tipc_port* port,
+                      handle_t chan,
+                      const struct uuid* peer,
+                      void** ctx_p) {
+    struct chan_ctx* ctx = calloc(1, sizeof(struct chan_ctx));
+    if (!ctx) {
+        TLOGE("failed to allocate channel context\n");
+        return ERR_NO_MEMORY;
+    }
+
+    ctx->shm_handle = INVALID_IPC_HANDLE;
+
+    *ctx_p = ctx;
+    return NO_ERROR;
+}
+
+static int on_message(const struct tipc_port* port,
+                      handle_t chan,
+                      void* chan_ctx) {
+    int rc;
+    struct spi_msg_req req;
+    union spi_msg_req_args args;
+    struct spi_dev_ctx* spi = (struct spi_dev_ctx*)port->priv;
+    struct chan_ctx* ctx = (struct chan_ctx*)chan_ctx;
+    handle_t h;
+
+    rc = recv_msg(chan, &req, &args, &h);
+    if (rc != NO_ERROR) {
+        TLOGE("failed (%d) to receive SPI message, closing connection\n", rc);
+        return rc;
+    }
+
+    switch (req.cmd & SPI_CMD_OP_MASK) {
+    case SPI_CMD_MSG_OP_SHM_MAP:
+        rc = handle_msg_shm_map_req(chan, ctx, &args.shm, h);
+        break;
+
+    case SPI_CMD_MSG_OP_BATCH_EXEC:
+        rc = handle_msg_batch_req(chan, spi, ctx, &args.batch);
+        break;
+
+    default:
+        TLOGE("cmd 0x%x: unknown command\n", req.cmd);
+        rc = ERR_CMD_UNKNOWN;
+    }
+
+    if (rc != NO_ERROR) {
+        TLOGE("failed (%d) to handle SPI message, closing connection\n", rc);
+        return rc;
+    }
+
+    return NO_ERROR;
+}
+
+static void on_disconnect(const struct tipc_port* port,
+                          handle_t chan,
+                          void* _ctx) {
+    int rc;
+    struct spi_dev_ctx* spi = (struct spi_dev_ctx*)port->priv;
+    struct chan_ctx* ctx = (struct chan_ctx*)_ctx;
+    struct spi_shm_hdr hdr;
+    struct mem_buf mb;
+    struct spi_batch_req req;
+    struct spi_batch_state state;
+
+    /* make sure CS is deasserted */
+    if (!ctx->cs) {
+        return;
+    }
+
+    /* Construct a batch with a single deassert command to recover CS state */
+    hdr.cmd = SPI_CMD_SHM_OP_CS_DEASSERT;
+
+    /* Deassert commands are header only */
+    mb_init(&mb, &hdr, sizeof(hdr), SPI_CMD_SHM_ALIGN);
+    mb_resize(&mb, sizeof(hdr));
+
+    req.len = sizeof(hdr);
+    req.num_cmds = 1;
+
+    state.cs = true;
+    state.num_cmds = 0;
+
+    rc = spi_srv_handle_batch(spi, &mb, &req, &state);
+    /* CS state will be out of sync. This is an unrecoverable error. */
+    assert(rc == NO_ERROR);
+
+    ctx->cs = false;
+}
+
+static void on_channel_cleanup(void* _ctx) {
+    struct chan_ctx* ctx = (struct chan_ctx*)_ctx;
+    assert(!ctx->cs);
+    shm_unmap(ctx);
+    free(ctx);
+}
+
+static const struct tipc_srv_ops spi_dev_ops = {
+        .on_connect = on_connect,
+        .on_message = on_message,
+        .on_disconnect = on_disconnect,
+        .on_channel_cleanup = on_channel_cleanup,
+};
+
+int add_spi_service(struct tipc_hset* hset,
+                    const struct tipc_port* ports,
+                    size_t num_ports) {
+    int rc;
+
+    for (size_t i = 0; i < num_ports; i++) {
+        if (!ports[i].priv) {
+            return ERR_INVALID_ARGS;
+        }
+
+        rc = tipc_add_service(hset, &ports[i], 1, 1, &spi_dev_ops);
+        if (rc != NO_ERROR) {
+            return rc;
+        }
+    }
+
+    return NO_ERROR;
+}