coreboot-libre-fam15h-rdimm/3rdparty/chromeec/chip/ish/heci.c

1027 lines
26 KiB
C

/* Copyright 2018 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "atomic.h"
#include "compile_time_macros.h"
#include "console.h"
#include "hbm.h"
#include "heci_client.h"
#include "ipc_heci.h"
#include "system_state.h"
#include "task.h"
#include "timer.h"
#include "util.h"
#define CPUTS(outstr) cputs(CC_LPC, outstr)
#define CPRINTS(format, args...) cprints(CC_LPC, format, ## args)
#define CPRINTF(format, args...) cprintf(CC_LPC, format, ## args)
struct heci_header {
uint8_t fw_addr;
uint8_t host_addr;
uint16_t length; /* [8:0] length, [14:9] reserved, [15] msg_complete */
} __packed;
#define HECI_MSG_CMPL_SHIFT 15
#define HECI_MSG_LENGTH_MASK 0x01FF
#define HECI_MSG_LENGTH(length) ((length) & HECI_MSG_LENGTH_MASK)
#define HECI_MSG_IS_COMPLETED(length) \
(!!((length) & (0x01 << HECI_MSG_CMPL_SHIFT)))
BUILD_ASSERT(HECI_IPC_PAYLOAD_SIZE ==
(IPC_MAX_PAYLOAD_SIZE - sizeof(struct heci_header)));
struct heci_msg {
struct heci_header hdr;
uint8_t payload[HECI_IPC_PAYLOAD_SIZE];
} __packed;
/* HECI addresses */
#define HECI_HBM_ADDRESS 0 /* HECI Bus Message */
#define HECI_DYN_CLIENT_ADDR_START 0x20 /* Dynamic client start addr */
/* A fw client has the same value for both handle and fw address */
#define TO_FW_ADDR(handle) ((uintptr_t)(handle))
#define TO_HECI_HANDLE(fw_addr) ((heci_handle_t)(uintptr_t)(fw_addr))
/* convert client fw address to client context index */
#define TO_CLIENT_CTX_IDX(fw_addr) ((fw_addr) - HECI_DYN_CLIENT_ADDR_START)
/* should be less than HECI_INVALID_HANDLE - 1 */
BUILD_ASSERT(HECI_MAX_NUM_OF_CLIENTS < 0x0FE);
struct heci_client_connect {
uint8_t is_connected; /* client is connected to host */
uint8_t host_addr; /* connected host address */
/* receiving message */
uint8_t ignore_rx_msg;
uint8_t rx_msg[HECI_MAX_MSG_SIZE];
size_t rx_msg_length;
uint32_t flow_ctrl_creds; /* flow control */
struct mutex lock; /* protects against 2 writers */
struct mutex cred_lock; /* protects flow ctrl */
int waiting_task;
};
struct heci_client_context {
const struct heci_client *client;
void *data; /* client specific data */
struct heci_client_connect connect; /* connection context */
struct ss_subsys_device ss_device; /* system state receiver device */
};
struct heci_bus_context {
ipc_handle_t ipc_handle; /* ipc handle for heci protocol */
int num_of_clients;
struct heci_client_context client_ctxs[HECI_MAX_NUM_OF_CLIENTS];
};
/* declare heci bus */
struct heci_bus_context heci_bus_ctx = {
.ipc_handle = IPC_INVALID_HANDLE,
};
static inline struct heci_client_context *
heci_get_client_context(const uint8_t fw_addr)
{
return &heci_bus_ctx.client_ctxs[TO_CLIENT_CTX_IDX(fw_addr)];
}
static inline struct heci_client_connect *
heci_get_client_connect(const uint8_t fw_addr)
{
struct heci_client_context *cli_ctx = heci_get_client_context(fw_addr);
return &cli_ctx->connect;
}
static inline int heci_is_client_connected(const uint8_t fw_addr)
{
struct heci_client_context *cli_ctx = heci_get_client_context(fw_addr);
return cli_ctx->connect.is_connected;
}
static inline int heci_is_valid_client_addr(const uint8_t fw_addr)
{
uint8_t cli_idx = TO_CLIENT_CTX_IDX(fw_addr);
return cli_idx < heci_bus_ctx.num_of_clients;
}
static inline int heci_is_valid_handle(const heci_handle_t handle)
{
return heci_is_valid_client_addr((uintptr_t)(handle));
}
/* find heci device that contains this system state device in it */
#define ss_device_to_heci_client_context(ss_dev) \
((struct heci_client_context *)((void *)(ss_dev) - \
(void *)(&(((struct heci_client_context *)0)->ss_device))))
#define client_context_to_handle(cli_ctx) \
((heci_handle_t)((uint32_t)((cli_ctx) - &heci_bus_ctx.client_ctxs[0]) \
/ sizeof(heci_bus_ctx.client_ctxs[0]) + 1))
/*
* each heci device registered as system state device which gets
* system state(e.g. suspend/resume, portrait/landscape) events
* through system state subsystem from host
*/
static int heci_client_suspend(struct ss_subsys_device *ss_device)
{
struct heci_client_context *cli_ctx =
ss_device_to_heci_client_context(ss_device);
heci_handle_t handle = client_context_to_handle(cli_ctx);
if (cli_ctx->client->cbs->suspend)
cli_ctx->client->cbs->suspend(handle);
return EC_SUCCESS;
}
static int heci_client_resume(struct ss_subsys_device *ss_device)
{
struct heci_client_context *cli_ctx =
ss_device_to_heci_client_context(ss_device);
heci_handle_t handle = client_context_to_handle(cli_ctx);
if (cli_ctx->client->cbs->resume)
cli_ctx->client->cbs->resume(handle);
return EC_SUCCESS;
}
struct system_state_callbacks heci_ss_cbs = {
.suspend = heci_client_suspend,
.resume = heci_client_resume,
};
/*
* This function should be called only by HECI_CLIENT_ENTRY()
*/
heci_handle_t heci_register_client(const struct heci_client *client)
{
int ret;
heci_handle_t handle;
struct heci_client_context *cli_ctx;
if (client == NULL || client->cbs == NULL)
return HECI_INVALID_HANDLE;
/*
* we don't need mutex here since this function is called by
* entry function which is serialized among heci clients.
*/
if (heci_bus_ctx.num_of_clients >= HECI_MAX_NUM_OF_CLIENTS)
return HECI_INVALID_HANDLE;
/* we only support 1 connection */
if (client->max_n_of_connections > 1)
return HECI_INVALID_HANDLE;
if (client->max_msg_size > HECI_MAX_MSG_SIZE)
return HECI_INVALID_HANDLE;
/* create handle with the same value of fw address */
handle = (heci_handle_t)(heci_bus_ctx.num_of_clients +
HECI_DYN_CLIENT_ADDR_START);
cli_ctx = &heci_bus_ctx.client_ctxs[heci_bus_ctx.num_of_clients++];
cli_ctx->client = client;
if (client->cbs->initialize) {
ret = client->cbs->initialize(handle);
if (ret) {
heci_bus_ctx.num_of_clients--;
return HECI_INVALID_HANDLE;
}
}
if (client->cbs->suspend || client->cbs->resume) {
cli_ctx->ss_device.cbs = &heci_ss_cbs;
ss_subsys_register_client(&cli_ctx->ss_device);
}
return handle;
}
static void heci_build_hbm_header(struct heci_header *hdr, uint32_t length)
{
hdr->fw_addr = HECI_HBM_ADDRESS;
hdr->host_addr = HECI_HBM_ADDRESS;
hdr->length = length;
/* payload of hbm is less than IPC payload */
hdr->length |= (uint16_t)1 << HECI_MSG_CMPL_SHIFT;
}
static void heci_build_fixed_client_header(struct heci_header *hdr,
const uint8_t fw_addr,
const uint32_t length)
{
hdr->fw_addr = fw_addr;
hdr->host_addr = 0;
hdr->length = length;
/* Fixed client payload < IPC payload */
hdr->length |= (uint16_t)1 << HECI_MSG_CMPL_SHIFT;
}
static int heci_send_heci_msg_timestamp(struct heci_msg *msg,
uint32_t *timestamp)
{
int length, written;
if (heci_bus_ctx.ipc_handle == IPC_INVALID_HANDLE)
return -1;
length = sizeof(msg->hdr) + HECI_MSG_LENGTH(msg->hdr.length);
written = ipc_write_timestamp(heci_bus_ctx.ipc_handle, msg, length,
timestamp);
if (written != length) {
CPRINTF("%s error : len = %d err = %d\n", __func__,
(int)length, written);
return -EC_ERROR_UNKNOWN;
}
return EC_SUCCESS;
}
static int heci_send_heci_msg(struct heci_msg *msg)
{
return heci_send_heci_msg_timestamp(msg, NULL);
}
int heci_set_client_data(const heci_handle_t handle, void *data)
{
struct heci_client_context *cli_ctx;
const uint8_t fw_addr = TO_FW_ADDR(handle);
if (!heci_is_valid_handle(handle))
return -EC_ERROR_INVAL;
cli_ctx = heci_get_client_context(fw_addr);
cli_ctx->data = data;
return EC_SUCCESS;
}
void *heci_get_client_data(const heci_handle_t handle)
{
struct heci_client_context *cli_ctx;
const uint8_t fw_addr = TO_FW_ADDR(handle);
if (!heci_is_valid_handle(handle))
return NULL;
cli_ctx = heci_get_client_context(fw_addr);
return cli_ctx->data;
}
/*
* Waits for flow control credit that allows TX transactions
*
* Returns true if credit was acquired, otherwise false
*/
static int wait_for_flow_ctrl_cred(struct heci_client_connect *connect)
{
int need_to_wait;
do {
mutex_lock(&connect->cred_lock);
need_to_wait = !connect->flow_ctrl_creds;
if (need_to_wait) {
connect->waiting_task = task_get_current();
} else {
connect->flow_ctrl_creds = 0;
connect->waiting_task = 0;
}
mutex_unlock(&connect->cred_lock);
if (need_to_wait) {
/*
* A second is more than enough, otherwise if will
* probably never happen.
*/
int ev = task_wait_event_mask(TASK_EVENT_IPC_READY,
SECOND);
if (ev & TASK_EVENT_TIMER) {
/* Return false, not able to get credit */
return 0;
}
}
} while (need_to_wait);
/* We successfully got flow control credit */
return 1;
}
int heci_send_msg_timestamp(const heci_handle_t handle, uint8_t *buf,
const size_t buf_size, uint32_t *timestamp)
{
int buf_offset = 0, ret = 0, remain, payload_size;
struct heci_client_connect *connect;
struct heci_msg msg;
const uint8_t fw_addr = TO_FW_ADDR(handle);
if (!heci_is_valid_handle(handle))
return -EC_ERROR_INVAL;
if (buf_size > HECI_MAX_MSG_SIZE)
return -EC_ERROR_OVERFLOW;
connect = heci_get_client_connect(fw_addr);
mutex_lock(&connect->lock);
if (!heci_is_client_connected(fw_addr)) {
ret = -HECI_ERR_CLIENT_IS_NOT_CONNECTED;
goto err_locked;
}
if (!wait_for_flow_ctrl_cred(connect)) {
CPRINTF("no cred\n");
ret = -HECI_ERR_NO_CRED_FROM_CLIENT_IN_HOST;
goto err_locked;
}
msg.hdr.fw_addr = fw_addr;
msg.hdr.host_addr = connect->host_addr;
remain = buf_size;
while (remain) {
if (remain > HECI_IPC_PAYLOAD_SIZE) {
msg.hdr.length = HECI_IPC_PAYLOAD_SIZE;
payload_size = HECI_IPC_PAYLOAD_SIZE;
} else {
msg.hdr.length = remain;
/* set as last heci msg */
msg.hdr.length |= (uint16_t)1 << HECI_MSG_CMPL_SHIFT;
payload_size = remain;
}
memcpy(msg.payload, buf + buf_offset, payload_size);
heci_send_heci_msg_timestamp(&msg, timestamp);
remain -= payload_size;
buf_offset += payload_size;
}
mutex_unlock(&connect->lock);
return buf_size;
err_locked:
mutex_unlock(&connect->lock);
return ret;
}
int heci_send_msg(const heci_handle_t handle, uint8_t *buf,
const size_t buf_size)
{
return heci_send_msg_timestamp(handle, buf, buf_size, NULL);
}
int heci_send_msgs(const heci_handle_t handle,
const struct heci_msg_list *msg_list)
{
struct heci_msg_item *cur_item;
int total_size = 0;
int i, msg_cur_pos, buf_size, copy_size, msg_sent;
struct heci_client_connect *connect;
struct heci_msg msg;
const uint8_t fw_addr = TO_FW_ADDR(handle);
if (!heci_is_valid_handle(handle))
return -EC_ERROR_INVAL;
for (i = 0; i < msg_list->num_of_items; i++) {
if (!msg_list->items[i]->size || !msg_list->items[i]->buf)
return -EC_ERROR_INVAL;
total_size += msg_list->items[i]->size;
}
if (total_size > HECI_MAX_MSG_SIZE)
return -EC_ERROR_OVERFLOW;
if (msg_list->num_of_items > HECI_MAX_MSGS)
return -HECI_ERR_TOO_MANY_MSG_ITEMS;
connect = heci_get_client_connect(fw_addr);
mutex_lock(&connect->lock);
if (!heci_is_client_connected(fw_addr)) {
total_size = -HECI_ERR_CLIENT_IS_NOT_CONNECTED;
goto err_locked;
}
if (!wait_for_flow_ctrl_cred(connect)) {
CPRINTF("no cred\n");
total_size = -HECI_ERR_NO_CRED_FROM_CLIENT_IN_HOST;
goto err_locked;
}
msg.hdr.fw_addr = fw_addr;
msg.hdr.host_addr = connect->host_addr;
i = 1;
msg_cur_pos = 0;
buf_size = 0;
cur_item = msg_list->items[0];
msg_sent = 0;
while (1) {
/* get next item if current item is consumed */
if (msg_cur_pos == cur_item->size) {
/*
* break if no more item.
* if "msg" contains data to be sent
* it will be sent after break.
*/
if (i == msg_list->num_of_items)
break;
/* get next item and reset msg_cur_pos */
cur_item = msg_list->items[i++];
msg_cur_pos = 0;
}
/* send data in ipc buf if it's completely filled */
if (buf_size == HECI_IPC_PAYLOAD_SIZE) {
msg.hdr.length = buf_size;
msg_sent += buf_size;
/* no leftovers, send the last msg here */
if (msg_sent == total_size) {
msg.hdr.length |=
(uint16_t)1 << HECI_MSG_CMPL_SHIFT;
}
heci_send_heci_msg(&msg);
buf_size = 0;
}
/* fill ipc msg buffer */
if (cur_item->size - msg_cur_pos >
HECI_IPC_PAYLOAD_SIZE - buf_size) {
copy_size = HECI_IPC_PAYLOAD_SIZE - buf_size;
} else {
copy_size = cur_item->size - msg_cur_pos;
}
memcpy(msg.payload + buf_size, cur_item->buf + msg_cur_pos,
copy_size);
msg_cur_pos += copy_size;
buf_size += copy_size;
}
/* leftovers ? send last msg */
if (buf_size != 0) {
msg.hdr.length = buf_size;
msg.hdr.length |= (uint16_t)1 << HECI_MSG_CMPL_SHIFT;
heci_send_heci_msg(&msg);
}
err_locked:
mutex_unlock(&connect->lock);
return total_size;
}
/* For now, we only support fixed client payload size < IPC payload size */
int heci_send_fixed_client_msg(const uint8_t fw_addr, uint8_t *buf,
const size_t buf_size)
{
struct heci_msg msg;
heci_build_fixed_client_header(&msg.hdr, fw_addr, buf_size);
memcpy(msg.payload, buf, buf_size);
heci_send_heci_msg(&msg);
return EC_SUCCESS;
}
static int handle_version_req(struct hbm_version_req *ver_req)
{
struct hbm_version_res *ver_res;
struct heci_msg heci_msg;
struct hbm_i2h *i2h;
heci_build_hbm_header(&heci_msg.hdr,
sizeof(i2h->cmd) + sizeof(*ver_res));
i2h = (struct hbm_i2h *)heci_msg.payload;
i2h->cmd = HECI_BUS_MSG_VERSION_RESP;
ver_res = (struct hbm_version_res *)&i2h->data;
memset(ver_res, 0, sizeof(*ver_res));
ver_res->version.major = HBM_MAJOR_VERSION;
ver_res->version.minor = HBM_MINOR_VERSION;
if (ver_req->version.major == HBM_MAJOR_VERSION &&
ver_req->version.minor == HBM_MINOR_VERSION) {
ver_res->supported = 1;
} else {
ver_res->supported = 0;
}
heci_send_heci_msg(&heci_msg);
return EC_SUCCESS;
}
#define BITS_PER_BYTE 8
/* get number of bits for one element of "valid_addresses" array */
#define BITS_PER_ELEMENT \
(sizeof(((struct hbm_enum_res *)0)->valid_addresses[0]) * BITS_PER_BYTE)
static int handle_enum_req(struct hbm_enum_req *enum_req)
{
struct hbm_enum_res *enum_res;
struct heci_msg heci_msg;
struct hbm_i2h *i2h;
int i;
heci_build_hbm_header(&heci_msg.hdr,
sizeof(i2h->cmd) + sizeof(*enum_res));
i2h = (struct hbm_i2h *)heci_msg.payload;
i2h->cmd = HECI_BUS_MSG_HOST_ENUM_RESP;
enum_res = (struct hbm_enum_res *)&i2h->data;
memset(enum_res, 0, sizeof(*enum_res));
/*
* fw address 0 is reserved for HECI Bus Message
* fw address 1 ~ 0x1f are reserved for fixed clients
* fw address 0x20 ~ 0xFF is for dynamic clients
* bit-0 set -> fw address "0", bit-1 set -> fw address "1"
*/
for (i = HECI_DYN_CLIENT_ADDR_START;
i < heci_bus_ctx.num_of_clients + HECI_DYN_CLIENT_ADDR_START;
i++) {
enum_res->valid_addresses[i / BITS_PER_ELEMENT] |=
1 << (i & (BITS_PER_ELEMENT - 1));
}
heci_send_heci_msg(&heci_msg);
return EC_SUCCESS;
}
static int handle_client_prop_req(struct hbm_client_prop_req *client_prop_req)
{
struct hbm_client_prop_res *client_prop_res;
struct heci_msg heci_msg;
struct hbm_i2h *i2h;
struct heci_client_context *client_ctx;
const struct heci_client *client;
heci_build_hbm_header(&heci_msg.hdr,
sizeof(i2h->cmd) + sizeof(*client_prop_res));
i2h = (struct hbm_i2h *)heci_msg.payload;
i2h->cmd = HECI_BUS_MSG_HOST_CLIENT_PROP_RESP;
client_prop_res = (struct hbm_client_prop_res *)&i2h->data;
memset(client_prop_res, 0, sizeof(*client_prop_res));
client_prop_res->address = client_prop_req->address;
if (!heci_is_valid_client_addr(client_prop_req->address)) {
client_prop_res->status = HECI_CONNECT_STATUS_CLIENT_NOT_FOUND;
} else {
struct hbm_client_properties *client_prop;
client_ctx = heci_get_client_context(client_prop_req->address);
client = client_ctx->client;
client_prop = &client_prop_res->client_prop;
client_prop->protocol_name = client->protocol_id;
client_prop->protocol_version = client->protocol_ver;
client_prop->max_number_of_connections =
client->max_n_of_connections;
client_prop->max_msg_length = client->max_msg_size;
client_prop->dma_hdr_len = client->dma_header_length;
client_prop->dma_hdr_len |= client->dma_enabled ?
CLIENT_DMA_ENABLE : 0;
}
heci_send_heci_msg(&heci_msg);
return EC_SUCCESS;
}
static int heci_send_flow_control(uint8_t fw_addr)
{
struct heci_client_connect *connect;
struct hbm_i2h *i2h;
struct hbm_flow_control *flow_ctrl;
struct heci_msg heci_msg;
connect = heci_get_client_connect(fw_addr);
heci_build_hbm_header(&heci_msg.hdr,
sizeof(i2h->cmd) + sizeof(*flow_ctrl));
i2h = (struct hbm_i2h *)heci_msg.payload;
i2h->cmd = HECI_BUS_MSG_FLOW_CONTROL;
flow_ctrl = (struct hbm_flow_control *)&i2h->data;
memset(flow_ctrl, 0, sizeof(*flow_ctrl));
flow_ctrl->fw_addr = fw_addr;
flow_ctrl->host_addr = connect->host_addr;
heci_send_heci_msg(&heci_msg);
return EC_SUCCESS;
}
static int handle_client_connect_req(
struct hbm_client_connect_req *client_connect_req)
{
struct hbm_client_connect_res *client_connect_res;
struct heci_msg heci_msg;
struct hbm_i2h *i2h;
struct heci_client_connect *connect;
heci_build_hbm_header(&heci_msg.hdr,
sizeof(i2h->cmd) + sizeof(*client_connect_res));
i2h = (struct hbm_i2h *)heci_msg.payload;
i2h->cmd = HECI_BUS_MSG_CLIENT_CONNECT_RESP;
client_connect_res = (struct hbm_client_connect_res *)&i2h->data;
memset(client_connect_res, 0, sizeof(*client_connect_res));
client_connect_res->fw_addr = client_connect_req->fw_addr;
client_connect_res->host_addr = client_connect_req->host_addr;
if (!heci_is_valid_client_addr(client_connect_req->fw_addr)) {
client_connect_res->status =
HECI_CONNECT_STATUS_CLIENT_NOT_FOUND;
} else if (!client_connect_req->host_addr) {
client_connect_res->status =
HECI_CONNECT_STATUS_INVALID_PARAMETER;
} else {
connect = heci_get_client_connect(client_connect_req->fw_addr);
if (connect->is_connected) {
client_connect_res->status =
HECI_CONNECT_STATUS_ALREADY_EXISTS;
} else {
connect->is_connected = 1;
connect->host_addr = client_connect_req->host_addr;
}
}
heci_send_heci_msg(&heci_msg);
/* no error, send flow control */
if (!client_connect_res->status)
heci_send_flow_control(client_connect_req->fw_addr);
return EC_SUCCESS;
}
static int handle_flow_control_cmd(struct hbm_flow_control *flow_ctrl)
{
struct heci_client_connect *connect;
int waiting_task;
if (!heci_is_valid_client_addr(flow_ctrl->fw_addr))
return -1;
if (!heci_is_client_connected(flow_ctrl->fw_addr))
return -1;
connect = heci_get_client_connect(flow_ctrl->fw_addr);
mutex_lock(&connect->cred_lock);
connect->flow_ctrl_creds = 1;
waiting_task = connect->waiting_task;
mutex_unlock(&connect->cred_lock);
if (waiting_task)
task_set_event(waiting_task, TASK_EVENT_IPC_READY, 0);
return EC_SUCCESS;
}
static void heci_handle_client_msg(struct heci_msg *msg, size_t length)
{
struct heci_client_context *cli_ctx;
struct heci_client_connect *connect;
const struct heci_client_callbacks *cbs;
int payload_size;
if (!heci_is_valid_client_addr(msg->hdr.fw_addr))
return;
if (!heci_is_client_connected(msg->hdr.fw_addr))
return;
cli_ctx = heci_get_client_context(msg->hdr.fw_addr);
cbs = cli_ctx->client->cbs;
connect = &cli_ctx->connect;
payload_size = HECI_MSG_LENGTH(msg->hdr.length);
if (connect->is_connected &&
msg->hdr.host_addr == connect->host_addr) {
if (!connect->ignore_rx_msg &&
connect->rx_msg_length + payload_size > HECI_MAX_MSG_SIZE) {
connect->ignore_rx_msg = 1; /* too big. discard */
}
if (!connect->ignore_rx_msg) {
memcpy(connect->rx_msg + connect->rx_msg_length,
msg->payload, payload_size);
connect->rx_msg_length += payload_size;
}
if (HECI_MSG_IS_COMPLETED(msg->hdr.length)) {
if (!connect->ignore_rx_msg) {
cbs->new_msg_received(
TO_HECI_HANDLE(msg->hdr.fw_addr),
connect->rx_msg,
connect->rx_msg_length);
}
connect->rx_msg_length = 0;
connect->ignore_rx_msg = 0;
heci_send_flow_control(msg->hdr.fw_addr);
}
}
}
static int handle_client_disconnect_req(
struct hbm_client_disconnect_req *client_disconnect_req)
{
struct hbm_client_disconnect_res *client_disconnect_res;
struct heci_msg heci_msg;
struct hbm_i2h *i2h;
struct heci_client_context *cli_ctx;
struct heci_client_connect *connect;
const struct heci_client_callbacks *cbs;
uint8_t fw_addr, host_addr;
CPRINTS("Got HECI disconnect request");
heci_build_hbm_header(&heci_msg.hdr, sizeof(i2h->cmd) +
sizeof(*client_disconnect_res));
i2h = (struct hbm_i2h *)heci_msg.payload;
i2h->cmd = HECI_BUS_MSG_CLIENT_DISCONNECT_RESP;
client_disconnect_res = (struct hbm_client_disconnect_res *)&i2h->data;
memset(client_disconnect_res, 0, sizeof(*client_disconnect_res));
fw_addr = client_disconnect_req->fw_addr;
host_addr = client_disconnect_req->host_addr;
client_disconnect_res->fw_addr = fw_addr;
client_disconnect_res->host_addr = host_addr;
if (!heci_is_valid_client_addr(fw_addr) ||
!heci_is_client_connected(fw_addr)) {
client_disconnect_res->status =
HECI_CONNECT_STATUS_CLIENT_NOT_FOUND;
} else {
connect = heci_get_client_connect(fw_addr);
if (connect->host_addr != host_addr) {
client_disconnect_res->status =
HECI_CONNECT_STATUS_INVALID_PARAMETER;
} else {
cli_ctx = heci_get_client_context(fw_addr);
cbs = cli_ctx->client->cbs;
mutex_lock(&connect->lock);
if (connect->is_connected) {
cbs->disconnected(TO_HECI_HANDLE(fw_addr));
connect->is_connected = 0;
}
mutex_unlock(&connect->lock);
}
}
heci_send_heci_msg(&heci_msg);
return EC_SUCCESS;
}
/* host stops due to version mismatch */
static int handle_host_stop_req(struct hbm_host_stop_req *host_stop_req)
{
struct hbm_host_stop_res *host_stop_res;
struct heci_msg heci_msg;
struct hbm_i2h *i2h;
heci_build_hbm_header(&heci_msg.hdr,
sizeof(i2h->cmd) + sizeof(*host_stop_res));
i2h = (struct hbm_i2h *)heci_msg.payload;
i2h->cmd = HECI_BUS_MSG_HOST_STOP_RESP;
host_stop_res = (struct hbm_host_stop_res *)&i2h->data;
memset(host_stop_res, 0, sizeof(*host_stop_res));
heci_send_heci_msg(&heci_msg);
return EC_SUCCESS;
}
static int is_hbm_validity(struct hbm_h2i *h2i, size_t length)
{
int valid_msg_len;
valid_msg_len = sizeof(h2i->cmd);
switch (h2i->cmd) {
case HECI_BUS_MSG_VERSION_REQ:
valid_msg_len += sizeof(struct hbm_version_req);
break;
case HECI_BUS_MSG_HOST_ENUM_REQ:
valid_msg_len += sizeof(struct hbm_enum_req);
break;
case HECI_BUS_MSG_HOST_CLIENT_PROP_REQ:
valid_msg_len += sizeof(struct hbm_client_prop_req);
break;
case HECI_BUS_MSG_CLIENT_CONNECT_REQ:
valid_msg_len += sizeof(struct hbm_client_connect_req);
break;
case HECI_BUS_MSG_FLOW_CONTROL:
valid_msg_len += sizeof(struct hbm_flow_control);
break;
case HECI_BUS_MSG_CLIENT_DISCONNECT_REQ:
valid_msg_len += sizeof(struct hbm_client_disconnect_req);
break;
case HECI_BUS_MSG_HOST_STOP_REQ:
valid_msg_len += sizeof(struct hbm_host_stop_req);
break;
/* TODO: DMA support for large data */
#if 0
case HECI_BUS_MSG_DMA_REQ:
valid_msg_len += sizeof(struct hbm_dma_req);
break;
case HECI_BUS_MSG_DMA_ALLOC_NOTIFY:
valid_msg_len += sizeof(struct hbm_dma_alloc_notify);
break;
case HECI_BUS_MSG_DMA_XFER_REQ: /* DMA transfer to FW */
valid_msg_len += sizeof(struct hbm_dma_xfer_req);
break;
case HECI_BUS_MSG_DMA_XFER_RESP: /* Ack for DMA transfer from FW */
valid_msg_len += sizeof(struct hbm_dma_xfer_resp);
break;
#endif
default:
break;
}
if (valid_msg_len != length) {
CPRINTF("invalid cmd(%d) valid : %d, cur : %d\n",
valid_msg_len, length);
/* TODO: invalid cmd. not sure to reply with error ? */
return 0;
}
return 1;
}
static void heci_handle_hbm(struct hbm_h2i *h2i, size_t length)
{
void *data = (void *)&h2i->data;
if (!is_hbm_validity(h2i, length))
return;
switch (h2i->cmd) {
case HECI_BUS_MSG_VERSION_REQ:
handle_version_req((struct hbm_version_req *)data);
break;
case HECI_BUS_MSG_HOST_ENUM_REQ:
handle_enum_req((struct hbm_enum_req *)data);
break;
case HECI_BUS_MSG_HOST_CLIENT_PROP_REQ:
handle_client_prop_req((struct hbm_client_prop_req *)data);
break;
case HECI_BUS_MSG_CLIENT_CONNECT_REQ:
handle_client_connect_req(
(struct hbm_client_connect_req *)data);
break;
case HECI_BUS_MSG_FLOW_CONTROL:
handle_flow_control_cmd((struct hbm_flow_control *)data);
break;
case HECI_BUS_MSG_CLIENT_DISCONNECT_REQ:
handle_client_disconnect_req(
(struct hbm_client_disconnect_req *)data);
break;
case HECI_BUS_MSG_HOST_STOP_REQ:
handle_host_stop_req((struct hbm_host_stop_req *)data);
break;
/* TODO: DMA transfer if data is too big >= ? KB */
#if 0
case HECI_BUS_MSG_DMA_REQ:
handle_dma_req((struct hbm_dma_req *)data);
break;
case HECI_BUS_MSG_DMA_ALLOC_NOTIFY:
handle_dma_alloc_notify((struct hbm_dma_alloc_notify *));
break;
case HECI_BUS_MSG_DMA_XFER_REQ: /* DMA transfer to FW */
handle_dma_xfer_req((struct hbm_dma_xfer_req *)data);
break;
case HECI_BUS_MSG_DMA_XFER_RESP: /* Ack for DMA transfer from FW */
handle_dma_xfer_resp((struct hbm_dma_xfer_resp *)data);
break;
#endif
default:
break;
}
}
static void heci_handle_heci_msg(struct heci_msg *heci_msg, size_t msg_length)
{
if (!heci_msg->hdr.host_addr) {
/*
* message for HECI bus or a fixed client should fit
* into one IPC message
*/
if (!HECI_MSG_IS_COMPLETED(heci_msg->hdr.length)) {
CPRINTS("message not completed");
return;
}
if (heci_msg->hdr.fw_addr == HECI_FIXED_SYSTEM_STATE_ADDR)
heci_handle_system_state_msg(
heci_msg->payload,
HECI_MSG_LENGTH(heci_msg->hdr.length));
else if (!heci_msg->hdr.fw_addr)
/* HECI Bus Message(fw_addr == 0 && host_addr == 0) */
heci_handle_hbm((struct hbm_h2i *)heci_msg->payload,
HECI_MSG_LENGTH(heci_msg->hdr.length));
else
CPRINTS("not supported fixed client(%d)",
heci_msg->hdr.fw_addr);
} else {
/* host_addr != 0 : Msg for Dynamic client */
heci_handle_client_msg(heci_msg, msg_length);
}
}
/* event flag for HECI msg */
#define EVENT_FLAG_BIT_HECI_MSG TASK_EVENT_CUSTOM_BIT(0)
void heci_rx_task(void)
{
int msg_len;
struct heci_msg heci_msg;
ipc_handle_t ipc_handle;
/* open IPC for HECI protocol */
heci_bus_ctx.ipc_handle = ipc_open(IPC_PEER_ID_HOST, IPC_PROTOCOL_HECI,
EVENT_FLAG_BIT_HECI_MSG);
ASSERT(heci_bus_ctx.ipc_handle != IPC_INVALID_HANDLE);
/* get ipc handle */
ipc_handle = heci_bus_ctx.ipc_handle;
while (1) {
/* task will be blocked here, waiting for event */
msg_len = ipc_read(ipc_handle, &heci_msg, sizeof(heci_msg), -1);
if (msg_len <= 0) {
CPRINTS("discard heci packet");
continue;
}
if (HECI_MSG_LENGTH(heci_msg.hdr.length) + sizeof(heci_msg.hdr)
== msg_len)
heci_handle_heci_msg(&heci_msg, msg_len);
else
CPRINTS("msg len mismatch.. discard..");
}
}