| /* |
| * Copyright 2021 Google LLC |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <string.h> |
| |
| #include <arpa/inet.h> |
| |
| #include "platforms/nemora/portable/ncsi.h" |
| #include "platforms/nemora/portable/ncsi_client.h" |
| #include "platforms/nemora/portable/ncsi_fsm.h" |
| |
| #define GO_TO_STATE(variable, state) do { *variable = state; } while (0) |
| #define GO_TO_NEXT_STATE(variable) do { (*variable)++; } while (0) |
| |
| // TODO - This state machine needs to be rewritten, now that we have a |
| // better idea of the states and transitions involved. |
| // The NC-SI related states of the state machine are currently organized in |
| // request/response pairs. However when I added support for the second channel |
| // this resulted in more hard-coded pairs which worked okay for X |
| // (despite some ugliness, see ch_under_test below) but broke down for X |
| // since it only supports 1 channel. For now just add a little more ugliness |
| // by stepping by 1 or 3 when going from a pair to the next depending on whether |
| // the second channel is supported (1) or not (3 - skip over the second channel |
| // pair). |
| #define GO_TO_NEXT_CHANNEL(variable, ncsi_state)\ |
| do { *variable += (ncsi_state->channel_count == 1)\ |
| ? 3 : 1; } while (0) |
| |
| static void ncsi_fsm_clear_state(ncsi_state_t* ncsi_state) { |
| // This implicitly resets: |
| // l2_config_state to NCSI_STATE_L2_CONFIG_BEGIN |
| // l3l4_config_state to NCSI_STATE_L3L4_CONFIG_BEGIN |
| // test_state to NCSI_STATE_TEST_BEGIN |
| memset(ncsi_state, 0, sizeof(ncsi_state_t)); |
| } |
| |
| static void ncsi_fsm_fail(ncsi_state_t* ncsi_state, |
| network_debug_t* network_debug) { |
| network_debug->ncsi.fail_count++; |
| memcpy(&network_debug->ncsi.state_that_failed, ncsi_state, |
| sizeof(network_debug->ncsi.state_that_failed)); |
| ncsi_fsm_clear_state(ncsi_state); |
| } |
| |
| ncsi_connection_state_t ncsi_fsm_connection_state( |
| const ncsi_state_t* ncsi_state, const network_debug_t* network_debug) { |
| if (!network_debug->ncsi.enabled) { |
| return NCSI_CONNECTION_DISABLED; |
| } |
| if (ncsi_state->l2_config_state != NCSI_STATE_L2_CONFIG_END) { |
| if (network_debug->ncsi.loopback) { |
| return NCSI_CONNECTION_LOOPBACK; |
| } else { |
| return NCSI_CONNECTION_DOWN; |
| } |
| } |
| if (ncsi_state->l3l4_config_state != NCSI_STATE_L3L4_CONFIG_END) { |
| return NCSI_CONNECTION_UP; |
| } |
| return NCSI_CONNECTION_UP_AND_CONFIGURED; |
| } |
| |
| ncsi_response_type_t ncsi_fsm_poll_l2_config(ncsi_state_t* ncsi_state, |
| network_debug_t* network_debug, |
| ncsi_buf_t* ncsi_buf, |
| mac_addr_t* mac) { |
| ncsi_l2_config_state_t* const state_variable = &ncsi_state->l2_config_state; |
| ncsi_response_type_t ncsi_response_type = NCSI_RESPONSE_NONE; |
| uint32_t len = 0; |
| |
| switch(*state_variable) { |
| case NCSI_STATE_RESTART: |
| if (++ncsi_state->restart_delay_count >= NCSI_FSM_RESTART_DELAY_COUNT) { |
| network_debug->ncsi.pending_restart = false; |
| GO_TO_NEXT_STATE(state_variable); |
| ncsi_state->restart_delay_count = 0; |
| } |
| break; |
| case NCSI_STATE_CLEAR_0: // necessary to get mac |
| len = ncsi_cmd_clear_initial_state(ncsi_buf->data, CHANNEL_0_ID); |
| GO_TO_NEXT_STATE(state_variable); |
| break; |
| case NCSI_STATE_CLEAR_0_RESPONSE: |
| { |
| bool loopback = false; |
| ncsi_response_type = ncsi_validate_std_response( |
| ncsi_buf->data, ncsi_buf->len, NCSI_CLEAR_INITIAL_STATE); |
| if (NCSI_RESPONSE_ACK == ncsi_response_type) { |
| GO_TO_NEXT_STATE(state_variable); |
| } else { |
| // If we did not receive a response but we did receive something, |
| // then maybe there is a physical loopback, so check that we received |
| // exactly what we sent |
| if (ncsi_buf->len >= sizeof(ncsi_simple_command_t)) { |
| ncsi_simple_command_t expected_loopback_data; |
| (void)ncsi_cmd_clear_initial_state((uint8_t*)&expected_loopback_data, |
| CHANNEL_0_ID); |
| if (0 == memcmp((uint8_t*)&expected_loopback_data, |
| ncsi_buf->data, sizeof(expected_loopback_data))) { |
| loopback = true; |
| } |
| } |
| ncsi_fsm_fail(ncsi_state, network_debug); |
| } |
| network_debug->ncsi.loopback = loopback; |
| } |
| break; |
| case NCSI_STATE_GET_VERSION: |
| len = ncsi_cmd_get_version(ncsi_buf->data, CHANNEL_0_ID); |
| GO_TO_NEXT_STATE(state_variable); |
| break; |
| case NCSI_STATE_GET_VERSION_RESPONSE: |
| ncsi_response_type = ncsi_validate_std_response( |
| ncsi_buf->data, ncsi_buf->len, NCSI_GET_VERSION_ID); |
| if (NCSI_RESPONSE_ACK == ncsi_response_type) { |
| ncsi_version_id_response_t* get_version_response = |
| (ncsi_version_id_response_t*)ncsi_buf->data; |
| // TODO - Add check for this being actually X |
| network_debug->ncsi.mlx_legacy = |
| ((ntohl(get_version_response->version.firmware_version) >> 24) == |
| 0x08); |
| GO_TO_NEXT_CHANNEL(state_variable, ncsi_state); |
| } else { |
| ncsi_fsm_fail(ncsi_state, network_debug); |
| } |
| break; |
| case NCSI_STATE_GET_CAPABILITIES: |
| len = ncsi_cmd_get_capabilities(ncsi_buf->data, CHANNEL_0_ID); |
| GO_TO_NEXT_STATE(state_variable); |
| break; |
| case NCSI_STATE_GET_CAPABILITIES_RESPONSE: |
| ncsi_response_type = ncsi_validate_std_response( |
| ncsi_buf->data, ncsi_buf->len, NCSI_GET_CAPABILITIES); |
| if (NCSI_RESPONSE_ACK == ncsi_response_type) { |
| const ncsi_capabilities_response_t* get_capabilities_response = |
| (ncsi_capabilities_response_t*) ncsi_buf->data; |
| if (1 != get_capabilities_response->channel_count && |
| 2 != get_capabilities_response->channel_count) { |
| /* TODO: Return Error |
| CPRINTF("[NCSI Unsupported channel count %d]\n", |
| get_capabilities_response->channel_count); |
| */ |
| ncsi_fsm_fail(ncsi_state, network_debug); |
| } else { |
| ncsi_state->channel_count = |
| get_capabilities_response->channel_count; |
| GO_TO_NEXT_CHANNEL(state_variable, ncsi_state); |
| } |
| } else{ |
| ncsi_fsm_fail(ncsi_state, network_debug); |
| } |
| break; |
| case NCSI_STATE_CLEAR_1: |
| len = ncsi_cmd_clear_initial_state(ncsi_buf->data, CHANNEL_1_ID); |
| GO_TO_NEXT_STATE(state_variable); |
| break; |
| case NCSI_STATE_CLEAR_1_RESPONSE: |
| ncsi_response_type = ncsi_validate_std_response( |
| ncsi_buf->data, ncsi_buf->len, NCSI_CLEAR_INITIAL_STATE); |
| if (NCSI_RESPONSE_ACK == ncsi_response_type) { |
| GO_TO_NEXT_STATE(state_variable); |
| } else { |
| ncsi_fsm_fail(ncsi_state, network_debug); |
| } |
| break; |
| case NCSI_STATE_RESET_CHANNEL_0: |
| if (network_debug->ncsi.pending_stop) { |
| len = ncsi_cmd_reset_channel(ncsi_buf->data, CHANNEL_0_ID); |
| GO_TO_NEXT_STATE(state_variable); |
| } else { |
| // skip resetting channels |
| GO_TO_STATE(state_variable, NCSI_STATE_GET_MAC); |
| } |
| break; |
| case NCSI_STATE_RESET_CHANNEL_0_RESPONSE: |
| ncsi_response_type = ncsi_validate_std_response( |
| ncsi_buf->data, ncsi_buf->len, NCSI_RESET_CHANNEL); |
| if (NCSI_RESPONSE_ACK == ncsi_response_type) { |
| GO_TO_NEXT_CHANNEL(state_variable, ncsi_state); |
| } else { |
| ncsi_fsm_fail(ncsi_state, network_debug); |
| } |
| break; |
| case NCSI_STATE_RESET_CHANNEL_1: |
| len = ncsi_cmd_reset_channel(ncsi_buf->data, CHANNEL_1_ID); |
| GO_TO_NEXT_STATE(state_variable); |
| break; |
| case NCSI_STATE_RESET_CHANNEL_1_RESPONSE: |
| ncsi_response_type = ncsi_validate_std_response( |
| ncsi_buf->data, ncsi_buf->len, NCSI_RESET_CHANNEL); |
| if (NCSI_RESPONSE_ACK == ncsi_response_type) { |
| GO_TO_NEXT_STATE(state_variable); |
| } else { |
| ncsi_fsm_fail(ncsi_state, network_debug); |
| } |
| break; |
| case NCSI_STATE_STOPPED: |
| network_debug->ncsi.pending_stop = false; |
| // Reset the L2 config state machine through fail(). This state machine |
| // will not be executed again so long as 'enabled' is false. |
| network_debug->ncsi.enabled = false; |
| ncsi_fsm_fail(ncsi_state, network_debug); |
| break; |
| // TODO: Add check for MFG ID and firmware version before trying |
| // any OEM commands. |
| case NCSI_STATE_GET_MAC: |
| // Only get MAC from channel 0, because that's the one that identifies the |
| // host machine (for both MDB and DHCP). |
| len = ncsi_oem_cmd_get_host_mac(ncsi_buf->data, CHANNEL_0_ID); |
| GO_TO_NEXT_STATE(state_variable); |
| break; |
| case NCSI_STATE_GET_MAC_RESPONSE: |
| ncsi_response_type = ncsi_validate_oem_response( |
| ncsi_buf->data, ncsi_buf->len, NCSI_OEM_COMMAND_GET_HOST_MAC); |
| if (NCSI_RESPONSE_ACK == ncsi_response_type) { |
| ncsi_host_mac_response_t* get_mac_response = |
| (ncsi_host_mac_response_t*) ncsi_buf->data; |
| memcpy(mac->octet, get_mac_response->mac, sizeof(mac_addr_t)); |
| GO_TO_NEXT_STATE(state_variable); |
| } else { |
| ncsi_fsm_fail(ncsi_state, network_debug); |
| } |
| break; |
| case NCSI_STATE_SET_MAC_FILTER_0: |
| len = ncsi_cmd_set_mac(ncsi_buf->data, CHANNEL_0_ID, mac); |
| GO_TO_NEXT_STATE(state_variable); |
| break; |
| case NCSI_STATE_SET_MAC_FILTER_0_RESPONSE: |
| ncsi_response_type = ncsi_validate_std_response( |
| ncsi_buf->data, ncsi_buf->len, NCSI_SET_MAC_ADDRESS); |
| if (NCSI_RESPONSE_ACK == ncsi_response_type) { |
| GO_TO_NEXT_CHANNEL(state_variable, ncsi_state); |
| } else{ |
| ncsi_fsm_fail(ncsi_state, network_debug); |
| } |
| break; |
| case NCSI_STATE_SET_MAC_FILTER_1: |
| len = ncsi_cmd_set_mac(ncsi_buf->data, CHANNEL_1_ID, mac); |
| GO_TO_NEXT_STATE(state_variable); |
| break; |
| case NCSI_STATE_SET_MAC_FILTER_1_RESPONSE: |
| ncsi_response_type = ncsi_validate_std_response( |
| ncsi_buf->data, ncsi_buf->len, NCSI_SET_MAC_ADDRESS); |
| if (NCSI_RESPONSE_ACK == ncsi_response_type) { |
| GO_TO_NEXT_STATE(state_variable); |
| } else{ |
| ncsi_fsm_fail(ncsi_state, network_debug); |
| } |
| break; |
| case NCSI_STATE_ENABLE_CHANNEL_0: |
| len = ncsi_cmd_enable_channel(ncsi_buf->data, CHANNEL_0_ID); |
| GO_TO_NEXT_STATE(state_variable); |
| break; |
| case NCSI_STATE_ENABLE_CHANNEL_0_RESPONSE: |
| ncsi_response_type = ncsi_validate_std_response( |
| ncsi_buf->data, ncsi_buf->len, NCSI_ENABLE_CHANNEL); |
| if (NCSI_RESPONSE_ACK == ncsi_response_type) { |
| GO_TO_NEXT_CHANNEL(state_variable, ncsi_state); |
| } else{ |
| ncsi_fsm_fail(ncsi_state, network_debug); |
| } |
| break; |
| case NCSI_STATE_ENABLE_CHANNEL_1: |
| len = ncsi_cmd_enable_channel(ncsi_buf->data, CHANNEL_1_ID); |
| GO_TO_NEXT_STATE(state_variable); |
| break; |
| case NCSI_STATE_ENABLE_CHANNEL_1_RESPONSE: |
| ncsi_response_type = ncsi_validate_std_response( |
| ncsi_buf->data, ncsi_buf->len, NCSI_ENABLE_CHANNEL); |
| if (NCSI_RESPONSE_ACK == ncsi_response_type) { |
| GO_TO_NEXT_STATE(state_variable); |
| } else{ |
| ncsi_fsm_fail(ncsi_state, network_debug); |
| } |
| break; |
| // TODO: Enable broadcast filter to block ARP. |
| case NCSI_STATE_ENABLE_TX: |
| // The NIC FW transmits all passthrough TX on the lowest enabled channel, |
| // so there is no point in enabling TX on the second channel. |
| // TODO: - In the future we may add a check for link status, |
| // in which case we may want to intelligently disable ch.0 |
| // (if down) and enable ch.1 |
| len = ncsi_cmd_enable_tx(ncsi_buf->data, CHANNEL_0_ID); |
| GO_TO_NEXT_STATE(state_variable); |
| break; |
| case NCSI_STATE_ENABLE_TX_RESPONSE: |
| ncsi_response_type = ncsi_validate_std_response( |
| ncsi_buf->data, ncsi_buf->len, NCSI_ENABLE_CHANNEL_NETWORK_TX); |
| if (NCSI_RESPONSE_ACK == ncsi_response_type) { |
| GO_TO_NEXT_STATE(state_variable); |
| } else{ |
| ncsi_fsm_fail(ncsi_state, network_debug); |
| } |
| break; |
| case NCSI_STATE_L2_CONFIG_END: |
| // Done |
| break; |
| default: |
| ncsi_fsm_fail(ncsi_state, network_debug); |
| break; |
| } |
| |
| ncsi_buf->len = len; |
| return ncsi_response_type; |
| } |
| |
| static uint32_t write_ncsi_oem_config_filter(uint8_t* buffer, uint8_t channel, |
| network_debug_t* network_debug, |
| mac_addr_t* mac, |
| uint32_t ipv4_addr, |
| uint16_t rx_port) { |
| uint32_t len; |
| (void)ipv4_addr; |
| if (network_debug->ncsi.oem_filter_disable) { |
| mac_addr_t zero_mac = {.octet = {0,}}; |
| len = ncsi_oem_cmd_set_filter(buffer, channel, &zero_mac, 0, 0, 0); |
| |
| } else { |
| len = ncsi_oem_cmd_set_filter(buffer, channel, mac, 0, rx_port, 1); |
| } |
| return len; |
| } |
| |
| ncsi_response_type_t ncsi_fsm_poll_l3l4_config(ncsi_state_t* ncsi_state, |
| network_debug_t* network_debug, |
| ncsi_buf_t* ncsi_buf, |
| mac_addr_t* mac, |
| uint32_t ipv4_addr, |
| uint16_t rx_port) { |
| uint32_t len = 0; |
| ncsi_response_type_t ncsi_response_type = NCSI_RESPONSE_NONE; |
| |
| if (ncsi_state->l3l4_config_state == NCSI_STATE_L3L4_CONFIG_BEGIN) { |
| ncsi_state->l3l4_channel = 0; |
| ncsi_state->l3l4_waiting_response = false; |
| ncsi_state->l3l4_config_state = NCSI_STATE_CONFIG_FILTERS; |
| } |
| |
| /* Go through every state with every channel. */ |
| if (ncsi_state->l3l4_waiting_response) { |
| ncsi_response_type = ncsi_validate_oem_response( |
| ncsi_buf->data, ncsi_buf->len, ncsi_state->l3l4_command); |
| if (NCSI_RESPONSE_ACK == ncsi_response_type) { |
| /* Current channel ACK'ed, go to the next one. */ |
| ncsi_state->l3l4_channel++; |
| if (ncsi_state->l3l4_channel >= ncsi_state->channel_count) { |
| /* All channels done, reset channel number and go to the next state. |
| * NOTE: This assumes that state numbers are sequential.*/ |
| ncsi_state->l3l4_config_state += 1; |
| ncsi_state->l3l4_channel = 0; |
| } |
| } else { |
| ncsi_fsm_fail(ncsi_state, network_debug); |
| } |
| |
| ncsi_state->l3l4_waiting_response = false; |
| } else { |
| // Send appropriate command. |
| switch(ncsi_state->l3l4_config_state) { |
| case NCSI_STATE_CONFIG_FILTERS: |
| len = |
| write_ncsi_oem_config_filter(ncsi_buf->data, ncsi_state->l3l4_channel, |
| network_debug, mac, ipv4_addr, rx_port); |
| ncsi_state->l3l4_command = NCSI_OEM_COMMAND_SET_FILTER; |
| ncsi_state->l3l4_waiting_response = true; |
| break; |
| default: |
| ncsi_fsm_fail(ncsi_state, network_debug); |
| break; |
| } |
| } |
| |
| ncsi_buf->len = len; |
| return ncsi_response_type; |
| } |
| |
| /* |
| * Start a sub-section of the state machine that runs health checks. |
| * This is dependent on the NC-SI configuration being completed |
| * (e.g. ncsi_channel_count must be known). |
| */ |
| static bool ncsi_fsm_start_test(network_debug_t* network_debug, |
| uint8_t channel_count) { |
| if (network_debug->ncsi.test.max_tries > 0) { |
| network_debug->ncsi.test.runs++; |
| if (2 == channel_count) { |
| network_debug->ncsi.test.ch_under_test ^= 1; |
| } else { |
| network_debug->ncsi.test.ch_under_test = 0; |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| /* |
| * Allow for a limited number of retries for the NC-SI test because |
| * it can fail under heavy TCP/IP load (since NC-SI responses share |
| * the RX buffers in chip/$(CHIP)/net.c with TCP/IP incoming traffic). |
| */ |
| static bool ncsi_fsm_retry_test(network_debug_t* network_debug) { |
| const uint8_t max_tries = network_debug->ncsi.test.max_tries; |
| if (max_tries) { |
| uint8_t remaining_tries = max_tries - 1 - network_debug->ncsi.test.tries; |
| if (remaining_tries > 0) { |
| network_debug->ncsi.test.tries++; |
| return true; |
| } |
| } |
| network_debug->ncsi.test.tries = 0; |
| return false; |
| } |
| |
| /* |
| * Returns true if we have executed an NC-SI Get OEM Filter command for all |
| * channels and the flags indicate that it is running in hostless mode. |
| * This means that we can DHCP/ARP if needed. |
| * Otherwise returns false. |
| * |
| * NOTE: We default to false, if we cannot complete the L2 config state |
| * machine or the test sequence. |
| */ |
| bool ncsi_fsm_is_nic_hostless(const ncsi_state_t* ncsi_state) { |
| uint8_t flags = ncsi_state->flowsteering[0].flags; |
| if (ncsi_state->channel_count > 1) { |
| flags &= ncsi_state->flowsteering[1].flags; |
| } |
| return flags & NCSI_OEM_FILTER_FLAGS_HOSTLESS; |
| } |
| |
| static void ncsi_fsm_update_passthrough_stats( |
| const ncsi_passthrough_stats_t* increment, network_debug_t* network_debug) { |
| ncsi_passthrough_stats_t* accumulated = |
| &network_debug->ncsi.pt_stats_be[network_debug->ncsi.test.ch_under_test]; |
| #define ACCUMULATE_PT_STATS(stat) accumulated->stat += increment->stat; |
| ACCUMULATE_PT_STATS(tx_packets_received_hi); |
| ACCUMULATE_PT_STATS(tx_packets_received_lo); |
| ACCUMULATE_PT_STATS(tx_packets_dropped); |
| ACCUMULATE_PT_STATS(tx_channel_errors); |
| ACCUMULATE_PT_STATS(tx_undersized_errors); |
| ACCUMULATE_PT_STATS(tx_oversized_errors); |
| ACCUMULATE_PT_STATS(rx_packets_received); |
| ACCUMULATE_PT_STATS(rx_packets_dropped); |
| ACCUMULATE_PT_STATS(rx_channel_errors); |
| ACCUMULATE_PT_STATS(rx_undersized_errors); |
| ACCUMULATE_PT_STATS(rx_oversized_errors); |
| #undef ACCUMULATE_PT_STATS |
| } |
| |
| static void ncsi_fsm_update_passthrough_stats_legacy( |
| const ncsi_passthrough_stats_legacy_t* read, |
| network_debug_t* network_debug) { |
| // Legacy MLX response does not include tx_packets_received_hi and also MLX |
| // counters |
| // are not reset on read (i.e. we cannot accumulate them). |
| ncsi_passthrough_stats_t* accumulated = |
| &network_debug->ncsi.pt_stats_be[network_debug->ncsi.test.ch_under_test]; |
| accumulated->tx_packets_received_hi = 0; |
| accumulated->tx_packets_received_lo = read->tx_packets_received; |
| #define COPY_PT_STATS(stat) accumulated->stat = read->stat; |
| COPY_PT_STATS(tx_packets_dropped); |
| COPY_PT_STATS(tx_channel_errors); |
| COPY_PT_STATS(tx_undersized_errors); |
| COPY_PT_STATS(tx_oversized_errors); |
| COPY_PT_STATS(rx_packets_received); |
| COPY_PT_STATS(rx_packets_dropped); |
| COPY_PT_STATS(rx_channel_errors); |
| COPY_PT_STATS(rx_undersized_errors); |
| COPY_PT_STATS(rx_oversized_errors); |
| #undef COPY_PT_STATS |
| } |
| |
| ncsi_response_type_t ncsi_fsm_poll_test(ncsi_state_t* ncsi_state, |
| network_debug_t* network_debug, |
| ncsi_buf_t* ncsi_buf, mac_addr_t* mac, |
| uint32_t ipv4_addr, uint16_t rx_port) { |
| ncsi_test_state_t* const state_variable = |
| &ncsi_state->test_state; |
| ncsi_response_type_t ncsi_response_type = NCSI_RESPONSE_NONE; |
| uint32_t len = 0; |
| |
| switch(*state_variable) { |
| case NCSI_STATE_TEST_PARAMS: |
| if (ncsi_fsm_start_test(network_debug, ncsi_state->channel_count)) { |
| GO_TO_NEXT_STATE(state_variable); |
| } else { |
| // debugging only - skip test by setting max_tries to 0 |
| GO_TO_STATE(state_variable, NCSI_STATE_TEST_END); |
| } |
| break; |
| case NCSI_STATE_ECHO: |
| len = ncsi_oem_cmd_echo(ncsi_buf->data, |
| network_debug->ncsi.test.ch_under_test, |
| network_debug->ncsi.test.ping.tx); |
| network_debug->ncsi.test.ping.tx_count++; |
| GO_TO_NEXT_STATE(state_variable); |
| break; |
| case NCSI_STATE_ECHO_RESPONSE: |
| ncsi_response_type = ncsi_validate_oem_response( |
| ncsi_buf->data, ncsi_buf->len, NCSI_OEM_COMMAND_ECHO); |
| if (NCSI_RESPONSE_ACK == ncsi_response_type) { |
| network_debug->ncsi.test.ping.rx_count++; |
| ncsi_oem_echo_response_t* echo_response = |
| (ncsi_oem_echo_response_t*) ncsi_buf->data; |
| if (0 == memcmp(echo_response->pattern, |
| network_debug->ncsi.test.ping.tx, |
| sizeof(echo_response->pattern))) { |
| GO_TO_NEXT_STATE(state_variable); |
| break; |
| } else { |
| network_debug->ncsi.test.ping.bad_rx_count++; |
| memcpy(network_debug->ncsi.test.ping.last_bad_rx, |
| echo_response->pattern, |
| sizeof(network_debug->ncsi.test.ping.last_bad_rx)); |
| } |
| } |
| if (ncsi_fsm_retry_test(network_debug)) { |
| GO_TO_STATE(state_variable, NCSI_STATE_TEST_BEGIN); |
| } else { |
| ncsi_fsm_fail(ncsi_state, network_debug); |
| } |
| break; |
| case NCSI_STATE_CHECK_FILTERS: |
| len = ncsi_oem_cmd_get_filter(ncsi_buf->data, |
| network_debug->ncsi.test.ch_under_test); |
| GO_TO_NEXT_STATE(state_variable); |
| break; |
| case NCSI_STATE_CHECK_FILTERS_RESPONSE: |
| ncsi_response_type = ncsi_validate_oem_response( |
| ncsi_buf->data, ncsi_buf->len, NCSI_OEM_COMMAND_GET_FILTER); |
| if (NCSI_RESPONSE_ACK == ncsi_response_type) { |
| ncsi_oem_get_filter_response_t* get_filter_response = |
| (ncsi_oem_get_filter_response_t*) ncsi_buf->data; |
| // Stash away response because it contains information about NIC mode |
| memcpy((void*)ncsi_state->flowsteering[ |
| network_debug->ncsi.test.ch_under_test].regid, |
| (void*)get_filter_response->filter.regid, |
| sizeof(ncsi_state->flowsteering[0].regid)); |
| ncsi_state->flowsteering[ |
| network_debug->ncsi.test.ch_under_test].flags = |
| get_filter_response->filter.flags; |
| // Test filter parameters only if we know that we configured the NIC, |
| // and if the NIC is in host-based mode (it appears to return all zeros's |
| // in hostless mode!). |
| if (NCSI_STATE_L3L4_CONFIG_END != ncsi_state->l3l4_config_state || |
| ncsi_fsm_is_nic_hostless(ncsi_state)) { |
| GO_TO_NEXT_STATE(state_variable); |
| break; |
| } |
| ncsi_oem_set_filter_cmd_t expected; |
| (void)write_ncsi_oem_config_filter( |
| (uint8_t*)&expected, network_debug->ncsi.test.ch_under_test, |
| network_debug, mac, ipv4_addr, rx_port); |
| /* TODO: handle these responses in error reporting routine */ |
| if (0 != memcmp((void*)&get_filter_response->filter.mac, |
| (void*)&expected.filter.mac, |
| sizeof(expected.filter.mac))) { |
| ncsi_response_type = NCSI_RESPONSE_UNEXPECTED_PARAMS; |
| } else if (get_filter_response->filter.ip != expected.filter.ip || |
| get_filter_response->filter.port != expected.filter.port) { |
| ncsi_response_type = NCSI_RESPONSE_UNEXPECTED_PARAMS; |
| } else { |
| GO_TO_NEXT_STATE(state_variable); |
| break; |
| } |
| } |
| if (ncsi_fsm_retry_test(network_debug)) { |
| GO_TO_STATE(state_variable, NCSI_STATE_TEST_BEGIN); |
| } else { |
| ncsi_fsm_fail(ncsi_state, network_debug); |
| } |
| break; |
| case NCSI_STATE_GET_PT_STATS: |
| len = ncsi_cmd_get_passthrough_stats( |
| ncsi_buf->data, network_debug->ncsi.test.ch_under_test); |
| GO_TO_NEXT_STATE(state_variable); |
| break; |
| case NCSI_STATE_GET_PT_STATS_RESPONSE: |
| if (!network_debug->ncsi.mlx_legacy) { |
| ncsi_response_type = ncsi_validate_std_response( |
| ncsi_buf->data, ncsi_buf->len, NCSI_GET_PASSTHROUGH_STATISTICS); |
| if (ncsi_response_type == NCSI_RESPONSE_ACK) { |
| const ncsi_passthrough_stats_response_t* response = |
| (const ncsi_passthrough_stats_response_t*)ncsi_buf->data; |
| ncsi_fsm_update_passthrough_stats(&response->stats, network_debug); |
| GO_TO_NEXT_STATE(state_variable); |
| break; |
| } |
| } else { |
| uint32_t response_size = |
| ncsi_get_response_size(NCSI_GET_PASSTHROUGH_STATISTICS) - |
| sizeof(uint32_t); |
| ncsi_response_type = ncsi_validate_response( |
| ncsi_buf->data, ncsi_buf->len, NCSI_GET_PASSTHROUGH_STATISTICS, false, |
| response_size); |
| if (NCSI_RESPONSE_ACK == ncsi_response_type) { |
| const ncsi_passthrough_stats_legacy_response_t* legacy_response = |
| (const ncsi_passthrough_stats_legacy_response_t*)ncsi_buf->data; |
| ncsi_fsm_update_passthrough_stats_legacy(&legacy_response->stats, |
| network_debug); |
| GO_TO_NEXT_STATE(state_variable); |
| break; |
| } |
| } |
| if (ncsi_fsm_retry_test(network_debug)) { |
| GO_TO_STATE(state_variable, NCSI_STATE_TEST_BEGIN); |
| } else { |
| ncsi_fsm_fail(ncsi_state, network_debug); |
| } |
| break; |
| case NCSI_STATE_GET_LINK_STATUS: |
| // We only care about ch.0 link status because that's the only one we use |
| // to transmit. |
| len = ncsi_cmd_get_link_status(ncsi_buf->data, 0); |
| GO_TO_NEXT_STATE(state_variable); |
| break; |
| case NCSI_STATE_GET_LINK_STATUS_RESPONSE: |
| ncsi_response_type = ncsi_validate_std_response( |
| ncsi_buf->data, ncsi_buf->len, NCSI_GET_LINK_STATUS); |
| if (NCSI_RESPONSE_ACK == ncsi_response_type) { |
| const ncsi_link_status_response_t* response = |
| (const ncsi_link_status_response_t*)ncsi_buf->data; |
| const uint32_t link_status = ntohl(response->link_status.link_status); |
| if (link_status & NCSI_LINK_STATUS_UP) { |
| GO_TO_NEXT_STATE(state_variable); |
| break; |
| } |
| // TODO: report this error. |
| // CPRINTF("[NCSI Link Status down 0x%08lx]\n", link_status); |
| } |
| if (ncsi_fsm_retry_test(network_debug)) { |
| GO_TO_STATE(state_variable, NCSI_STATE_TEST_BEGIN); |
| } else { |
| ncsi_fsm_fail(ncsi_state, network_debug); |
| } |
| break; |
| case NCSI_STATE_TEST_END: |
| network_debug->ncsi.test.tries = 0; |
| if (network_debug->ncsi.pending_restart) { |
| ncsi_fsm_fail(ncsi_state, network_debug); // (Ab)use fail to restart. |
| } |
| if (++ncsi_state->retest_delay_count >= NCSI_FSM_RETEST_DELAY_COUNT) { |
| GO_TO_STATE(state_variable, NCSI_STATE_TEST_BEGIN); |
| ncsi_state->retest_delay_count = 0; |
| } |
| break; |
| default: |
| ncsi_fsm_fail(ncsi_state, network_debug); |
| break; |
| } |
| |
| ncsi_buf->len = len; |
| return ncsi_response_type; |
| } |