1 // Copyright 2021 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #include "ncsi_state_machine.h" 16 17 #include "common_defs.h" 18 #include "platforms/nemora/portable/default_addresses.h" 19 #include "platforms/nemora/portable/ncsi_fsm.h" 20 21 #include <arpa/inet.h> 22 #include <netinet/ether.h> 23 #include <unistd.h> 24 25 #include <stdplus/print.hpp> 26 27 #include <chrono> 28 #include <cstdint> 29 #include <cstdlib> 30 #include <cstring> 31 #include <string> 32 #include <thread> 33 #include <utility> 34 35 #define ETHER_NCSI 0x88f8 36 37 // Only log messages a single time and drop all duplicates to prevent log spam. 38 // Having duplicate messages printed has historically not been helpful in 39 // debugging issues with this program. 40 static void do_log(std::string&& line) 41 { 42 constexpr auto line_dup_time = std::chrono::hours(1); 43 static std::chrono::steady_clock::time_point last_line_time; 44 static size_t line_rep_count = 0; 45 static std::string last_line; 46 47 auto now = std::chrono::steady_clock::now(); 48 if (line != last_line || line_dup_time + last_line_time < now) 49 { 50 if (line_rep_count > 0) 51 { 52 stdplus::println(stderr, "... Repeated {} times ...", 53 line_rep_count); 54 } 55 stdplus::print(stderr, "{}", line); 56 last_line = std::move(line); 57 last_line_time = now; 58 line_rep_count = 0; 59 } 60 else 61 { 62 line_rep_count++; 63 } 64 } 65 66 #define CPRINT(...) do_log(std::format(__VA_ARGS__)) 67 68 #ifdef NCSID_VERBOSE_LOGGING 69 #define DEBUG_PRINTF printf 70 #else 71 #define DEBUG_PRINTF(...) 72 #endif 73 74 namespace ncsi 75 { 76 77 namespace 78 { 79 80 const char kStateFormat[] = "l2_config=%d/%d l3l4_config=%d/%d test=%d/%d"; 81 // This assumes that the number of states is < 100, so each number 82 // in the format above does not take more than two characters to represent, 83 // thus %d (two characters) is substituted for the number which is also 84 // two characters max. 85 constexpr auto kStateFormatLen = sizeof(kStateFormat); 86 87 void snprintf_state(char* buffer, int size, const ncsi_state_t* state) 88 { 89 (void)snprintf(buffer, size, kStateFormat, state->l2_config_state, 90 NCSI_STATE_L2_CONFIG_END, state->l3l4_config_state, 91 NCSI_STATE_L3L4_CONFIG_END, state->test_state, 92 NCSI_STATE_TEST_END); 93 } 94 95 void print_state(const ncsi_state_t& state) 96 { 97 (void)state; 98 DEBUG_PRINTF(kStateFormat, state.l2_config_state, NCSI_STATE_L2_CONFIG_END, 99 state.l3l4_config_state, NCSI_STATE_L3L4_CONFIG_END, 100 state.test_state, NCSI_STATE_TEST_END); 101 DEBUG_PRINTF(" restart_delay_count=%d\n", state.restart_delay_count); 102 } 103 104 const uint8_t echo_pattern[NCSI_OEM_ECHO_PATTERN_SIZE] = { 105 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 106 0xFF, 0xFF, 0xFF, 0xFF, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 107 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0x12, 0x34, 0x56, 0x78, 108 0x9A, 0xBC, 0xDE, 0xF0, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}; 109 110 } // namespace 111 112 void StateMachine::reset() 113 { 114 std::memset(&ncsi_state_, 0, sizeof(ncsi_state_)); 115 ncsi_state_.restart_delay_count = NCSI_FSM_RESTART_DELAY_COUNT - 1; 116 network_debug_.ncsi.test.max_tries = MAX_TRIES; 117 // This needs to be initialized in the firmware. 118 network_debug_.ncsi.test.ch_under_test = 0; 119 network_debug_.ncsi.oem_filter_disable = false; 120 121 network_debug_.ncsi.pending_stop = false; 122 network_debug_.ncsi.enabled = true; 123 network_debug_.ncsi.loopback = false; 124 } 125 126 StateMachine::StateMachine() 127 { 128 reset(); 129 network_debug_.ncsi.pending_restart = true; 130 std::memcpy(network_debug_.ncsi.test.ping.tx, echo_pattern, 131 sizeof(echo_pattern)); 132 } 133 134 StateMachine::~StateMachine() 135 { 136 CPRINT("[NCSI stopping]\n"); 137 } 138 139 size_t StateMachine::poll_l2_config() 140 { 141 size_t len = 0; 142 mac_addr_t mac; 143 net_config_->get_mac_addr(&mac); 144 ncsi_response_type_t response_type = ncsi_fsm_poll_l2_config( 145 &ncsi_state_, &network_debug_, &ncsi_buf_, &mac); 146 147 auto* response = reinterpret_cast<ncsi_simple_response_t*>(ncsi_buf_.data); 148 149 if (response_type == NCSI_RESPONSE_ACK) 150 { 151 /* If the response is MAC response, some extra handling needed. */ 152 if ((NCSI_RESPONSE | NCSI_OEM_COMMAND) == 153 response->hdr.control_packet_type) 154 { 155 auto* oem_response = 156 reinterpret_cast<ncsi_oem_simple_response_t*>(ncsi_buf_.data); 157 if (oem_response->oem_header.oem_cmd == 158 NCSI_OEM_COMMAND_GET_HOST_MAC) 159 { 160 net_config_->set_mac_addr(mac); 161 } 162 } 163 } 164 else if (NCSI_RESPONSE_NONE == response_type) 165 { 166 /* Buffer is ready to be sent. */ 167 len = ncsi_buf_.len; 168 ncsi_buf_.len = 0; 169 } 170 else 171 { 172 report_ncsi_error(response_type); 173 } 174 175 return len; 176 } 177 178 size_t StateMachine::poll_simple(ncsi_simple_poll_f poll_func) 179 { 180 mac_addr_t mac; 181 net_config_->get_mac_addr(&mac); 182 const uint16_t rx_port = DEFAULT_ADDRESSES_RX_PORT; 183 184 ncsi_response_type_t response_type = 185 poll_func(&ncsi_state_, &network_debug_, &ncsi_buf_, &mac, 0, rx_port); 186 187 auto* response = reinterpret_cast<ncsi_simple_response_t*>(ncsi_buf_.data); 188 189 size_t len = 0; 190 if (response_type == NCSI_RESPONSE_NONE) 191 { 192 /* Buffer is ready to be sent, or we are done. */ 193 len = ncsi_buf_.len; 194 ncsi_buf_.len = 0; 195 } 196 else if (response->hdr.control_packet_type == 197 (NCSI_RESPONSE | NCSI_GET_LINK_STATUS)) 198 { 199 auto status_response = 200 reinterpret_cast<ncsi_link_status_response_t*>(response); 201 bool new_link_up = ntohl(status_response->link_status.link_status) & 202 NCSI_LINK_STATUS_UP; 203 if (!link_up_ || new_link_up != *link_up_) 204 { 205 CPRINT("[NCSI link {}]\n", new_link_up ? "up" : "down"); 206 link_up_ = new_link_up; 207 } 208 } 209 else if (response->hdr.control_packet_type == 210 (NCSI_RESPONSE | NCSI_OEM_COMMAND)) 211 { 212 auto* oem_response = 213 reinterpret_cast<ncsi_oem_simple_response_t*>(ncsi_buf_.data); 214 if (oem_response->oem_header.oem_cmd == NCSI_OEM_COMMAND_GET_FILTER) 215 { 216 bool new_hostless = ncsi_fsm_is_nic_hostless(&ncsi_state_); 217 if (!hostless_ || new_hostless != *hostless_) 218 { 219 CPRINT("[NCSI nic {}]\n", 220 new_hostless ? "hostless" : "hostfull"); 221 net_config_->set_nic_hostless(new_hostless); 222 hostless_ = new_hostless; 223 } 224 } 225 } 226 else if (response_type != NCSI_RESPONSE_ACK) 227 { 228 report_ncsi_error(response_type); 229 } 230 231 return len; 232 } 233 234 void StateMachine::report_ncsi_error(ncsi_response_type_t response_type) 235 { 236 char state_string[kStateFormatLen]; 237 snprintf_state(state_string, sizeof(state_string), &ncsi_state_); 238 auto* response = 239 reinterpret_cast<const ncsi_simple_response_t*>(ncsi_buf_.data); 240 switch (response_type) 241 { 242 case NCSI_RESPONSE_UNDERSIZED: 243 if (!ncsi_buf_.len) 244 { 245 network_debug_.ncsi.rx_error.timeout_count++; 246 CPRINT("[NCSI timeout in state {}]\n", state_string); 247 } 248 else 249 { 250 network_debug_.ncsi.rx_error.undersized_count++; 251 CPRINT("[NCSI undersized response in state {}]\n", 252 state_string); 253 } 254 break; 255 case NCSI_RESPONSE_NACK: 256 network_debug_.ncsi.rx_error.nack_count++; 257 CPRINT( 258 "[NCSI nack in state {}. Response: {:#04x} Reason: {:#04x}]\n", 259 state_string, ntohs(response->response_code), 260 ntohs(response->reason_code)); 261 break; 262 case NCSI_RESPONSE_UNEXPECTED_TYPE: 263 network_debug_.ncsi.rx_error.unexpected_type_count++; 264 CPRINT( 265 "[NCSI unexpected response in state {}. Response type: {:#02x}]\n", 266 state_string, response->hdr.control_packet_type); 267 break; 268 case NCSI_RESPONSE_UNEXPECTED_SIZE: 269 { 270 int expected_size; 271 if ((NCSI_RESPONSE | NCSI_OEM_COMMAND) == 272 response->hdr.control_packet_type) 273 { 274 auto* oem_response = 275 reinterpret_cast<ncsi_oem_simple_response_t*>( 276 ncsi_buf_.data); 277 expected_size = ncsi_oem_get_response_size( 278 oem_response->oem_header.oem_cmd); 279 } 280 else 281 { 282 expected_size = ncsi_get_response_size( 283 response->hdr.control_packet_type & (~NCSI_RESPONSE)); 284 } 285 network_debug_.ncsi.rx_error.unexpected_size_count++; 286 CPRINT("[NCSI unexpected response size in state {}." 287 " Expected {}]\n", 288 state_string, expected_size); 289 } 290 break; 291 case NCSI_RESPONSE_OEM_FORMAT_ERROR: 292 network_debug_.ncsi.rx_error.unexpected_type_count++; 293 CPRINT("[NCSI OEM format error]\n"); 294 break; 295 case NCSI_RESPONSE_UNEXPECTED_PARAMS: 296 CPRINT("[NCSI OEM Filter MAC or TCP/IP Config Mismatch]\n"); 297 break; 298 default: 299 /* NCSI_RESPONSE_ACK and NCSI_RESPONSE_NONE are not errors and need 300 * not be handled here, so this branch is just to complete the 301 * switch. 302 */ 303 CPRINT("[NCSI OK]\n"); 304 break; 305 } 306 } 307 308 int StateMachine::receive_ncsi() 309 { 310 int len; 311 do 312 { 313 // Return value of zero means timeout 314 len = sock_io_->recv(ncsi_buf_.data, sizeof(ncsi_buf_.data)); 315 if (len > 0) 316 { 317 auto* hdr = reinterpret_cast<struct ether_header*>(ncsi_buf_.data); 318 if (ETHER_NCSI == ntohs(hdr->ether_type)) 319 { 320 ncsi_buf_.len = len; 321 break; 322 } 323 } 324 325 ncsi_buf_.len = 0; 326 } while (len > 0); 327 328 return ncsi_buf_.len; 329 } 330 331 void StateMachine::run_test_fsm(size_t* tx_len) 332 { 333 // Sleep and restart when test FSM finishes. 334 if (is_test_done()) 335 { 336 std::this_thread::sleep_for(std::chrono::seconds(retest_delay_s_)); 337 // Skip over busy wait in state machine - already waited. 338 ncsi_state_.retest_delay_count = NCSI_FSM_RESTART_DELAY_COUNT; 339 } 340 // until NCSI_STATE_TEST_END 341 *tx_len = poll_simple(ncsi_fsm_poll_test); 342 } 343 344 void StateMachine::run(int max_rounds) 345 { 346 if (!net_config_ || !sock_io_) 347 { 348 CPRINT("StateMachine configuration incomplete: " 349 "net_config_: <{}>, sock_io_: <{}>", 350 reinterpret_cast<void*>(net_config_), 351 reinterpret_cast<void*>(sock_io_)); 352 return; 353 } 354 355 bool infinite_loop = (max_rounds <= 0); 356 while (infinite_loop || --max_rounds >= 0) 357 { 358 receive_ncsi(); 359 360 size_t tx_len = 0; 361 switch (ncsi_fsm_connection_state(&ncsi_state_, &network_debug_)) 362 { 363 case NCSI_CONNECTION_DOWN: 364 case NCSI_CONNECTION_LOOPBACK: 365 tx_len = poll_l2_config(); 366 break; 367 case NCSI_CONNECTION_UP: 368 if (!is_test_done() || ncsi_fsm_is_nic_hostless(&ncsi_state_)) 369 { 370 run_test_fsm(&tx_len); 371 } 372 else 373 { 374 // - Only start L3/L4 config when test is finished 375 // (it will last until success 376 // (i.e. NCSI_CONNECTION_UP_AND_CONFIGURED) or fail. 377 tx_len = poll_simple(ncsi_fsm_poll_l3l4_config); 378 } 379 break; 380 case NCSI_CONNECTION_UP_AND_CONFIGURED: 381 run_test_fsm(&tx_len); 382 break; 383 case NCSI_CONNECTION_DISABLED: 384 if (network_debug_.ncsi.pending_restart) 385 { 386 network_debug_.ncsi.enabled = true; 387 } 388 break; 389 default: 390 fail(); 391 } 392 393 if (tx_len) 394 { 395 print_state(ncsi_state_); 396 397 sock_io_->write(ncsi_buf_.data, tx_len); 398 } 399 } 400 } 401 402 void StateMachine::clear_state() 403 { 404 // This implicitly resets: 405 // l2_config_state to NCSI_STATE_L2_CONFIG_BEGIN 406 // l3l4_config_state to NCSI_STATE_L3L4_CONFIG_BEGIN 407 // test_state to NCSI_STATE_TEST_BEGIN 408 std::memset(&ncsi_state_, 0, sizeof(ncsi_state_)); 409 } 410 411 void StateMachine::fail() 412 { 413 network_debug_.ncsi.fail_count++; 414 clear_state(); 415 } 416 417 void StateMachine::set_sockio(net::SockIO* sock_io) 418 { 419 sock_io_ = sock_io; 420 } 421 422 void StateMachine::set_net_config(net::ConfigBase* net_config) 423 { 424 net_config_ = net_config; 425 } 426 427 void StateMachine::set_retest_delay(unsigned delay) 428 { 429 retest_delay_s_ = delay; 430 } 431 432 } // namespace ncsi 433