1020 lines
23 KiB
C
1020 lines
23 KiB
C
/*
|
|
* Copyright (C) 2018-2022 Intel Corporation.
|
|
*
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*
|
|
*/
|
|
/*
|
|
* Carrier Board Commnucation protocol stack.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdbool.h>
|
|
|
|
#include "ioc.h"
|
|
#include "monitor.h"
|
|
#include "log.h"
|
|
|
|
/*
|
|
* Debug printf
|
|
*/
|
|
static int ioc_cbc_debug;
|
|
#define DPRINTF(format, arg...) \
|
|
do { if (ioc_cbc_debug) { pr_dbg(format, arg); } } while (0)
|
|
#define WPRINTF(format, arg...) pr_err(format, ##arg)
|
|
|
|
static void cbc_send_pkt(struct cbc_pkt *pkt);
|
|
|
|
/*
|
|
* Buffer bytes of reading from the virtual UART, because the bytes maybe can
|
|
* not generate one complete CBC frame.
|
|
*/
|
|
int
|
|
cbc_copy_to_ring(const uint8_t *buf, size_t size, struct cbc_ring *ring)
|
|
{
|
|
int i, pos;
|
|
|
|
/* TODO: memcpy or other implementation instead copy byte one by one */
|
|
for (i = 0; i < size; i++) {
|
|
pos = (ring->tail + 1) & (CBC_RING_BUFFER_SIZE - 1);
|
|
if (pos != ring->head) {
|
|
ring->buf[ring->tail] = buf[i];
|
|
|
|
ring->tail = pos;
|
|
} else {
|
|
WPRINTF("ioc cbc ring buffer is full!!\r\n");
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Drop the bytes from the ring buffer.
|
|
*/
|
|
static inline void
|
|
cbc_ring_skips(struct cbc_ring *ring, size_t bytes)
|
|
{
|
|
ring->head = (ring->head + bytes) & (CBC_RING_BUFFER_SIZE - 1);
|
|
|
|
}
|
|
|
|
/*
|
|
* Caculate checksum
|
|
*/
|
|
static inline uint16_t
|
|
cbc_cal_chksum(const uint8_t *data, size_t size)
|
|
{
|
|
int i;
|
|
uint16_t value = 0;
|
|
|
|
for (i = 0; i < size; i++)
|
|
value += 0x100 - *(data + i);
|
|
return value;
|
|
}
|
|
|
|
/*
|
|
* Checksum verification
|
|
*/
|
|
static inline int
|
|
cbc_verify_chksum(struct cbc_ring *ring, size_t size, uint8_t c)
|
|
{
|
|
int i, pos;
|
|
uint16_t value = 0;
|
|
|
|
for (i = 0; i < size; i++) {
|
|
pos = (ring->head + i) & (CBC_RING_BUFFER_SIZE - 1);
|
|
|
|
value += 0x100 - *(ring->buf + pos);
|
|
|
|
}
|
|
return ((value & 0xFF) == c ? 0 : -1);
|
|
}
|
|
|
|
/*
|
|
* Make the buf alignment with unit and pack out 0xFF if need.
|
|
*/
|
|
static size_t
|
|
cbc_fill_padding(uint8_t *buf, size_t size, int unit)
|
|
{
|
|
size_t i, left;
|
|
size_t paddings = size;
|
|
|
|
left = size % unit;
|
|
if (left != 0) {
|
|
paddings = size + unit - left;
|
|
for (i = size - CBC_CHKSUM_SIZE; i < paddings; i++)
|
|
buf[i] = 0xFF;
|
|
}
|
|
return paddings;
|
|
}
|
|
|
|
/*
|
|
* Unpack CBC link layer data.
|
|
* The function trys to generate a CBC link frame then build a cbc_request and
|
|
* put the cbc_request to the CBC rx queue.
|
|
*/
|
|
void
|
|
cbc_unpack_link(struct ioc_dev *ioc)
|
|
{
|
|
/*
|
|
* To build one complete frame from the ring buffer,
|
|
* The remains means the real length of current frame.
|
|
* The avalids means the bytes in the ring buffer.
|
|
* If the avalids can not meet minimum frame length or remains,
|
|
* need to wait more bytes before CBC link unpack.
|
|
*/
|
|
static int remains, rx_seq_counter;
|
|
uint8_t seq, len, ext, checksum;
|
|
int avalids, frame_len, els_pos, chksum_pos;
|
|
struct cbc_ring *ring = &ioc->ring;
|
|
|
|
for (;;) {
|
|
|
|
/* Get the avalid bytes in the ring buffer */
|
|
avalids = ring->tail - ring->head;
|
|
avalids = avalids >= 0 ? avalids :
|
|
(CBC_RING_BUFFER_SIZE + avalids);
|
|
|
|
/*
|
|
* The avalid bytes from ring buffer must be a minimum frame
|
|
* or the part of one complete frame remain bytes
|
|
*/
|
|
if (avalids < CBC_MIN_FRAME_SIZE || avalids < remains)
|
|
break;
|
|
|
|
/* Reset the remains flag if the avalid bytes can be parsed */
|
|
remains = 0;
|
|
|
|
/*
|
|
* If start of frame value is incorrect, drop the byte
|
|
* of ring buffer head
|
|
*/
|
|
if (ring->buf[ring->head] != CBC_SOF_VALUE) {
|
|
cbc_ring_skips(ring, 1);
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Parse the extension, frame length and sequence
|
|
*/
|
|
els_pos = (ring->head + CBC_ELS_POS) &
|
|
(CBC_RING_BUFFER_SIZE - 1);
|
|
|
|
ext = (ring->buf[els_pos] >> CBC_EXT_OFFSET) & CBC_EXT_MASK;
|
|
len = (ring->buf[els_pos] >> CBC_LEN_OFFSET) & CBC_LEN_MASK;
|
|
seq = (ring->buf[els_pos] >> CBC_SEQ_OFFSET) & CBC_SEQ_MASK;
|
|
|
|
/* FIXME: Extension defined in CBC protocol, but not use yet */
|
|
(void)ext;
|
|
|
|
/*
|
|
* CBC service block aligns with four types
|
|
* length is zero means four bytes, so length needs to add one
|
|
* and real len contains all CBC protocol headers length.
|
|
*/
|
|
len = (len + 1) * 4;
|
|
frame_len = len + CBC_LINK_HDR_SIZE + CBC_ADDR_HDR_SIZE;
|
|
|
|
/* Safty check */
|
|
if (frame_len > CBC_MAX_FRAME_SIZE) {
|
|
cbc_ring_skips(ring, 1);
|
|
continue;
|
|
}
|
|
|
|
/* Need more bytes to build one complete CBC frame */
|
|
if (avalids < frame_len) {
|
|
remains = frame_len;
|
|
continue;
|
|
}
|
|
|
|
/* Checksum verification */
|
|
chksum_pos = (ring->head + frame_len - 1) &
|
|
(CBC_RING_BUFFER_SIZE - 1);
|
|
|
|
checksum = ring->buf[chksum_pos];
|
|
if (cbc_verify_chksum(ring, frame_len - 1, checksum) != 0) {
|
|
cbc_ring_skips(ring, 1);
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Rx sequence check
|
|
* TODO: just warning now, need to drop the frame or not?
|
|
*/
|
|
rx_seq_counter = (rx_seq_counter + 1) & CBC_SEQ_MASK;
|
|
if (rx_seq_counter != seq) {
|
|
WPRINTF("%s", "ioc rx sequence check falied\r\n");
|
|
rx_seq_counter = seq;
|
|
}
|
|
|
|
/* Build a cbc_request and put it on the queue */
|
|
ioc_build_request(ioc, frame_len, len);
|
|
|
|
/* Drop the bytes from the ring buffer */
|
|
cbc_ring_skips(ring, frame_len);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Find a CBC signal from CBC signal table.
|
|
*/
|
|
static inline struct cbc_signal *
|
|
cbc_find_signal(uint16_t id, struct cbc_signal *table, size_t size)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < size; i++) {
|
|
if (id == table[i].id)
|
|
return &table[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Find a CBC signal group from CBC signal group table.
|
|
*/
|
|
static inline struct cbc_group *
|
|
cbc_find_signal_group(uint16_t id, struct cbc_group *table, size_t size)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < size; i++) {
|
|
if (id == table[i].id)
|
|
return &table[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Signal length unit is bit in signal definition not byte,
|
|
* if the length is 3 bits then return 1 byte,
|
|
* if the length is 10 bits then return 2 bytes.
|
|
*/
|
|
static int
|
|
cbc_get_signal_len(uint16_t id, struct cbc_signal *table, size_t size)
|
|
{
|
|
struct cbc_signal *p;
|
|
|
|
p = cbc_find_signal(id, table, size);
|
|
return (p == NULL ? 0 : (p->len + 7)/8);
|
|
}
|
|
|
|
/*
|
|
* Set signal flag to inactive.
|
|
*/
|
|
static void
|
|
cbc_disable_signal(uint16_t id, struct cbc_signal *table, size_t size)
|
|
{
|
|
struct cbc_signal *p;
|
|
|
|
p = cbc_find_signal(id, table, size);
|
|
if (p)
|
|
p->flag = CBC_INACTIVE;
|
|
}
|
|
|
|
/*
|
|
* Set signal group flag to inactive.
|
|
*/
|
|
static void
|
|
cbc_disable_signal_group(uint16_t id, struct cbc_group *table, size_t size)
|
|
{
|
|
struct cbc_group *p;
|
|
|
|
p = cbc_find_signal_group(id, table, size);
|
|
if (p)
|
|
p->flag = CBC_INACTIVE;
|
|
}
|
|
|
|
/*
|
|
* Search one cbc_signal with signal id in the whitelist table.
|
|
*/
|
|
static struct cbc_signal *
|
|
wlist_find_signal(uint16_t id, struct wlist_signal *list, size_t size)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < size; i++) {
|
|
if (id == list[i].id)
|
|
return list[i].sig;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Search one cbc_group with group id in the whitelist table.
|
|
*/
|
|
static struct
|
|
cbc_group *wlist_find_group(uint16_t id, struct wlist_group *list, size_t size)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < size; i++) {
|
|
if (id == list[i].id)
|
|
return list[i].grp;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Whitelist verification for a signal.
|
|
*/
|
|
static int
|
|
wlist_verify_signal(uint16_t id, struct wlist_signal *list, size_t size)
|
|
{
|
|
struct cbc_signal *sig;
|
|
|
|
sig = wlist_find_signal(id, list, size);
|
|
if (!sig || sig->flag == CBC_INACTIVE)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Whiltelist verification for a signal group.
|
|
*/
|
|
static int
|
|
wlist_verify_group(uint16_t id, struct wlist_group *list, size_t size)
|
|
{
|
|
struct cbc_group *grp;
|
|
|
|
grp = wlist_find_group(id, list, size);
|
|
if (!grp || grp->flag == CBC_INACTIVE)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* CBC invalidates signals/groups.
|
|
*/
|
|
static void
|
|
cbc_set_invalidation(struct cbc_pkt *pkt, int type)
|
|
{
|
|
int i;
|
|
uint8_t *payload;
|
|
uint8_t num;
|
|
uint16_t id;
|
|
|
|
payload = pkt->req->buf + CBC_PAYLOAD_POS;
|
|
|
|
/* Number of signals or groups */
|
|
num = payload[1];
|
|
|
|
/*
|
|
* Safty check.
|
|
* Each signal/group id length is 2 bytes and 2 bytes of service header,
|
|
* the service length should less than the maximum service size.
|
|
*/
|
|
if ((num * 2 + 2) >= CBC_MAX_SERVICE_SIZE) {
|
|
DPRINTF("ioc cbc group number is invalid, number is %d\r\n",
|
|
num);
|
|
return;
|
|
}
|
|
for (i = 0; i < num; i++) {
|
|
id = payload[i * 2 + 2] | payload[i * 2 + 3] << 8;
|
|
if (type == CBC_INVAL_T_SIGNAL)
|
|
cbc_disable_signal(id, pkt->cfg->cbc_sig_tbl,
|
|
pkt->cfg->cbc_sig_num);
|
|
else if (type == CBC_INVAL_T_GROUP)
|
|
cbc_disable_signal_group(id, pkt->cfg->cbc_grp_tbl,
|
|
pkt->cfg->cbc_grp_num);
|
|
else
|
|
DPRINTF("%s", "ioc invalidation is not defined\r\n");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* CBC multi-signal data process.
|
|
* Forwarding signal should be in whitelist, otherwise abandon the signal.
|
|
*/
|
|
static void
|
|
cbc_forward_signals(struct cbc_pkt *pkt)
|
|
{
|
|
int i, j;
|
|
int offset = 1;
|
|
uint8_t *payload = pkt->req->buf + CBC_PAYLOAD_POS;
|
|
uint8_t num = 0;
|
|
uint16_t id;
|
|
int signal_len;
|
|
int valids = 1;
|
|
|
|
for (i = 0; i < payload[0]; i++) {
|
|
id = payload[offset] | payload[offset + 1] << 8;
|
|
|
|
/* The length includes two bytes of signal ID occupation */
|
|
signal_len = cbc_get_signal_len(id, pkt->cfg->cbc_sig_tbl,
|
|
pkt->cfg->cbc_sig_num) + 2;
|
|
|
|
/* Whitelist verification */
|
|
if (wlist_verify_signal(id, pkt->cfg->wlist_sig_tbl,
|
|
pkt->cfg->wlist_sig_num) == 0) {
|
|
|
|
num++;
|
|
if (valids < offset) {
|
|
for (j = 0; j < signal_len; j++, valids++)
|
|
payload[valids] = payload[offset + j];
|
|
} else
|
|
valids += signal_len;
|
|
}
|
|
offset += signal_len;
|
|
|
|
/* Safty check */
|
|
if (offset + 1 > CBC_MAX_SERVICE_SIZE) {
|
|
DPRINTF("ioc offset=%d is error in forward signal\r\n",
|
|
offset);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Send permitted signals */
|
|
if (num > 0) {
|
|
/*
|
|
* Set permitted signal numbers
|
|
*/
|
|
payload[0] = num;
|
|
|
|
/*
|
|
* Set multi-signal value for CBC service layer header,
|
|
* one service frame is generated completely.
|
|
*/
|
|
pkt->req->buf[CBC_SRV_POS] = CBC_SD_MULTI_SIGNAL;
|
|
pkt->req->srv_len = valids + CBC_SRV_HDR_SIZE;
|
|
|
|
/* Send the CBC packet */
|
|
cbc_send_pkt(pkt);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Pack CBC link header includes SOF value, extension bits, frame length bits,
|
|
* tx sequence bits, link alignment paddings and checksum byte.
|
|
*/
|
|
static void
|
|
cbc_pack_link(struct cbc_pkt *pkt)
|
|
{
|
|
static size_t tx_seq_counter;
|
|
size_t len;
|
|
uint16_t checksum = 0;
|
|
|
|
/* Safty check */
|
|
if (pkt->req->srv_len > CBC_MAX_SERVICE_SIZE) {
|
|
DPRINTF("ioc pack req with wrong service length:%d\r\n",
|
|
pkt->req->srv_len);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Compute one CBC frame length
|
|
* and aligh with CBC default granularity
|
|
*/
|
|
len = pkt->req->srv_len + CBC_ADDR_HDR_SIZE + CBC_LINK_HDR_SIZE;
|
|
len = cbc_fill_padding(pkt->req->buf, len, CBC_GRANULARITY);
|
|
|
|
/* Fill start of frame */
|
|
pkt->req->buf[CBC_SOF_POS] = CBC_SOF_VALUE;
|
|
|
|
/* Fill extension bits ,frame length bits and tx sequence bits */
|
|
pkt->req->buf[CBC_ELS_POS] = (CBC_EXT_VALUE & CBC_EXT_MASK)
|
|
<< CBC_EXT_OFFSET;
|
|
|
|
pkt->req->buf[CBC_ELS_POS] |= (((pkt->req->srv_len - 1)/CBC_LEN_UNIT)
|
|
& CBC_LEN_MASK) << CBC_LEN_OFFSET;
|
|
|
|
pkt->req->buf[CBC_ELS_POS] |= (tx_seq_counter & CBC_SEQ_MASK)
|
|
<< CBC_SEQ_OFFSET;
|
|
|
|
/* Fill checksum that is last byte */
|
|
checksum = cbc_cal_chksum(pkt->req->buf, len - 1);
|
|
pkt->req->buf[len - 1] = checksum & 0xFF;
|
|
|
|
/* Set the CBC link frame length */
|
|
pkt->req->link_len = len;
|
|
|
|
/* Increase tx sequence */
|
|
tx_seq_counter = (tx_seq_counter + 1) & CBC_SEQ_MASK;
|
|
}
|
|
|
|
/*
|
|
* Pack CBC address layer header includes channel mux and priority.
|
|
*/
|
|
static void
|
|
cbc_pack_address(struct cbc_pkt *pkt)
|
|
{
|
|
uint8_t prio, mux;
|
|
|
|
mux = pkt->req->id;
|
|
switch (mux) {
|
|
case IOC_NATIVE_PMT:
|
|
case IOC_NATIVE_LFCC:
|
|
case IOC_NATIVE_SIGNAL:
|
|
case IOC_NATIVE_DLT:
|
|
prio = CBC_PRIO_HIGH;
|
|
break;
|
|
case IOC_NATIVE_DIAG:
|
|
prio = CBC_PRIO_LOW;
|
|
break;
|
|
default:
|
|
prio = CBC_PRIO_MEDIUM;
|
|
break;
|
|
}
|
|
pkt->req->buf[CBC_ADDR_POS] = ((mux & CBC_MUX_MASK) << CBC_MUX_OFFSET);
|
|
pkt->req->buf[CBC_ADDR_POS] |=
|
|
((prio & CBC_PRIO_MASK)<<CBC_PRIO_OFFSET);
|
|
|
|
}
|
|
|
|
/*
|
|
* Send CBC packet buf to IOC xmit channel.
|
|
* TODO: Due to rx/tx threads share the function, it is better to seperate.
|
|
*/
|
|
static void
|
|
cbc_send_pkt(struct cbc_pkt *pkt)
|
|
{
|
|
int rc;
|
|
uint8_t *data;
|
|
size_t len;
|
|
enum ioc_ch_id id;
|
|
|
|
/*
|
|
* link_len is 0 means the packet from CBC cdev that there is no data
|
|
* of link layer, the packet is transmitted to virtual UART.
|
|
* Opposite, the packet is transmitted to CBC cdev.
|
|
*/
|
|
if (pkt->req->link_len == 0) {
|
|
cbc_pack_address(pkt);
|
|
cbc_pack_link(pkt);
|
|
id = IOC_VIRTUAL_UART;
|
|
data = pkt->req->buf;
|
|
len = pkt->req->link_len;
|
|
} else {
|
|
id = pkt->req->id;
|
|
data = pkt->req->buf + CBC_SRV_POS;
|
|
len = pkt->req->srv_len;
|
|
}
|
|
rc = ioc_ch_xmit(id, data, len);
|
|
if (rc < 0)
|
|
DPRINTF("ioc xmit failed on channel id=%d\n\r", id);
|
|
}
|
|
|
|
/*
|
|
* Update heartbeat state.
|
|
*/
|
|
static void
|
|
cbc_update_heartbeat(struct cbc_pkt *pkt, uint8_t cmd, uint8_t sus_action)
|
|
{
|
|
enum ioc_event_type evt;
|
|
|
|
if (cmd == CBC_HB_INITIAL || cmd == CBC_HB_ACTIVE ||
|
|
cmd == CBC_HB_STANDBY ||
|
|
cmd == CBC_HB_SD_DLY)
|
|
evt = IOC_E_HB_ACTIVE;
|
|
else if (cmd == CBC_HB_SD_PREP)
|
|
evt = (sus_action == CBC_SS_REFRESH) ? IOC_E_RAM_REFRESH :
|
|
IOC_E_HB_INACTIVE;
|
|
else
|
|
return;
|
|
|
|
/* Update IOC state with a new event */
|
|
if (evt != pkt->evt) {
|
|
ioc_update_event(pkt->ioc->evt_fd, evt);
|
|
pkt->evt = evt;
|
|
}
|
|
|
|
/* Tigger a wakeup reason immediately */
|
|
if (cmd == CBC_HB_INITIAL) {
|
|
|
|
/* Route the cbc request to the tx thread */
|
|
pkt->qtype = CBC_QUEUE_T_TX;
|
|
pkt->req->rtype = CBC_REQ_T_HB_INIT;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Update wakeup reason value and notify User VM immediately.
|
|
* Some events can change the wakeup reason include periodic wakeup reason
|
|
* from IOC firmware, IOC boot/resume reason, heartbeat state changing.
|
|
*/
|
|
static void
|
|
cbc_update_wakeup_reason(struct cbc_pkt *pkt, uint32_t reason)
|
|
{
|
|
uint8_t *payload;
|
|
|
|
/*
|
|
* Mask the bits of wakeup reason that are not allowed by IOC mediator.
|
|
* Only allow Ignition button, cardoor, RTC, SOC and force S5 currently.
|
|
*/
|
|
reason &= CBC_WK_RSN_ALL;
|
|
|
|
/* Wakeup reason only has three bytes in CBC payload */
|
|
payload = pkt->req->buf + CBC_PAYLOAD_POS;
|
|
payload[0] = reason;
|
|
payload[1] = reason >> 8;
|
|
payload[2] = reason >> 16;
|
|
|
|
/* For CBC address layer header packing */
|
|
pkt->req->id = IOC_NATIVE_LFCC;
|
|
|
|
/* Fill service header */
|
|
pkt->req->buf[CBC_SRV_POS] = CBC_SC_WK_RSN;
|
|
pkt->req->srv_len = 4;
|
|
pkt->req->link_len = 0;
|
|
}
|
|
|
|
/*
|
|
* CBC wakeup reason processing is main entry for Tx(IOC->User VM) lifecycle
|
|
* service.
|
|
*/
|
|
static void
|
|
cbc_process_wakeup_reason(struct cbc_pkt *pkt)
|
|
{
|
|
uint8_t cmd;
|
|
uint8_t *payload;
|
|
uint32_t reason;
|
|
|
|
cmd = pkt->req->buf[CBC_SRV_POS];
|
|
payload = pkt->req->buf + CBC_PAYLOAD_POS;
|
|
if (cmd != CBC_SC_WK_RSN) {
|
|
DPRINTF("Only handle wakeup reason cmd, the cmd:%d\r\n", cmd);
|
|
return;
|
|
}
|
|
reason = payload[0] | (payload[1] << 8) | (payload[2] << 16);
|
|
|
|
/*
|
|
* Save the reason for User VM status switching from inactive to active,
|
|
* since need to send a wakeup reason immediatly after the switching.
|
|
*/
|
|
pkt->reason = reason;
|
|
|
|
if (pkt->user_vm_active) {
|
|
reason |= CBC_WK_RSN_SOC;
|
|
|
|
/* Unset RTC bit if User VM sends active heartbeat */
|
|
reason &= ~CBC_WK_RSN_RTC;
|
|
} else {
|
|
/*
|
|
* If User VM is inactive, indicate the acrnd boot reason
|
|
* as User VM periodic wakeup reason.
|
|
*/
|
|
reason = pkt->ioc->boot_reason;
|
|
|
|
/* Unset SoC bit */
|
|
reason &= ~CBC_WK_RSN_SOC;
|
|
}
|
|
|
|
/* Update periodic wakeup reason */
|
|
cbc_update_wakeup_reason(pkt, reason);
|
|
|
|
/* Send wakeup reason */
|
|
cbc_send_pkt(pkt);
|
|
}
|
|
|
|
/*
|
|
* CBC update RTC timer
|
|
*/
|
|
static void
|
|
cbc_update_rtc_timer(uint16_t value, uint8_t unit)
|
|
{
|
|
time_t timestamp = 0;
|
|
|
|
if (!value) {
|
|
DPRINTF("%s", "ioc sets RTC timer failure, timer is 0\r\n");
|
|
return;
|
|
}
|
|
|
|
switch (unit) {
|
|
case CBC_RTC_TIMER_U_SEC:
|
|
timestamp += value;
|
|
break;
|
|
case CBC_RTC_TIMER_U_MIN:
|
|
timestamp += value * 60;
|
|
break;
|
|
case CBC_RTC_TIMER_U_HOUR:
|
|
timestamp += value * 60 * 60;
|
|
break;
|
|
case CBC_RTC_TIMER_U_DAY:
|
|
timestamp += value * 60 * 60 * 24;
|
|
break;
|
|
case CBC_RTC_TIMER_U_WEEK:
|
|
timestamp += value * 60 * 60 * 24 * 7;
|
|
break;
|
|
default:
|
|
DPRINTF("ioc sets RTC timer failure, invalid timer unit:%d\r\n",
|
|
unit);
|
|
return;
|
|
}
|
|
|
|
/* Call RTC interface of VM monitor */
|
|
set_wakeup_timer(timestamp);
|
|
}
|
|
|
|
/*
|
|
* CBC heartbeat processing is main entry for Rx(User_VM->IOC) lifecycle service.
|
|
*/
|
|
static void
|
|
cbc_process_heartbeat(struct cbc_pkt *pkt)
|
|
{
|
|
uint8_t cmd;
|
|
uint8_t *payload;
|
|
|
|
cmd = pkt->req->buf[CBC_SRV_POS];
|
|
payload = pkt->req->buf + CBC_PAYLOAD_POS;
|
|
if (cmd == CBC_SC_HB) {
|
|
cbc_update_heartbeat(pkt, payload[0], payload[1]);
|
|
} else if (cmd == CBC_SC_RTC) {
|
|
uint16_t timer = payload[0] | payload[1] << 8;
|
|
|
|
cbc_update_rtc_timer(timer, payload[2]);
|
|
}
|
|
|
|
DPRINTF("ioc discards the lifecycle rx cmd: %d\r\n", cmd);
|
|
}
|
|
|
|
/*
|
|
* CBC service signal data process.
|
|
* FIXME: called in rx and tx threads, seperating two functions is better.
|
|
*/
|
|
static void
|
|
cbc_process_signal(struct cbc_pkt *pkt)
|
|
{
|
|
/*
|
|
* TODO: put the is_active into pkt structure instead local static
|
|
* variable when the cbc_process_signal is seperated.
|
|
*/
|
|
static bool is_active;
|
|
uint8_t cmd;
|
|
uint8_t *payload;
|
|
uint16_t id;
|
|
|
|
payload = pkt->req->buf + CBC_PAYLOAD_POS;
|
|
cmd = pkt->req->buf[CBC_SRV_POS];
|
|
|
|
/*
|
|
* FIXME:seperate the logic in two functions
|
|
* link_len is 0 means the packet is transmitted to PTY(UART DM)
|
|
* if the signal channel is not active, do not transmit it to PTY
|
|
* to CBC cdevs, always forward the signals because signal channel
|
|
* status only for User VM
|
|
*/
|
|
if (pkt->req->link_len == 0 && is_active == false &&
|
|
(cmd == CBC_SD_SINGLE_SIGNAL ||
|
|
cmd == CBC_SD_MULTI_SIGNAL ||
|
|
cmd == CBC_SD_GROUP_SIGNAL))
|
|
return;
|
|
|
|
switch (cmd) {
|
|
/* Bidirectional command */
|
|
case CBC_SD_SINGLE_SIGNAL:
|
|
id = payload[0] | payload[1] << 8;
|
|
if (wlist_verify_signal(id, pkt->cfg->wlist_sig_tbl,
|
|
pkt->cfg->wlist_sig_num) == 0)
|
|
cbc_send_pkt(pkt);
|
|
break;
|
|
/* Bidirectional command */
|
|
case CBC_SD_MULTI_SIGNAL:
|
|
cbc_forward_signals(pkt);
|
|
break;
|
|
/* Bidirectional command */
|
|
case CBC_SD_GROUP_SIGNAL:
|
|
id = payload[0] | payload[1] << 8;
|
|
if (wlist_verify_group(id, pkt->cfg->wlist_grp_tbl,
|
|
pkt->cfg->wlist_grp_num) == 0)
|
|
cbc_send_pkt(pkt);
|
|
break;
|
|
/* Bidirectional command */
|
|
case CBC_SD_INVAL_SSIG:
|
|
id = payload[0] | payload[1] << 8;
|
|
cbc_disable_signal(id, pkt->cfg->cbc_sig_tbl,
|
|
pkt->cfg->cbc_sig_num);
|
|
break;
|
|
/* Bidirectional command */
|
|
case CBC_SD_INVAL_MSIG:
|
|
cbc_set_invalidation(pkt, CBC_INVAL_T_SIGNAL);
|
|
break;
|
|
/* Bidirectional command */
|
|
case CBC_SD_INVAL_SGRP:
|
|
id = payload[0] | payload[1] << 8;
|
|
cbc_disable_signal_group(id, pkt->cfg->cbc_grp_tbl,
|
|
pkt->cfg->cbc_grp_num);
|
|
break;
|
|
/* Bidirectional command */
|
|
case CBC_SD_INVAL_MGRP:
|
|
cbc_set_invalidation(pkt, CBC_INVAL_T_GROUP);
|
|
break;
|
|
/*
|
|
* FIXME: seperate into rx signal process
|
|
* Open/reset/close are not bidirectional operations
|
|
* only for IOC rx thread
|
|
*/
|
|
case CBC_SD_OPEN_CHANNEL:
|
|
case CBC_SD_RESET_CHANNEL:
|
|
is_active = true;
|
|
break;
|
|
case CBC_SD_CLOSE_CHANNEL:
|
|
is_active = false;
|
|
break;
|
|
default:
|
|
DPRINTF("ioc got an new operation of signal channel=%d\r\n",
|
|
cmd);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Rx handler mainly processes rx direction data flow
|
|
* the rx direction is that virtual UART -> native CBC cdevs
|
|
*/
|
|
void
|
|
cbc_rx_handler(struct cbc_pkt *pkt)
|
|
{
|
|
uint8_t mux, prio;
|
|
|
|
/*
|
|
* Rx only handle CBC protocol packet currently.
|
|
* Drop the packet when the CBC protocl status is not enable.
|
|
*/
|
|
if (pkt->req->rtype != CBC_REQ_T_PROT ||
|
|
pkt->ioc->cbc_enable == false) {
|
|
|
|
/* Discard the packet */
|
|
DPRINTF("ioc rx discard the packet, type:%d\r\n",
|
|
pkt->req->rtype);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* TODO: use this prio to enable dynamic cbc priority configuration
|
|
* feature in the future, currently ignore it.
|
|
*/
|
|
prio = (pkt->req->buf[CBC_ADDR_POS] >> CBC_PRIO_OFFSET) & CBC_PRIO_MASK;
|
|
(void) prio;
|
|
|
|
mux = (pkt->req->buf[CBC_ADDR_POS] >> CBC_MUX_OFFSET) & CBC_MUX_MASK;
|
|
pkt->req->id = mux;
|
|
switch (mux) {
|
|
case IOC_NATIVE_LFCC:
|
|
cbc_process_heartbeat(pkt);
|
|
break;
|
|
case IOC_NATIVE_SIGNAL:
|
|
cbc_process_signal(pkt);
|
|
break;
|
|
|
|
/* Forward directly */
|
|
case IOC_NATIVE_RAW0 ... IOC_NATIVE_RAW11:
|
|
cbc_send_pkt(pkt);
|
|
break;
|
|
default:
|
|
DPRINTF("ioc unpack wrong channel=%d\r\n", mux);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Convert VM request to the wakeup reason.
|
|
*/
|
|
static bool
|
|
send_wakeup_reason_of_vm_request(struct cbc_pkt *pkt)
|
|
{
|
|
uint32_t reason;
|
|
|
|
switch (pkt->ioc->vm_req) {
|
|
case VM_REQ_STOP:
|
|
/*
|
|
* Force S5 and SoC bits are set for emulating
|
|
* shutdown wakeup reason that VM initiates stop
|
|
*/
|
|
reason = CBC_WK_RSN_FS5 | CBC_WK_RSN_SOC;
|
|
break;
|
|
case VM_REQ_SUSPEND:
|
|
/*
|
|
* Only SoC bit is set for emulating suspend
|
|
* wakeup reason that VM initiates suspend.
|
|
*/
|
|
reason = CBC_WK_RSN_SOC;
|
|
break;
|
|
default:
|
|
/*
|
|
* There is no need to emulate wakeup reasons for VM_REQ_RESUME
|
|
* and VM_REQ_NONE VM requests since VM manager just only asks
|
|
* IOC mediator to emulate ignition off wakeup reason for
|
|
* VM_REQ_STOP and VM_REQ_SUSPEND, otherwise call primary
|
|
* periodic wakeup reason.
|
|
*/
|
|
return false;
|
|
}
|
|
|
|
cbc_update_wakeup_reason(pkt, reason);
|
|
cbc_send_pkt(pkt);
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Tx handler mainly processes tx direction data flow,
|
|
* the tx direction is that native CBC cdevs -> virtual UART.
|
|
*/
|
|
void
|
|
cbc_tx_handler(struct cbc_pkt *pkt)
|
|
{
|
|
if (pkt->req->rtype == CBC_REQ_T_PROT && pkt->ioc->cbc_enable) {
|
|
switch (pkt->req->id) {
|
|
case IOC_NATIVE_LFCC:
|
|
/* Check VM request firstly */
|
|
if (send_wakeup_reason_of_vm_request(pkt) == false) {
|
|
|
|
/* Primary periodic wakeup reason */
|
|
cbc_process_wakeup_reason(pkt);
|
|
}
|
|
break;
|
|
case IOC_NATIVE_SIGNAL:
|
|
cbc_process_signal(pkt);
|
|
break;
|
|
case IOC_NATIVE_RAW0 ... IOC_NATIVE_RAW11:
|
|
cbc_send_pkt(pkt);
|
|
break;
|
|
default:
|
|
DPRINTF("ioc cbc tx handler got invalid channel=%d\r\n",
|
|
pkt->req->id);
|
|
break;
|
|
}
|
|
} else if (pkt->req->rtype == CBC_REQ_T_HB_INIT) {
|
|
|
|
/*
|
|
* Boot reason represents the wakeup reason from IOC mediator
|
|
* boot command line or resume callback.
|
|
*/
|
|
cbc_update_wakeup_reason(pkt, pkt->ioc->boot_reason |
|
|
CBC_WK_RSN_SOC);
|
|
cbc_send_pkt(pkt);
|
|
|
|
/* Heartbeat init also indicates User VM enter active state */
|
|
pkt->user_vm_active = true;
|
|
} else if (pkt->req->rtype == CBC_REQ_T_USER_VM_ACTIVE) {
|
|
cbc_update_wakeup_reason(pkt, pkt->ioc->boot_reason |
|
|
CBC_WK_RSN_SOC);
|
|
cbc_send_pkt(pkt);
|
|
|
|
/* Enable User VM active flag */
|
|
pkt->user_vm_active = true;
|
|
} else if (pkt->req->rtype == CBC_REQ_T_USER_VM_INACTIVE) {
|
|
cbc_update_wakeup_reason(pkt, CBC_WK_RSN_SHUTDOWN);
|
|
|
|
cbc_send_pkt(pkt);
|
|
|
|
/* Disable User VM active flag */
|
|
pkt->user_vm_active = false;
|
|
|
|
/*
|
|
* After sending shutdown wakeup reason, then trigger shutdown
|
|
* IOC event that IOC mediator can enter suspended.
|
|
*/
|
|
ioc_update_event(pkt->ioc->evt_fd, IOC_E_SHUTDOWN);
|
|
} else {
|
|
/* Discard the packet */
|
|
DPRINTF("ioc tx discard the packet, type:%d\r\n",
|
|
pkt->req->rtype);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Initialize whitelist node with cbc_group,
|
|
* so that whitelist can access cbc_group flag via group id.
|
|
*/
|
|
void
|
|
wlist_init_group(struct cbc_group *cbc_tbl, size_t cbc_size,
|
|
struct wlist_group *wlist_tbl, size_t wlist_size)
|
|
{
|
|
int i, j;
|
|
|
|
if (!cbc_tbl || cbc_size == 0 || !wlist_tbl || wlist_size == 0)
|
|
return;
|
|
for (i = 0; i < wlist_size; i++) {
|
|
for (j = 0; j < cbc_size; j++) {
|
|
if (wlist_tbl[i].id == cbc_tbl[j].id) {
|
|
wlist_tbl[i].grp = &cbc_tbl[j];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Initialize whitelist node with cbc_signal,
|
|
* so that whitelist can access cbc_signal flag via signal id.
|
|
*/
|
|
void
|
|
wlist_init_signal(struct cbc_signal *cbc_tbl, size_t cbc_size,
|
|
struct wlist_signal *wlist_tbl, size_t wlist_size)
|
|
{
|
|
int i, j;
|
|
|
|
if (!cbc_tbl || cbc_size == 0 || !wlist_tbl || wlist_size == 0)
|
|
return;
|
|
for (i = 0; i < wlist_size; i++) {
|
|
for (j = 0; j < cbc_size; j++) {
|
|
if (wlist_tbl[i].id == cbc_tbl[j].id) {
|
|
wlist_tbl[i].sig = &cbc_tbl[j];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|