ncsid: Import from gBMC

This is the initial code drop from gBMC.

Google-Bug-Id: 179618516
Upstream: 1e71af914bc8c54d8b91d0a1cf377e2696713c2f
Change-Id: Ic653e8271dacd205e04f2bc713071ef2ec5936a4
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/ncsid/src/platforms/nemora/portable/default_addresses.h b/ncsid/src/platforms/nemora/portable/default_addresses.h
new file mode 100644
index 0000000..469264b
--- /dev/null
+++ b/ncsid/src/platforms/nemora/portable/default_addresses.h
@@ -0,0 +1,7 @@
+#ifndef PLATFORMS_NEMORA_PORTABLE_DEFAULT_ADDRESSES_H_
+#define PLATFORMS_NEMORA_PORTABLE_DEFAULT_ADDRESSES_H_
+//
+// Nemora dedicated port. Filtered by NIC. See //depot/eng/ports.
+#define DEFAULT_ADDRESSES_RX_PORT 3959
+
+#endif  // PLATFORMS_NEMORA_PORTABLE_DEFAULT_ADDRESSES_H_
diff --git a/ncsid/src/platforms/nemora/portable/ncsi.h b/ncsid/src/platforms/nemora/portable/ncsi.h
new file mode 100644
index 0000000..18c535d
--- /dev/null
+++ b/ncsid/src/platforms/nemora/portable/ncsi.h
@@ -0,0 +1,420 @@
+#ifndef PLATFORMS_NEMORA_PORTABLE_NCSI_H_
+#define PLATFORMS_NEMORA_PORTABLE_NCSI_H_
+
+/*
+ * Module for interacting with NC-SI capable network cards.
+ *
+ * DMTF v1.0.0 NC-SI specification:
+ * http://www.dmtf.org/sites/default/files/standards/documents/DSP0222_1.0.0.pdf
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "platforms/nemora/portable/net_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef __packed
+#define __packed __attribute__((packed))
+#endif
+
+// Define states for our NC-SI connection to the NIC.
+// There is no mapping to the NC-SI specification for these states, but they
+// reflect the outcome of NC-SI commands used in our configuration state
+// machine.
+//
+// 'DOWN' - while in this state, periodically restart the configuration state
+//   machine until it succeeds.
+// 'LOOPBACK' - the response to the first NC-SI command of the configuration
+//   state machine was identical to the command: from this we infer we are in
+//   loopback. While in this state, periodically restart the configuration state
+//   machine.
+// 'UP' - all commands were responded successfully, but need DHCP configuration
+//   to go to the next state. While in this state, the connection is tested
+//   periodically for failures, which can bring back to 'DOWN'.
+// 'UP_AND_CONFIGURED' - NC-SI OEM commands for L3/L4 configuration (which
+//   depend on DHCP configuration) were responded successfully. While in this
+//   state, the connection and configuration are tested periodically for
+//   failures, which can bring back to 'DOWN'.
+// 'DISABLED' - reset default state. As soon as network is enabled (which
+//   noticeably means that ProdID must be disabled), the state goes to DOWN.
+// TODO: connection state has nothing to do with ncsi protocol and needs
+// to be moved to ncsi_fsm.h. The main problem with the move is that
+// ncsi_client.h defines an extern function with this return type, that is used
+// in a lot of places that have no business including ncsi_fsm.h
+typedef enum {
+  NCSI_CONNECTION_DOWN,
+  NCSI_CONNECTION_LOOPBACK,
+  NCSI_CONNECTION_UP,
+  NCSI_CONNECTION_UP_AND_CONFIGURED,
+  NCSI_CONNECTION_DISABLED,
+} ncsi_connection_state_t;
+
+typedef enum {
+  NCSI_RESPONSE_NONE,
+  NCSI_RESPONSE_ACK,
+  NCSI_RESPONSE_NACK,
+  NCSI_RESPONSE_UNDERSIZED,
+  NCSI_RESPONSE_UNEXPECTED_TYPE,
+  NCSI_RESPONSE_UNEXPECTED_SIZE,
+  NCSI_RESPONSE_OEM_FORMAT_ERROR,
+  NCSI_RESPONSE_TIMEOUT,
+  NCSI_RESPONSE_UNEXPECTED_PARAMS,
+} ncsi_response_type_t;
+
+// For NC-SI Rev 1.0.0, the management controller ID (mc_id) is 0.
+#define NCSI_MC_ID 0
+// For NC-SI Rev 1.0.0, the header revision is 0x01.
+#define NCSI_HEADER_REV 1
+#define NCSI_ETHERTYPE 0x88F8
+#define NCSI_RESPONSE 0x80
+
+// Command IDs
+enum {
+  NCSI_CLEAR_INITIAL_STATE,
+  NCSI_SELECT_PACKAGE,
+  NCSI_DESELECT_PACKAGE,
+  NCSI_ENABLE_CHANNEL,
+  NCSI_DISABLE_CHANNEL,
+  NCSI_RESET_CHANNEL,
+  NCSI_ENABLE_CHANNEL_NETWORK_TX,
+  NCSI_DISABLE_CHANNEL_NETWORK_TX,
+  NCSI_AEN_ENABLE,
+  NCSI_SET_LINK,
+  NCSI_GET_LINK_STATUS,
+  NCSI_SET_VLAN_FILTER,
+  NCSI_ENABLE_VLAN,
+  NCSI_DISABLE_VLAN,
+  NCSI_SET_MAC_ADDRESS,
+  // 0x0F is not a valid command
+  NCSI_ENABLE_BROADCAST_FILTER = 0x10,
+  NCSI_DISABLE_BROADCAST_FILTER,
+  NCSI_ENABLE_GLOBAL_MULTICAST_FILTER,
+  NCSI_DISABLE_GLOBAL_MULTICAST_FILTER,
+  NCSI_SET_NCSI_FLOW_CONTROL,
+  NCSI_GET_VERSION_ID,
+  NCSI_GET_CAPABILITIES,
+  NCSI_GET_PARAMETERS,
+  NCSI_GET_CONTROLLER_PACKET_STATISTICS,
+  NCSI_GET_NCSI_STATISTICS,
+  NCSI_GET_PASSTHROUGH_STATISTICS,
+  // 0x1B-0x4F are not valid commands
+  NCSI_OEM_COMMAND = 0x50,
+};
+// OEM Command IDs (subtypes of NCSI_OEM_COMMAND)
+#define NCSI_OEM_COMMAND_GET_HOST_MAC 0x00
+#define NCSI_OEM_COMMAND_SET_FILTER 0x01
+#define NCSI_OEM_COMMAND_GET_FILTER 0x02
+#define NCSI_OEM_COMMAND_ECHO 0x03
+
+#define NCSI_OEM_MANUFACTURER_ID 11129  // IANA Enterprise Number for Google.
+#define NCSI_OEM_ECHO_PATTERN_SIZE 64
+
+/*
+ * NCSI command frame with packet header as described in section 8.2.1.
+ * Prepended with an ethernet header.
+ */
+typedef struct __packed {
+  eth_hdr_t ethhdr;
+  uint8_t mc_id;
+  uint8_t header_revision;
+  uint8_t reserved_00;
+  uint8_t instance_id;          // Destinguish retried commands from new ones.
+  uint8_t control_packet_type;  // See section 8.3, and Table 17.
+  uint8_t channel_id;
+  uint16_t payload_length;  // In Bytes. Excludes header, checksum, padding.
+  uint16_t reserved_01[4];
+} ncsi_header_t;
+
+/*
+ * Simple NCSI response packet.
+ */
+typedef struct __packed {
+  ncsi_header_t hdr;
+  uint16_t response_code;
+  uint16_t reason_code;
+} ncsi_simple_response_t;
+
+/*
+ * Simple NCSI command packet.
+ */
+typedef struct {
+  ncsi_header_t hdr;
+} ncsi_simple_command_t;
+
+/*
+ * Get Link Status Response. 8.4.24
+ */
+typedef struct __packed {
+  uint32_t link_status;
+  uint32_t other_indications;
+  uint32_t oem_link_status;
+} ncsi_link_status_t;
+
+typedef struct __packed {
+  ncsi_header_t hdr;
+  uint16_t response_code;
+  uint16_t reason_code;
+  ncsi_link_status_t link_status;
+} ncsi_link_status_response_t;
+
+#define NCSI_LINK_STATUS_UP (1 << 0)
+
+/*
+ * Set MAC Address packet. 8.4.31
+ */
+typedef struct __packed {
+  ncsi_header_t hdr;
+  mac_addr_t mac_addr;
+  uint8_t mac_addr_num;
+  uint8_t misc;
+} ncsi_set_mac_command_t;
+
+/*
+ * Enable Broadcast Filter packet. 8.4.33
+ */
+typedef struct __packed {
+  ncsi_header_t hdr;
+  uint32_t filter_settings;
+} ncsi_enable_broadcast_filter_command_t;
+
+#define NCSI_BROADCAST_FILTER_MASK_ARP         (1 << 0)
+#define NCSI_BROADCAST_FILTER_MASK_DHCP_CLIENT (1 << 1)
+#define NCSI_BROADCAST_FILTER_MASK_DHCP_SERVER (1 << 2)
+#define NCSI_BROADCAST_FILTER_MASK_NETBIOS     (1 << 3)
+
+/*
+ * Get Version ID Response. 8.4.44
+ */
+typedef struct __packed {
+  struct {
+    uint8_t major;
+    uint8_t minor;
+    uint8_t update;
+    uint8_t alpha1;
+    uint8_t reserved[3];
+    uint8_t alpha2;
+  } ncsi_version;
+  uint8_t firmware_name_string[12];
+  uint32_t firmware_version;
+  uint16_t pci_did;
+  uint16_t pci_vid;
+  uint16_t pci_ssid;
+  uint16_t pci_svid;
+  uint32_t manufacturer_id;
+} ncsi_version_id_t;
+
+typedef struct __packed {
+  ncsi_header_t hdr;
+  uint16_t response_code;
+  uint16_t reason_code;
+  ncsi_version_id_t version;
+} ncsi_version_id_response_t;
+
+/*
+ * Get Capabilities Response 8.4.46
+ */
+typedef struct __packed {
+  ncsi_header_t hdr;
+  uint16_t response_code;
+  uint16_t reason_code;
+  uint32_t capabilities_flags;
+  uint32_t broadcast_packet_filter_capabilties;
+  uint32_t multicast_packet_filter_capabilties;
+  uint32_t buffering_capability;
+  uint32_t aen_control_support;
+  uint8_t vlan_filter_count;
+  uint8_t mixed_filter_count;
+  uint8_t multicast_filter_count;
+  uint8_t unicast_filter_count;
+  uint16_t reserved;
+  uint8_t vlan_mode_support;
+  uint8_t channel_count;
+} ncsi_capabilities_response_t;
+
+/*
+ * Get Parameters Response 8.4.48
+ */
+typedef struct __packed {
+  ncsi_header_t hdr;
+  uint16_t response_code;
+  uint16_t reason_code;
+  // TODO: Note: Mellanox 1.4 FW has mac count swapped with mac flags.
+  uint8_t mac_address_count;
+  uint16_t reserved_01;
+  uint8_t mac_address_flags;
+  uint8_t vlan_tag_count;
+  uint8_t reserved_02;
+  uint16_t vlan_tag_flags;
+  uint32_t link_settings;
+  uint32_t broadcast_settings;
+  uint32_t configuration_flags;
+  uint8_t vlan_mode;
+  uint8_t flow_control_enable;
+  uint16_t reserved_03;
+  uint32_t aen_control;
+  mac_addr_t mac_address[2];
+  // TODO: Variable number of mac address filters (max 8. See 8.4.48)
+  uint16_t vlan_tags[2];
+  // TODO: Variable of vlan filters (up to 15 based on 8.4.48)
+} ncsi_parameters_response_t;
+
+/*
+ * Get Passthrough statistics response. 8.4.54
+ *
+ * The legacy data structure matches MLX implementation up to vX
+ * (Google vX), however the standard requires the first field to be
+ * 64bits and MLX fixed it in vX (Google vX).
+ *
+ */
+typedef struct __packed {
+  uint32_t tx_packets_received;  // EC -> NIC
+  uint32_t tx_packets_dropped;
+  uint32_t tx_channel_errors;
+  uint32_t tx_undersized_errors;
+  uint32_t tx_oversized_errors;
+  uint32_t rx_packets_received;  // Network -> NIC
+  uint32_t rx_packets_dropped;
+  uint32_t rx_channel_errors;
+  uint32_t rx_undersized_errors;
+  uint32_t rx_oversized_errors;
+} ncsi_passthrough_stats_legacy_t;
+
+typedef struct __packed {
+  uint32_t tx_packets_received_hi;  // EC -> NIC (higher 32bit)
+  uint32_t tx_packets_received_lo;  // EC -> NIC (lower 32bit)
+  uint32_t tx_packets_dropped;
+  uint32_t tx_channel_errors;
+  uint32_t tx_undersized_errors;
+  uint32_t tx_oversized_errors;
+  uint32_t rx_packets_received;  // Network -> NIC
+  uint32_t rx_packets_dropped;
+  uint32_t rx_channel_errors;
+  uint32_t rx_undersized_errors;
+  uint32_t rx_oversized_errors;
+} ncsi_passthrough_stats_t;
+
+typedef struct __packed {
+  ncsi_header_t hdr;
+  uint16_t response_code;
+  uint16_t reason_code;
+  ncsi_passthrough_stats_legacy_t stats;
+} ncsi_passthrough_stats_legacy_response_t;
+
+typedef struct __packed {
+  ncsi_header_t hdr;
+  uint16_t response_code;
+  uint16_t reason_code;
+  ncsi_passthrough_stats_t stats;
+} ncsi_passthrough_stats_response_t;
+
+/*
+ * OEM extension header for custom commands.
+ */
+typedef struct __packed {
+  uint32_t manufacturer_id;
+  uint8_t reserved[3];
+  uint8_t oem_cmd;
+} ncsi_oem_extension_header_t;
+
+/*
+ * Response format for simple OEM command.
+ */
+typedef struct __packed {
+  ncsi_header_t hdr;
+  uint16_t response_code;
+  uint16_t reason_code;
+  ncsi_oem_extension_header_t oem_header;
+} ncsi_oem_simple_response_t;
+
+/*
+ * Response format for OEM get MAC command.
+ */
+typedef struct __packed {
+  ncsi_header_t hdr;
+  uint16_t response_code;
+  uint16_t reason_code;
+  ncsi_oem_extension_header_t oem_header;
+  uint16_t reserved0;
+  uint8_t mac[6];
+} ncsi_host_mac_response_t;
+
+/*
+ * Format for OEM filter.
+ */
+typedef struct __packed {
+  uint16_t reserved0;
+  uint8_t mac[6];
+  // If ip is set to zero, the filter will match any IP address, including any
+  // IPv6 address.
+  uint32_t ip;  // Network order
+  uint16_t port;  // Network order
+  uint8_t reserved1;
+  uint8_t flags;
+  uint8_t regid[8];
+} ncsi_oem_filter_t;
+
+// Set flags
+#define NCSI_OEM_FILTER_FLAGS_ENABLE      (0x01)
+
+// Get flags
+#define NCSI_OEM_FILTER_FLAGS_ENABLED     (0x01)
+#define NCSI_OEM_FILTER_FLAGS_REGISTERED  (0x02)
+#define NCSI_OEM_FILTER_FLAGS_HOSTLESS    (0x04)
+
+/*
+ * Command format for simple OEM command.
+ */
+typedef struct __packed {
+  ncsi_header_t hdr;
+  ncsi_oem_extension_header_t oem_header;
+} ncsi_oem_simple_cmd_t;
+
+/*
+ * Response format for OEM get filter command.
+ */
+typedef struct __packed {
+  ncsi_header_t hdr;
+  uint16_t response_code;
+  uint16_t reason_code;
+  ncsi_oem_extension_header_t oem_header;
+  ncsi_oem_filter_t filter;
+} ncsi_oem_get_filter_response_t;
+
+/*
+ * Command format for OEM set filter command.
+ */
+typedef struct __packed {
+  ncsi_header_t hdr;
+  ncsi_oem_extension_header_t oem_header;
+  ncsi_oem_filter_t filter;
+} ncsi_oem_set_filter_cmd_t;
+
+/*
+ * Command format for OEM echo command.
+ */
+typedef struct __packed {
+  ncsi_header_t hdr;
+  ncsi_oem_extension_header_t oem_header;
+  uint8_t pattern[NCSI_OEM_ECHO_PATTERN_SIZE];
+} ncsi_oem_echo_cmd_t;
+
+/*
+ * Response format for OEM echo command.
+ */
+typedef struct __packed {
+  ncsi_header_t hdr;
+  uint16_t response_code;
+  uint16_t reason_code;
+  ncsi_oem_extension_header_t oem_header;
+  uint8_t pattern[NCSI_OEM_ECHO_PATTERN_SIZE];
+} ncsi_oem_echo_response_t;
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif  // PLATFORMS_NEMORA_PORTABLE_NCSI_H_
diff --git a/ncsid/src/platforms/nemora/portable/ncsi_client.c b/ncsid/src/platforms/nemora/portable/ncsi_client.c
new file mode 100644
index 0000000..38ab9e3
--- /dev/null
+++ b/ncsid/src/platforms/nemora/portable/ncsi_client.c
@@ -0,0 +1,307 @@
+/*
+ * Library of NC-SI commands compliant with version 1.0.0.
+ *
+ * This implements a subset of the commands provided in the specification.
+ *
+ * Checksums are optional and not implemented here. All NC-SI checksums are set
+ * to 0 to indicate that per 8.2.2.3.
+ */
+
+#include <stdint.h>
+#include <string.h>
+
+#include <arpa/inet.h>
+
+#include "platforms/nemora/portable/ncsi_client.h"
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+#endif
+
+// todo - To save space these tables use the notion that no response is
+//            larger than 255 bytes. Need a BUILD_ASSERT.
+// todo - Replace 0's with actual sizes once the commands are supported
+static const uint8_t ncsi_response_size_table[] = {
+  sizeof(ncsi_simple_response_t),        // NCSI_CLEAR_INITIAL_STATE
+  sizeof(ncsi_simple_response_t),        // NCSI_SELECT_PACKAGE
+  sizeof(ncsi_simple_response_t),        // NCSI_DESELECT_PACKAGE
+  sizeof(ncsi_simple_response_t),        // NCSI_ENABLE_CHANNEL
+  sizeof(ncsi_simple_response_t),        // NCSI_DISABLE_CHANNEL
+  sizeof(ncsi_simple_response_t),        // NCSI_RESET_CHANNEL
+  sizeof(ncsi_simple_response_t),        // NCSI_ENABLE_CHANNEL_NETWORK_TX
+  sizeof(ncsi_simple_response_t),        // NCSI_DISABLE_CHANNEL_NETWORK_TX
+  sizeof(ncsi_simple_response_t),        // NCSI_AEN_ENABLE
+  sizeof(ncsi_simple_response_t),        // NCSI_SET_LINK
+  sizeof(ncsi_link_status_response_t),   // NCSI_GET_LINK_STATUS
+  sizeof(ncsi_simple_response_t),        // NCSI_SET_VLAN_FILTER
+  sizeof(ncsi_simple_response_t),        // NCSI_ENABLE_VLAN
+  sizeof(ncsi_simple_response_t),        // NCSI_DISABLE_VLAN
+  sizeof(ncsi_simple_response_t),        // NCSI_SET_MAC_ADDRESS
+  0,                                     // 0x0F is not a valid command
+  sizeof(ncsi_simple_response_t),        // NCSI_ENABLE_BROADCAST_FILTER
+  sizeof(ncsi_simple_response_t),        // NCSI_DISABLE_BROADCAST_FILTER
+  sizeof(ncsi_simple_response_t),        // NCSI_ENABLE_GLOBAL_MULTICAST_FILTER
+  sizeof(ncsi_simple_response_t),        // NCSI_DISABLE_GLOBAL_MULTICAST_FILTER
+  sizeof(ncsi_simple_response_t),        // NCSI_SET_NCSI_FLOW_CONTROL
+  sizeof(ncsi_version_id_response_t),    // NCSI_GET_VERSION_ID
+  sizeof(ncsi_capabilities_response_t),  // NCSI_GET_CAPABILITIES
+  sizeof(ncsi_parameters_response_t),    // NCSI_GET_PARAMETERS
+  0,  // NCSI_GET_CONTROLLER_PACKET_STATISTICS
+  0,  // NCSI_GET_NCSI_STATISTICS
+  sizeof(ncsi_passthrough_stats_response_t),  // NCSI_GET_PASSTHROUGH_STATISTICS
+};
+
+static const uint8_t ncsi_oem_response_size_table[] = {
+  sizeof(ncsi_host_mac_response_t),        // NCSI_OEM_COMMAND_GET_HOST_MAC
+  sizeof(ncsi_oem_simple_response_t),      // NCSI_OEM_COMMAND_SET_FILTER
+  sizeof(ncsi_oem_get_filter_response_t),  // NCSI_OEM_COMMAND_GET_FILTER
+  sizeof(ncsi_oem_echo_response_t),        // NCSI_OEM_COMMAND_ECHO
+};
+
+// TODO Should increment when we send a packet that is not a retry.
+static uint8_t current_instance_id;
+
+/*
+ * Sets _most_ of the NC-SI header fields. Caller is expected to set
+ * payload_length field if it is > 0. For many NC-SI commands it is 0.
+ */
+static void set_header_fields(ncsi_header_t* header, uint8_t ch_id,
+                              uint8_t cmd_type)
+{
+  // Destination MAC must be all 0xFF.
+  memset(header->ethhdr.dest.octet, 0xFF, sizeof(header->ethhdr.dest.octet));
+  // Source MAC can be any value.
+  memset(header->ethhdr.src.octet, 0xAB, sizeof(header->ethhdr.src.octet));
+  header->ethhdr.ethertype = htons(NCSI_ETHERTYPE);
+
+  // NC-SI Header
+  header->mc_id = NCSI_MC_ID;
+  header->header_revision = NCSI_HEADER_REV;
+  header->reserved_00 = 0;
+  header->instance_id = current_instance_id;
+  header->control_packet_type = cmd_type;
+  header->channel_id = ch_id;
+  header->payload_length = 0;  // Caller is expected to set this if != 0.
+  memset(header->reserved_01, 0, sizeof(header->reserved_01));
+}
+
+uint32_t ncsi_get_response_size(uint8_t cmd_type)
+{
+  return (cmd_type < ARRAY_SIZE(ncsi_response_size_table))
+             ? ncsi_response_size_table[cmd_type]
+             : 0;
+}
+
+uint32_t ncsi_oem_get_response_size(uint8_t oem_cmd_type)
+{
+  return (oem_cmd_type < ARRAY_SIZE(ncsi_oem_response_size_table))
+             ? ncsi_oem_response_size_table[oem_cmd_type]
+             : 0;
+}
+
+/*
+ * Clear initial state.
+ */
+uint32_t ncsi_cmd_clear_initial_state(uint8_t* buf, uint8_t channel)
+{
+  set_header_fields((ncsi_header_t*)buf, channel, NCSI_CLEAR_INITIAL_STATE);
+  return sizeof(ncsi_simple_command_t);
+}
+
+/*
+ * Set MAC address filters.
+ */
+uint32_t ncsi_cmd_set_mac(uint8_t* buf, uint8_t channel_id, mac_addr_t* mac)
+{
+  ncsi_set_mac_command_t* cmd = (ncsi_set_mac_command_t*)buf;
+
+  set_header_fields((ncsi_header_t*)buf, channel_id, NCSI_SET_MAC_ADDRESS);
+  cmd->hdr.payload_length =
+      htons(sizeof(ncsi_set_mac_command_t) - sizeof(ncsi_header_t));
+  memcpy(cmd->mac_addr.octet, mac->octet, sizeof(cmd->mac_addr.octet));
+  cmd->mac_addr_num = 1;
+  // Unicast MAC address (AT=0), enabled (E=1).
+  cmd->misc = 0x01;
+  return sizeof(ncsi_set_mac_command_t);
+}
+
+uint32_t ncsi_cmd_enable_broadcast_filter(uint8_t* buf, uint8_t channel,
+                                          uint32_t filter_settings)
+{
+  ncsi_enable_broadcast_filter_command_t* cmd =
+      (ncsi_enable_broadcast_filter_command_t*)buf;
+  set_header_fields((ncsi_header_t*)buf, channel,
+                    NCSI_ENABLE_BROADCAST_FILTER);
+  cmd->hdr.payload_length = htons(
+      sizeof(ncsi_enable_broadcast_filter_command_t) - sizeof(ncsi_header_t));
+  cmd->filter_settings = htonl(filter_settings);
+  return sizeof(ncsi_enable_broadcast_filter_command_t);
+}
+
+
+uint32_t ncsi_cmd_disable_broadcast_filter(uint8_t* buf, uint8_t channel)
+{
+  set_header_fields((ncsi_header_t*)buf, channel,
+                    NCSI_DISABLE_BROADCAST_FILTER);
+  return sizeof(ncsi_simple_command_t);
+}
+
+uint32_t ncsi_cmd_enable_channel(uint8_t* buf, uint8_t channel)
+{
+  set_header_fields((ncsi_header_t*)buf, channel, NCSI_ENABLE_CHANNEL);
+  return sizeof(ncsi_simple_command_t);
+}
+
+uint32_t ncsi_cmd_get_link_status(uint8_t* buf, uint8_t channel)
+{
+  set_header_fields((ncsi_header_t*)buf, channel, NCSI_GET_LINK_STATUS);
+  return sizeof(ncsi_simple_command_t);
+}
+
+uint32_t ncsi_cmd_reset_channel(uint8_t* buf, uint8_t channel)
+{
+  set_header_fields((ncsi_header_t*)buf, channel, NCSI_RESET_CHANNEL);
+  return sizeof(ncsi_simple_command_t);
+}
+
+uint32_t ncsi_cmd_enable_tx(uint8_t* buf, uint8_t channel)
+{
+  set_header_fields((ncsi_header_t*)buf, channel,
+                    NCSI_ENABLE_CHANNEL_NETWORK_TX);
+  return sizeof(ncsi_simple_command_t);
+}
+
+uint32_t ncsi_cmd_get_version(uint8_t* buf, uint8_t channel)
+{
+  set_header_fields((ncsi_header_t*)buf, channel, NCSI_GET_VERSION_ID);
+  return sizeof(ncsi_simple_command_t);
+}
+
+uint32_t ncsi_cmd_get_capabilities(uint8_t* buf, uint8_t channel)
+{
+  set_header_fields((ncsi_header_t*)buf, channel, NCSI_GET_CAPABILITIES);
+  return sizeof(ncsi_simple_command_t);
+}
+
+uint32_t ncsi_cmd_get_parameters(uint8_t* buf, uint8_t channel)
+{
+  set_header_fields((ncsi_header_t*)buf, channel, NCSI_GET_PARAMETERS);
+  return sizeof(ncsi_simple_command_t);
+}
+
+uint32_t ncsi_cmd_get_passthrough_stats(uint8_t* buf, uint8_t channel)
+{
+  set_header_fields((ncsi_header_t*)buf, channel,
+                    NCSI_GET_PASSTHROUGH_STATISTICS);
+  return sizeof(ncsi_simple_command_t);
+}
+
+/* OEM commands */
+
+uint32_t ncsi_oem_cmd_get_host_mac(uint8_t* buf, uint8_t channel)
+{
+  ncsi_oem_simple_cmd_t* cmd = (ncsi_oem_simple_cmd_t*)buf;
+  set_header_fields((ncsi_header_t*)buf, channel, NCSI_OEM_COMMAND);
+  cmd->hdr.payload_length =
+      htons(sizeof(ncsi_oem_simple_cmd_t) - sizeof(ncsi_header_t));
+  cmd->oem_header.manufacturer_id = htonl(NCSI_OEM_MANUFACTURER_ID);
+  memset(cmd->oem_header.reserved, 0, sizeof(cmd->oem_header.reserved));
+  cmd->oem_header.oem_cmd = NCSI_OEM_COMMAND_GET_HOST_MAC;
+  return sizeof(ncsi_oem_simple_cmd_t);
+}
+
+uint32_t ncsi_oem_cmd_get_filter(uint8_t* buf, uint8_t channel)
+{
+  ncsi_oem_simple_cmd_t* cmd = (ncsi_oem_simple_cmd_t*)buf;
+  set_header_fields((ncsi_header_t*)buf, channel, NCSI_OEM_COMMAND);
+  cmd->hdr.payload_length =
+      htons(sizeof(ncsi_oem_simple_cmd_t) - sizeof(ncsi_header_t));
+  cmd->oem_header.manufacturer_id = htonl(NCSI_OEM_MANUFACTURER_ID);
+  memset(cmd->oem_header.reserved, 0, sizeof(cmd->oem_header.reserved));
+  cmd->oem_header.oem_cmd = NCSI_OEM_COMMAND_GET_FILTER;
+  return sizeof(ncsi_oem_simple_cmd_t);
+}
+
+uint32_t ncsi_oem_cmd_set_filter(uint8_t* buf, uint8_t channel, mac_addr_t* mac,
+                                 uint32_t ip, uint16_t port, uint8_t flags)
+{
+  ncsi_oem_set_filter_cmd_t* cmd = (ncsi_oem_set_filter_cmd_t*)buf;
+  set_header_fields((ncsi_header_t*)buf, channel, NCSI_OEM_COMMAND);
+  cmd->hdr.payload_length =
+      htons(sizeof(ncsi_oem_set_filter_cmd_t) - sizeof(ncsi_header_t));
+  cmd->oem_header.manufacturer_id = htonl(NCSI_OEM_MANUFACTURER_ID);
+  memset(cmd->oem_header.reserved, 0, sizeof(cmd->oem_header.reserved));
+  cmd->oem_header.oem_cmd = NCSI_OEM_COMMAND_SET_FILTER;
+
+  cmd->filter.reserved0 = 0;
+  memcpy(cmd->filter.mac, mac->octet, sizeof(cmd->filter.mac));
+  cmd->filter.ip = htonl(ip);
+  cmd->filter.port = htons(port);
+  cmd->filter.reserved1 = 0;
+  cmd->filter.flags = flags;
+  memset(cmd->filter.regid, 0, sizeof(cmd->filter.regid));  // reserved for set
+  return sizeof(ncsi_oem_set_filter_cmd_t);
+}
+
+uint32_t ncsi_oem_cmd_echo(uint8_t* buf, uint8_t channel, uint8_t pattern[64])
+{
+  ncsi_oem_echo_cmd_t* cmd = (ncsi_oem_echo_cmd_t*)buf;
+  set_header_fields((ncsi_header_t*)buf, channel, NCSI_OEM_COMMAND);
+  cmd->hdr.payload_length =
+      htons(sizeof(ncsi_oem_echo_cmd_t) - sizeof(ncsi_header_t));
+  cmd->oem_header.manufacturer_id = htonl(NCSI_OEM_MANUFACTURER_ID);
+  memset(cmd->oem_header.reserved, 0, sizeof(cmd->oem_header.reserved));
+  cmd->oem_header.oem_cmd = NCSI_OEM_COMMAND_ECHO;
+  memcpy(cmd->pattern, pattern, sizeof(cmd->pattern));
+  return sizeof(ncsi_oem_echo_cmd_t);
+}
+
+ncsi_response_type_t ncsi_validate_response(uint8_t* buf, uint32_t len,
+                                            uint8_t cmd_type, bool is_oem,
+                                            uint32_t expected_size) {
+  if (len < sizeof(ncsi_simple_response_t)) {
+    return NCSI_RESPONSE_UNDERSIZED;
+  }
+
+  const ncsi_simple_response_t* response = (ncsi_simple_response_t*)buf;
+  if (response->response_code || response->reason_code) {
+    return NCSI_RESPONSE_NACK;
+  }
+
+  const uint8_t std_cmd_type = is_oem ? NCSI_OEM_COMMAND : cmd_type;
+  if (response->hdr.control_packet_type != (std_cmd_type | NCSI_RESPONSE)) {
+    return NCSI_RESPONSE_UNEXPECTED_TYPE;
+  }
+
+  if (len < expected_size ||
+      ntohs(response->hdr.payload_length) !=
+          expected_size - sizeof(ncsi_header_t)) {
+    return NCSI_RESPONSE_UNEXPECTED_SIZE;
+  }
+
+  if (is_oem) {
+    /* Since the expected_size was checked above, we know that if this is an oem
+     * command, it is the right size. */
+    const ncsi_oem_simple_response_t* oem_response =
+        (ncsi_oem_simple_response_t*)buf;
+    if (oem_response->oem_header.manufacturer_id !=
+            htonl(NCSI_OEM_MANUFACTURER_ID) ||
+        oem_response->oem_header.oem_cmd != cmd_type) {
+      return NCSI_RESPONSE_OEM_FORMAT_ERROR;
+    }
+  }
+
+  return NCSI_RESPONSE_ACK;
+}
+
+ncsi_response_type_t ncsi_validate_std_response(uint8_t* buf, uint32_t len,
+                                                uint8_t cmd_type) {
+  const uint32_t expected_size = ncsi_get_response_size(cmd_type);
+  return ncsi_validate_response(buf, len, cmd_type, false, expected_size);
+}
+
+ncsi_response_type_t ncsi_validate_oem_response(uint8_t* buf, uint32_t len,
+                                                uint8_t cmd_type) {
+  const uint32_t expected_size = ncsi_oem_get_response_size(cmd_type);
+  return ncsi_validate_response(buf, len, cmd_type, true, expected_size);
+}
diff --git a/ncsid/src/platforms/nemora/portable/ncsi_client.h b/ncsid/src/platforms/nemora/portable/ncsi_client.h
new file mode 100644
index 0000000..0354b30
--- /dev/null
+++ b/ncsid/src/platforms/nemora/portable/ncsi_client.h
@@ -0,0 +1,263 @@
+#ifndef PLATFORMS_NEMORA_PORTABLE_NCSI_CLIENT_H_
+#define PLATFORMS_NEMORA_PORTABLE_NCSI_CLIENT_H_
+
+/*
+ * Client module for interacting with NC-SI capable network cards.
+ *
+ * DMTF v1.0.0 NC-SI specification:
+ * http://www.dmtf.org/sites/default/files/standards/documents/DSP0222_1.0.0.pdf
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "platforms/nemora/portable/ncsi.h"
+#include "platforms/nemora/portable/net_types.h"
+
+#define CHANNEL_0_ID 0
+#define CHANNEL_1_ID 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Return the state of our connection to the NIC. Does not map to NC-SI spec.
+ * TODO remove this function from here. It is neither defined nor used
+ * in ncsi_client.c.
+ */
+ncsi_connection_state_t ncsi_connection_state(void);
+
+/*
+ * Return the expected length for the response to a given NC-SI comamnds
+ *
+ * Args:
+ *  cmd_type: id for the given commands as defined in the NC-SI spec
+ *
+ * Caveat: returns 0 for commands that have not been implemented yet or for
+ *         NCSI_OEM_COMMAND.
+ */
+uint32_t ncsi_get_response_size(uint8_t cmd_type);
+
+/*
+ * Return the expected length for the response to a given OEM NC-SI comamnds
+ *
+ * Args:
+ *  oem_cmd_type: id for the given OEM command as defined in the
+ *                ncsi_oem_extension_header_t (and not to be confused with the
+ *                id of standard commands)
+ */
+uint32_t ncsi_oem_get_response_size(uint8_t oem_cmd_type);
+
+/*
+ * The following commands write the message to the buffer provided.
+ * The length of the message (including the ethernet header and padding) is
+ * returned.
+ */
+
+/* Standard NC-SI commands */
+
+/*
+ * Construct MAC address filtering command. 8.4.15
+ *
+ * Args:
+ *  buf: buffer of length >= sizeof(ncsi_set_mac_command_t) where command will
+ *       be placed.
+ *  channel: NC-SI channel to filter on (corresponds to a physical port).
+ *  mac_filter: MAC address to match against incoming traffic.
+ */
+uint32_t ncsi_cmd_set_mac(uint8_t* buf, uint8_t channel,
+                          mac_addr_t* mac_filter);
+
+/*
+ * Construct clear initial state command. 8.4.3
+ *
+ * Args:
+ *  buf: buffer of length >= sizeof(ncsi_simple_command_t) where command will be
+ *       placed.
+ *  channel: NC-SI channel targeted (corresponds to a physical port).
+ */
+uint32_t ncsi_cmd_clear_initial_state(uint8_t* buf, uint8_t channel);
+
+/*
+ * Construct enable broadcast filter command. 8.4.33
+ *
+ * Args:
+ *  filter_settings: filter mask (host order).
+ */
+uint32_t ncsi_cmd_enable_broadcast_filter(uint8_t* buf, uint8_t channel,
+                                          uint32_t filter_settings);
+/*
+ * Construct disable broadcast filter command. 8.4.35
+ *
+ * Note: disable filtering == allow forwarding of broadcast traffic
+ *
+ * Args:
+ *  buf: buffer of length >= sizeof(ncsi_simple_command_t)
+ *  channel: NC-SI channel targeted (corresponds to a physical port).
+ */
+uint32_t ncsi_cmd_disable_broadcast_filter(uint8_t* buf, uint8_t channel);
+
+/*
+ * Construct enable channel command. 8.4.9
+ *
+ * Required before any NC-SI passthrough traffic will go in or out of that
+ * channel.
+ *
+ * Args:
+ *  buf: buffer of length >= sizeof(ncsi_simple_command_t)
+ *  channel: NC-SI channel targeted (corresponds to a physical port).
+ */
+uint32_t ncsi_cmd_enable_channel(uint8_t* buf, uint8_t channel);
+
+/*
+ * Construct reset channel command. 8.4.13
+ *
+ * Put channel into its initial state
+ *
+ * Args:
+ *  buf: buffer of length >= sizeof(ncsi_simple_command_t)
+ *  channel: NC-SI channel targeted (corresponds to a physical port).
+ */
+uint32_t ncsi_cmd_reset_channel(uint8_t* buf, uint8_t channel);
+
+/*
+ * Construct enable TX command. 8.4.15
+ *
+ * Args:
+ *  buf: buffer of length >= sizeof(ncsi_simple_command_t)
+ *  channel: NC-SI channel targeted (corresponds to a physical port).
+ */
+uint32_t ncsi_cmd_enable_tx(uint8_t* buf, uint8_t channel);
+
+/*
+ * Construct get link status command. 8.4.23
+ *
+ * Args:
+ *  buf: buffer of length >= sizeof(ncsi_simple_command_t)
+ *  channel: NC-SI channel targeted (corresponds to a physical port).
+ */
+uint32_t ncsi_cmd_get_link_status(uint8_t* buf, uint8_t channel);
+
+/*
+ * Construct get capabilities command. 8.4.44
+ *
+ * Args:
+ *  buf: buffer of length >= sizeof(ncsi_simple_command_t)
+ *  channel: NC-SI channel targeted (corresponds to a physical port).
+ */
+uint32_t ncsi_cmd_get_version(uint8_t* buf, uint8_t channel);
+
+/*
+ * Construct get capabilities command. 8.4.45
+ *
+ * Args:
+ *  buf: buffer of length >= sizeof(ncsi_simple_command_t)
+ *  channel: NC-SI channel targeted (corresponds to a physical port).
+ */
+uint32_t ncsi_cmd_get_capabilities(uint8_t* buf, uint8_t channel);
+
+/*
+ * Construct get parameters command. 8.4.47
+ *
+ * Args:
+ *  buf: buffer of length >= sizeof(ncsi_simple_command_t)
+ *  channel: NC-SI channel targeted (corresponds to a physical port).
+ */
+uint32_t ncsi_cmd_get_parameters(uint8_t* buf, uint8_t channel);
+
+/*
+ * Construct get pass-through statistics. 8.4.53
+ *
+ * Args:
+ *  buf: buffer of length >= sizeof(ncsi_simple_command_t)
+ *  channel: NC-SI channel targeted (corresponds to a physical port).
+ */
+uint32_t ncsi_cmd_get_passthrough_stats(uint8_t* buf, uint8_t channel);
+
+/* OEM commands */
+// TODO: Move OEM commands to another file.
+
+/*
+ * Get Host MAC address. Query the NIC for its MAC address(es).
+ *
+ * Args:
+ *  buf: buffer of length >= sizeof(ncsi_oem_simple_cmd_t)
+ *  channel: NC-SI channel targeted (corresponds to a physical port).
+ */
+uint32_t ncsi_oem_cmd_get_host_mac(uint8_t* buf, uint8_t channel);
+
+/*
+ * Get filter used for RX traffic.
+ */
+uint32_t ncsi_oem_cmd_get_filter(uint8_t* buf, uint8_t channel);
+
+/*
+ * Set filter for RX traffic. Incoming packets that match all the fields
+ * specified here will be forwarded over the NC-SI link.
+ *
+ * Args:
+ *  buf: buffer of length >= sizeof(ncsi_oem_set_filter_cmd_t)
+ *  channel: NC-SI channel targeted (corresponds to a physical port).
+ *  mac: mac address to filter on (byte array in network order)
+ *  ip: IPv4 address to filter on (little-endian)
+ *  port: TCP/UDP port number to filter on (little-endian)
+ *  flags: bitfield of options.
+ */
+uint32_t ncsi_oem_cmd_set_filter(uint8_t* buf, uint8_t channel, mac_addr_t* mac,
+                                 uint32_t ip, uint16_t port, uint8_t flags);
+
+/*
+ * Send NC-SI packet to test connectivity with NIC.
+ *
+ * Args:
+ *  buf: buffer of length >= sizeof(ncsi_oem_echo_cmd_t)
+ *  channel: NC-SI channel targeted (corresponds to a physical port).
+ *  pattern: echo payload.
+ */
+uint32_t ncsi_oem_cmd_echo(uint8_t* buf, uint8_t channel,
+                           uint8_t pattern[NCSI_OEM_ECHO_PATTERN_SIZE]);
+
+/*
+ * Validate NC-SI response in the buffer and return validation result.
+ * Exposes "expected_size" as part of interface to handle legacy NICs. Avoid
+ * using this function directly, use ncsi_validate_std_response or
+ * ncsi_validate_oem_response instead.
+ *
+ * Args:
+ *  buf: buffer containint NC-SI response.
+ *  len: size of the response in the buffer.
+ *  cmd_type: Id of the command *that was sent* to NIC.
+ *  is_eom: true if the response in the buffer is OEM response.
+ *  expected_size: expected size of the response.
+ */
+ncsi_response_type_t ncsi_validate_response(uint8_t* buf, uint32_t len,
+                                            uint8_t cmd_type, bool is_oem,
+                                            uint32_t expected_size);
+/*
+ * Validate NC-SI response in the buffer and return validation result.
+ *
+ * Args:
+ *  buf: buffer containint NC-SI response.
+ *  len: size of the response in the buffer.
+ *  cmd_type: Id of the command *that was sent* to NIC.
+ */
+ncsi_response_type_t ncsi_validate_std_response(uint8_t* buf, uint32_t len,
+                                                uint8_t cmd_type);
+
+/*
+ * Validate NC-SI OEM response in the buffer and return validation result.
+ *
+ * Args:
+ *  buf: buffer containint NC-SI response.
+ *  len: size of the response in the buffer.
+ *  cmd_type: Id of the OEM command *that was sent* to NIC.
+ */
+ncsi_response_type_t ncsi_validate_oem_response(uint8_t* buf, uint32_t len,
+                                                uint8_t cmd_type);
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif  // PLATFORMS_NEMORA_PORTABLE_NCSI_CLIENT_H_
diff --git a/ncsid/src/platforms/nemora/portable/ncsi_fsm.c b/ncsid/src/platforms/nemora/portable/ncsi_fsm.c
new file mode 100644
index 0000000..0bef65d
--- /dev/null
+++ b/ncsid/src/platforms/nemora/portable/ncsi_fsm.c
@@ -0,0 +1,651 @@
+#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;
+}
diff --git a/ncsid/src/platforms/nemora/portable/ncsi_fsm.h b/ncsid/src/platforms/nemora/portable/ncsi_fsm.h
new file mode 100644
index 0000000..9ab2d82
--- /dev/null
+++ b/ncsid/src/platforms/nemora/portable/ncsi_fsm.h
@@ -0,0 +1,219 @@
+#ifndef PLATFORMS_NEMORA_PORTABLE_NCSI_FSM_H_
+#define PLATFORMS_NEMORA_PORTABLE_NCSI_FSM_H_
+
+/* Nemora NC-SI (Finite) State Machine implementation */
+
+#include <stdint.h>
+
+#include "platforms/nemora/portable/ncsi.h"
+#include "platforms/nemora/portable/net_types.h"
+
+/* TODO put this into config somewhere? */
+#define NCSI_FSM_RESTART_DELAY_COUNT 100
+#define NCSI_FSM_RETEST_DELAY_COUNT 100
+
+/* The network state is defined as a combination of the NC-SI connection state
+ * and the network configuration. However the two cannot be decoupled:
+ * - we cannot DHCP unless the NC-SI connection is up
+ * - we cannot do the OEM L3/L4 NC-SI configuration unless we have a valid
+ *   network configuration
+ *
+ * For additional complexity we cannot get DHCP/ARP responses after the host
+ * has loaded the Mellanox NIC driver but we want to be able to periodically
+ * test the NC-SI connection regardless of whether we have network configuration
+ * (so that flaky cables can be troubleshooted using the host interface).
+ *
+ * For this reason there are actually 3 NC-SI finite state machines:
+ * - L2 configuration (i.e. enabling all available NC-SI channel for passthrough
+ *   RX and TX, although only TX will work after the host loads the NIC driver)
+ * - L3/L4 configuration (i.e. configuring flow steering for RX traffic that
+ *   matches our IP address and dedicated Nemora port so that we can receive
+ *   Nemora requests even after the host loaded the NIC driver)
+ * - Connection test (i.e. periodically doing a ping test between the EC and the
+ *   NIC) and also ensuring that L3/L4 configuration parameters have not been
+ *   wiped out)
+ *
+ * For good karma, try to keep the state machines as linear as possible (one
+ * step after the other).
+ */
+
+typedef enum {
+  // First
+  NCSI_STATE_L2_CONFIG_BEGIN,
+  // Actual sequence
+  NCSI_STATE_RESTART = NCSI_STATE_L2_CONFIG_BEGIN,
+  NCSI_STATE_CLEAR_0,
+  NCSI_STATE_CLEAR_0_RESPONSE,
+  NCSI_STATE_GET_VERSION,
+  NCSI_STATE_GET_VERSION_RESPONSE,
+  NCSI_STATE_GET_CAPABILITIES,
+  NCSI_STATE_GET_CAPABILITIES_RESPONSE,
+  NCSI_STATE_CLEAR_1,
+  NCSI_STATE_CLEAR_1_RESPONSE,
+  NCSI_STATE_RESET_CHANNEL_0,
+  NCSI_STATE_RESET_CHANNEL_0_RESPONSE,
+  NCSI_STATE_RESET_CHANNEL_1,
+  NCSI_STATE_RESET_CHANNEL_1_RESPONSE,
+  NCSI_STATE_STOPPED,
+  NCSI_STATE_GET_MAC,
+  NCSI_STATE_GET_MAC_RESPONSE,
+  NCSI_STATE_SET_MAC_FILTER_0,
+  NCSI_STATE_SET_MAC_FILTER_0_RESPONSE,
+  NCSI_STATE_SET_MAC_FILTER_1,
+  NCSI_STATE_SET_MAC_FILTER_1_RESPONSE,
+  NCSI_STATE_ENABLE_CHANNEL_0,
+  NCSI_STATE_ENABLE_CHANNEL_0_RESPONSE,
+  NCSI_STATE_ENABLE_CHANNEL_1,
+  NCSI_STATE_ENABLE_CHANNEL_1_RESPONSE,
+  NCSI_STATE_ENABLE_TX,
+  NCSI_STATE_ENABLE_TX_RESPONSE,
+  // Last
+  NCSI_STATE_L2_CONFIG_END
+} ncsi_l2_config_state_t;
+
+typedef enum {
+  // First
+  NCSI_STATE_L3L4_CONFIG_BEGIN,
+  // Actual sequence
+  NCSI_STATE_CONFIG_FILTERS,
+  // Last
+  NCSI_STATE_L3L4_CONFIG_END
+} ncsi_l3l4_config_state_t;
+
+typedef enum {
+  // First
+  NCSI_STATE_TEST_BEGIN,
+  // Actual sequence
+  NCSI_STATE_TEST_PARAMS = NCSI_STATE_TEST_BEGIN,
+  NCSI_STATE_ECHO,
+  NCSI_STATE_ECHO_RESPONSE,
+  NCSI_STATE_CHECK_FILTERS,
+  NCSI_STATE_CHECK_FILTERS_RESPONSE,
+  NCSI_STATE_GET_PT_STATS,
+  NCSI_STATE_GET_PT_STATS_RESPONSE,
+  NCSI_STATE_GET_LINK_STATUS,
+  NCSI_STATE_GET_LINK_STATUS_RESPONSE,
+  // Last
+  NCSI_STATE_TEST_END
+} ncsi_test_state_t;
+
+typedef struct {
+  ncsi_l2_config_state_t l2_config_state;
+  ncsi_l3l4_config_state_t l3l4_config_state;
+  ncsi_test_state_t test_state;
+  // Last (OEM) command that was sent. (L3L4 SM only)
+  // Valid only if l3l4_waiting_response is true.
+  uint8_t l3l4_command;
+  // Number of the channel we are currently operating on. (L3L4 SM only)
+  uint8_t l3l4_channel;
+  // If true, means the request was sent and we are waiting for response.
+  bool l3l4_waiting_response;
+  uint8_t channel_count;
+  // The re-start and re-test delays ensures that we can flush the DMA
+  // buffers of potential out-of-sequence NC-SI packets (e.g. from
+  // packet that may have been received shortly after we timed out on
+  // them). The re-test delays also reduce the effect of NC-SI
+  // testing on more useful traffic.
+  uint8_t restart_delay_count;
+  uint8_t retest_delay_count;
+  struct {
+    uint8_t flags;
+    uint8_t regid[8];
+  } flowsteering[2];
+} ncsi_state_t;
+
+// Debug variables.
+// TODO - Change name to something more meaningful since the NC-SI test
+//   is not a debug-only feature.
+typedef struct {
+  uint32_t task_count;
+  uint32_t host_ctrl_flags;
+  struct {
+    bool enabled;
+    bool pending_stop;
+    bool pending_restart;
+    bool oem_filter_disable;
+    bool loopback;
+    bool mlx_legacy;
+    uint32_t fail_count;
+    ncsi_state_t state_that_failed;
+    uint32_t tx_count;
+    uint32_t rx_count;
+    uint32_t tx_error_count;
+    struct {
+      uint32_t timeout_count;
+      uint32_t oversized_count;
+      uint32_t undersized_count;
+      uint32_t nack_count;
+      uint32_t unexpected_size_count;
+      uint32_t unexpected_type_count;
+    } rx_error;
+    struct {
+      uint32_t runs;
+      uint8_t ch_under_test;
+      uint8_t tries;
+      uint8_t max_tries;  // 0 = skip test, 1 = restart on failure, > 1 = retry
+      struct {
+        uint8_t tx[NCSI_OEM_ECHO_PATTERN_SIZE];
+        uint32_t tx_count;
+        uint32_t rx_count;
+        uint32_t bad_rx_count;
+        uint8_t last_bad_rx[NCSI_OEM_ECHO_PATTERN_SIZE];
+      } ping;
+    } test;
+    ncsi_passthrough_stats_t pt_stats_be[2];  // big-endian as received from NIC
+  } ncsi;
+} network_debug_t;
+
+typedef struct {
+  uint8_t data[ETH_BUFFER_SIZE];
+  uint32_t len;  // Non-zero when there's a new NC-SI response.
+} ncsi_buf_t;
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+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_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);
+
+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);
+
+/*
+ * Report a global state of the NC-SI connection as a function of the state
+ * of the 3 finite state machines.
+ * Note: Additionally for the case where the connection is down it reports
+ *   whether a loopback is inferred.
+ */
+ncsi_connection_state_t ncsi_fsm_connection_state(
+    const ncsi_state_t* ncsi_state, const network_debug_t* network_debug);
+
+/*
+ * 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);
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif  // PLATFORMS_NEMORA_PORTABLE_NCSI_FSM_H_
diff --git a/ncsid/src/platforms/nemora/portable/ncsi_server.c b/ncsid/src/platforms/nemora/portable/ncsi_server.c
new file mode 100644
index 0000000..c7bc5e7
--- /dev/null
+++ b/ncsid/src/platforms/nemora/portable/ncsi_server.c
@@ -0,0 +1,160 @@
+/*
+ * Library of NC-SI commands compliant with version 1.0.0.
+ *
+ * This implements a subset of the commands provided in the specification.
+ *
+ * Checksums are optional and not implemented here. All NC-SI checksums are set
+ * to 0 to indicate that per 8.2.2.3.
+ */
+
+#include <stdint.h>
+#include <string.h>
+
+#include <netinet/in.h>
+
+#include "platforms/nemora/portable/ncsi.h"
+#include "platforms/nemora/portable/ncsi_server.h"
+
+
+void ncsi_build_response_header(const uint8_t* request_buf,
+                                uint8_t* response_buf, uint16_t response_code,
+                                uint16_t reason_code, uint16_t payload_length) {
+  /* Copy the header from the command */
+  memcpy(response_buf, request_buf, sizeof(ncsi_header_t));
+  ncsi_simple_response_t* response = (ncsi_simple_response_t*)response_buf;
+  response->response_code = response_code;
+  response->reason_code = reason_code;
+
+  const ncsi_header_t* request_header = (const ncsi_header_t*)request_buf;
+  response->hdr.control_packet_type =
+      request_header->control_packet_type | NCSI_RESPONSE;
+  response->hdr.payload_length = htons(payload_length);
+}
+
+uint32_t ncsi_build_simple_ack(const uint8_t* request_buf,
+                               uint8_t* response_buf) {
+  /* Copy the header from the command */
+  ncsi_build_response_header(
+      request_buf, response_buf, 0, 0,
+      sizeof(ncsi_simple_response_t) - sizeof(ncsi_header_t));
+
+  return sizeof(ncsi_simple_response_t);
+}
+
+uint32_t ncsi_build_simple_nack(const uint8_t* request_buf,
+                                uint8_t* response_buf, uint16_t response_code,
+                                uint16_t reason_code) {
+  ncsi_build_response_header(
+      request_buf, response_buf, response_code, reason_code,
+      sizeof(ncsi_simple_response_t) - sizeof(ncsi_header_t));
+
+  return sizeof(ncsi_simple_response_t);
+}
+
+static void ncsi_build_oem_ack(const uint8_t* request_buf,
+                               uint8_t* response_buf, uint32_t response_size) {
+  ncsi_build_response_header(
+      request_buf, response_buf, 0, 0,
+      response_size - sizeof(ncsi_header_t));
+  const ncsi_oem_simple_cmd_t* oem_command =
+      (const ncsi_oem_simple_cmd_t*)request_buf;
+  ncsi_oem_simple_response_t* oem_response =
+      (ncsi_oem_simple_response_t*)response_buf;
+  memmove(&oem_response->oem_header, &oem_command->oem_header,
+          sizeof(ncsi_oem_extension_header_t));
+  oem_response->oem_header.manufacturer_id = htonl(NCSI_OEM_MANUFACTURER_ID);
+}
+
+uint32_t ncsi_build_version_id_ack(const uint8_t* request_buf,
+                                   uint8_t* response_buf,
+                                   const ncsi_version_id_t* version_id) {
+  ncsi_build_response_header(
+      request_buf, response_buf, 0, 0,
+      sizeof(ncsi_version_id_response_t) - sizeof(ncsi_header_t));
+  ncsi_version_id_response_t* version_id_response =
+      (ncsi_version_id_response_t*)response_buf;
+  memcpy(&version_id_response->version, version_id, sizeof(ncsi_version_id_t));
+  return sizeof(ncsi_version_id_response_t);
+}
+
+uint32_t ncsi_build_oem_get_mac_ack(const uint8_t* request_buf,
+                                    uint8_t* response_buf,
+                                    const mac_addr_t* mac) {
+  ncsi_build_oem_ack(request_buf, response_buf,
+                     sizeof(ncsi_host_mac_response_t));
+  ncsi_host_mac_response_t* response = (ncsi_host_mac_response_t*)response_buf;
+  memcpy(response->mac, mac->octet, MAC_ADDR_SIZE);
+  return sizeof(ncsi_host_mac_response_t);
+}
+
+uint32_t ncsi_build_oem_simple_ack(const uint8_t* request_buf,
+                                   uint8_t* response_buf) {
+  ncsi_build_oem_ack(request_buf, response_buf,
+                     sizeof(ncsi_oem_simple_response_t));
+  return sizeof(ncsi_oem_simple_response_t);
+}
+
+uint32_t ncsi_build_oem_echo_ack(const uint8_t* request_buf,
+                                 uint8_t* response_buf) {
+  ncsi_oem_echo_response_t* echo_response =
+      (ncsi_oem_echo_response_t*)response_buf;
+  const ncsi_oem_echo_cmd_t* echo_cmd = (const ncsi_oem_echo_cmd_t*)request_buf;
+  memmove(echo_response->pattern, echo_cmd->pattern, sizeof(echo_cmd->pattern));
+  // Because we allow request and response to be the same buffer, it is
+  // important that pattern copy precedes the call to ncsi_build_oem_ack.
+  ncsi_build_oem_ack(request_buf, response_buf,
+                     sizeof(ncsi_oem_echo_response_t));
+
+  return sizeof(ncsi_oem_echo_response_t);
+}
+
+uint32_t ncsi_build_oem_get_filter_ack(const uint8_t* request_buf,
+                                       uint8_t* response_buf,
+                                       const ncsi_oem_filter_t* filter) {
+  ncsi_build_oem_ack(request_buf, response_buf,
+                     sizeof(ncsi_oem_get_filter_response_t));
+  ncsi_oem_get_filter_response_t* get_filter_response =
+      (ncsi_oem_get_filter_response_t*)response_buf;
+  memcpy(&get_filter_response->filter, filter,
+         sizeof(get_filter_response->filter));
+  return sizeof(ncsi_oem_get_filter_response_t);
+}
+
+uint32_t ncsi_build_pt_stats_ack(const uint8_t* request_buf,
+                                 uint8_t* response_buf,
+                                 const ncsi_passthrough_stats_t* stats) {
+  ncsi_build_response_header(
+      request_buf, response_buf, 0, 0,
+      sizeof(ncsi_passthrough_stats_response_t) - sizeof(ncsi_header_t));
+  ncsi_passthrough_stats_response_t* pt_stats_response =
+      (ncsi_passthrough_stats_response_t*)response_buf;
+  /* TODO: endianness? */
+  memcpy(&pt_stats_response->stats, stats, sizeof(pt_stats_response->stats));
+  return sizeof(ncsi_passthrough_stats_response_t);
+}
+
+uint32_t ncsi_build_pt_stats_legacy_ack(
+    const uint8_t* request_buf, uint8_t* response_buf,
+    const ncsi_passthrough_stats_legacy_t* stats) {
+  ncsi_build_response_header(
+      request_buf, response_buf, 0, 0,
+      sizeof(ncsi_passthrough_stats_legacy_response_t) - sizeof(ncsi_header_t));
+  ncsi_passthrough_stats_legacy_response_t* pt_stats_response =
+      (ncsi_passthrough_stats_legacy_response_t*)response_buf;
+  /* TODO: endianness? */
+  memcpy(&pt_stats_response->stats, stats, sizeof(pt_stats_response->stats));
+  return sizeof(ncsi_passthrough_stats_legacy_response_t);
+}
+
+uint32_t ncsi_build_link_status_ack(const uint8_t* request_buf,
+                                    uint8_t* response_buf,
+                                    const ncsi_link_status_t* link_status) {
+  ncsi_build_response_header(
+      request_buf, response_buf, 0, 0,
+      sizeof(ncsi_link_status_response_t) - sizeof(ncsi_header_t));
+  ncsi_link_status_response_t* link_status_response =
+      (ncsi_link_status_response_t*)response_buf;
+  memcpy(&link_status_response->link_status, link_status,
+         sizeof(link_status_response->link_status));
+  return sizeof(ncsi_link_status_response_t);
+}
diff --git a/ncsid/src/platforms/nemora/portable/ncsi_server.h b/ncsid/src/platforms/nemora/portable/ncsi_server.h
new file mode 100644
index 0000000..6a411aa
--- /dev/null
+++ b/ncsid/src/platforms/nemora/portable/ncsi_server.h
@@ -0,0 +1,189 @@
+#ifndef PLATFORMS_NEMORA_PORTABLE_NCSI_SERVER_H_
+#define PLATFORMS_NEMORA_PORTABLE_NCSI_SERVER_H_
+
+/*
+ * Module for constructing NC-SI response commands on the NIC
+ *
+ * DMTF v1.0.0 NC-SI specification:
+ * http://www.dmtf.org/sites/default/files/standards/documents/DSP0222_1.0.0.pdf
+ */
+
+#include <stdint.h>
+
+#include "platforms/nemora/portable/ncsi.h"
+#include "platforms/nemora/portable/net_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Build the header for the response to the command in the buffer.
+ *
+ * Args:
+ *  request_buf: buffer containing NC-SI request.
+ *  response_buf: buffer, where to put the response. Must be big enough to fit
+ *    the response.
+ *  response_code: Response Code. Must be zero for ACK.
+ *  reason_code: Reason Code. Must be zero for ACK.
+ *  payload_length: size of a payload.
+ */
+void ncsi_build_response_header(const uint8_t* request_buf,
+                                uint8_t* response_buf, uint16_t response_code,
+                                uint16_t reason_code, uint16_t payload_length);
+
+/*
+ * Construct simple ACK command in the buffer, given the buffer with the
+ * received command.
+ *
+ * Args:
+ *  request_buf: buffer containing NC-SI request.
+ *  response_buf: buffer, where to put the response. Must be big enough to fit
+ *    the response.
+ *
+ * Returns the size of the response in the buffer.
+ */
+uint32_t ncsi_build_simple_ack(const uint8_t* request_buf,
+                               uint8_t* response_buf);
+
+/*
+ * Construct simple NACK command in the buffer, given the buffer with the
+ * received command.
+ *
+ * Args:
+ *  request_buf: buffer containing NC-SI request.
+ *  response_buf: buffer, where to put the response. Must be big enough to fit
+ *    the response.
+ *  response_code: Response Code
+ *  reason_code: Reason Code
+ *
+ * Returns the size of the response in the buffer.
+ */
+uint32_t ncsi_build_simple_nack(const uint8_t* request_buf,
+                                uint8_t* response_buf, uint16_t response_code,
+                                uint16_t reason_code);
+
+/*
+ * Construct ACK command in the buffer, given the buffer with the
+ * received command, which in this case must be NCSI_GET_VERSION_ID command.
+ *
+ * Args:
+ *  request_buf: buffer containing NC-SI request.
+ *  response_buf: buffer, where to put the response. Must be big enough to fit
+ *    the response.
+ *  version_id: Version ID struct.
+ *
+ * Returns the size of the response in the buffer.
+ */
+uint32_t ncsi_build_version_id_ack(const uint8_t* request_buf,
+                                   uint8_t* response_buf,
+                                   const ncsi_version_id_t* version_id);
+
+/*
+ * Construct OEM ACK command in the buffer, given the buffer with the
+ * received OEM command, which in this case must be
+ * NCSI_OEM_COMMAND_GET_HOST_MAC.
+ *
+ * Args:
+ *  request_buf: buffer containing NC-SI request.
+ *  response_buf: buffer, where to put the response. Must be big enough to fit
+ *    the response.
+ *  mac: NIC's MAC address.
+ *
+ * Returns the size of the response in the buffer.
+ */
+uint32_t ncsi_build_oem_get_mac_ack(const uint8_t* request_buf,
+                                    uint8_t* response_buf,
+                                    const mac_addr_t* mac);
+
+/*
+ * Construct simple OEM ACK command in the buffer, given the buffer with the
+ * received command.
+ *
+ * Args:
+ *  request_buf: buffer containing NC-SI request.
+ *  response_buf: buffer, where to put the response. Must be big enough to fit
+ *    the response.
+ *
+ * Returns the size of the response in the buffer.
+ */
+uint32_t ncsi_build_oem_simple_ack(const uint8_t* request_buf,
+                                   uint8_t* response_buf);
+
+/*
+ * Construct OEM ACK command in the buffer, given the buffer with the
+ * received command, which in this case must be NCSI_OEM_COMMAND_ECHO.
+ *
+ * Args:
+ *  request_buf: buffer containing NC-SI request.
+ *  response_buf: buffer, where to put the response. Must be big enough to fit
+ *    the response.
+ *
+ * Returns the size of the response in the buffer.
+ */
+uint32_t ncsi_build_oem_echo_ack(const uint8_t* request_buf,
+                                 uint8_t* response_buf);
+
+/*
+ * Construct ACK response in the buffer, given the buffer with the
+ * received NCSI_OEM_COMMAND_GET_FILTER.
+ *
+ * Args:
+ *  request_buf: buffer containing NC-SI request.
+ *  response_buf: buffer, where to put the response. Must be big enough to fit
+ *    the response.
+ *  filter: active NIC traffic filter.
+ *
+ * Returns the size of the response in the buffer.
+ */
+uint32_t ncsi_build_oem_get_filter_ack(const uint8_t* request_buf,
+                                       uint8_t* response_buf,
+                                       const ncsi_oem_filter_t* filter);
+
+/*
+ * Construct ACK response in the buffer, given the buffer with the
+ * received NCSI_GET_PASSTHROUGH_STATISTICS command.
+ *
+ * Args:
+ *  request_buf: buffer containing NC-SI request.
+ *  response_buf: buffer, where to put the response. Must be big enough to fit
+ *    the response.
+ *  stats: ncsi_passthrough_stats_t struct with stats.
+ *  TODO): it is not clear what is the endianness of the data in stats
+ *  struct. There does not seem to be any conversion on EC side.
+ *
+ * Returns the size of the response in the buffer.
+ */
+uint32_t ncsi_build_pt_stats_ack(const uint8_t* request_buf,
+                                 uint8_t* response_buf,
+                                 const ncsi_passthrough_stats_t* stats);
+
+/*
+ * This function is similar to ncsi_build_pt_stats_ack, except that it simulates
+ * the bug in MLX X - X firmware. It should not be used outside of tests.
+ */
+uint32_t ncsi_build_pt_stats_legacy_ack(
+    const uint8_t* request_buf, uint8_t* response_buf,
+    const ncsi_passthrough_stats_legacy_t* stats);
+
+/*
+ * Construct ACK response in the buffer, given the buffer with the
+ * received NCSI_GET_LINK_STATUS command.
+ *
+ * Args:
+ *  request_buf: buffer containing NC-SI request.
+ *  response_buf: buffer, where to put the response. Must be big enough to fit
+ *    the response.
+ *  link_status: ncsi_link_status_t struct.
+ *
+ * Returns the size of the response in the buffer.
+ */
+uint32_t ncsi_build_link_status_ack(const uint8_t* request_buf,
+                                    uint8_t* response_buf,
+                                    const ncsi_link_status_t* link_status);
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif  // PLATFORMS_NEMORA_PORTABLE_NCSI_SERVER_H_
diff --git a/ncsid/src/platforms/nemora/portable/net_types.h b/ncsid/src/platforms/nemora/portable/net_types.h
new file mode 100644
index 0000000..872d94f
--- /dev/null
+++ b/ncsid/src/platforms/nemora/portable/net_types.h
@@ -0,0 +1,37 @@
+#ifndef PLATFORMS_NEMORA_PORTABLE_NET_TYPES_H_
+#define PLATFORMS_NEMORA_PORTABLE_NET_TYPES_H_
+
+#include <stdint.h>
+
+// Buffer big enough for largest frame we expect
+// to receive from EMAC (in bytes)
+//   1500 (max payload IEEE 802.3) +
+//   14 (header) +
+//   4 (crc, if not stripped by EMAC) +
+//   4 (optional VLAN tag, if not stripped by EMAC)
+#define ETH_BUFFER_SIZE 1522
+#define IPV4_ETHERTYPE 0x0800
+#define IPV6_ADDR_SIZE 16
+#define MAC_ADDR_SIZE 6
+
+#ifndef __packed
+#define __packed __attribute__((packed))
+#endif
+
+/* MAC address */
+typedef struct __packed {
+  uint8_t octet[MAC_ADDR_SIZE];  // network order
+} mac_addr_t;
+
+/*
+ * Ethernet header.
+ * Note: This assumes a packet without VLAN tags.
+ * TODO: configure HW to strip VLAN field.
+ */
+typedef struct __packed {
+  mac_addr_t dest;
+  mac_addr_t src;
+  uint16_t ethertype;
+} eth_hdr_t;
+
+#endif  // PLATFORMS_NEMORA_PORTABLE_NET_TYPES_H_