blob: 23d5e931c7b3b98db355de04078c428886d6e891 [file] [log] [blame]
Andrew Jeffery2c07f6f2018-08-10 16:24:32 +09301// SPDX-License-Identifier: Apache-2.0
2// Copyright (C) 2018 IBM Corp.
3
4#include "config.h"
5
6#include "hiomap.hpp"
7
8#include <endian.h>
9#include <host-ipmid/ipmid-api.h>
10#include <string.h>
11#include <systemd/sd-bus.h>
12
13#include <fstream>
Andrew Jeffery0a3358e2018-08-21 10:42:09 +093014#include <functional>
15#include <host-ipmid/ipmid-host-cmd-utils.hpp>
16#include <host-ipmid/ipmid-host-cmd.hpp>
17#include <iostream>
18#include <phosphor-logging/log.hpp>
Andrew Jeffery2c07f6f2018-08-10 16:24:32 +093019#include <sdbusplus/bus.hpp>
Andrew Jeffery0a3358e2018-08-21 10:42:09 +093020#include <sdbusplus/bus/match.hpp>
Andrew Jeffery2c07f6f2018-08-10 16:24:32 +093021#include <sdbusplus/exception.hpp>
22
23using namespace sdbusplus;
Andrew Jeffery0a3358e2018-08-21 10:42:09 +093024using namespace phosphor::host::command;
Andrew Jeffery2c07f6f2018-08-10 16:24:32 +093025
26static void register_openpower_hiomap_commands() __attribute__((constructor));
27
28namespace openpower
29{
30namespace flash
31{
Andrew Jeffery0a3358e2018-08-21 10:42:09 +093032constexpr auto BMC_EVENT_DAEMON_READY = 1 << 7;
33constexpr auto BMC_EVENT_FLASH_CTRL_LOST = 1 << 6;
34constexpr auto BMC_EVENT_WINDOW_RESET = 1 << 1;
35constexpr auto BMC_EVENT_PROTOCOL_RESET = 1 << 0;
36
37constexpr auto IPMI_CMD_HIOMAP_EVENT = 0x0f;
38
39constexpr auto HIOMAPD_SERVICE = "xyz.openbmc_project.Hiomapd";
40constexpr auto HIOMAPD_OBJECT = "/xyz/openbmc_project/Hiomapd";
41constexpr auto HIOMAPD_IFACE = "xyz.openbmc_project.Hiomapd.Protocol";
42constexpr auto HIOMAPD_IFACE_V2 = "xyz.openbmc_project.Hiomapd.Protocol.V2";
43
44constexpr auto DBUS_IFACE_PROPERTIES = "org.freedesktop.DBus.Properties";
45
46struct hiomap
47{
48 bus::bus *bus;
49
50 /* Signals */
51 bus::match::match *properties;
52 bus::match::match *window_reset;
53 bus::match::match *bmc_reboot;
54
55 /* Protocol state */
56 std::map<std::string, int> event_lookup;
57 uint8_t bmc_events;
Andrew Jeffery04d75132018-09-26 00:58:52 +093058 uint8_t seq;
Andrew Jeffery0a3358e2018-08-21 10:42:09 +093059};
Andrew Jeffery2c07f6f2018-08-10 16:24:32 +093060
61/* TODO: Replace get/put with packed structs and direct assignment */
62template <typename T> static inline T get(void *buf)
63{
64 T t;
65 memcpy(&t, buf, sizeof(t));
66 return t;
67}
68
69template <typename T> static inline void put(void *buf, T &&t)
70{
71 memcpy(buf, &t, sizeof(t));
72}
73
74typedef ipmi_ret_t (*hiomap_command)(ipmi_request_t req, ipmi_response_t resp,
75 ipmi_data_len_t data_len,
76 ipmi_context_t context);
77
78struct errno_cc_entry
79{
80 int err;
81 int cc;
82};
83
84static const errno_cc_entry errno_cc_map[] = {
85 {0, IPMI_CC_OK},
86 {EBUSY, IPMI_CC_BUSY},
87 {ENOTSUP, IPMI_CC_INVALID},
88 {ETIMEDOUT, 0xc3}, /* FIXME: Replace when defined in ipmid-api.h */
89 {ENOSPC, 0xc4}, /* FIXME: Replace when defined in ipmid-api.h */
90 {EINVAL, IPMI_CC_PARM_OUT_OF_RANGE},
91 {ENODEV, IPMI_CC_SENSOR_INVALID},
92 {EPERM, IPMI_CC_INSUFFICIENT_PRIVILEGE},
93 {EACCES, IPMI_CC_INSUFFICIENT_PRIVILEGE},
94 {-1, IPMI_CC_UNSPECIFIED_ERROR},
95};
96
97static int hiomap_xlate_errno(int err)
98{
99 const errno_cc_entry *entry = &errno_cc_map[0];
100
101 while (!(entry->err == err || entry->err == -1))
102 {
103 entry++;
104 }
105
106 return entry->cc;
107}
108
Andrew Jeffery0a3358e2018-08-21 10:42:09 +0930109static void ipmi_hiomap_event_response(IpmiCmdData cmd, bool status)
110{
111 using namespace phosphor::logging;
112
113 if (!status)
114 {
115 log<level::ERR>("Failed to deliver host command",
116 entry("SEL_COMMAND=%x:%x", cmd.first, cmd.second));
117 }
118}
119
120static int hiomap_handle_property_update(struct hiomap *ctx,
121 sdbusplus::message::message &msg)
122{
123 std::map<std::string, sdbusplus::message::variant<bool>> msgData;
124
125 std::string iface;
126 msg.read(iface, msgData);
127
128 for (auto const &x : msgData)
129 {
130 if (!ctx->event_lookup.count(x.first))
131 {
132 /* Unsupported event? */
133 continue;
134 }
135
136 uint8_t mask = ctx->event_lookup[x.first];
137 auto value = sdbusplus::message::variant_ns::get<bool>(x.second);
138
139 if (value)
140 {
141 ctx->bmc_events |= mask;
142 }
143 else
144 {
145 ctx->bmc_events &= ~mask;
146 }
147 }
148
149 auto cmd = std::make_pair(IPMI_CMD_HIOMAP_EVENT, ctx->bmc_events);
150
151 ipmid_send_cmd_to_host(std::make_tuple(cmd, ipmi_hiomap_event_response));
152
153 return 0;
154}
155
156static bus::match::match hiomap_match_properties(struct hiomap *ctx)
157{
158 auto properties =
159 bus::match::rules::propertiesChanged(HIOMAPD_OBJECT, HIOMAPD_IFACE_V2);
160
161 bus::match::match match(
162 *ctx->bus, properties,
163 std::bind(hiomap_handle_property_update, ctx, std::placeholders::_1));
164
165 return match;
166}
167
168static int hiomap_handle_signal_v2(struct hiomap *ctx, const char *name)
169{
170 ctx->bmc_events |= ctx->event_lookup[name];
171
172 auto cmd = std::make_pair(IPMI_CMD_HIOMAP_EVENT, ctx->bmc_events);
173
174 ipmid_send_cmd_to_host(std::make_tuple(cmd, ipmi_hiomap_event_response));
175
176 return 0;
177}
178
179static bus::match::match hiomap_match_signal_v2(struct hiomap *ctx,
180 const char *name)
181{
182 using namespace bus::match;
183
184 auto signals = rules::type::signal() + rules::path(HIOMAPD_OBJECT) +
185 rules::interface(HIOMAPD_IFACE_V2) + rules::member(name);
186
187 bus::match::match match(*ctx->bus, signals,
188 std::bind(hiomap_handle_signal_v2, ctx, name));
189
190 return match;
191}
192
193static ipmi_ret_t hiomap_reset(ipmi_request_t request, ipmi_response_t response,
194 ipmi_data_len_t data_len, ipmi_context_t context)
195{
196 struct hiomap *ctx = static_cast<struct hiomap *>(context);
197
198 auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT,
199 HIOMAPD_IFACE, "Reset");
200 try
201 {
202 ctx->bus->call(m);
203
204 *data_len = 0;
205 }
206 catch (const exception::SdBusError &e)
207 {
208 return hiomap_xlate_errno(e.get_errno());
209 }
210
211 return IPMI_CC_OK;
212}
213
Andrew Jeffery2c07f6f2018-08-10 16:24:32 +0930214static ipmi_ret_t hiomap_get_info(ipmi_request_t request,
215 ipmi_response_t response,
216 ipmi_data_len_t data_len,
217 ipmi_context_t context)
218{
Andrew Jeffery0a3358e2018-08-21 10:42:09 +0930219 struct hiomap *ctx = static_cast<struct hiomap *>(context);
220
Andrew Jeffery2c07f6f2018-08-10 16:24:32 +0930221 if (*data_len < 1)
222 {
223 return IPMI_CC_REQ_DATA_LEN_INVALID;
224 }
225
226 uint8_t *reqdata = (uint8_t *)request;
Andrew Jeffery0a3358e2018-08-21 10:42:09 +0930227 auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT,
228 HIOMAPD_IFACE, "GetInfo");
Andrew Jeffery2c07f6f2018-08-10 16:24:32 +0930229 m.append(reqdata[0]);
230
231 try
232 {
Andrew Jeffery0a3358e2018-08-21 10:42:09 +0930233 auto reply = ctx->bus->call(m);
Andrew Jeffery2c07f6f2018-08-10 16:24:32 +0930234
235 uint8_t version;
236 uint8_t blockSizeShift;
237 uint16_t timeout;
238 reply.read(version, blockSizeShift, timeout);
239
240 uint8_t *respdata = (uint8_t *)response;
241
242 /* FIXME: Assumes v2! */
243 put(&respdata[0], version);
244 put(&respdata[1], blockSizeShift);
245 put(&respdata[2], htole16(timeout));
246
247 *data_len = 4;
248 }
249 catch (const exception::SdBusError &e)
250 {
251 return hiomap_xlate_errno(e.get_errno());
252 }
253
254 return IPMI_CC_OK;
255}
256
Andrew Jefferydb688e92018-08-23 21:21:30 +0930257static ipmi_ret_t hiomap_get_flash_info(ipmi_request_t request,
258 ipmi_response_t response,
259 ipmi_data_len_t data_len,
260 ipmi_context_t context)
261{
262 struct hiomap *ctx = static_cast<struct hiomap *>(context);
263
264 auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT,
265 HIOMAPD_IFACE_V2, "GetFlashInfo");
266 try
267 {
268 auto reply = ctx->bus->call(m);
269
270 uint16_t flashSize, eraseSize;
271 reply.read(flashSize, eraseSize);
272
273 uint8_t *respdata = (uint8_t *)response;
274 put(&respdata[0], htole16(flashSize));
275 put(&respdata[2], htole16(eraseSize));
276
277 *data_len = 4;
278 }
279 catch (const exception::SdBusError &e)
280 {
281 return hiomap_xlate_errno(e.get_errno());
282 }
283
284 return IPMI_CC_OK;
285}
286
Andrew Jefferya00f59b2018-08-23 22:25:49 +0930287static ipmi_ret_t hiomap_create_window(struct hiomap *ctx, bool ro,
288 ipmi_request_t request,
289 ipmi_response_t response,
290 ipmi_data_len_t data_len)
291{
292 if (*data_len < 4)
293 {
294 return IPMI_CC_REQ_DATA_LEN_INVALID;
295 }
296
297 uint8_t *reqdata = (uint8_t *)request;
298 auto windowType = ro ? "CreateReadWindow" : "CreateWriteWindow";
299
300 auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT,
301 HIOMAPD_IFACE_V2, windowType);
302 m.append(le16toh(get<uint16_t>(&reqdata[0])));
303 m.append(le16toh(get<uint16_t>(&reqdata[2])));
304
305 try
306 {
307 auto reply = ctx->bus->call(m);
308
309 uint16_t lpcAddress, size, offset;
310 reply.read(lpcAddress, size, offset);
311
312 uint8_t *respdata = (uint8_t *)response;
313
314 /* FIXME: Assumes v2! */
315 put(&respdata[0], htole16(lpcAddress));
316 put(&respdata[2], htole16(size));
317 put(&respdata[4], htole16(offset));
318
319 *data_len = 6;
320 }
321 catch (const exception::SdBusError &e)
322 {
323 return hiomap_xlate_errno(e.get_errno());
324 }
325
326 return IPMI_CC_OK;
327}
328
329static ipmi_ret_t hiomap_create_read_window(ipmi_request_t request,
330 ipmi_response_t response,
331 ipmi_data_len_t data_len,
332 ipmi_context_t context)
333{
334 struct hiomap *ctx = static_cast<struct hiomap *>(context);
335
336 return hiomap_create_window(ctx, true, request, response, data_len);
337}
338
339static ipmi_ret_t hiomap_create_write_window(ipmi_request_t request,
340 ipmi_response_t response,
341 ipmi_data_len_t data_len,
342 ipmi_context_t context)
343{
344 struct hiomap *ctx = static_cast<struct hiomap *>(context);
345
346 return hiomap_create_window(ctx, false, request, response, data_len);
347}
348
Andrew Jefferyb52822c2018-08-23 23:01:39 +0930349static ipmi_ret_t hiomap_close_window(ipmi_request_t request,
350 ipmi_response_t response,
351 ipmi_data_len_t data_len,
352 ipmi_context_t context)
353{
354 struct hiomap *ctx = static_cast<struct hiomap *>(context);
355
356 if (*data_len < 1)
357 {
358 return IPMI_CC_REQ_DATA_LEN_INVALID;
359 }
360
361 uint8_t *reqdata = (uint8_t *)request;
362 auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT,
363 HIOMAPD_IFACE_V2, "CloseWindow");
364 m.append(reqdata[0]);
365
366 try
367 {
368 auto reply = ctx->bus->call(m);
369
370 *data_len = 0;
371 }
372 catch (const exception::SdBusError &e)
373 {
374 return hiomap_xlate_errno(e.get_errno());
375 }
376
377 return IPMI_CC_OK;
378}
379
Andrew Jeffery9847f1c2018-08-24 09:13:40 +0930380static ipmi_ret_t hiomap_mark_dirty(ipmi_request_t request,
381 ipmi_response_t response,
382 ipmi_data_len_t data_len,
383 ipmi_context_t context)
384{
385 struct hiomap *ctx = static_cast<struct hiomap *>(context);
386
387 if (*data_len < 4)
388 {
389 return IPMI_CC_REQ_DATA_LEN_INVALID;
390 }
391
392 uint8_t *reqdata = (uint8_t *)request;
393 auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT,
394 HIOMAPD_IFACE_V2, "MarkDirty");
395 /* FIXME: Assumes v2 */
396 m.append(le16toh(get<uint16_t>(&reqdata[0]))); /* offset */
397 m.append(le16toh(get<uint16_t>(&reqdata[2]))); /* size */
398
399 try
400 {
401 auto reply = ctx->bus->call(m);
402
403 *data_len = 0;
404 }
405 catch (const exception::SdBusError &e)
406 {
407 return hiomap_xlate_errno(e.get_errno());
408 }
409
410 return IPMI_CC_OK;
411}
412
Andrew Jeffery7b225fb2018-08-24 09:19:21 +0930413static ipmi_ret_t hiomap_flush(ipmi_request_t request, ipmi_response_t response,
414 ipmi_data_len_t data_len, ipmi_context_t context)
415{
416 struct hiomap *ctx = static_cast<struct hiomap *>(context);
417
418 auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT,
419 HIOMAPD_IFACE_V2, "Flush");
420
421 try
422 {
423 /* FIXME: No argument call assumes v2 */
424 auto reply = ctx->bus->call(m);
425
426 *data_len = 0;
427 }
428 catch (const exception::SdBusError &e)
429 {
430 return hiomap_xlate_errno(e.get_errno());
431 }
432
433 return IPMI_CC_OK;
434}
435
Andrew Jeffery99f277a2018-08-24 09:24:04 +0930436static ipmi_ret_t hiomap_ack(ipmi_request_t request, ipmi_response_t response,
437 ipmi_data_len_t data_len, ipmi_context_t context)
438{
439 struct hiomap *ctx = static_cast<struct hiomap *>(context);
440
441 if (*data_len < 1)
442 {
443 return IPMI_CC_REQ_DATA_LEN_INVALID;
444 }
445
446 uint8_t *reqdata = (uint8_t *)request;
447 auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT,
448 HIOMAPD_IFACE_V2, "Ack");
449 auto acked = reqdata[0];
450 m.append(acked);
451
452 try
453 {
454 auto reply = ctx->bus->call(m);
455
456 /* Update our cache: Necessary because the signals do not carry a value
457 */
458 ctx->bmc_events &= ~acked;
459
460 *data_len = 0;
461 }
462 catch (const exception::SdBusError &e)
463 {
464 return hiomap_xlate_errno(e.get_errno());
465 }
466
467 return IPMI_CC_OK;
468}
469
Andrew Jefferya1e35b82018-08-24 09:39:10 +0930470static ipmi_ret_t hiomap_erase(ipmi_request_t request, ipmi_response_t response,
471 ipmi_data_len_t data_len, ipmi_context_t context)
472{
473 struct hiomap *ctx = static_cast<struct hiomap *>(context);
474
475 if (*data_len < 4)
476 {
477 return IPMI_CC_REQ_DATA_LEN_INVALID;
478 }
479
480 uint8_t *reqdata = (uint8_t *)request;
481 auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT,
482 HIOMAPD_IFACE_V2, "Erase");
483 /* FIXME: Assumes v2 */
484 m.append(le16toh(get<uint16_t>(&reqdata[0]))); /* offset */
485 m.append(le16toh(get<uint16_t>(&reqdata[2]))); /* size */
486
487 try
488 {
489 auto reply = ctx->bus->call(m);
490
491 *data_len = 0;
492 }
493 catch (const exception::SdBusError &e)
494 {
495 return hiomap_xlate_errno(e.get_errno());
496 }
497
498 return IPMI_CC_OK;
499}
500
Andrew Jeffery04d75132018-09-26 00:58:52 +0930501#define HIOMAP_C_RESET 1
502#define HIOMAP_C_GET_INFO 2
503#define HIOMAP_C_GET_FLASH_INFO 3
504#define HIOMAP_C_CREATE_READ_WINDOW 4
505#define HIOMAP_C_CLOSE_WINDOW 5
506#define HIOMAP_C_CREATE_WRITE_WINDOW 6
507#define HIOMAP_C_MARK_DIRTY 7
508#define HIOMAP_C_FLUSH 8
509#define HIOMAP_C_ACK 9
510#define HIOMAP_C_ERASE 10
511
Andrew Jeffery2c07f6f2018-08-10 16:24:32 +0930512static const hiomap_command hiomap_commands[] = {
Andrew Jeffery04d75132018-09-26 00:58:52 +0930513 [0] = NULL, /* Invalid command ID */
514 [HIOMAP_C_RESET] = hiomap_reset,
515 [HIOMAP_C_GET_INFO] = hiomap_get_info,
516 [HIOMAP_C_GET_FLASH_INFO] = hiomap_get_flash_info,
517 [HIOMAP_C_CREATE_READ_WINDOW] = hiomap_create_read_window,
518 [HIOMAP_C_CLOSE_WINDOW] = hiomap_close_window,
519 [HIOMAP_C_CREATE_WRITE_WINDOW] = hiomap_create_write_window,
520 [HIOMAP_C_MARK_DIRTY] = hiomap_mark_dirty,
521 [HIOMAP_C_FLUSH] = hiomap_flush,
522 [HIOMAP_C_ACK] = hiomap_ack,
523 [HIOMAP_C_ERASE] = hiomap_erase,
Andrew Jeffery2c07f6f2018-08-10 16:24:32 +0930524};
525
526/* FIXME: Define this in the "right" place, wherever that is */
527/* FIXME: Double evaluation */
528#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
529
530static ipmi_ret_t hiomap_dispatch(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
531 ipmi_request_t request,
532 ipmi_response_t response,
533 ipmi_data_len_t data_len,
534 ipmi_context_t context)
535{
Andrew Jeffery0a3358e2018-08-21 10:42:09 +0930536 struct hiomap *ctx = static_cast<struct hiomap *>(context);
537
Andrew Jeffery2c07f6f2018-08-10 16:24:32 +0930538 if (*data_len < 2)
539 {
540 *data_len = 0;
541 return IPMI_CC_REQ_DATA_LEN_INVALID;
542 }
543
544 uint8_t *ipmi_req = (uint8_t *)request;
545 uint8_t *ipmi_resp = (uint8_t *)response;
546 uint8_t hiomap_cmd = ipmi_req[0];
547
548 if (hiomap_cmd == 0 || hiomap_cmd > ARRAY_SIZE(hiomap_commands) - 1)
549 {
550 *data_len = 0;
551 return IPMI_CC_PARM_OUT_OF_RANGE;
552 }
Andrew Jeffery04d75132018-09-26 00:58:52 +0930553
554 bool is_unversioned =
555 (hiomap_cmd == HIOMAP_C_RESET || hiomap_cmd == HIOMAP_C_GET_INFO ||
556 hiomap_cmd == HIOMAP_C_ACK);
557 if (!is_unversioned && ctx->seq == ipmi_req[1])
558 {
559 *data_len = 0;
560 return IPMI_CC_INVALID_FIELD_REQUEST;
561 }
562
563 ctx->seq = ipmi_req[1];
564
Andrew Jeffery2c07f6f2018-08-10 16:24:32 +0930565 uint8_t *flash_req = ipmi_req + 2;
566 size_t flash_len = *data_len - 2;
567 uint8_t *flash_resp = ipmi_resp + 2;
568
569 ipmi_ret_t cc =
570 hiomap_commands[hiomap_cmd](flash_req, flash_resp, &flash_len, context);
571 if (cc != IPMI_CC_OK)
572 {
573 *data_len = 0;
574 return cc;
575 }
576
577 /* Populate the response command and sequence */
Andrew Jeffery0a3358e2018-08-21 10:42:09 +0930578 ipmi_resp[0] = hiomap_cmd;
Andrew Jeffery04d75132018-09-26 00:58:52 +0930579 ipmi_resp[1] = ctx->seq;
Andrew Jeffery2c07f6f2018-08-10 16:24:32 +0930580
581 *data_len = flash_len + 2;
582
583 return cc;
584}
585} // namespace flash
586} // namespace openpower
587
588static void register_openpower_hiomap_commands()
589{
Andrew Jeffery0a3358e2018-08-21 10:42:09 +0930590 using namespace openpower::flash;
591
592 /* FIXME: Clean this up? Can we unregister? */
593 struct hiomap *ctx = new hiomap();
594
595 /* Initialise mapping from signal and property names to status bit */
596 ctx->event_lookup["DaemonReady"] = BMC_EVENT_DAEMON_READY;
597 ctx->event_lookup["FlashControlLost"] = BMC_EVENT_FLASH_CTRL_LOST;
598 ctx->event_lookup["WindowReset"] = BMC_EVENT_WINDOW_RESET;
599 ctx->event_lookup["ProtocolReset"] = BMC_EVENT_PROTOCOL_RESET;
600
601 ctx->bus = new bus::bus(ipmid_get_sd_bus_connection());
602
603 /* Initialise signal handling */
604
605 /*
606 * Can't use temporaries here because that causes SEGFAULTs due to slot
607 * destruction (!?), so enjoy the weird wrapping.
608 */
609 ctx->properties =
610 new bus::match::match(std::move(hiomap_match_properties(ctx)));
611 ctx->bmc_reboot = new bus::match::match(
612 std::move(hiomap_match_signal_v2(ctx, "ProtocolReset")));
613 ctx->window_reset = new bus::match::match(
614 std::move(hiomap_match_signal_v2(ctx, "WindowReset")));
615
616 ipmi_register_callback(NETFUN_IBM_OEM, IPMI_CMD_HIOMAP, ctx,
Andrew Jeffery2c07f6f2018-08-10 16:24:32 +0930617 openpower::flash::hiomap_dispatch, SYSTEM_INTERFACE);
618}