blob: 01b154b456e0450886afb7eadccdb3f0ccf2e5dd [file] [log] [blame]
Brandon Kimdab96f12021-02-18 11:21:37 -08001// 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
William A. Kennington III7d6fa422021-02-08 17:04:02 -080015#include "nic_mock.h"
16
17#include "platforms/nemora/portable/ncsi.h"
18
19#include <algorithm>
20#include <cstddef>
21#include <stdexcept>
22
23namespace mock
24{
25
26bool NCSIFrame::parse_ethernet_frame(const ncsi_buf_t& ncsi_buf)
27{
28 std::memcpy(&dst_mac_, ncsi_buf.data, sizeof(dst_mac_));
29 std::memcpy(&src_mac_, ncsi_buf.data + sizeof(dst_mac_), sizeof(src_mac_));
30 // The constant defined in a way that assumes big-endian platform, so we are
31 // just going to calculate it here properly.
32 const uint8_t et_hi = *(ncsi_buf.data + 2 * sizeof(mac_addr_t));
33 const uint8_t et_lo = *(ncsi_buf.data + 2 * sizeof(mac_addr_t) + 1);
34 ethertype_ = (et_hi << 8) + et_lo;
35
36 if (ethertype_ != NCSI_ETHERTYPE)
37 {
38 return false;
39 }
40
41 // This code parses the NC-SI command, according to spec and
42 // as defined in platforms/nemora/portable/ncsi.h
43 // It takes some shortcuts to only retrieve the data we are interested in,
44 // such as using offsetof ot get to a particular field.
45 control_packet_type_ =
46 *(ncsi_buf.data + offsetof(ncsi_header_t, control_packet_type));
47 channel_id_ = *(ncsi_buf.data + offsetof(ncsi_header_t, channel_id));
48
49 size_t payload_offset = sizeof(ncsi_header_t);
50 if (control_packet_type_ & NCSI_RESPONSE)
51 {
52 is_response_ = true;
53 control_packet_type_ &= ~NCSI_RESPONSE;
54 std::memcpy(&response_code_, ncsi_buf.data + payload_offset,
55 sizeof(response_code_));
56 response_code_ = ntohs(response_code_);
57 std::memcpy(&reason_code_,
58 ncsi_buf.data + payload_offset + sizeof(reason_code_),
59 sizeof(reason_code_));
60 reason_code_ = ntohs(reason_code_);
61 payload_offset += sizeof(reason_code_) + sizeof(response_code_);
62 }
63
64 if (control_packet_type_ == NCSI_OEM_COMMAND)
65 {
66 std::memcpy(&manufacturer_id_, ncsi_buf.data + payload_offset,
67 sizeof(manufacturer_id_));
68 manufacturer_id_ = ntohl(manufacturer_id_);
69 // Number of reserved bytes after manufacturer_id_ = 3
70 oem_command_ =
71 *(ncsi_buf.data + payload_offset + sizeof(manufacturer_id_) + 3);
72 payload_offset += sizeof(ncsi_oem_extension_header_t);
73 }
74
75 packet_raw_ =
76 std::vector<uint8_t>(ncsi_buf.data, ncsi_buf.data + ncsi_buf.len);
77 // TODO: Verify payload length.
78
79 return true;
80}
81
82uint32_t NIC::handle_request(const ncsi_buf_t& request_buf,
83 ncsi_buf_t* response_buf)
84{
85 const ncsi_header_t* ncsi_header =
86 reinterpret_cast<const ncsi_header_t*>(request_buf.data);
87
88 NCSIFrame request_frame;
89 request_frame.parse_ethernet_frame(request_buf);
90 save_frame_to_log(request_frame);
91
92 uint32_t response_size;
93 if (is_loopback_)
94 {
95 std::memcpy(response_buf, &request_buf, sizeof(request_buf));
96 response_size = request_buf.len;
97 }
98 else if (std::find(simple_commands_.begin(), simple_commands_.end(),
99 ncsi_header->control_packet_type) !=
100 simple_commands_.end())
101 {
102 // Simple Response
103 response_size =
104 ncsi_build_simple_ack(request_buf.data, response_buf->data);
105 }
106 else
107 {
108 // Not-so-Simple Response
109 switch (ncsi_header->control_packet_type)
110 {
111 case NCSI_GET_VERSION_ID:
112 response_size = ncsi_build_version_id_ack(
113 request_buf.data, response_buf->data, &version_);
114 break;
115 case NCSI_GET_CAPABILITIES:
116 response_size = sizeof(ncsi_capabilities_response_t);
117 {
118 ncsi_capabilities_response_t response;
119 ncsi_build_response_header(
120 request_buf.data, reinterpret_cast<uint8_t*>(&response),
121 0, 0, response_size - sizeof(ncsi_header_t));
122 response.channel_count = channel_count_;
123 std::memcpy(response_buf->data, &response,
124 sizeof(response));
125 }
126 break;
127 case NCSI_GET_PASSTHROUGH_STATISTICS:
128 if (is_legacy_)
129 {
130 response_size = ncsi_build_pt_stats_legacy_ack(
131 request_buf.data, response_buf->data, &stats_legacy_);
132 }
133 else
134 {
135 response_size = ncsi_build_pt_stats_ack(
136 request_buf.data, response_buf->data, &stats_);
137 }
138 break;
139 case NCSI_GET_LINK_STATUS:
140 response_size = ncsi_build_link_status_ack(
141 request_buf.data, response_buf->data, &link_status_);
142 break;
143 case NCSI_OEM_COMMAND:
144 response_size = handle_oem_request(request_buf, response_buf);
145 break;
146 default:
147 response_size = ncsi_build_simple_nack(
148 request_buf.data, response_buf->data, 1, 1);
149 break;
150 }
151 }
152
153 response_buf->len = response_size;
154
155 return response_size;
156}
157
158uint32_t NIC::handle_oem_request(const ncsi_buf_t& request_buf,
159 ncsi_buf_t* response_buf)
160{
161 const ncsi_oem_simple_cmd_t* oem_cmd =
162 reinterpret_cast<const ncsi_oem_simple_cmd_t*>(request_buf.data);
163 uint32_t response_size;
164 switch (oem_cmd->oem_header.oem_cmd)
165 {
166 case NCSI_OEM_COMMAND_GET_HOST_MAC:
167 response_size = ncsi_build_oem_get_mac_ack(
168 request_buf.data, response_buf->data, &mac_);
169 break;
170 case NCSI_OEM_COMMAND_SET_FILTER:
171 {
172 const ncsi_oem_set_filter_cmd_t* cmd =
173 reinterpret_cast<const ncsi_oem_set_filter_cmd_t*>(
174 request_buf.data);
175 if (set_filter(cmd->hdr.channel_id, cmd->filter))
176 {
177 response_size = ncsi_build_oem_simple_ack(request_buf.data,
178 response_buf->data);
179 }
180 else
181 {
182 response_size = ncsi_build_simple_nack(
183 request_buf.data, response_buf->data, 3, 4);
184 }
185 }
186 break;
187 case NCSI_OEM_COMMAND_ECHO:
188 response_size =
189 ncsi_build_oem_echo_ack(request_buf.data, response_buf->data);
190 break;
191 case NCSI_OEM_COMMAND_GET_FILTER:
192 {
193 const ncsi_simple_command_t* cmd =
194 reinterpret_cast<const ncsi_simple_command_t*>(
195 request_buf.data);
196 if (cmd->hdr.channel_id == 0)
197 {
198 response_size = ncsi_build_oem_get_filter_ack(
199 request_buf.data, response_buf->data, &ch0_filter_);
200 }
201 else if (cmd->hdr.channel_id == 1)
202 {
203 response_size = ncsi_build_oem_get_filter_ack(
204 request_buf.data, response_buf->data, &ch1_filter_);
205 }
206 else
207 {
208 response_size = ncsi_build_simple_nack(
209 request_buf.data, response_buf->data, 3, 4);
210 }
211 }
212 break;
213 default:
214 response_size = ncsi_build_simple_nack(request_buf.data,
215 response_buf->data, 1, 2);
216 break;
217 }
218
219 return response_size;
220}
221
222bool NIC::is_filter_configured(uint8_t channel) const
223{
224 if (channel == 0)
225 {
226 return is_ch0_filter_configured_;
227 }
228 else if (channel == 1)
229 {
230 return is_ch1_filter_configured_;
231 }
232
233 throw std::invalid_argument("Unsupported channel");
234}
235
236bool NIC::set_filter(uint8_t channel, const ncsi_oem_filter_t& filter)
237{
238 ncsi_oem_filter_t* nic_filter;
239 if (channel == 0)
240 {
241 nic_filter = &ch0_filter_;
242 is_ch0_filter_configured_ = true;
243 }
244 else if (channel == 1)
245 {
246 nic_filter = &ch1_filter_;
247 is_ch1_filter_configured_ = true;
248 }
249 else
250 {
251 throw std::invalid_argument("Unsupported channel");
252 }
253
254 std::memcpy(nic_filter->mac, filter.mac, MAC_ADDR_SIZE);
255 nic_filter->ip = 0;
256 nic_filter->port = filter.port;
257 return true;
258}
259
260const ncsi_oem_filter_t& NIC::get_filter(uint8_t channel) const
261{
262 if (channel == 0)
263 {
264 return ch0_filter_;
265 }
266 else if (channel == 1)
267 {
268 return ch1_filter_;
269 }
270
271 throw std::invalid_argument("Unsupported channel");
272}
273
274void NIC::set_hostless(bool is_hostless)
275{
276 auto set_flag_op = [](uint8_t lhs, uint8_t rhs) -> auto
277 {
278 return lhs | rhs;
279 };
280
281 auto clear_flag_op = [](uint8_t lhs, uint8_t rhs) -> auto
282 {
283 return lhs & ~rhs;
284 };
285
286 auto flag_op = is_hostless ? set_flag_op : clear_flag_op;
287
288 if (channel_count_ > 0)
289 {
290 ch0_filter_.flags =
291 flag_op(ch0_filter_.flags, NCSI_OEM_FILTER_FLAGS_HOSTLESS);
292 }
293
294 if (channel_count_ > 1)
295 {
296 ch1_filter_.flags =
297 flag_op(ch1_filter_.flags, NCSI_OEM_FILTER_FLAGS_HOSTLESS);
298 }
299}
300
301void NIC::toggle_hostless()
302{
303 if (channel_count_ > 0)
304 {
305 ch0_filter_.flags ^= NCSI_OEM_FILTER_FLAGS_HOSTLESS;
306 }
307
308 if (channel_count_ > 1)
309 {
310 ch1_filter_.flags ^= NCSI_OEM_FILTER_FLAGS_HOSTLESS;
311 }
312}
313
314bool NIC::is_hostless()
315{
316 return ch0_filter_.flags & NCSI_OEM_FILTER_FLAGS_HOSTLESS;
317}
318
319void NIC::save_frame_to_log(const NCSIFrame& frame)
320{
321 if (cmd_log_.size() >= max_log_size_)
322 {
323 cmd_log_.erase(cmd_log_.begin());
324 }
325
326 cmd_log_.push_back(frame);
327}
328
329const std::vector<uint8_t> NIC::simple_commands_ = {
330 NCSI_CLEAR_INITIAL_STATE,
331 NCSI_SELECT_PACKAGE,
332 NCSI_DESELECT_PACKAGE,
333 NCSI_ENABLE_CHANNEL,
334 NCSI_DISABLE_CHANNEL,
335 NCSI_RESET_CHANNEL,
336 NCSI_ENABLE_CHANNEL_NETWORK_TX,
337 NCSI_DISABLE_CHANNEL_NETWORK_TX,
338 NCSI_AEN_ENABLE,
339 NCSI_SET_LINK,
340 NCSI_SET_VLAN_FILTER,
341 NCSI_ENABLE_VLAN,
342 NCSI_DISABLE_VLAN,
343 NCSI_SET_MAC_ADDRESS,
344 NCSI_ENABLE_BROADCAST_FILTER,
345 NCSI_DISABLE_BROADCAST_FILTER,
346 NCSI_ENABLE_GLOBAL_MULTICAST_FILTER,
347 NCSI_DISABLE_GLOBAL_MULTICAST_FILTER,
348 NCSI_SET_NCSI_FLOW_CONTROL,
349};
350
351} // namespace mock