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