driver-hashbusterusb.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617
  1. /*
  2. * Copyright 2013 Luke Dashjr
  3. * Copyright 2013 Vladimir Strinski
  4. * Copyright 2013 HashBuster team
  5. *
  6. * This program is free software; you can redistribute it and/or modify it
  7. * under the terms of the GNU General Public License as published by the Free
  8. * Software Foundation; either version 3 of the License, or (at your option)
  9. * any later version. See COPYING for more details.
  10. */
  11. #include "config.h"
  12. #include <stdbool.h>
  13. #include <stdint.h>
  14. #include <string.h>
  15. #include "deviceapi.h"
  16. #include "driver-bitfury.h"
  17. #include "libbitfury.h"
  18. #include "logging.h"
  19. #include "lowlevel.h"
  20. #include "lowl-usb.h"
  21. #include "miner.h"
  22. #define HASHBUSTER_USB_PRODUCT "HashBuster"
  23. #define HASHBUSTER_MAX_BYTES_PER_SPI_TRANSFER 61
  24. BFG_REGISTER_DRIVER(hashbusterusb_drv)
  25. static const struct bfg_set_device_definition hashbusterusb_set_device_funcs[];
  26. struct hashbusterusb_state {
  27. uint16_t voltage;
  28. struct timeval identify_started;
  29. bool identify_requested;
  30. };
  31. static
  32. bool hashbusterusb_io(struct lowl_usb_endpoint * const h, unsigned char *buf, unsigned char *cmd)
  33. {
  34. char x[0x81];
  35. bool rv = true;
  36. if (unlikely(opt_dev_protocol))
  37. {
  38. bin2hex(x, cmd, 0x40);
  39. applog(LOG_DEBUG, "%s(%p): SEND: %s", __func__, h, x);
  40. }
  41. do // Workaround for PIC USB buffer corruption. We should repeat last packet if receive FF
  42. {
  43. do
  44. {
  45. usb_write(h, cmd, 64);
  46. } while (usb_read(h, buf, 64) != 64);
  47. } while(buf[0]==0xFF);
  48. if (unlikely(opt_dev_protocol))
  49. {
  50. bin2hex(x, buf, 0x40);
  51. applog(LOG_DEBUG, "%s(%p): RECV: %s", __func__, h, x);
  52. }
  53. return rv;
  54. }
  55. static
  56. bool hashbusterusb_spi_config(struct lowl_usb_endpoint * const h, const uint8_t mode, const uint8_t miso, const uint32_t freq)
  57. {
  58. uint8_t buf[0x40] = {'\x01', '\x01'};
  59. if (!hashbusterusb_io(h, buf, buf))
  60. return false;
  61. return (buf[1] == '\x00');
  62. }
  63. static
  64. bool hashbusterusb_spi_disable(struct lowl_usb_endpoint * const h)
  65. {
  66. uint8_t buf[0x40] = {'\x01', '\x00'};
  67. if (!hashbusterusb_io(h, buf, buf))
  68. return false;
  69. return (buf[1] == '\x00');
  70. }
  71. static
  72. bool hashbusterusb_spi_reset(struct lowl_usb_endpoint * const h, uint8_t chips)
  73. {
  74. uint8_t buf[0x40] = {'\x02', '\x00', chips};
  75. if (!hashbusterusb_io(h, buf, buf))
  76. return false;
  77. return (buf[1] == '\x00');
  78. }
  79. static
  80. bool hashbusterusb_spi_transfer(struct lowl_usb_endpoint * const h, void * const buf, const void * const data, size_t datasz)
  81. {
  82. if (datasz > HASHBUSTER_MAX_BYTES_PER_SPI_TRANSFER)
  83. return false;
  84. uint8_t cbuf[0x40] = {'\x03', '\x00', datasz};
  85. memcpy(&cbuf[3], data, datasz);
  86. if (!hashbusterusb_io(h, cbuf, cbuf))
  87. return false;
  88. if (cbuf[2] != datasz)
  89. return false;
  90. memcpy(buf, &cbuf[3], datasz);
  91. return true;
  92. }
  93. static
  94. bool hashbusterusb_spi_txrx(struct spi_port * const port)
  95. {
  96. struct lowl_usb_endpoint * const h = port->userp;
  97. const uint8_t *wrbuf = spi_gettxbuf(port);
  98. uint8_t *rdbuf = spi_getrxbuf(port);
  99. size_t bufsz = spi_getbufsz(port);
  100. hashbusterusb_spi_disable(h);
  101. hashbusterusb_spi_reset(h, 0x10);
  102. hashbusterusb_spi_config(h, port->mode, 0, port->speed);
  103. while (bufsz >= HASHBUSTER_MAX_BYTES_PER_SPI_TRANSFER)
  104. {
  105. if (!hashbusterusb_spi_transfer(h, rdbuf, wrbuf, HASHBUSTER_MAX_BYTES_PER_SPI_TRANSFER))
  106. return false;
  107. rdbuf += HASHBUSTER_MAX_BYTES_PER_SPI_TRANSFER;
  108. wrbuf += HASHBUSTER_MAX_BYTES_PER_SPI_TRANSFER;
  109. bufsz -= HASHBUSTER_MAX_BYTES_PER_SPI_TRANSFER;
  110. }
  111. if (bufsz > 0)
  112. {
  113. if (!hashbusterusb_spi_transfer(h, rdbuf, wrbuf, bufsz))
  114. return false;
  115. }
  116. return true;
  117. }
  118. static
  119. bool hashbusterusb_lowl_match(const struct lowlevel_device_info * const info)
  120. {
  121. return lowlevel_match_id(info, &lowl_usb, 0xFA04, 0x000D);
  122. }
  123. static
  124. bool hashbusterusb_lowl_probe(const struct lowlevel_device_info * const info)
  125. {
  126. struct cgpu_info *cgpu = NULL;
  127. struct bitfury_device **devicelist, *bitfury;
  128. struct spi_port *port;
  129. int j;
  130. struct cgpu_info dummy_cgpu;
  131. const char * const product = info->product;
  132. char *serial = info->serial;
  133. libusb_device_handle *h;
  134. if (info->lowl != &lowl_usb)
  135. {
  136. bfg_probe_result_flags = BPR_WRONG_DEVTYPE;
  137. applogr(false, LOG_DEBUG, "%s: Matched \"%s\" %s, but lowlevel driver is not usb_generic!",
  138. __func__, product, info->devid);
  139. }
  140. if (info->vid != 0xFA04 || info->pid != 0x000D)
  141. {
  142. bfg_probe_result_flags = BPR_WRONG_DEVTYPE;
  143. applogr(false, LOG_DEBUG, "%s: Wrong VID/PID", __func__);
  144. }
  145. libusb_device *dev = info->lowl_data;
  146. if ( (j = libusb_open(dev, &h)) )
  147. applogr(false, LOG_ERR, "%s: Failed to open %s: %s",
  148. __func__, info->devid, bfg_strerror(j, BST_LIBUSB));
  149. if ( (j = libusb_set_configuration(h, 1)) )
  150. {
  151. libusb_close(h);
  152. applogr(false, LOG_ERR, "%s: Failed to set configuration 1 on %s: %s",
  153. __func__, info->devid, bfg_strerror(j, BST_LIBUSB));
  154. }
  155. if ( (j = libusb_claim_interface(h, 0)) )
  156. {
  157. libusb_close(h);
  158. applogr(false, LOG_ERR, "%s: Failed to claim interface 0 on %s: %s",
  159. __func__, info->devid, bfg_strerror(j, BST_LIBUSB));
  160. }
  161. struct lowl_usb_endpoint * const ep = usb_open_ep_pair(h, 0x81, 64, 0x01, 64);
  162. usb_ep_set_timeouts_ms(ep, 100, 0);
  163. unsigned char OUTPacket[64] = { 0xfe };
  164. unsigned char INPacket[64];
  165. hashbusterusb_io(ep, INPacket, OUTPacket);
  166. if (INPacket[1] == 0x18)
  167. {
  168. // Turn on miner PSU
  169. OUTPacket[0] = 0x10;
  170. OUTPacket[1] = 0x00;
  171. OUTPacket[2] = 0x01;
  172. hashbusterusb_io(ep, INPacket, OUTPacket);
  173. }
  174. OUTPacket[0] = '\x20';
  175. hashbusterusb_io(ep, INPacket, OUTPacket);
  176. if (!memcmp(INPacket, "\x20\0", 2))
  177. {
  178. // 64-bit BE serial number
  179. uint64_t sernum = 0;
  180. for (j = 0; j < 8; ++j)
  181. sernum |= (uint64_t)INPacket[j + 2] << (j * 8);
  182. serial = malloc((8 * 2) + 1);
  183. sprintf(serial, "%08"PRIX64, sernum);
  184. }
  185. else
  186. serial = maybe_strdup(info->serial);
  187. int chip_n;
  188. port = malloc(sizeof(*port));
  189. port->cgpu = &dummy_cgpu;
  190. port->txrx = hashbusterusb_spi_txrx;
  191. port->userp = ep;
  192. port->repr = hashbusterusb_drv.dname;
  193. port->logprio = LOG_DEBUG;
  194. port->speed = 100000;
  195. port->mode = 0;
  196. chip_n = libbitfury_detectChips1(port);
  197. if (unlikely(!chip_n))
  198. chip_n = libbitfury_detectChips1(port);
  199. if (unlikely(!chip_n))
  200. {
  201. applog(LOG_WARNING, "%s: No chips found on %s (serial \"%s\")",
  202. __func__, info->devid, serial);
  203. fail:
  204. usb_close_ep(ep);
  205. free(port);
  206. free(serial);
  207. libusb_release_interface(h, 0);
  208. libusb_close(h);
  209. return false;
  210. }
  211. if (bfg_claim_libusb(&hashbusterusb_drv, true, dev))
  212. goto fail;
  213. {
  214. devicelist = malloc(sizeof(*devicelist) * chip_n);
  215. for (j = 0; j < chip_n; ++j)
  216. {
  217. devicelist[j] = bitfury = malloc(sizeof(*bitfury));
  218. *bitfury = (struct bitfury_device){
  219. .spi = port,
  220. .slot = 0,
  221. .fasync = j,
  222. };
  223. }
  224. cgpu = malloc(sizeof(*cgpu));
  225. *cgpu = (struct cgpu_info){
  226. .drv = &hashbusterusb_drv,
  227. .set_device_funcs = hashbusterusb_set_device_funcs,
  228. .procs = chip_n,
  229. .device_data = devicelist,
  230. .cutofftemp = 200,
  231. .threads = 1,
  232. .device_path = strdup(info->devid),
  233. .dev_manufacturer = maybe_strdup(info->manufacturer),
  234. .dev_product = maybe_strdup(product),
  235. .dev_serial = serial,
  236. .deven = DEV_ENABLED,
  237. };
  238. }
  239. return add_cgpu(cgpu);
  240. }
  241. static
  242. bool hashbusterusb_init(struct thr_info * const thr)
  243. {
  244. struct cgpu_info * const cgpu = thr->cgpu, *proc;
  245. struct bitfury_device **devicelist;
  246. struct bitfury_device *bitfury;
  247. struct hashbusterusb_state * const state = malloc(sizeof(*state));
  248. *state = (struct hashbusterusb_state){
  249. .voltage = 0,
  250. };
  251. cgpu_setup_control_requests(cgpu);
  252. for (proc = thr->cgpu; proc; proc = proc->next_proc)
  253. {
  254. devicelist = proc->device_data;
  255. bitfury = devicelist[proc->proc_id];
  256. proc->device_data = bitfury;
  257. proc->thr[0]->cgpu_data = state;
  258. bitfury->spi->cgpu = proc;
  259. bitfury_init_chip(proc);
  260. bitfury->osc6_bits = 53;
  261. bitfury_send_reinit(bitfury->spi, bitfury->slot, bitfury->fasync, bitfury->osc6_bits);
  262. bitfury_init_freq_stat(&bitfury->chip_stat, 52, 56);
  263. if (proc->proc_id == proc->procs - 1)
  264. free(devicelist);
  265. }
  266. timer_set_now(&thr->tv_poll);
  267. cgpu->status = LIFE_INIT2;
  268. return true;
  269. }
  270. static void hashbusterusb_set_colour(struct cgpu_info *, uint8_t, uint8_t, uint8_t);
  271. static
  272. void hashbusterusb_poll(struct thr_info * const master_thr)
  273. {
  274. struct hashbusterusb_state * const state = master_thr->cgpu_data;
  275. struct cgpu_info * const cgpu = master_thr->cgpu;
  276. if (state->identify_requested)
  277. {
  278. if (!timer_isset(&state->identify_started))
  279. hashbusterusb_set_colour(cgpu, 0xff, 0, 0xff);
  280. timer_set_delay_from_now(&state->identify_started, 5000000);
  281. state->identify_requested = false;
  282. }
  283. bitfury_do_io(master_thr);
  284. if (timer_passed(&state->identify_started, NULL))
  285. {
  286. hashbusterusb_set_colour(cgpu, 0, 0x7e, 0);
  287. timer_unset(&state->identify_started);
  288. }
  289. }
  290. static
  291. bool hashbusterusb_get_stats(struct cgpu_info * const cgpu)
  292. {
  293. bool rv = false;
  294. struct cgpu_info *proc;
  295. if (cgpu != cgpu->device)
  296. return true;
  297. struct bitfury_device * const bitfury = cgpu->device_data;
  298. struct spi_port * const spi = bitfury->spi;
  299. struct lowl_usb_endpoint * const h = spi->userp;
  300. uint8_t buf[0x40] = {'\x04'};
  301. if (hashbusterusb_io(h, buf, buf))
  302. {
  303. if (buf[1])
  304. {
  305. rv = true;
  306. for (proc = cgpu; proc; proc = proc->next_proc)
  307. proc->temp = buf[1];
  308. }
  309. }
  310. buf[0] = '\x15';
  311. if (hashbusterusb_io(h, buf, buf))
  312. {
  313. if (!memcmp(buf, "\x15\0", 2))
  314. {
  315. rv = true;
  316. const uint16_t voltage = (buf[3] << 8) | buf[2];
  317. for (proc = cgpu; proc; proc = proc->next_proc)
  318. {
  319. struct hashbusterusb_state * const state = proc->thr[0]->cgpu_data;
  320. state->voltage = voltage;
  321. }
  322. }
  323. }
  324. return rv;
  325. }
  326. static
  327. void hashbusterusb_shutdown(struct thr_info *thr)
  328. {
  329. struct cgpu_info *cgpu = thr->cgpu;
  330. struct bitfury_device * const bitfury = cgpu->device_data;
  331. struct spi_port * const spi = bitfury->spi;
  332. struct lowl_usb_endpoint * const h = spi->userp;
  333. // Shutdown PSU
  334. unsigned char OUTPacket[64] = { 0x10 };
  335. unsigned char INPacket[64];
  336. hashbusterusb_io(h, INPacket, OUTPacket);
  337. }
  338. static
  339. void hashbusterusb_set_colour(struct cgpu_info * const cgpu, const uint8_t red, const uint8_t green, const uint8_t blue)
  340. {
  341. struct bitfury_device * const bitfury = cgpu->device_data;
  342. struct spi_port * const spi = bitfury->spi;
  343. struct lowl_usb_endpoint * const h = spi->userp;
  344. uint8_t buf[0x40] = {'\x30', 0, red, green, blue};
  345. hashbusterusb_io(h, buf, buf);
  346. applog(LOG_DEBUG, "%s: Set LED colour to r=0x%x g=0x%x b=0x%x",
  347. cgpu->dev_repr, (unsigned)red, (unsigned)green, (unsigned)blue);
  348. }
  349. static
  350. bool hashbusterusb_identify(struct cgpu_info * const proc)
  351. {
  352. struct hashbusterusb_state * const state = proc->thr[0]->cgpu_data;
  353. state->identify_requested = true;
  354. return true;
  355. }
  356. static
  357. bool hashbusterusb_set_voltage(struct cgpu_info * const proc, const uint16_t nv)
  358. {
  359. struct bitfury_device * const bitfury = proc->device_data;
  360. struct spi_port * const spi = bitfury->spi;
  361. struct lowl_usb_endpoint * const h = spi->userp;
  362. unsigned char buf[0x40] = {0x11, 0, (nv & 0xff), (nv >> 8)};
  363. hashbusterusb_io(h, buf, buf);
  364. return !memcmp(buf, "\x11\0", 2);
  365. }
  366. static
  367. bool hashbusterusb_vrm_unlock(struct cgpu_info * const proc, const char * const code)
  368. {
  369. struct bitfury_device * const bitfury = proc->device_data;
  370. struct spi_port * const spi = bitfury->spi;
  371. struct lowl_usb_endpoint * const h = spi->userp;
  372. unsigned char buf[0x40] = {0x12};
  373. size_t size;
  374. size = strlen(code) >> 1;
  375. if (size > 63)
  376. size = 63;
  377. hex2bin(&buf[1], code, size);
  378. hashbusterusb_io(h, buf, buf);
  379. return !memcmp(buf, "\x12\0", 2);
  380. }
  381. static
  382. void hashbusterusb_vrm_lock(struct cgpu_info * const proc)
  383. {
  384. struct bitfury_device * const bitfury = proc->device_data;
  385. struct spi_port * const spi = bitfury->spi;
  386. struct lowl_usb_endpoint * const h = spi->userp;
  387. unsigned char buf[0x40] = {0x14};
  388. hashbusterusb_io(h, buf, buf);
  389. }
  390. static
  391. struct api_data *hashbusterusb_api_extra_device_stats(struct cgpu_info * const cgpu)
  392. {
  393. struct hashbusterusb_state * const state = cgpu->thr[0]->cgpu_data;
  394. struct api_data *root = bitfury_api_device_status(cgpu);
  395. float volts = state->voltage;
  396. volts /= 1000.;
  397. root = api_add_volts(root, "Voltage", &volts, true);
  398. return root;
  399. }
  400. static
  401. const char *hashbusterusb_rpcset_vrmlock(struct cgpu_info * const proc, const char * const option, const char * const setting, char * const replybuf, enum bfg_set_device_replytype * const success)
  402. {
  403. cgpu_request_control(proc->device);
  404. hashbusterusb_vrm_lock(proc);
  405. cgpu_release_control(proc->device);
  406. return NULL;
  407. }
  408. static
  409. const char *hashbusterusb_rpcset_vrmunlock(struct cgpu_info * const proc, const char * const option, const char * const setting, char * const replybuf, enum bfg_set_device_replytype * const success)
  410. {
  411. cgpu_request_control(proc->device);
  412. const bool rv = hashbusterusb_vrm_unlock(proc, setting);
  413. cgpu_release_control(proc->device);
  414. if (!rv)
  415. return "Unlock error";
  416. return NULL;
  417. }
  418. static
  419. const char *hashbusterusb_rpcset_voltage(struct cgpu_info * const proc, const char * const option, const char * const setting, char * const replybuf, enum bfg_set_device_replytype * const success)
  420. {
  421. const int val = atof(setting) * 1000;
  422. if (val < 600 || val > 1100)
  423. return "Invalid PSU voltage value";
  424. cgpu_request_control(proc->device);
  425. const bool rv = hashbusterusb_set_voltage(proc, val);
  426. cgpu_release_control(proc->device);
  427. if (!rv)
  428. return "Voltage change error";
  429. return NULL;
  430. }
  431. static const struct bfg_set_device_definition hashbusterusb_set_device_funcs[] = {
  432. {"baud" , bitfury_set_baud , "SPI baud rate"},
  433. {"osc6_bits", bitfury_set_osc6_bits , "range 1-"BITFURY_MAX_OSC6_BITS_S" (slow to fast)"},
  434. {"vrmlock" , hashbusterusb_rpcset_vrmlock , "Lock the VRM voltage to safe range"},
  435. {"vrmunlock", hashbusterusb_rpcset_vrmunlock, "Allow setting potentially unsafe voltages (requires unlock code)"},
  436. {"voltage" , hashbusterusb_rpcset_voltage , "Set voltage"},
  437. {NULL},
  438. };
  439. #ifdef HAVE_CURSES
  440. void hashbusterusb_tui_wlogprint_choices(struct cgpu_info * const proc)
  441. {
  442. wlogprint("[V]oltage ");
  443. wlogprint("[O]scillator bits ");
  444. //wlogprint("[F]an speed "); // To be implemented
  445. wlogprint("[U]nlock VRM ");
  446. wlogprint("[L]ock VRM ");
  447. }
  448. const char *hashbusterusb_tui_handle_choice(struct cgpu_info * const proc, const int input)
  449. {
  450. switch (input)
  451. {
  452. case 'v': case 'V':
  453. {
  454. const int val = curses_int("Set PSU voltage (range 600mV-1100mV. VRM unlock is required for over 870mV)");
  455. if (val < 600 || val > 1100)
  456. return "Invalid PSU voltage value\n";
  457. cgpu_request_control(proc->device);
  458. const bool rv = hashbusterusb_set_voltage(proc, val);
  459. cgpu_release_control(proc->device);
  460. if (!rv)
  461. return "Voltage change error\n";
  462. return "Voltage change successful\n";
  463. }
  464. case 'u': case 'U':
  465. {
  466. char *input = curses_input("VRM unlock code");
  467. if (!input)
  468. input = calloc(1, 1);
  469. cgpu_request_control(proc->device);
  470. const bool rv = hashbusterusb_vrm_unlock(proc, input);
  471. cgpu_release_control(proc->device);
  472. free(input);
  473. if (!rv)
  474. return "Unlock error\n";
  475. return "Unlocking PSU\n";
  476. }
  477. case 'o': case 'O':
  478. return bitfury_tui_handle_choice(proc, input);
  479. case 'l': case 'L':
  480. {
  481. cgpu_request_control(proc->device);
  482. hashbusterusb_vrm_lock(proc);
  483. cgpu_release_control(proc->device);
  484. return "VRM lock\n";
  485. }
  486. }
  487. return NULL;
  488. }
  489. void hashbusterusb_wlogprint_status(struct cgpu_info * const proc)
  490. {
  491. struct hashbusterusb_state * const state = proc->thr[0]->cgpu_data;
  492. bitfury_wlogprint_status(proc);
  493. wlogprint("PSU voltage: %umV\n", (unsigned)state->voltage);
  494. }
  495. #endif
  496. struct device_drv hashbusterusb_drv = {
  497. .dname = "hashbusterusb",
  498. .name = "HBR",
  499. .lowl_match = hashbusterusb_lowl_match,
  500. .lowl_probe = hashbusterusb_lowl_probe,
  501. .thread_init = hashbusterusb_init,
  502. .thread_disable = bitfury_disable,
  503. .thread_enable = bitfury_enable,
  504. .thread_shutdown = hashbusterusb_shutdown,
  505. .minerloop = minerloop_async,
  506. .job_prepare = bitfury_job_prepare,
  507. .job_start = bitfury_noop_job_start,
  508. .poll = hashbusterusb_poll,
  509. .job_process_results = bitfury_job_process_results,
  510. .get_stats = hashbusterusb_get_stats,
  511. .get_api_extra_device_detail = bitfury_api_device_detail,
  512. .get_api_extra_device_status = hashbusterusb_api_extra_device_stats,
  513. .identify_device = hashbusterusb_identify,
  514. #ifdef HAVE_CURSES
  515. .proc_wlogprint_status = hashbusterusb_wlogprint_status,
  516. .proc_tui_wlogprint_choices = hashbusterusb_tui_wlogprint_choices,
  517. .proc_tui_handle_choice = hashbusterusb_tui_handle_choice,
  518. #endif
  519. };