acrn-hypervisor/devicemodel/hw/platform/ioc_cbc.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;
}
}
}
}