/* -*- c++ -*- */ /* * Copyright 2007,2008 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 . */ /* * Double Buffering State Machine */ #include "dbsm.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]; bool dbsm_nop_inspector(dbsm_t *sm, int buf_this) { return false; } void dbsm_init(dbsm_t *sm, int buf0, const buf_cmd_args_t *recv, const buf_cmd_args_t *send, inspector_t inspect) { if (buf0 & 0x1) // must be even abort(); sm->buf0 = buf0; sm->running = false; sm->recv_args = *recv; sm->send_args = *send; sm->rx_idle = true; sm->tx_idle = true; sm->inspect = inspect; // 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; sm->last_line_adj = 1; buffer_state[sm->buf0] = BS_EMPTY; buffer_state[sm->buf0 ^ 1] = BS_EMPTY; sm->precomputed_receive_to_buf_ctrl_word[0] = (BPC_READ | BPC_BUFFER(sm->buf0) | 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)); sm->precomputed_receive_to_buf_ctrl_word[1] = (BPC_READ | BPC_BUFFER(sm->buf0 ^ 1) | 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)); sm->precomputed_send_from_buf_ctrl_word[0] = (BPC_WRITE | BPC_BUFFER(sm->buf0) | BPC_PORT(sm->send_args.port) | BPC_STEP(1) | BPC_FIRST_LINE(sm->send_args.first_line) | BPC_LAST_LINE(0)); // last line filled in at runtime sm->precomputed_send_from_buf_ctrl_word[1] = (BPC_WRITE | BPC_BUFFER(sm->buf0 ^ 1) | BPC_PORT(sm->send_args.port) | BPC_STEP(1) | BPC_FIRST_LINE(sm->send_args.first_line) | BPC_LAST_LINE(0)); // last line filled in at runtime } static inline void dbsm_receive_to_buf(dbsm_t *sm, int bufno) { buffer_pool_ctrl->ctrl = sm->precomputed_receive_to_buf_ctrl_word[bufno & 1]; } static inline void dbsm_send_from_buf(dbsm_t *sm, int bufno) { buffer_pool_ctrl->ctrl = (sm->precomputed_send_from_buf_ctrl_word[bufno & 1] | BPC_LAST_LINE(buffer_pool_status->last_line[bufno] - sm->last_line_adj)); } void dbsm_start(dbsm_t *sm) { // printf("dbsm_start: buf0 = %d, recv_port = %d\n", sm->buf0, sm->recv_args.port); sm->running = true; buffer_state[sm->buf0] = BS_EMPTY; buffer_state[sm->buf0 ^ 1] = BS_EMPTY; bp_clear_buf(sm->buf0); bp_clear_buf(sm->buf0 ^ 1); sm->tx_idle = true; sm->rx_idle = false; dbsm_receive_to_buf(sm, sm->buf0); buffer_state[sm->buf0] = BS_FILLING; } void dbsm_stop(dbsm_t *sm) { sm->running = false; bp_clear_buf(sm->buf0); bp_clear_buf(sm->buf0 ^ 1); buffer_state[sm->buf0] = BS_EMPTY; buffer_state[sm->buf0 ^ 1] = BS_EMPTY; } static void dbsm_process_helper(dbsm_t *sm, int buf_this); static void dbsm_error_helper(dbsm_t *sm, int buf_this); void dbsm_process_status(dbsm_t *sm, uint32_t status) { if (!sm->running) return; if (status & (BPS_ERROR(sm->buf0) | BPS_ERROR(sm->buf0 ^ 1))){ putchar('E'); // Most likely an ethernet Rx error. We just restart the transfer. if (status & (BPS_ERROR(sm->buf0))) dbsm_error_helper(sm, sm->buf0); if (status & (BPS_ERROR(sm->buf0 ^ 1))) dbsm_error_helper(sm, sm->buf0 ^ 1); } if (status & BPS_DONE(sm->buf0)) dbsm_process_helper(sm, sm->buf0); if (status & BPS_DONE(sm->buf0 ^ 1)) dbsm_process_helper(sm, sm->buf0 ^ 1); } static void dbsm_process_helper(dbsm_t *sm, int buf_this) { int buf_other = buf_this ^ 1; bp_clear_buf(buf_this); if (buffer_state[buf_this] == BS_FILLING){ buffer_state[buf_this] = BS_FULL; // // does s/w handle this packet? // if (sm->inspect(sm, buf_this)){ // s/w handled the packet; refill the buffer dbsm_receive_to_buf(sm, buf_this); buffer_state[buf_this] = BS_FILLING; } else { // s/w didn't handle this; pass it on if(buffer_state[buf_other] == BS_EMPTY){ dbsm_receive_to_buf(sm, buf_other); buffer_state[buf_other] = BS_FILLING; } else sm->rx_idle = true; if (sm->tx_idle){ sm->tx_idle = false; dbsm_send_from_buf(sm, buf_this); buffer_state[buf_this] = BS_EMPTYING; } } } else { // buffer was emptying buffer_state[buf_this] = BS_EMPTY; if (sm->rx_idle){ sm->rx_idle = false; dbsm_receive_to_buf(sm, buf_this); buffer_state[buf_this] = BS_FILLING; } if (buffer_state[buf_other] == BS_FULL){ dbsm_send_from_buf(sm, buf_other); buffer_state[buf_other] = BS_EMPTYING; } else sm->tx_idle = true; } } static void dbsm_error_helper(dbsm_t *sm, int buf_this) { bp_clear_buf(buf_this); // clears ERROR flag if (buffer_state[buf_this] == BS_FILLING){ dbsm_receive_to_buf(sm, buf_this); // restart the xfer } else { // buffer was emptying dbsm_send_from_buf(sm, buf_this); // restart the xfer } } /* * Handle DSP Tx underrun */ void dbsm_handle_tx_underrun(dbsm_t *sm) { // clear the DSP Tx state machine dsp_tx_regs->clear_state = 1; // If there's a buffer that's empyting, clear it & flush xfer if (buffer_state[sm->buf0] == BS_EMPTYING){ bp_clear_buf(sm->buf0); dsp_tx_regs->clear_state = 1; // flush partial packet // drop frame in progress on ground. Pretend it finished dbsm_process_helper(sm, sm->buf0); } else if (buffer_state[sm->buf0 ^ 1] == BS_EMPTYING){ bp_clear_buf(sm->buf0 ^ 1); dsp_tx_regs->clear_state = 1; // flush partial packet // drop frame in progress on ground. Pretend it finished dbsm_process_helper(sm, sm->buf0 ^ 1); } } /* * Handle DSP Rx overrun */ void dbsm_handle_rx_overrun(dbsm_t *sm) { dsp_rx_regs->clear_state = 1; // If there's a buffer that's filling, clear it. // Any restart will be the job of the caller. if (buffer_state[sm->buf0] == BS_FILLING) bp_clear_buf(sm->buf0); if (buffer_state[sm->buf0 ^1] == BS_FILLING) bp_clear_buf(sm->buf0 ^ 1); } void dbsm_wait_for_opening(dbsm_t *sm) { if (buffer_state[sm->buf0] == BS_EMPTYING){ // wait for xfer to complete int mask = BPS_DONE(sm->buf0) | BPS_ERROR(sm->buf0) | BPS_IDLE(sm->buf0); while ((buffer_pool_status->status & mask) == 0) ; } else if (buffer_state[sm->buf0 ^ 1] == BS_EMPTYING){ // wait for xfer to complete int mask = BPS_DONE(sm->buf0 ^ 1) | BPS_ERROR(sm->buf0 ^ 1) | BPS_IDLE(sm->buf0 ^ 1); while ((buffer_pool_status->status & mask) == 0) ; } }