/* -*- c++ -*- */
/*
* Copyright 2009 Free Software Foundation, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
/*
* buffer state machine: 1 input to two outputs
*
* Typically used to read packets from the ethernet and then after inspecting,
* handle the packet in firmware or pass it on to 1 of the 2 buffer destinations.
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include "bsm12.h"
#include "memory_map.h"
#include "buffer_pool.h"
#include "bool.h"
#include "nonstdio.h"
#include
typedef enum {
BS_EMPTY,
BS_FILLING,
BS_FULL,
BS_EMPTYING,
} buffer_state_t;
static buffer_state_t buffer_state[NBUFFERS];
static uint32_t last_send_ctrl[NBUFFERS];
static int8_t buffer_target[NBUFFERS]; // -1, 0 or 1.
static uint8_t buffer_dst[NBUFFERS]; // 0 or 1. Valid only when BF_EMPTYING
#define ST_IDLE (-1)
void
bsm12_init(bsm12_t *sm, int buf0,
const buf_cmd_args_t *recv,
const buf_cmd_args_t *send0,
const buf_cmd_args_t *send1,
bsm12_inspector_t inspect)
{
if (buf0 & 0x3) // precondition: buf0 % 4 == 0
abort();
sm->buf0 = buf0;
sm->running = false;
sm->recv_args = *recv;
sm->send_args[0] = *send0;
sm->send_args[1] = *send1;
sm->rx_state = ST_IDLE;
sm->tx_state[0] = ST_IDLE;
sm->tx_state[1] = ST_IDLE;
sm->inspect = inspect;
sm->bps_error = BPS_ERROR(buf0 + 0) | BPS_ERROR(buf0 + 1) | BPS_ERROR(buf0 + 2);
sm->bps_done = BPS_DONE(buf0 + 0) | BPS_DONE(buf0 + 1) | BPS_DONE(buf0 + 2);
sm->bps_error_or_done = sm->bps_error | sm->bps_done;
// How much to adjust the last_line register.
// It's 1 for everything but the ethernet.
sm->last_line_adj = recv->port == PORT_ETH ? 3 : 1;
buffer_state[sm->buf0 + 0] = BS_EMPTY;
buffer_state[sm->buf0 + 1] = BS_EMPTY;
buffer_state[sm->buf0 + 2] = BS_EMPTY;
buffer_target[sm->buf0 + 0] = -1;
buffer_target[sm->buf0 + 1] = -1;
buffer_target[sm->buf0 + 2] = -1;
for (int i = 0; i < NBUFFERS; i++)
sm->next_buf[i] = buf0;
sm->next_buf[buf0 + 0] = buf0 + 1;
sm->next_buf[buf0 + 1] = buf0 + 2;
sm->next_buf[buf0 + 2] = buf0 + 0;
for (int i = 0; i < 3; i++){
sm->precomputed_receive_to_buf_ctrl_word[i] =
(BPC_READ
| BPC_BUFFER(sm->buf0 + i)
| BPC_PORT(sm->recv_args.port)
| BPC_STEP(1)
| BPC_FIRST_LINE(sm->recv_args.first_line)
| BPC_LAST_LINE(sm->recv_args.last_line));
for (int j = 0; j < 2; j++){
sm->precomputed_send_from_buf_ctrl_word[i][j] =
(BPC_WRITE
| BPC_BUFFER(sm->buf0 + i)
| BPC_PORT(sm->send_args[j].port)
| BPC_STEP(1)
| BPC_FIRST_LINE(sm->send_args[j].first_line)
| BPC_LAST_LINE(0)); // last line filled in at runtime
}
}
}
static inline void
bsm12_receive_to_buf(bsm12_t *sm, int bufno)
{
buffer_pool_ctrl->ctrl = sm->precomputed_receive_to_buf_ctrl_word[bufno & 0x3];
}
static inline void
bsm12_send_from_buf(bsm12_t *sm, int bufno, int dst_idx)
{
dst_idx &= 0x1;
uint32_t t =
(sm->precomputed_send_from_buf_ctrl_word[bufno & 0x3][dst_idx]
| BPC_LAST_LINE(buffer_pool_status->last_line[bufno] - sm->last_line_adj));
buffer_pool_ctrl->ctrl = t;
last_send_ctrl[bufno] = t;
buffer_dst[bufno] = dst_idx;
}
static inline void
bsm12_resend_from_buf(bsm12_t *sm, int bufno)
{
buffer_pool_ctrl->ctrl = last_send_ctrl[bufno];
}
void
bsm12_start(bsm12_t *sm)
{
sm->running = true;
buffer_state[sm->buf0 + 0] = BS_EMPTY;
buffer_state[sm->buf0 + 1] = BS_EMPTY;
buffer_state[sm->buf0 + 2] = BS_EMPTY;
buffer_target[sm->buf0 + 0] = -1;
buffer_target[sm->buf0 + 1] = -1;
buffer_target[sm->buf0 + 2] = -1;
bp_clear_buf(sm->buf0 + 0);
bp_clear_buf(sm->buf0 + 1);
bp_clear_buf(sm->buf0 + 2);
sm->rx_state = 0;
sm->tx_state[0] = ST_IDLE;
sm->tx_state[1] = ST_IDLE;
bsm12_receive_to_buf(sm, sm->buf0);
buffer_state[sm->buf0] = BS_FILLING;
}
void
bsm12_stop(bsm12_t *sm)
{
sm->running = false;
bp_clear_buf(sm->buf0 + 0);
bp_clear_buf(sm->buf0 + 1);
bp_clear_buf(sm->buf0 + 2);
buffer_state[sm->buf0 + 0] = BS_EMPTY;
buffer_state[sm->buf0 + 1] = BS_EMPTY;
buffer_state[sm->buf0 + 2] = BS_EMPTY;
}
static void bsm12_process_helper(bsm12_t *sm, int buf_this);
static void bsm12_error_helper(bsm12_t *sm, int buf_this);
void
bsm12_process_status(bsm12_t *sm, uint32_t status)
{
// anything for us?
if ((status & sm->bps_error_or_done) == 0 || !sm->running)
return;
if (status & sm->bps_error){
// Most likely an ethernet Rx error. We just restart the transfer.
if (status & (BPS_ERROR(sm->buf0 + 0)))
bsm12_error_helper(sm, sm->buf0 + 0);
if (status & (BPS_ERROR(sm->buf0 + 1)))
bsm12_error_helper(sm, sm->buf0 + 1);
if (status & (BPS_ERROR(sm->buf0 + 2)))
bsm12_error_helper(sm, sm->buf0 + 2);
}
if (status & BPS_DONE(sm->buf0 + 0))
bsm12_process_helper(sm, sm->buf0 + 0);
if (status & BPS_DONE(sm->buf0 + 1))
bsm12_process_helper(sm, sm->buf0 + 1);
if (status & BPS_DONE(sm->buf0 + 2))
bsm12_process_helper(sm, sm->buf0 + 2);
}
static void
bsm12_process_helper(bsm12_t *sm, int buf_this)
{
bp_clear_buf(buf_this);
if (buffer_state[buf_this] == BS_FILLING){
buffer_state[buf_this] = BS_FULL;
buffer_target[buf_this] = -1;
//
// where does this packet go?
//
int dst = sm->inspect(sm, buf_this);
if (dst == -1){
//
// f/w handled the packet; refill the buffer
//
bsm12_receive_to_buf(sm, buf_this);
buffer_state[buf_this] = BS_FILLING;
buffer_target[buf_this] = -1;
sm->rx_state = buf_this & 0x3;
}
else { // goes to dst 0 or 1
//
// If the next buffer is empty, start a receive on it
//
int t = sm->next_buf[buf_this];
if (buffer_state[t] == BS_EMPTY){
bsm12_receive_to_buf(sm, t);
buffer_state[t] = BS_FILLING;
buffer_target[t] = -1;
sm->rx_state = t & 0x3;
}
else
sm->rx_state = ST_IDLE;
//
// If the destination is idle, start the xfer, othewise remember it
//
if (sm->tx_state[dst] == ST_IDLE){
bsm12_send_from_buf(sm, buf_this, dst);
sm->tx_state[dst] = buf_this & 0x3;
buffer_state[buf_this] = BS_EMPTYING;
buffer_target[buf_this] = -1;
}
else {
buffer_target[buf_this] = dst;
}
}
}
else { // BS_EMPTYING
buffer_state[buf_this] = BS_EMPTY;
buffer_target[buf_this] = -1;
if (sm->rx_state == ST_IDLE){ // fire off another receive
sm->rx_state = buf_this & 0x3;
bsm12_receive_to_buf(sm, buf_this);
buffer_state[buf_this] = BS_FILLING;
buffer_target[buf_this] = -1;
}
int dst = buffer_dst[buf_this]; // the dst we were emptying into
// is the next buffer full and for us?
int t = sm->next_buf[buf_this];
if (buffer_target[t] == dst){ // yes,
bsm12_send_from_buf(sm, t, dst); // send it
buffer_state[t] = BS_EMPTYING;
buffer_target[t] = -1;
sm->tx_state[dst] = t & 0x3;
}
// how about the one after that?
else if (buffer_target[t=sm->next_buf[t]] == dst){ // yes,
bsm12_send_from_buf(sm, t, dst); // send it
buffer_state[t] = BS_EMPTYING;
buffer_target[t] = -1;
sm->tx_state[dst] = t & 0x3;
}
else {
sm->tx_state[dst] = ST_IDLE;
}
}
}
static void
bsm12_error_helper(bsm12_t *sm, int buf_this)
{
bp_clear_buf(buf_this); // clears ERROR flag
if (buffer_state[buf_this] == BS_FILLING){
bsm12_receive_to_buf(sm, buf_this); // restart the xfer
}
else { // buffer was emptying
bsm12_resend_from_buf(sm, buf_this); // restart the xfer
}
}
void
bsm12_handle_tx_underrun(bsm12_t *sm)
{
}
void
bsm12_handle_rx_overrun(bsm12_t *sm)
{
}