| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Permission is hereby granted, free of charge, to any person |
| * obtaining a copy of this software and associated documentation |
| * files (the "Software"), to deal in the Software without |
| * restriction, including without limitation the rights to use, copy, |
| * modify, merge, publish, distribute, sublicense, and/or sell copies |
| * of the Software, and to permit persons to whom the Software is |
| * furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be |
| * included in all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
| * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
| * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
| * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| * SOFTWARE. |
| */ |
| |
| #include <trusty/trusty_dev.h> |
| #include <trusty/trusty_ipc.h> |
| #include <trusty/trusty_mem.h> |
| #include <trusty/util.h> |
| |
| #define NS_PTE_PHYSADDR(pte) ((pte) & 0xFFFFFFFFF000ULL) |
| |
| #define QL_TIPC_DEV_RESP 0x8000 |
| #define QL_TIPC_DEV_CONNECT 0x1 |
| #define QL_TIPC_DEV_GET_EVENT 0x2 |
| #define QL_TIPC_DEV_SEND 0x3 |
| #define QL_TIPC_DEV_RECV 0x4 |
| #define QL_TIPC_DEV_DISCONNECT 0x5 |
| |
| #define LOCAL_LOG 0 |
| |
| struct trusty_ipc_cmd_hdr { |
| uint16_t opcode; |
| uint16_t flags; |
| uint32_t status; |
| uint32_t handle; |
| uint32_t payload_len; |
| uint8_t payload[0]; |
| }; |
| |
| struct trusty_ipc_wait_req { |
| uint64_t reserved; |
| }; |
| |
| struct trusty_ipc_connect_req { |
| uint64_t cookie; |
| uint64_t reserved; |
| uint8_t name[0]; |
| }; |
| |
| static size_t iovec_size(const struct trusty_ipc_iovec *iovs, size_t iovs_cnt) |
| { |
| size_t i; |
| size_t cb = 0; |
| |
| trusty_assert(iovs); |
| |
| for (i = 0; i < iovs_cnt; i++) { |
| cb += iovs[i].len; |
| } |
| |
| return cb; |
| } |
| |
| static size_t iovec_to_buf(void *buf, size_t buf_len, |
| const struct trusty_ipc_iovec *iovs, size_t iovs_cnt) |
| { |
| size_t i; |
| size_t buf_pos = 0; |
| |
| trusty_assert(iovs); |
| |
| for (i = 0; i < iovs_cnt; i++) { |
| size_t to_copy = (size_t)iovs[i].len; |
| |
| if (!to_copy) |
| continue; |
| |
| if (to_copy > buf_len) |
| to_copy = buf_len; |
| |
| trusty_memcpy((uint8_t *)buf + buf_pos, iovs[i].base, to_copy); |
| |
| buf_pos += to_copy; |
| buf_len -= to_copy; |
| |
| if (buf_len == 0) |
| break; |
| } |
| |
| return buf_pos; |
| } |
| |
| static size_t buf_to_iovec(const struct trusty_ipc_iovec *iovs, size_t iovs_cnt, |
| const void *buf, size_t buf_len) |
| { |
| size_t i; |
| size_t copied = 0; |
| const uint8_t *buf_ptr = buf; |
| |
| trusty_assert(buf_ptr); |
| trusty_assert(iovs); |
| |
| if (iovs_cnt == 0 || buf_len == 0) |
| return 0; |
| |
| for (i = 0; i < iovs_cnt; i++) { |
| size_t to_copy = buf_len; |
| |
| if (to_copy > iovs[i].len) |
| to_copy = iovs[i].len; |
| |
| if (!to_copy) |
| continue; |
| |
| trusty_memcpy(iovs[i].base, buf_ptr, to_copy); |
| |
| copied += to_copy; |
| buf_ptr += to_copy; |
| buf_len -= to_copy; |
| |
| if (buf_len == 0) |
| break; |
| } |
| |
| return copied; |
| } |
| |
| static int check_response(struct trusty_ipc_dev *dev, |
| volatile struct trusty_ipc_cmd_hdr *hdr, uint16_t cmd) |
| { |
| if (hdr->opcode != (cmd | QL_TIPC_DEV_RESP)) { |
| /* malformed response */ |
| trusty_error("%s: malformed response cmd: 0x%x\n", |
| __func__, hdr->opcode); |
| return TRUSTY_ERR_SECOS_ERR; |
| } |
| |
| if (hdr->status) { |
| /* secure OS responded with error: TODO need error code */ |
| trusty_error("%s: cmd 0x%x: status = %d\n", |
| __func__, hdr->opcode, hdr->status); |
| return TRUSTY_ERR_SECOS_ERR; |
| } |
| |
| return TRUSTY_ERR_NONE; |
| } |
| |
| int trusty_ipc_dev_create(struct trusty_ipc_dev **idev, |
| struct trusty_dev *tdev, |
| size_t shared_buf_size) |
| { |
| int rc; |
| struct trusty_ipc_dev *dev; |
| |
| trusty_assert(idev); |
| trusty_assert(!(shared_buf_size % PAGE_SIZE)); |
| trusty_debug("%s: Create new Trusty IPC device (%zu)\n", __func__, |
| shared_buf_size); |
| |
| /* allocate device context */ |
| dev = trusty_calloc(1, sizeof(*dev)); |
| if (!dev) { |
| trusty_error("%s: failed to allocate Trusty IPC device\n", __func__); |
| return TRUSTY_ERR_NO_MEMORY; |
| } |
| dev->tdev = tdev; |
| |
| /* allocate shared buffer */ |
| dev->buf_size = shared_buf_size; |
| dev->buf_vaddr = trusty_alloc_pages(shared_buf_size / PAGE_SIZE); |
| if (!dev->buf_vaddr) { |
| trusty_error("%s: failed to allocate shared memory\n", __func__); |
| rc = TRUSTY_ERR_NO_MEMORY; |
| goto err_alloc_pages; |
| } |
| |
| /* Get memory attributes */ |
| rc = trusty_encode_page_info(&dev->buf_ns, dev->buf_vaddr); |
| if (rc != 0) { |
| trusty_error("%s: failed to get shared memory attributes\n", __func__); |
| rc = TRUSTY_ERR_GENERIC; |
| goto err_page_info; |
| } |
| /* call secure OS to register shared buffer */ |
| rc = trusty_dev_init_ipc(dev->tdev, &dev->buf_ns, dev->buf_size); |
| if (rc != 0) { |
| trusty_error("%s: failed (%d) to create Trusty IPC device\n", |
| __func__, rc); |
| rc = TRUSTY_ERR_SECOS_ERR; |
| goto err_create_sec_dev; |
| } |
| |
| trusty_debug("%s: new Trusty IPC device (%p)\n", __func__, dev); |
| |
| *idev = dev; |
| return TRUSTY_ERR_NONE; |
| |
| err_page_info: |
| err_create_sec_dev: |
| trusty_free_pages(dev->buf_vaddr, dev->buf_size / PAGE_SIZE); |
| err_alloc_pages: |
| trusty_free(dev); |
| return rc; |
| } |
| |
| void trusty_ipc_dev_shutdown(struct trusty_ipc_dev *dev) |
| { |
| int rc; |
| trusty_assert(dev); |
| |
| trusty_debug("%s: shutting down Trusty IPC device (%p)\n", __func__, dev); |
| |
| /* shutdown Trusty IPC device */ |
| rc = trusty_dev_shutdown_ipc(dev->tdev, &dev->buf_ns, dev->buf_size); |
| trusty_assert(!rc); |
| if (rc != 0) { |
| trusty_error("%s: failed (%d) to shutdown Trusty IPC device\n", |
| __func__, rc); |
| } |
| trusty_free_pages(dev->buf_vaddr, dev->buf_size / PAGE_SIZE); |
| trusty_free(dev); |
| } |
| |
| int trusty_ipc_dev_connect(struct trusty_ipc_dev *dev, const char *port, |
| uint64_t cookie) |
| { |
| int rc; |
| size_t port_len; |
| volatile struct trusty_ipc_cmd_hdr *cmd; |
| struct trusty_ipc_connect_req *req; |
| |
| trusty_assert(dev); |
| trusty_assert(port); |
| |
| trusty_debug("%s: connecting to '%s'\n", __func__, port); |
| |
| /* check port name length */ |
| port_len = trusty_strlen(port) + 1; |
| if (port_len > (dev->buf_size - sizeof(*cmd) + sizeof(*req))) { |
| /* it would not fit into buffer */ |
| trusty_error("%s: port name is too long (%zu)\n", __func__, port_len); |
| return TRUSTY_ERR_INVALID_ARGS; |
| } |
| |
| /* prepare command */ |
| cmd = dev->buf_vaddr; |
| trusty_memset((void *)cmd, 0, sizeof(*cmd)); |
| cmd->opcode = QL_TIPC_DEV_CONNECT; |
| |
| /* prepare payload */ |
| req = (struct trusty_ipc_connect_req *)cmd->payload; |
| trusty_memset((void *)req, 0, sizeof(*req)); |
| req->cookie = cookie; |
| trusty_strcpy((char *)req->name, port); |
| cmd->payload_len = sizeof(*req) + port_len; |
| |
| /* call secure os */ |
| rc = trusty_dev_exec_ipc(dev->tdev, |
| &dev->buf_ns, sizeof(*cmd) + cmd->payload_len); |
| if (rc) { |
| /* secure OS returned an error */ |
| trusty_error("%s: secure OS returned (%d)\n", __func__, rc); |
| return TRUSTY_ERR_SECOS_ERR; |
| } |
| |
| rc = check_response(dev, cmd, QL_TIPC_DEV_CONNECT); |
| if (rc) { |
| trusty_error("%s: connect cmd failed (%d)\n", __func__, rc); |
| return rc; |
| } |
| |
| /* success */ |
| return cmd->handle; |
| } |
| |
| int trusty_ipc_dev_close(struct trusty_ipc_dev *dev, handle_t handle) |
| { |
| int rc; |
| volatile struct trusty_ipc_cmd_hdr *cmd; |
| |
| trusty_assert(dev); |
| |
| trusty_debug("%s: chan %d: closing\n", __func__, handle); |
| |
| /* prepare command */ |
| cmd = dev->buf_vaddr; |
| trusty_memset((void *)cmd, 0, sizeof(*cmd)); |
| cmd->opcode = QL_TIPC_DEV_DISCONNECT; |
| cmd->handle = handle; |
| /* no payload */ |
| |
| /* call into secure os */ |
| rc = trusty_dev_exec_ipc(dev->tdev, |
| &dev->buf_ns, sizeof(*cmd) + cmd->payload_len); |
| if (rc) { |
| trusty_error("%s: secure OS returned (%d)\n", __func__, rc); |
| return TRUSTY_ERR_SECOS_ERR; |
| } |
| |
| rc = check_response(dev, cmd, QL_TIPC_DEV_DISCONNECT); |
| if (rc) { |
| trusty_error("%s: disconnect cmd failed (%d)\n", __func__, rc); |
| return rc; |
| } |
| |
| trusty_debug("%s: chan %d: closed\n", __func__, handle); |
| |
| return TRUSTY_ERR_NONE; |
| } |
| |
| int trusty_ipc_dev_get_event(struct trusty_ipc_dev *dev, handle_t chan, |
| struct trusty_ipc_event *event) |
| { |
| int rc; |
| volatile struct trusty_ipc_cmd_hdr *cmd; |
| |
| trusty_assert(dev); |
| trusty_assert(event); |
| |
| /* prepare command */ |
| cmd = dev->buf_vaddr; |
| trusty_memset((void *)cmd, 0, sizeof(*cmd)); |
| cmd->opcode = QL_TIPC_DEV_GET_EVENT; |
| cmd->handle = chan; |
| |
| /* prepare payload */ |
| trusty_memset((void *)cmd->payload, 0, sizeof(struct trusty_ipc_wait_req)); |
| cmd->payload_len = sizeof(struct trusty_ipc_wait_req); |
| |
| /* call into secure os */ |
| rc = trusty_dev_exec_ipc(dev->tdev, |
| &dev->buf_ns, sizeof(*cmd) + cmd->payload_len); |
| if (rc) { |
| trusty_error("%s: secure OS returned (%d)\n", __func__, rc); |
| return TRUSTY_ERR_SECOS_ERR; |
| } |
| |
| rc = check_response(dev, cmd, QL_TIPC_DEV_GET_EVENT); |
| if (rc) { |
| trusty_error("%s: get event cmd failed (%d)\n", __func__, rc); |
| return rc; |
| } |
| |
| if ((size_t)cmd->payload_len < sizeof(*event)) { |
| trusty_error("%s: invalid response length (%zd)\n", |
| __func__, (size_t)cmd->payload_len); |
| return TRUSTY_ERR_SECOS_ERR; |
| } |
| |
| /* copy out event */ |
| trusty_memcpy(event, (const void *)cmd->payload, sizeof(*event)); |
| return TRUSTY_ERR_NONE; |
| } |
| |
| int trusty_ipc_dev_send(struct trusty_ipc_dev *dev, handle_t chan, |
| const struct trusty_ipc_iovec *iovs, size_t iovs_cnt) |
| { |
| int rc; |
| size_t msg_size; |
| volatile struct trusty_ipc_cmd_hdr *cmd; |
| |
| trusty_assert(dev); |
| /* calc message length */ |
| msg_size = iovec_size(iovs, iovs_cnt); |
| if (msg_size > dev->buf_size - sizeof(*cmd)) { |
| /* msg is too big to fit provided buffer */ |
| trusty_error("%s: chan %d: msg is too long (%zu)\n", __func__, |
| chan, msg_size); |
| return TRUSTY_ERR_MSG_TOO_BIG; |
| } |
| |
| /* prepare command */ |
| cmd = dev->buf_vaddr; |
| trusty_memset((void *)cmd, 0, sizeof(*cmd)); |
| cmd->opcode = QL_TIPC_DEV_SEND; |
| cmd->handle = chan; |
| |
| /* copy in message data */ |
| cmd->payload_len = (uint32_t)msg_size; |
| msg_size = iovec_to_buf(dev->buf_vaddr + sizeof(*cmd), dev->buf_size - sizeof(*cmd), |
| iovs, iovs_cnt); |
| trusty_assert(msg_size == (size_t)cmd->payload_len); |
| |
| /* call into secure os */ |
| rc = trusty_dev_exec_ipc(dev->tdev, |
| &dev->buf_ns, sizeof(*cmd) + cmd->payload_len); |
| if (rc < 0) { |
| trusty_error("%s: secure OS returned (%d)\n", __func__, rc); |
| return TRUSTY_ERR_SECOS_ERR; |
| } |
| |
| rc = check_response(dev, cmd, QL_TIPC_DEV_SEND); |
| if (rc) { |
| trusty_error("%s: send msg failed (%d)\n", __func__, rc); |
| } |
| |
| return rc; |
| } |
| |
| |
| int trusty_ipc_dev_recv(struct trusty_ipc_dev *dev, handle_t chan, |
| const struct trusty_ipc_iovec *iovs, size_t iovs_cnt) |
| { |
| int rc; |
| size_t copied; |
| volatile struct trusty_ipc_cmd_hdr *cmd; |
| |
| trusty_assert(dev); |
| |
| /* prepare command */ |
| cmd = dev->buf_vaddr; |
| trusty_memset((void *)cmd, 0, sizeof(*cmd)); |
| cmd->opcode = QL_TIPC_DEV_RECV; |
| cmd->handle = chan; |
| /* no payload */ |
| |
| /* call into secure os */ |
| rc = trusty_dev_exec_ipc(dev->tdev, |
| &dev->buf_ns, sizeof(*cmd) + cmd->payload_len); |
| if (rc < 0) { |
| trusty_error("%s: secure OS returned (%d)\n", __func__, rc); |
| return TRUSTY_ERR_SECOS_ERR; |
| } |
| |
| rc = check_response(dev, cmd, QL_TIPC_DEV_RECV); |
| if (rc) { |
| trusty_error("%s: recv cmd failed (%d)\n", __func__, rc); |
| return rc; |
| } |
| |
| /* copy data out to proper destination */ |
| copied = buf_to_iovec(iovs, iovs_cnt, |
| (const void *)cmd->payload, cmd->payload_len); |
| if (copied != (size_t)cmd->payload_len) { |
| /* msg is too big to fit provided buffer */ |
| trusty_error("%s: chan %d: buffer too small (%zu vs. %zu)\n", |
| __func__, chan, copied, (size_t)cmd->payload_len); |
| return TRUSTY_ERR_MSG_TOO_BIG; |
| } |
| |
| return (int)copied; |
| } |
| |
| void trusty_ipc_dev_idle(struct trusty_ipc_dev *dev) |
| { |
| trusty_idle(dev->tdev); |
| } |
| |