| // 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 "ncsi_state_machine.h" |
| |
| #include "common_defs.h" |
| #include "platforms/nemora/portable/default_addresses.h" |
| #include "platforms/nemora/portable/ncsi_fsm.h" |
| |
| #include <arpa/inet.h> |
| #include <fmt/format.h> |
| #include <fmt/printf.h> |
| #include <netinet/ether.h> |
| #include <unistd.h> |
| |
| #include <chrono> |
| #include <cstdint> |
| #include <cstdlib> |
| #include <cstring> |
| #include <iostream> |
| #include <string> |
| #include <thread> |
| #include <utility> |
| |
| #define ETHER_NCSI 0x88f8 |
| |
| // Only log messages a single time and drop all duplicates to prevent log spam. |
| // Having duplicate messages printed has historically not been helpful in |
| // debugging issues with this program. |
| static void do_log(std::string&& line) |
| { |
| constexpr auto line_dup_time = std::chrono::hours(1); |
| static std::chrono::steady_clock::time_point last_line_time; |
| static size_t line_rep_count = 0; |
| static std::string last_line; |
| |
| auto now = std::chrono::steady_clock::now(); |
| if (line != last_line || line_dup_time + last_line_time < now) |
| { |
| if (line_rep_count > 0) |
| { |
| fmt::print(stderr, "... Repeated {} times ...\n", line_rep_count); |
| } |
| fmt::print(stderr, "{}", line); |
| last_line = std::move(line); |
| last_line_time = now; |
| line_rep_count = 0; |
| } |
| else |
| { |
| line_rep_count++; |
| } |
| } |
| |
| #define CPRINTF(...) do_log(fmt::sprintf(__VA_ARGS__)) |
| |
| #ifdef NCSID_VERBOSE_LOGGING |
| #define DEBUG_PRINTF printf |
| #else |
| #define DEBUG_PRINTF(...) |
| #endif |
| |
| namespace ncsi |
| { |
| |
| namespace |
| { |
| |
| const char kStateFormat[] = "l2_config=%d/%d l3l4_config=%d/%d test=%d/%d"; |
| // This assumes that the number of states is < 100, so each number |
| // in the format above does not take more than two characters to represent, |
| // thus %d (two characters) is substituted for the number which is also |
| // two characters max. |
| constexpr auto kStateFormatLen = sizeof(kStateFormat); |
| |
| void snprintf_state(char* buffer, int size, const ncsi_state_t* state) |
| { |
| (void)snprintf(buffer, size, kStateFormat, state->l2_config_state, |
| NCSI_STATE_L2_CONFIG_END, state->l3l4_config_state, |
| NCSI_STATE_L3L4_CONFIG_END, state->test_state, |
| NCSI_STATE_TEST_END); |
| } |
| |
| void print_state(const ncsi_state_t& state) |
| { |
| (void)state; |
| DEBUG_PRINTF(kStateFormat, state.l2_config_state, NCSI_STATE_L2_CONFIG_END, |
| state.l3l4_config_state, NCSI_STATE_L3L4_CONFIG_END, |
| state.test_state, NCSI_STATE_TEST_END); |
| DEBUG_PRINTF(" restart_delay_count=%d\n", state.restart_delay_count); |
| } |
| |
| const uint8_t echo_pattern[NCSI_OEM_ECHO_PATTERN_SIZE] = { |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, |
| 0xFF, 0xFF, 0xFF, 0xFF, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, |
| 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0x12, 0x34, 0x56, 0x78, |
| 0x9A, 0xBC, 0xDE, 0xF0, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}; |
| |
| } // namespace |
| |
| void StateMachine::reset() |
| { |
| std::memset(&ncsi_state_, 0, sizeof(ncsi_state_)); |
| ncsi_state_.restart_delay_count = NCSI_FSM_RESTART_DELAY_COUNT - 1; |
| network_debug_.ncsi.test.max_tries = MAX_TRIES; |
| // This needs to be initialized in the firmware. |
| network_debug_.ncsi.test.ch_under_test = 0; |
| network_debug_.ncsi.oem_filter_disable = false; |
| |
| network_debug_.ncsi.pending_stop = false; |
| network_debug_.ncsi.enabled = true; |
| network_debug_.ncsi.loopback = false; |
| } |
| |
| StateMachine::StateMachine() |
| { |
| reset(); |
| network_debug_.ncsi.pending_restart = true; |
| std::memcpy(network_debug_.ncsi.test.ping.tx, echo_pattern, |
| sizeof(echo_pattern)); |
| } |
| |
| StateMachine::~StateMachine() |
| { |
| CPRINTF("[NCSI stopping]\n"); |
| } |
| |
| size_t StateMachine::poll_l2_config() |
| { |
| size_t len = 0; |
| mac_addr_t mac; |
| net_config_->get_mac_addr(&mac); |
| ncsi_response_type_t response_type = ncsi_fsm_poll_l2_config( |
| &ncsi_state_, &network_debug_, &ncsi_buf_, &mac); |
| |
| auto* response = reinterpret_cast<ncsi_simple_response_t*>(ncsi_buf_.data); |
| |
| if (response_type == NCSI_RESPONSE_ACK) |
| { |
| /* If the response is MAC response, some extra handling needed. */ |
| if ((NCSI_RESPONSE | NCSI_OEM_COMMAND) == |
| response->hdr.control_packet_type) |
| { |
| auto* oem_response = |
| reinterpret_cast<ncsi_oem_simple_response_t*>(ncsi_buf_.data); |
| if (oem_response->oem_header.oem_cmd == |
| NCSI_OEM_COMMAND_GET_HOST_MAC) |
| { |
| net_config_->set_mac_addr(mac); |
| } |
| } |
| } |
| else if (NCSI_RESPONSE_NONE == response_type) |
| { |
| /* Buffer is ready to be sent. */ |
| len = ncsi_buf_.len; |
| ncsi_buf_.len = 0; |
| } |
| else |
| { |
| report_ncsi_error(response_type); |
| } |
| |
| return len; |
| } |
| |
| size_t StateMachine::poll_simple(ncsi_simple_poll_f poll_func) |
| { |
| mac_addr_t mac; |
| net_config_->get_mac_addr(&mac); |
| const uint16_t rx_port = DEFAULT_ADDRESSES_RX_PORT; |
| |
| ncsi_response_type_t response_type = |
| poll_func(&ncsi_state_, &network_debug_, &ncsi_buf_, &mac, 0, rx_port); |
| |
| auto* response = reinterpret_cast<ncsi_simple_response_t*>(ncsi_buf_.data); |
| |
| size_t len = 0; |
| if (response_type == NCSI_RESPONSE_NONE) |
| { |
| /* Buffer is ready to be sent, or we are done. */ |
| len = ncsi_buf_.len; |
| ncsi_buf_.len = 0; |
| } |
| else if (response->hdr.control_packet_type == |
| (NCSI_RESPONSE | NCSI_GET_LINK_STATUS)) |
| { |
| auto status_response = |
| reinterpret_cast<ncsi_link_status_response_t*>(response); |
| bool new_link_up = ntohl(status_response->link_status.link_status) & |
| NCSI_LINK_STATUS_UP; |
| if (!link_up_ || new_link_up != *link_up_) |
| { |
| CPRINTF("[NCSI link %s]\n", new_link_up ? "up" : "down"); |
| link_up_ = new_link_up; |
| } |
| } |
| else if (response->hdr.control_packet_type == |
| (NCSI_RESPONSE | NCSI_OEM_COMMAND)) |
| { |
| auto* oem_response = |
| reinterpret_cast<ncsi_oem_simple_response_t*>(ncsi_buf_.data); |
| if (oem_response->oem_header.oem_cmd == NCSI_OEM_COMMAND_GET_FILTER) |
| { |
| bool new_hostless = ncsi_fsm_is_nic_hostless(&ncsi_state_); |
| if (!hostless_ || new_hostless != *hostless_) |
| { |
| CPRINTF("[NCSI nic %s]\n", |
| new_hostless ? "hostless" : "hostfull"); |
| net_config_->set_nic_hostless(new_hostless); |
| hostless_ = new_hostless; |
| } |
| } |
| } |
| else if (response_type != NCSI_RESPONSE_ACK) |
| { |
| report_ncsi_error(response_type); |
| } |
| |
| return len; |
| } |
| |
| void StateMachine::report_ncsi_error(ncsi_response_type_t response_type) |
| { |
| char state_string[kStateFormatLen]; |
| snprintf_state(state_string, sizeof(state_string), &ncsi_state_); |
| auto* response = |
| reinterpret_cast<const ncsi_simple_response_t*>(ncsi_buf_.data); |
| switch (response_type) |
| { |
| case NCSI_RESPONSE_UNDERSIZED: |
| if (!ncsi_buf_.len) |
| { |
| network_debug_.ncsi.rx_error.timeout_count++; |
| CPRINTF("[NCSI timeout in state %s]\n", state_string); |
| } |
| else |
| { |
| network_debug_.ncsi.rx_error.undersized_count++; |
| CPRINTF("[NCSI undersized response in state %s]\n", |
| state_string); |
| } |
| break; |
| case NCSI_RESPONSE_NACK: |
| network_debug_.ncsi.rx_error.nack_count++; |
| CPRINTF( |
| "[NCSI nack in state %s. Response: 0x%04x Reason: 0x%04x]\n", |
| state_string, ntohs(response->response_code), |
| ntohs(response->reason_code)); |
| break; |
| case NCSI_RESPONSE_UNEXPECTED_TYPE: |
| network_debug_.ncsi.rx_error.unexpected_type_count++; |
| CPRINTF("[NCSI unexpected response in state %s. Response type: " |
| "0x%02x]\n", |
| state_string, response->hdr.control_packet_type); |
| break; |
| case NCSI_RESPONSE_UNEXPECTED_SIZE: |
| { |
| int expected_size; |
| if ((NCSI_RESPONSE | NCSI_OEM_COMMAND) == |
| response->hdr.control_packet_type) |
| { |
| auto* oem_response = |
| reinterpret_cast<ncsi_oem_simple_response_t*>( |
| ncsi_buf_.data); |
| expected_size = ncsi_oem_get_response_size( |
| oem_response->oem_header.oem_cmd); |
| } |
| else |
| { |
| expected_size = ncsi_get_response_size( |
| response->hdr.control_packet_type & (~NCSI_RESPONSE)); |
| } |
| network_debug_.ncsi.rx_error.unexpected_size_count++; |
| CPRINTF("[NCSI unexpected response size in state %s." |
| " Expected %d]\n", |
| state_string, expected_size); |
| } |
| break; |
| case NCSI_RESPONSE_OEM_FORMAT_ERROR: |
| network_debug_.ncsi.rx_error.unexpected_type_count++; |
| CPRINTF("[NCSI OEM format error]\n"); |
| break; |
| case NCSI_RESPONSE_UNEXPECTED_PARAMS: |
| CPRINTF("[NCSI OEM Filter MAC or TCP/IP Config Mismatch]\n"); |
| break; |
| default: |
| /* NCSI_RESPONSE_ACK and NCSI_RESPONSE_NONE are not errors and need |
| * not be handled here, so this branch is just to complete the |
| * switch. |
| */ |
| CPRINTF("[NCSI OK]\n"); |
| break; |
| } |
| } |
| |
| int StateMachine::receive_ncsi() |
| { |
| int len; |
| do |
| { |
| // Return value of zero means timeout |
| len = sock_io_->recv(ncsi_buf_.data, sizeof(ncsi_buf_.data)); |
| if (len > 0) |
| { |
| auto* hdr = reinterpret_cast<struct ether_header*>(ncsi_buf_.data); |
| if (ETHER_NCSI == ntohs(hdr->ether_type)) |
| { |
| ncsi_buf_.len = len; |
| break; |
| } |
| } |
| |
| ncsi_buf_.len = 0; |
| } while (len > 0); |
| |
| return ncsi_buf_.len; |
| } |
| |
| void StateMachine::run_test_fsm(size_t* tx_len) |
| { |
| // Sleep and restart when test FSM finishes. |
| if (is_test_done()) |
| { |
| std::this_thread::sleep_for(std::chrono::seconds(retest_delay_s_)); |
| // Skip over busy wait in state machine - already waited. |
| ncsi_state_.retest_delay_count = NCSI_FSM_RESTART_DELAY_COUNT; |
| } |
| // until NCSI_STATE_TEST_END |
| *tx_len = poll_simple(ncsi_fsm_poll_test); |
| } |
| |
| void StateMachine::run(int max_rounds) |
| { |
| if (!net_config_ || !sock_io_) |
| { |
| CPRINTF("StateMachine configuration incomplete: " |
| "net_config_: <%p>, sock_io_: <%p>", |
| reinterpret_cast<void*>(net_config_), |
| reinterpret_cast<void*>(sock_io_)); |
| return; |
| } |
| |
| bool infinite_loop = (max_rounds <= 0); |
| while (infinite_loop || --max_rounds >= 0) |
| { |
| receive_ncsi(); |
| |
| size_t tx_len = 0; |
| switch (ncsi_fsm_connection_state(&ncsi_state_, &network_debug_)) |
| { |
| case NCSI_CONNECTION_DOWN: |
| case NCSI_CONNECTION_LOOPBACK: |
| tx_len = poll_l2_config(); |
| break; |
| case NCSI_CONNECTION_UP: |
| if (!is_test_done() || ncsi_fsm_is_nic_hostless(&ncsi_state_)) |
| { |
| run_test_fsm(&tx_len); |
| } |
| else |
| { |
| // - Only start L3/L4 config when test is finished |
| // (it will last until success |
| // (i.e. NCSI_CONNECTION_UP_AND_CONFIGURED) or fail. |
| tx_len = poll_simple(ncsi_fsm_poll_l3l4_config); |
| } |
| break; |
| case NCSI_CONNECTION_UP_AND_CONFIGURED: |
| run_test_fsm(&tx_len); |
| break; |
| case NCSI_CONNECTION_DISABLED: |
| if (network_debug_.ncsi.pending_restart) |
| { |
| network_debug_.ncsi.enabled = true; |
| } |
| break; |
| default: |
| fail(); |
| } |
| |
| if (tx_len) |
| { |
| print_state(ncsi_state_); |
| |
| sock_io_->write(ncsi_buf_.data, tx_len); |
| } |
| } |
| } |
| |
| void StateMachine::clear_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 |
| std::memset(&ncsi_state_, 0, sizeof(ncsi_state_)); |
| } |
| |
| void StateMachine::fail() |
| { |
| network_debug_.ncsi.fail_count++; |
| clear_state(); |
| } |
| |
| void StateMachine::set_sockio(net::SockIO* sock_io) |
| { |
| sock_io_ = sock_io; |
| } |
| |
| void StateMachine::set_net_config(net::ConfigBase* net_config) |
| { |
| net_config_ = net_config; |
| } |
| |
| void StateMachine::set_retest_delay(unsigned delay) |
| { |
| retest_delay_s_ = delay; |
| } |
| |
| } // namespace ncsi |