blob: 64919d1de2ec630fa5f8e1be1095ef1fe14e3336 [file] [log] [blame]
/*
* Copyright (c) 2016, Google, Inc. All rights reserved
*
* 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 <assert.h>
#include <compiler.h>
#include <err.h>
#include <kernel/mutex.h>
#include <kernel/vm.h>
#include <list.h>
#include <lib/sm/sm_err.h>
#include <lib/trusty/handle.h>
#include <lib/trusty/ipc.h>
#include <lib/trusty/ipc_msg.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <trace.h>
#include "tipc_dev_ql.h"
#define LOCAL_TRACE 0
/*
* Max number of sync tipc devices
*/
#define QL_TIPC_DEV_MAX_NUM 2
/*
* Max number of opened channels supported
*/
#define QL_TIPC_ADDR_MAX_NUM 32
/*
* Local addresses base
*/
#define QL_TIPC_ADDR_BASE 32
/*
* Maximum service name size
*/
#define TIPC_MAX_SRV_NAME_LEN (256)
struct tipc_ept {
handle_t *chan;
uint64_t cookie;
};
struct ql_tipc_dev {
struct list_node node;
handle_list_t handle_list;
uint ns_mmu_flags;
ns_size_t ns_sz;
ns_addr_t ns_pa;
void *ns_va;
const uuid_t *uuid;
unsigned long inuse[BITMAP_NUM_WORDS(QL_TIPC_ADDR_MAX_NUM)];
struct tipc_ept epts[QL_TIPC_ADDR_MAX_NUM];
};
struct tipc_cmd_hdr {
uint16_t opcode;
uint16_t flags;
uint32_t status;
uint32_t handle;
uint32_t payload_len;
};
struct tipc_event {
uint32_t event;
uint32_t handle;
uint64_t cookie;
};
struct tipc_wait_req {
uint64_t reserved;
};
struct tipc_connect_req {
uint64_t cookie;
uint64_t reserved;
uint8_t name[0];
};
static uint _dev_cnt;
static struct list_node _dev_list = LIST_INITIAL_VALUE(_dev_list);
static inline uint addr_to_slot(uint32_t addr)
{
return (uint)(addr - QL_TIPC_ADDR_BASE);
}
static inline uint32_t slot_to_addr(uint slot)
{
return (uint32_t) (slot + QL_TIPC_ADDR_BASE);
}
static uint32_t alloc_local_addr(struct ql_tipc_dev *dev, handle_t *chan,
uint64_t cookie)
{
int slot = bitmap_ffz(dev->inuse, QL_TIPC_ADDR_MAX_NUM);
if (slot >= 0) {
bitmap_set(dev->inuse, slot);
dev->epts[slot].chan = chan;
dev->epts[slot].cookie = cookie;
return slot_to_addr(slot);
}
return 0;
}
static struct tipc_ept *ept_lookup(struct ql_tipc_dev *dev, uint32_t local)
{
uint slot = addr_to_slot(local);
if (slot < QL_TIPC_ADDR_MAX_NUM) {
if (bitmap_test(dev->inuse, slot)) {
return &dev->epts[slot];
}
}
return NULL;
}
static uint32_t ept_to_addr(struct ql_tipc_dev *dev, struct tipc_ept *ept)
{
return slot_to_addr(ept - dev->epts);
}
static void free_local_addr(struct ql_tipc_dev *dev, uint32_t local)
{
uint slot = addr_to_slot(local);
if (slot < QL_TIPC_ADDR_MAX_NUM) {
bitmap_clear(dev->inuse, slot);
dev->epts[slot].chan = NULL;
dev->epts[slot].cookie = 0;
}
}
static struct ql_tipc_dev *dev_lookup(ns_addr_t buf_pa)
{
struct ql_tipc_dev *dev;
list_for_every_entry(&_dev_list, dev, struct ql_tipc_dev, node) {
if(dev->ns_pa == buf_pa) {
return dev;
}
}
return NULL;
}
static long dev_create(ns_addr_t buf_pa, ns_size_t buf_sz, uint buf_mmu_flags)
{
status_t res;
struct ql_tipc_dev *dev;
dev = dev_lookup(buf_pa);
if (dev) {
LTRACEF("0x%llx: device already exists\n", buf_pa);
return SM_ERR_INVALID_PARAMETERS;
}
if (buf_pa & (PAGE_SIZE-1)) {
LTRACEF("shared buffer addr is not page aligned: 0x%llx\n", buf_pa);
return SM_ERR_INVALID_PARAMETERS;
}
if (!buf_sz) {
LTRACEF("zero size shared buffer specified\n");
return SM_ERR_INVALID_PARAMETERS;
}
if (buf_sz & (PAGE_SIZE-1)) {
LTRACEF("shared buffer size is not page aligned: 0x%x\n", buf_sz);
return SM_ERR_INVALID_PARAMETERS;
}
if (_dev_cnt >= QL_TIPC_DEV_MAX_NUM) {
LTRACEF("max number of devices reached: %d\n", _dev_cnt);
return SM_ERR_NOT_ALLOWED;
}
dev = calloc(1, sizeof(*dev));
if (!dev) {
LTRACEF("out of memory creating sync tipc dev\n");
return SM_ERR_INTERNAL_FAILURE;
}
dev->uuid = &zero_uuid;
list_clear_node(&dev->node);
handle_list_init(&dev->handle_list);
/* map shared buffer into address space */
dev->ns_pa = buf_pa;
dev->ns_sz = buf_sz;
dev->ns_mmu_flags = buf_mmu_flags;
res = vmm_alloc_physical(vmm_get_kernel_aspace(), "tipc",
ROUNDUP(buf_sz, PAGE_SIZE),
&dev->ns_va, PAGE_SIZE_SHIFT,
(paddr_t)buf_pa, 0, buf_mmu_flags);
if (res != NO_ERROR) {
LTRACEF("failed (%d) to map shared buffer\n", res);
free(dev);
return SM_ERR_INTERNAL_FAILURE;
}
list_add_head(&_dev_list, &dev->node);
_dev_cnt++;
LTRACEF("tipc dev: %u bytes @ 0x%llx (%p) (flags=0x%x)\n",
dev->ns_sz, dev->ns_pa, dev->ns_va, dev->ns_mmu_flags);
return 0;
}
static void dev_shutdown(struct ql_tipc_dev *dev)
{
DEBUG_ASSERT(dev);
DEBUG_ASSERT(dev->ns_va);
/* remove from list */
list_delete(&dev->node);
_dev_cnt--;
/* unmap shared region */
vmm_free_region(vmm_get_kernel_aspace(), (vaddr_t)dev->ns_va);
dev->ns_va = NULL;
/* close all channels */
for (uint slot = 0; slot < countof(dev->epts); slot++) {
struct tipc_ept *ept = &dev->epts[slot];
if (!bitmap_test(dev->inuse, slot))
continue;
if (!ept->chan)
continue;
handle_list_del(&dev->handle_list, ept->chan);
handle_set_cookie(ept->chan, NULL);
handle_close(ept->chan);
}
free(dev);
}
static long set_status(struct ql_tipc_dev *dev, int cmd, int err, size_t len)
{
struct tipc_cmd_hdr *ns_hdr = dev->ns_va;
ns_hdr->status = (err < 0) ? 1 : 0;
ns_hdr->payload_len = len;
ns_hdr->opcode = cmd | QL_TIPC_DEV_RESP;
smp_wmb();
return err;
}
static int dev_connect(struct ql_tipc_dev *dev, void *ns_payload,
size_t ns_payload_len)
{
int rc;
uint32_t local = 0;
handle_t *chan = NULL;
int opcode = QL_TIPC_DEV_CONNECT;
struct tipc_cmd_hdr *ns_hdr = dev->ns_va;
struct {
struct tipc_connect_req hdr;
uint8_t body[TIPC_MAX_SRV_NAME_LEN + 1];
} req;
if (ns_payload_len <= sizeof(req.hdr))
return set_status(dev, opcode, ERR_INVALID_ARGS, 0);
if (ns_payload_len >= sizeof(req))
return set_status(dev, opcode, ERR_INVALID_ARGS, 0);
/* copy out and zero terminate */
memcpy(&req, ns_payload, ns_payload_len);
req.body[ns_payload_len - sizeof(req.hdr)] = 0;
/* open ipc channel */
rc = ipc_port_connect_async(dev->uuid, (const char *)req.body,
ns_payload_len - sizeof(req.hdr), 0, &chan);
if (rc != NO_ERROR) {
LTRACEF("failed to open ipc channel: %d\n", rc);
return set_status(dev, opcode, rc, 0);
}
/* allocate slot */
local = alloc_local_addr(dev, chan, req.hdr.cookie);
if (local == 0) {
LTRACEF("failed to alloc local address\n");
handle_close(chan);
chan = NULL;
return set_status(dev, opcode, ERR_NO_RESOURCES, 0);
}
LTRACEF("new handle: 0x%x\n", local);
handle_set_cookie(chan, ept_lookup(dev, local));
handle_list_add(&dev->handle_list, chan);
ns_hdr->handle = local;
return set_status(dev, opcode, 0, 0);
}
static long dev_disconnect(struct ql_tipc_dev *dev, uint32_t target)
{
struct tipc_ept *ept;
int opcode = QL_TIPC_DEV_DISCONNECT;
ept = ept_lookup(dev, target);
if (!ept || !ept->chan)
return SM_ERR_INVALID_PARAMETERS;
handle_list_del(&dev->handle_list, ept->chan);
handle_set_cookie(ept->chan, NULL);
handle_close(ept->chan);
free_local_addr(dev, target);
return set_status(dev, opcode, 0, 0);
}
static long dev_send(struct ql_tipc_dev *dev, void *ns_data, size_t ns_sz,
uint32_t target)
{
int opcode = QL_TIPC_DEV_SEND;
struct tipc_ept *ept = ept_lookup(dev, target);
if (!ept || !ept->chan)
return set_status(dev, opcode, ERR_INVALID_ARGS, 0);
ipc_msg_kern_t msg = {
.iov = (iovec_kern_t[]) {
[0] = {
.base = ns_data,
.len = ns_sz,
},
},
.num_iov = 1,
.num_handles = 0,
};
return set_status(dev, opcode, ipc_send_msg(ept->chan, &msg), 0);
}
static long dev_recv(struct ql_tipc_dev *dev, uint32_t target)
{
int rc;
int opcode = QL_TIPC_DEV_RECV;
struct tipc_ept *ept = ept_lookup(dev, target);
if (!ept || !ept->chan)
return set_status(dev, opcode, ERR_INVALID_ARGS, 0);
ipc_msg_info_t mi;
rc = ipc_get_msg(ept->chan, &mi);
if (rc < 0)
return set_status(dev, opcode, rc, 0);
ipc_msg_kern_t msg = {
.iov = (iovec_kern_t[]) {
[0] = {
.base = dev->ns_va + sizeof(struct tipc_cmd_hdr),
.len = dev->ns_sz - sizeof(struct tipc_cmd_hdr),
},
},
.num_iov = 1,
.num_handles = 0,
};
rc = ipc_read_msg(ept->chan, mi.id, 0, &msg);
ipc_put_msg(ept->chan, mi.id);
if (rc < 0)
return set_status(dev, opcode, rc, 0);
if (rc < (int)mi.len)
return set_status(dev, opcode, ERR_BAD_LEN, 0);
return set_status(dev, opcode, rc, mi.len);
}
static long dev_get_event(struct ql_tipc_dev *dev, void *ns_data, size_t ns_sz,
uint32_t target)
{
int rc;
handle_t *chan;
struct tipc_wait_req req;
uint32_t chan_event = 0;
struct tipc_ept *ept = NULL;
int opcode = QL_TIPC_DEV_GET_EVENT;
struct tipc_event *evt = (struct tipc_event*)((uint8_t*)dev->ns_va +
sizeof(struct tipc_cmd_hdr));
if (ns_sz < sizeof(req))
return set_status(dev, opcode, ERR_INVALID_ARGS, 0);
if (target) {
/* wait on specific handle */
ept = ept_lookup(dev, target);
if (!ept || !ept->chan)
return set_status(dev, opcode, ERR_INVALID_ARGS, 0);
chan = ept->chan;
rc = handle_wait(chan, &chan_event, 0);
if (rc == ERR_TIMED_OUT) {
/* no events return an empty event */
evt->handle = 0;
evt->event = 0;
evt->cookie = 0;
}
else if (rc < 0) {
/* only possible if something is corrupted or somebody is
* already waiting on the same handle
*/
panic("%s: couldn't wait for handle events (%d)\n",
__func__, rc);
} else {
/* got an event: return it */
evt->handle = target;
evt->event = chan_event;
evt->cookie = ept->cookie;
}
} else {
/* wait for event with 0-timeout */
rc = handle_list_wait(&dev->handle_list, &chan,
&chan_event, 0);
if (rc == ERR_NOT_FOUND) {
/* no handles left */
return set_status(dev, opcode, rc, 0);
}
if (rc < 0) {
if (rc == ERR_TIMED_OUT) {
/* no events: return an empty event */
evt->handle = 0;
evt->event = 0;
evt->cookie = 0;
}
else {
/* only possible if somebody else is waiting
on the same handle which should never happen */
panic("%s: couldn't wait for handle events (%d)\n",
__func__, rc);
}
} else {
/* got an event: return it */
ept = handle_get_cookie(chan);
evt->handle = ept_to_addr(dev, ept);
evt->event = chan_event;
evt->cookie = ept->cookie;
/* drop ref obtained by handle_list_wait */
handle_decref(chan);
}
}
return set_status(dev, opcode, 0, sizeof(*evt));
}
static long dev_handle_cmd(struct ql_tipc_dev *dev,
const struct tipc_cmd_hdr *cmd, void *ns_payload)
{
DEBUG_ASSERT(dev);
switch (cmd->opcode) {
case QL_TIPC_DEV_SEND:
return dev_send(dev, ns_payload, cmd->payload_len, cmd->handle);
case QL_TIPC_DEV_RECV:
return dev_recv(dev, cmd->handle);
case QL_TIPC_DEV_GET_EVENT:
return dev_get_event(dev, ns_payload, cmd->payload_len, cmd->handle);
case QL_TIPC_DEV_CONNECT:
return dev_connect(dev, ns_payload, cmd->payload_len);
case QL_TIPC_DEV_DISCONNECT:
return dev_disconnect(dev, cmd->handle);
default:
LTRACEF("0x%x: unhandled cmd\n", cmd->opcode);
return set_status(dev, cmd->opcode, ERR_NOT_SUPPORTED, 0);
}
}
long ql_tipc_create_device(ns_addr_t buf_pa, ns_size_t buf_sz,
uint buf_mmu_flags)
{
return dev_create(buf_pa, buf_sz, buf_mmu_flags);
}
long ql_tipc_shutdown_device(ns_addr_t buf_pa)
{
struct ql_tipc_dev *dev = dev_lookup(buf_pa);
if (!dev) {
LTRACEF("0x%llx: device not found\n", buf_pa);
return SM_ERR_INVALID_PARAMETERS;
}
dev_shutdown(dev);
return 0;
}
long ql_tipc_handle_cmd(ns_addr_t buf_pa, ns_size_t cmd_sz)
{
struct tipc_cmd_hdr cmd_hdr;
/* lookup device */
struct ql_tipc_dev *dev = dev_lookup(buf_pa);
if (!dev) {
LTRACEF("0x%llx: device not found\n", buf_pa);
return SM_ERR_INVALID_PARAMETERS;
}
/* check for minimum size */
if (cmd_sz < sizeof(cmd_hdr)) {
LTRACEF("message is too short (%zd)\n", cmd_sz);
return SM_ERR_INVALID_PARAMETERS;
}
/* copy out command header */
memcpy(&cmd_hdr, dev->ns_va, sizeof(cmd_hdr));
/* check for consistency */
if (cmd_hdr.payload_len != (cmd_sz - sizeof(cmd_hdr))) {
LTRACEF("malformed command\n");
return SM_ERR_INVALID_PARAMETERS;
}
return dev_handle_cmd(dev, &cmd_hdr, dev->ns_va + sizeof(cmd_hdr));
}