blob: 76e51b736aa3feff5820c68baa5d2d568d7fd78d [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include "sw/device/lib/testing/test_framework/ottf_flow_control.h"
#include <stdbool.h>
#include <stdint.h>
#include "sw/device/lib/base/mmio.h"
#include "sw/device/lib/base/status.h"
#include "sw/device/lib/dif/dif_rv_plic.h"
#include "sw/device/lib/dif/dif_uart.h"
#include "sw/device/lib/runtime/ibex.h"
#include "sw/device/lib/runtime/irq.h"
#include "sw/device/lib/testing/test_framework/check.h"
#include "sw/device/lib/testing/test_framework/ottf_isrs.h"
#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
// We declare this `extern` here to avoid a circular dependency with
// `ottf_main.h`.
extern dif_uart_t *ottf_console(void);
#define FLOW_CONTROL_LOW_WATERMARK 4
#define FLOW_CONTROL_HIGH_WATERMARK 8
#define FLOW_CONRTOL_WATERMARK_CONFIG kDifUartWatermarkByte8
const uint32_t kPlicTarget = kTopEarlgreyPlicTargetIbex0;
// The flow_control_state and ottf_flow_control_intr varibles are shared between
// the interrupt service handler and user code.
static volatile flow_control_t flow_control_state;
volatile uint32_t ottf_flow_control_intr;
void ottf_flow_control_enable(void) {
CHECK_DIF_OK(dif_rv_plic_init(
mmio_region_from_addr(TOP_EARLGREY_RV_PLIC_BASE_ADDR), &ottf_plic));
dif_uart_t *uart = ottf_console();
CHECK_DIF_OK(dif_uart_watermark_rx_set(uart, FLOW_CONRTOL_WATERMARK_CONFIG));
CHECK_DIF_OK(dif_uart_irq_set_enabled(uart, kDifUartIrqRxWatermark,
kDifToggleEnabled));
// Set IRQ priorities to MAX
CHECK_DIF_OK(dif_rv_plic_irq_set_priority(
&ottf_plic, kTopEarlgreyPlicIrqIdUart0RxWatermark,
kDifRvPlicMaxPriority));
// Set Ibex IRQ priority threshold level
CHECK_DIF_OK(dif_rv_plic_target_set_threshold(&ottf_plic, kPlicTarget,
kDifRvPlicMinPriority));
// Enable IRQs in PLIC
CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(
&ottf_plic, kTopEarlgreyPlicIrqIdUart0RxWatermark, kPlicTarget,
kDifToggleEnabled));
flow_control_state = kFlowControlAuto;
irq_global_ctrl(true);
irq_external_ctrl(true);
// Make sure we're in the Resume state and we emit a Resume to the UART.
ottf_flow_control(ottf_console(), kFlowControlResume);
}
// This version of the function is safe to call from within the ISR.
static status_t manage_flow_control(const dif_uart_t *uart,
flow_control_t ctrl) {
if (flow_control_state == kFlowControlNone) {
return OK_STATUS(flow_control_state);
}
if (ctrl == kFlowControlAuto) {
uint32_t avail;
TRY(dif_uart_rx_bytes_available(uart, &avail));
if (avail < FLOW_CONTROL_LOW_WATERMARK &&
flow_control_state != kFlowControlResume) {
ctrl = kFlowControlResume;
} else if (avail >= FLOW_CONTROL_HIGH_WATERMARK &&
flow_control_state != kFlowControlPause) {
ctrl = kFlowControlPause;
} else {
return OK_STATUS(flow_control_state);
}
}
uint8_t byte = (uint8_t)ctrl;
CHECK_DIF_OK(dif_uart_bytes_send(uart, &byte, 1, NULL));
flow_control_state = ctrl;
return OK_STATUS(flow_control_state);
}
bool ottf_flow_control_isr(void) {
dif_uart_t *uart = ottf_console();
ottf_flow_control_intr += 1;
bool rx;
CHECK_DIF_OK(dif_uart_irq_is_pending(uart, kDifUartIrqRxWatermark, &rx));
if (rx) {
manage_flow_control(uart, kFlowControlAuto);
CHECK_DIF_OK(dif_uart_irq_acknowledge(uart, kDifUartIrqRxWatermark));
return true;
}
return false;
}
// The public API has to save and restore interrupts to avoid an
// unexpected write to the global `flow_control_state`.
status_t ottf_flow_control(const dif_uart_t *uart, flow_control_t ctrl) {
dif_uart_irq_enable_snapshot_t snapshot;
CHECK_DIF_OK(dif_uart_irq_disable_all(uart, &snapshot));
status_t s = manage_flow_control(uart, ctrl);
CHECK_DIF_OK(dif_uart_irq_restore_all(uart, &snapshot));
return s;
}