您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  1. /**
  2. * Copyright (c) 2022 Brian Starkey <stark3y@gmail.com>
  3. *
  4. * Based on the Pico W tcp_server example:
  5. * Copyright (c) 2022 Raspberry Pi (Trading) Ltd.
  6. *
  7. * SPDX-License-Identifier: BSD-3-Clause
  8. */
  9. #include <string.h>
  10. #include <stdlib.h>
  11. #include "RP2040.h"
  12. #include "pico/critical_section.h"
  13. #include "pico/time.h"
  14. #include "pico/util/queue.h"
  15. #include "hardware/dma.h"
  16. #include "hardware/flash.h"
  17. #include "hardware/structs/dma.h"
  18. #include "hardware/structs/watchdog.h"
  19. #include "hardware/gpio.h"
  20. #include "hardware/resets.h"
  21. #include "hardware/uart.h"
  22. #include "hardware/watchdog.h"
  23. #include "pico/stdlib.h"
  24. #include "pico/cyw43_arch.h"
  25. #include "tcp_comm.h"
  26. #include "picowota/reboot.h"
  27. #ifdef DEBUG
  28. #include <stdio.h>
  29. #include "pico/stdio_usb.h"
  30. #define DBG_PRINTF_INIT() stdio_usb_init()
  31. #define DBG_PRINTF(...) printf(__VA_ARGS__)
  32. #else
  33. #define DBG_PRINTF_INIT() { }
  34. #define DBG_PRINTF(...) { }
  35. #endif
  36. extern const char *wifi_ssid;
  37. extern const char *wifi_pass;
  38. critical_section_t critical_section;
  39. #define EVENT_QUEUE_LENGTH 8
  40. queue_t event_queue;
  41. enum event_type {
  42. EVENT_TYPE_REBOOT = 1,
  43. EVENT_TYPE_GO,
  44. EVENT_TYPE_SERVER_DONE,
  45. };
  46. struct event {
  47. enum event_type type;
  48. union {
  49. struct {
  50. bool to_bootloader;
  51. } reboot;
  52. struct {
  53. uint32_t vtor;
  54. } go;
  55. };
  56. };
  57. #define BOOTLOADER_ENTRY_PIN 15
  58. #define TCP_PORT 4242
  59. #define IMAGE_HEADER_OFFSET (360 * 1024)
  60. #define WRITE_ADDR_MIN (XIP_BASE + IMAGE_HEADER_OFFSET + FLASH_SECTOR_SIZE)
  61. #define ERASE_ADDR_MIN (XIP_BASE + IMAGE_HEADER_OFFSET)
  62. #define FLASH_ADDR_MAX (XIP_BASE + PICO_FLASH_SIZE_BYTES)
  63. #define CMD_SYNC (('S' << 0) | ('Y' << 8) | ('N' << 16) | ('C' << 24))
  64. #define RSP_SYNC (('W' << 0) | ('O' << 8) | ('T' << 16) | ('A' << 24))
  65. #define CMD_INFO (('I' << 0) | ('N' << 8) | ('F' << 16) | ('O' << 24))
  66. #define CMD_READ (('R' << 0) | ('E' << 8) | ('A' << 16) | ('D' << 24))
  67. #define CMD_CSUM (('C' << 0) | ('S' << 8) | ('U' << 16) | ('M' << 24))
  68. #define CMD_CRC (('C' << 0) | ('R' << 8) | ('C' << 16) | ('C' << 24))
  69. #define CMD_ERASE (('E' << 0) | ('R' << 8) | ('A' << 16) | ('S' << 24))
  70. #define CMD_WRITE (('W' << 0) | ('R' << 8) | ('I' << 16) | ('T' << 24))
  71. #define CMD_SEAL (('S' << 0) | ('E' << 8) | ('A' << 16) | ('L' << 24))
  72. #define CMD_GO (('G' << 0) | ('O' << 8) | ('G' << 16) | ('O' << 24))
  73. #define CMD_REBOOT (('B' << 0) | ('O' << 8) | ('O' << 16) | ('T' << 24))
  74. static uint32_t handle_sync(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out)
  75. {
  76. return RSP_SYNC;
  77. }
  78. const struct comm_command sync_cmd = {
  79. .opcode = CMD_SYNC,
  80. .nargs = 0,
  81. .resp_nargs = 0,
  82. .size = NULL,
  83. .handle = &handle_sync,
  84. };
  85. static uint32_t size_read(uint32_t *args_in, uint32_t *data_len_out, uint32_t *resp_data_len_out)
  86. {
  87. uint32_t size = args_in[1];
  88. if (size > TCP_COMM_MAX_DATA_LEN) {
  89. return TCP_COMM_RSP_ERR;
  90. }
  91. // TODO: Validate address
  92. *data_len_out = 0;
  93. *resp_data_len_out = size;
  94. return TCP_COMM_RSP_OK;
  95. }
  96. static uint32_t handle_read(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out)
  97. {
  98. uint32_t addr = args_in[0];
  99. uint32_t size = args_in[1];
  100. memcpy(resp_data_out, (void *)addr, size);
  101. return TCP_COMM_RSP_OK;
  102. }
  103. const struct comm_command read_cmd = {
  104. // READ addr len
  105. // OKOK [data]
  106. .opcode = CMD_READ,
  107. .nargs = 2,
  108. .resp_nargs = 0,
  109. .size = &size_read,
  110. .handle = &handle_read,
  111. };
  112. static uint32_t size_csum(uint32_t *args_in, uint32_t *data_len_out, uint32_t *resp_data_len_out)
  113. {
  114. uint32_t addr = args_in[0];
  115. uint32_t size = args_in[1];
  116. if ((addr & 0x3) || (size & 0x3)) {
  117. // Must be aligned
  118. return TCP_COMM_RSP_ERR;
  119. }
  120. // TODO: Validate address
  121. *data_len_out = 0;
  122. *resp_data_len_out = 0;
  123. return TCP_COMM_RSP_OK;
  124. }
  125. static uint32_t handle_csum(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out)
  126. {
  127. uint32_t dummy_dest;
  128. uint32_t addr = args_in[0];
  129. uint32_t size = args_in[1];
  130. int channel = dma_claim_unused_channel(true);
  131. dma_channel_config c = dma_channel_get_default_config(channel);
  132. channel_config_set_transfer_data_size(&c, DMA_SIZE_32);
  133. channel_config_set_read_increment(&c, true);
  134. channel_config_set_write_increment(&c, false);
  135. channel_config_set_sniff_enable(&c, true);
  136. dma_hw->sniff_data = 0;
  137. dma_sniffer_enable(channel, 0xf, true);
  138. dma_channel_configure(channel, &c, &dummy_dest, (void *)addr, size / 4, true);
  139. dma_channel_wait_for_finish_blocking(channel);
  140. dma_sniffer_disable();
  141. dma_channel_unclaim(channel);
  142. *resp_args_out = dma_hw->sniff_data;
  143. return TCP_COMM_RSP_OK;
  144. }
  145. struct comm_command csum_cmd = {
  146. // CSUM addr len
  147. // OKOK csum
  148. .opcode = CMD_CSUM,
  149. .nargs = 2,
  150. .resp_nargs = 1,
  151. .size = &size_csum,
  152. .handle = &handle_csum,
  153. };
  154. static uint32_t size_crc(uint32_t *args_in, uint32_t *data_len_out, uint32_t *resp_data_len_out)
  155. {
  156. uint32_t addr = args_in[0];
  157. uint32_t size = args_in[1];
  158. if ((addr & 0x3) || (size & 0x3)) {
  159. // Must be aligned
  160. return TCP_COMM_RSP_ERR;
  161. }
  162. // TODO: Validate address
  163. *data_len_out = 0;
  164. *resp_data_len_out = 0;
  165. return TCP_COMM_RSP_OK;
  166. }
  167. // ptr must be 4-byte aligned and len must be a multiple of 4
  168. static uint32_t calc_crc32(void *ptr, uint32_t len)
  169. {
  170. uint32_t dummy_dest, crc;
  171. int channel = dma_claim_unused_channel(true);
  172. dma_channel_config c = dma_channel_get_default_config(channel);
  173. channel_config_set_transfer_data_size(&c, DMA_SIZE_32);
  174. channel_config_set_read_increment(&c, true);
  175. channel_config_set_write_increment(&c, false);
  176. channel_config_set_sniff_enable(&c, true);
  177. // Seed the CRC calculation
  178. dma_hw->sniff_data = 0xffffffff;
  179. // Mode 1, then bit-reverse the result gives the same result as
  180. // golang's IEEE802.3 implementation
  181. dma_sniffer_enable(channel, 0x1, true);
  182. dma_hw->sniff_ctrl |= DMA_SNIFF_CTRL_OUT_REV_BITS;
  183. dma_channel_configure(channel, &c, &dummy_dest, ptr, len / 4, true);
  184. dma_channel_wait_for_finish_blocking(channel);
  185. // Read the result before resetting
  186. crc = dma_hw->sniff_data ^ 0xffffffff;
  187. dma_sniffer_disable();
  188. dma_channel_unclaim(channel);
  189. return crc;
  190. }
  191. static uint32_t handle_crc(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out)
  192. {
  193. uint32_t addr = args_in[0];
  194. uint32_t size = args_in[1];
  195. resp_args_out[0] = calc_crc32((void *)addr, size);
  196. return TCP_COMM_RSP_OK;
  197. }
  198. struct comm_command crc_cmd = {
  199. // CRCC addr len
  200. // OKOK crc
  201. .opcode = CMD_CRC,
  202. .nargs = 2,
  203. .resp_nargs = 1,
  204. .size = &size_crc,
  205. .handle = &handle_crc,
  206. };
  207. static uint32_t handle_erase(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out)
  208. {
  209. uint32_t addr = args_in[0];
  210. uint32_t size = args_in[1];
  211. if ((addr < ERASE_ADDR_MIN) || (addr + size >= FLASH_ADDR_MAX)) {
  212. // Outside flash
  213. return TCP_COMM_RSP_ERR;
  214. }
  215. if ((addr & (FLASH_SECTOR_SIZE - 1)) || (size & (FLASH_SECTOR_SIZE - 1))) {
  216. // Must be aligned
  217. return TCP_COMM_RSP_ERR;
  218. }
  219. critical_section_enter_blocking(&critical_section);
  220. flash_range_erase(addr - XIP_BASE, size);
  221. critical_section_exit(&critical_section);
  222. return TCP_COMM_RSP_OK;
  223. }
  224. struct comm_command erase_cmd = {
  225. // ERAS addr len
  226. // OKOK
  227. .opcode = CMD_ERASE,
  228. .nargs = 2,
  229. .resp_nargs = 0,
  230. .size = NULL,
  231. .handle = &handle_erase,
  232. };
  233. static uint32_t size_write(uint32_t *args_in, uint32_t *data_len_out, uint32_t *resp_data_len_out)
  234. {
  235. uint32_t addr = args_in[0];
  236. uint32_t size = args_in[1];
  237. if ((addr < WRITE_ADDR_MIN) || (addr + size >= FLASH_ADDR_MAX)) {
  238. // Outside flash
  239. return TCP_COMM_RSP_ERR;
  240. }
  241. if ((addr & (FLASH_PAGE_SIZE - 1)) || (size & (FLASH_PAGE_SIZE -1))) {
  242. // Must be aligned
  243. return TCP_COMM_RSP_ERR;
  244. }
  245. if (size > TCP_COMM_MAX_DATA_LEN) {
  246. return TCP_COMM_RSP_ERR;
  247. }
  248. // TODO: Validate address
  249. *data_len_out = size;
  250. *resp_data_len_out = 0;
  251. return TCP_COMM_RSP_OK;
  252. }
  253. static uint32_t handle_write(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out)
  254. {
  255. uint32_t addr = args_in[0];
  256. uint32_t size = args_in[1];
  257. critical_section_enter_blocking(&critical_section);
  258. flash_range_program(addr - XIP_BASE, data_in, size);
  259. critical_section_exit(&critical_section);
  260. resp_args_out[0] = calc_crc32((void *)addr, size);
  261. return TCP_COMM_RSP_OK;
  262. }
  263. struct comm_command write_cmd = {
  264. // WRIT addr len [data]
  265. // OKOK crc
  266. .opcode = CMD_WRITE,
  267. .nargs = 2,
  268. .resp_nargs = 1,
  269. .size = &size_write,
  270. .handle = &handle_write,
  271. };
  272. struct image_header {
  273. uint32_t vtor;
  274. uint32_t size;
  275. uint32_t crc;
  276. uint8_t pad[FLASH_PAGE_SIZE - (3 * 4)];
  277. };
  278. static_assert(sizeof(struct image_header) == FLASH_PAGE_SIZE, "image_header must be FLASH_PAGE_SIZE bytes");
  279. static bool image_header_ok(struct image_header *hdr)
  280. {
  281. uint32_t *vtor = (uint32_t *)hdr->vtor;
  282. uint32_t calc = calc_crc32((void *)hdr->vtor, hdr->size);
  283. // CRC has to match
  284. if (calc != hdr->crc) {
  285. return false;
  286. }
  287. // Stack pointer needs to be in RAM
  288. if (vtor[0] < SRAM_BASE) {
  289. return false;
  290. }
  291. // Reset vector should be in the image, and thumb (bit 0 set)
  292. if ((vtor[1] < hdr->vtor) || (vtor[1] > hdr->vtor + hdr->size) || !(vtor[1] & 1)) {
  293. return false;
  294. }
  295. // Looks OK.
  296. return true;
  297. }
  298. static uint32_t handle_seal(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out)
  299. {
  300. struct image_header hdr = {
  301. .vtor = args_in[0],
  302. .size = args_in[1],
  303. .crc = args_in[2],
  304. };
  305. if ((hdr.vtor & 0xff) || (hdr.size & 0x3)) {
  306. // Must be aligned
  307. return TCP_COMM_RSP_ERR;
  308. }
  309. if (!image_header_ok(&hdr)) {
  310. return TCP_COMM_RSP_ERR;
  311. }
  312. critical_section_enter_blocking(&critical_section);
  313. flash_range_erase(IMAGE_HEADER_OFFSET, FLASH_SECTOR_SIZE);
  314. flash_range_program(IMAGE_HEADER_OFFSET, (const uint8_t *)&hdr, sizeof(hdr));
  315. critical_section_exit(&critical_section);
  316. struct image_header *check = (struct image_header *)(XIP_BASE + IMAGE_HEADER_OFFSET);
  317. if (memcmp(&hdr, check, sizeof(hdr))) {
  318. return TCP_COMM_RSP_ERR;
  319. }
  320. return TCP_COMM_RSP_OK;
  321. }
  322. struct comm_command seal_cmd = {
  323. // SEAL vtor len crc
  324. // OKOK
  325. .opcode = CMD_SEAL,
  326. .nargs = 3,
  327. .resp_nargs = 0,
  328. .size = NULL,
  329. .handle = &handle_seal,
  330. };
  331. static void disable_interrupts(void)
  332. {
  333. SysTick->CTRL &= ~1;
  334. NVIC->ICER[0] = 0xFFFFFFFF;
  335. NVIC->ICPR[0] = 0xFFFFFFFF;
  336. }
  337. static void reset_peripherals(void)
  338. {
  339. reset_block(~(
  340. RESETS_RESET_IO_QSPI_BITS |
  341. RESETS_RESET_PADS_QSPI_BITS |
  342. RESETS_RESET_SYSCFG_BITS |
  343. RESETS_RESET_PLL_SYS_BITS
  344. ));
  345. }
  346. static void jump_to_vtor(uint32_t vtor)
  347. {
  348. // Derived from the Leaf Labs Cortex-M3 bootloader.
  349. // Copyright (c) 2010 LeafLabs LLC.
  350. // Modified 2021 Brian Starkey <stark3y@gmail.com>
  351. // Originally under The MIT License
  352. uint32_t reset_vector = *(volatile uint32_t *)(vtor + 0x04);
  353. SCB->VTOR = (volatile uint32_t)(vtor);
  354. asm volatile("msr msp, %0"::"g"
  355. (*(volatile uint32_t *)vtor));
  356. asm volatile("bx %0"::"r" (reset_vector));
  357. }
  358. static uint32_t handle_go(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out)
  359. {
  360. struct event ev = {
  361. .type = EVENT_TYPE_GO,
  362. .go = {
  363. .vtor = args_in[0],
  364. },
  365. };
  366. if (!queue_try_add(&event_queue, &ev)) {
  367. return TCP_COMM_RSP_ERR;
  368. }
  369. return TCP_COMM_RSP_OK;
  370. }
  371. struct comm_command go_cmd = {
  372. // GOGO vtor
  373. // NO RESPONSE
  374. .opcode = CMD_GO,
  375. .nargs = 1,
  376. .resp_nargs = 0,
  377. .size = NULL,
  378. .handle = &handle_go,
  379. };
  380. static uint32_t handle_info(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out)
  381. {
  382. resp_args_out[0] = WRITE_ADDR_MIN;
  383. resp_args_out[1] = (XIP_BASE + PICO_FLASH_SIZE_BYTES) - WRITE_ADDR_MIN;
  384. resp_args_out[2] = FLASH_SECTOR_SIZE;
  385. resp_args_out[3] = FLASH_PAGE_SIZE;
  386. resp_args_out[4] = TCP_COMM_MAX_DATA_LEN;
  387. return TCP_COMM_RSP_OK;
  388. }
  389. const struct comm_command info_cmd = {
  390. // INFO
  391. // OKOK flash_start flash_size erase_size write_size max_data_len
  392. .opcode = CMD_INFO,
  393. .nargs = 0,
  394. .resp_nargs = 5,
  395. .size = NULL,
  396. .handle = &handle_info,
  397. };
  398. static uint32_t size_reboot(uint32_t *args_in, uint32_t *data_len_out, uint32_t *resp_data_len_out)
  399. {
  400. *data_len_out = 0;
  401. *resp_data_len_out = 0;
  402. return TCP_COMM_RSP_OK;
  403. }
  404. static uint32_t handle_reboot(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out)
  405. {
  406. struct event ev = {
  407. .type = EVENT_TYPE_REBOOT,
  408. .reboot = {
  409. .to_bootloader = !!args_in[0],
  410. },
  411. };
  412. if (!queue_try_add(&event_queue, &ev)) {
  413. return TCP_COMM_RSP_ERR;
  414. }
  415. return TCP_COMM_RSP_OK;
  416. }
  417. struct comm_command reboot_cmd = {
  418. // BOOT to_bootloader
  419. // NO RESPONSE
  420. .opcode = CMD_REBOOT,
  421. .nargs = 1,
  422. .resp_nargs = 0,
  423. .size = &size_reboot,
  424. .handle = &handle_reboot,
  425. };
  426. static bool should_stay_in_bootloader()
  427. {
  428. bool wd_says_so = (watchdog_hw->scratch[5] == PICOWOTA_BOOTLOADER_ENTRY_MAGIC) &&
  429. (watchdog_hw->scratch[6] == ~PICOWOTA_BOOTLOADER_ENTRY_MAGIC);
  430. return !gpio_get(BOOTLOADER_ENTRY_PIN) || wd_says_so;
  431. }
  432. int main()
  433. {
  434. err_t err;
  435. gpio_init(BOOTLOADER_ENTRY_PIN);
  436. gpio_pull_up(BOOTLOADER_ENTRY_PIN);
  437. gpio_set_dir(BOOTLOADER_ENTRY_PIN, 0);
  438. sleep_ms(10);
  439. struct image_header *hdr = (struct image_header *)(XIP_BASE + IMAGE_HEADER_OFFSET);
  440. if (!should_stay_in_bootloader() && image_header_ok(hdr)) {
  441. uint32_t vtor = *((uint32_t *)(XIP_BASE + IMAGE_HEADER_OFFSET));
  442. disable_interrupts();
  443. reset_peripherals();
  444. jump_to_vtor(vtor);
  445. }
  446. DBG_PRINTF_INIT();
  447. queue_init(&event_queue, sizeof(struct event), EVENT_QUEUE_LENGTH);
  448. if (cyw43_arch_init()) {
  449. DBG_PRINTF("failed to initialise\n");
  450. return 1;
  451. }
  452. cyw43_arch_enable_sta_mode();
  453. DBG_PRINTF("Connecting to WiFi...\n");
  454. if (cyw43_arch_wifi_connect_timeout_ms(wifi_ssid, wifi_pass, CYW43_AUTH_WPA2_AES_PSK, 30000)) {
  455. DBG_PRINTF("failed to connect.\n");
  456. return 1;
  457. } else {
  458. DBG_PRINTF("Connected.\n");
  459. }
  460. critical_section_init(&critical_section);
  461. const struct comm_command *cmds[] = {
  462. &sync_cmd,
  463. &read_cmd,
  464. &csum_cmd,
  465. &crc_cmd,
  466. &erase_cmd,
  467. &write_cmd,
  468. &seal_cmd,
  469. &go_cmd,
  470. &info_cmd,
  471. &reboot_cmd,
  472. };
  473. struct tcp_comm_ctx *tcp = tcp_comm_new(cmds, sizeof(cmds) / sizeof(cmds[0]), CMD_SYNC);
  474. struct event ev = {
  475. .type = EVENT_TYPE_SERVER_DONE,
  476. };
  477. queue_add_blocking(&event_queue, &ev);
  478. for ( ; ; ) {
  479. while (queue_try_remove(&event_queue, &ev)) {
  480. switch (ev.type) {
  481. case EVENT_TYPE_SERVER_DONE:
  482. err = tcp_comm_listen(tcp, TCP_PORT);
  483. if (err != ERR_OK) {
  484. DBG_PRINTF("Failed to start server: %d\n", err);
  485. }
  486. break;
  487. case EVENT_TYPE_REBOOT:
  488. tcp_comm_server_close(tcp);
  489. cyw43_arch_deinit();
  490. picowota_reboot(ev.reboot.to_bootloader);
  491. /* Should never get here */
  492. break;
  493. case EVENT_TYPE_GO:
  494. tcp_comm_server_close(tcp);
  495. cyw43_arch_deinit();
  496. disable_interrupts();
  497. reset_peripherals();
  498. jump_to_vtor(ev.go.vtor);
  499. /* Should never get here */
  500. break;
  501. };
  502. }
  503. cyw43_arch_poll();
  504. sleep_ms(5);
  505. }
  506. cyw43_arch_deinit();
  507. return 0;
  508. }