PP502x: Add some important information about CPU/COP_CTL register to the header glean...
[Rockbox.git] / apps / plugins / iriver_flash.c
blobf3fbbb45b84648f19a9ac3f4fcc00a8380e4cb22
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * !!! DON'T MESS WITH THIS CODE UNLESS YOU'RE ABSOLUTELY SURE WHAT YOU DO !!!
12 * Copyright (C) 2006 by Miika Pekkarinen
14 * All files in this archive are subject to the GNU General Public License.
15 * See the file COPYING in the source tree root for full license agreement.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
21 #include "plugin.h"
23 /* All CFI flash routines are copied and ported from firmware_flash.c */
25 #ifndef SIMULATOR /* only for target */
27 unsigned char *audiobuf;
28 ssize_t audiobuf_size;
30 #if defined(IRIVER_H120)
31 #define PLATFORM_ID ID_IRIVER_H100
32 #else
33 #undef PLATFORM_ID /* this platform is not (yet) flashable */
34 #endif
36 #ifdef PLATFORM_ID
38 PLUGIN_HEADER
40 #if CONFIG_KEYPAD == IRIVER_H100_PAD
41 #define KEY1 BUTTON_OFF
42 #define KEY2 BUTTON_ON
43 #define KEY3 BUTTON_SELECT
44 #define KEYNAME1 "[Stop]"
45 #define KEYNAME2 "[On]"
46 #define KEYNAME3 "[Select]"
47 #endif
49 struct flash_info
51 uint8_t manufacturer;
52 uint8_t id;
53 int size;
54 char name[32];
57 static struct plugin_api* rb; /* here is a global api struct pointer */
59 #ifdef IRIVER_H100_SERIES
60 #define SEC_SIZE 4096
61 #define BOOTLOADER_ERASEGUARD (BOOTLOADER_ENTRYPOINT / SEC_SIZE)
62 enum sections {
63 SECT_RAMIMAGE = 1,
64 SECT_ROMIMAGE = 2,
67 static volatile uint16_t* FB = (uint16_t*)0x00000000; /* Flash base address */
68 #endif
70 /* read the manufacturer and device ID */
71 bool cfi_read_id(volatile uint16_t* pBase, uint8_t* pManufacturerID, uint8_t* pDeviceID)
73 uint8_t not_manu, not_id; /* read values before switching to ID mode */
74 uint8_t manu, id; /* read values when in ID mode */
76 pBase = (uint16_t*)((uint32_t)pBase & 0xFFF80000); /* down to 512k align */
78 /* read the normal content */
79 not_manu = pBase[0]; /* should be 'A' (0x41) and 'R' (0x52) */
80 not_id = pBase[1]; /* from the "ARCH" marker */
82 pBase[0x5555] = 0xAA; /* enter command mode */
83 pBase[0x2AAA] = 0x55;
84 pBase[0x5555] = 0x90; /* ID command */
85 rb->sleep(HZ/50); /* Atmel wants 20ms pause here */
87 manu = pBase[0];
88 id = pBase[1];
90 pBase[0] = 0xF0; /* reset flash (back to normal read mode) */
91 rb->sleep(HZ/50); /* Atmel wants 20ms pause here */
93 /* I assume success if the obtained values are different from
94 the normal flash content. This is not perfectly bulletproof, they
95 could theoretically be the same by chance, causing us to fail. */
96 if (not_manu != manu || not_id != id) /* a value has changed */
98 *pManufacturerID = manu; /* return the results */
99 *pDeviceID = id;
100 return true; /* success */
102 return false; /* fail */
106 /* erase the sector which contains the given address */
107 bool cfi_erase_sector(volatile uint16_t* pAddr)
109 unsigned timeout = 430000; /* the timeout loop should be no less than 25ms */
111 FB[0x5555] = 0xAA; /* enter command mode */
112 FB[0x2AAA] = 0x55;
113 FB[0x5555] = 0x80; /* erase command */
114 FB[0x5555] = 0xAA; /* enter command mode */
115 FB[0x2AAA] = 0x55;
116 *pAddr = 0x30; /* erase the sector */
118 /* I counted 7 instructions for this loop -> min. 0.58 us per round */
119 /* Plus memory waitstates it will be much more, gives margin */
120 while (*pAddr != 0xFFFF && --timeout); /* poll for erased */
122 return (timeout != 0);
126 /* address must be in an erased location */
127 inline bool cfi_program_word(volatile uint16_t* pAddr, uint16_t data)
129 unsigned timeout = 85; /* the timeout loop should be no less than 20us */
131 if (~*pAddr & data) /* just a safety feature, not really necessary */
132 return false; /* can't set any bit from 0 to 1 */
134 FB[0x5555] = 0xAA; /* enter command mode */
135 FB[0x2AAA] = 0x55;
136 FB[0x5555] = 0xA0; /* byte program command */
138 *pAddr = data;
140 /* I counted 7 instructions for this loop -> min. 0.58 us per round */
141 /* Plus memory waitstates it will be much more, gives margin */
142 while (*pAddr != data && --timeout); /* poll for programmed */
144 return (timeout != 0);
148 /* this returns true if supported and fills the info struct */
149 bool cfi_get_flash_info(struct flash_info* pInfo)
151 rb->memset(pInfo, 0, sizeof(struct flash_info));
153 if (!cfi_read_id(FB, &pInfo->manufacturer, &pInfo->id))
154 return false;
156 if (pInfo->manufacturer == 0xBF) /* SST */
158 if (pInfo->id == 0xD6)
160 pInfo->size = 256* 1024; /* 256k */
161 rb->strcpy(pInfo->name, "SST39VF020");
162 return true;
164 else if (pInfo->id == 0xD7)
166 pInfo->size = 512* 1024; /* 512k */
167 rb->strcpy(pInfo->name, "SST39VF040");
168 return true;
170 else if (pInfo->id == 0x82)
172 pInfo->size = 2048* 1024; /* 2 MiB */
173 rb->strcpy(pInfo->name, "SST39VF160");
174 return true;
176 else
177 return false;
179 return false;
183 /*********** Utility Functions ************/
186 /* Tool function to calculate a CRC32 across some buffer */
187 /* third argument is either 0xFFFFFFFF to start or value from last piece */
188 unsigned crc_32(const unsigned char* buf, unsigned len, unsigned crc32)
190 /* CCITT standard polynomial 0x04C11DB7 */
191 static const unsigned crc32_lookup[16] =
192 { /* lookup table for 4 bits at a time is affordable */
193 0x00000000, 0x04C11DB7, 0x09823B6E, 0x0D4326D9,
194 0x130476DC, 0x17C56B6B, 0x1A864DB2, 0x1E475005,
195 0x2608EDB8, 0x22C9F00F, 0x2F8AD6D6, 0x2B4BCB61,
196 0x350C9B64, 0x31CD86D3, 0x3C8EA00A, 0x384FBDBD
199 unsigned char byte;
200 unsigned t;
202 while (len--)
204 byte = *buf++; /* get one byte of data */
206 /* upper nibble of our data */
207 t = crc32 >> 28; /* extract the 4 most significant bits */
208 t ^= byte >> 4; /* XOR in 4 bits of data into the extracted bits */
209 crc32 <<= 4; /* shift the CRC register left 4 bits */
210 crc32 ^= crc32_lookup[t]; /* do the table lookup and XOR the result */
212 /* lower nibble of our data */
213 t = crc32 >> 28; /* extract the 4 most significant bits */
214 t ^= byte & 0x0F; /* XOR in 4 bits of data into the extracted bits */
215 crc32 <<= 4; /* shift the CRC register left 4 bits */
216 crc32 ^= crc32_lookup[t]; /* do the table lookup and XOR the result */
219 return crc32;
223 /***************** User Interface Functions *****************/
224 int wait_for_button(void)
226 int button;
230 button = rb->button_get(true);
231 } while (button & BUTTON_REL);
233 return button;
236 /* helper for DoUserDialog() */
237 void ShowFlashInfo(struct flash_info* pInfo)
239 char buf[32];
241 if (!pInfo->manufacturer)
243 rb->lcd_puts(0, 0, "Flash: M=?? D=??");
244 rb->lcd_puts(0, 1, "Impossible to program");
246 else
248 rb->snprintf(buf, sizeof(buf), "Flash: M=%02x D=%02x",
249 pInfo->manufacturer, pInfo->id);
250 rb->lcd_puts(0, 0, buf);
253 if (pInfo->size)
255 rb->lcd_puts(0, 1, pInfo->name);
256 rb->snprintf(buf, sizeof(buf), "Size: %d KB", pInfo->size / 1024);
257 rb->lcd_puts(0, 2, buf);
259 else
261 rb->lcd_puts(0, 1, "Unsupported chip");
266 rb->lcd_update();
269 bool show_info(void)
271 struct flash_info fi;
273 rb->lcd_clear_display();
274 cfi_get_flash_info(&fi);
275 ShowFlashInfo(&fi);
276 if (fi.size == 0) /* no valid chip */
278 rb->splash(HZ*3, "Sorry!");
279 return false; /* exit */
282 return true;
285 bool confirm(const char *msg)
287 char buf[128];
288 bool ret;
290 rb->snprintf(buf, sizeof buf, "%s ([PLAY] to CONFIRM)", msg);
291 rb->splash(0, buf);
293 ret = (wait_for_button() == BUTTON_ON);
294 show_info();
296 return ret;
299 int load_firmware_file(const char *filename, uint32_t *checksum)
301 int fd;
302 int len, rc;
303 int i;
304 uint32_t sum;
306 fd = rb->open(filename, O_RDONLY);
307 if (fd < 0)
308 return -1;
310 len = rb->filesize(fd);
312 if (audiobuf_size < len)
314 rb->splash(HZ*3, "Aborting: Out of memory!");
315 rb->close(fd);
316 return -2;
319 rb->read(fd, checksum, 4);
320 rb->lseek(fd, FIRMWARE_OFFSET_FILE_DATA, SEEK_SET);
321 len -= FIRMWARE_OFFSET_FILE_DATA;
323 rc = rb->read(fd, audiobuf, len);
324 rb->close(fd);
325 if (rc != len)
327 rb->splash(HZ*3, "Aborting: Read failure");
328 return -3;
331 /* Verify the checksum */
332 sum = 0;
333 for (i = 0; i < len; i++)
334 sum += audiobuf[i];
336 if (sum != *checksum)
338 rb->splash(HZ*3, "Aborting: Checksums mismatch!");
339 return -4;
342 return len;
345 unsigned long valid_bootloaders[][2] = {
346 /* Size-8 CRC32 */
347 { 63788, 0x08ff01a9 }, /* 7-pre3, improved failsafe functions */
348 { 0, 0 }
352 bool detect_valid_bootloader(const unsigned char *addr, int len)
354 int i;
355 unsigned long crc32;
357 /* Try to scan through all valid bootloaders. */
358 for (i = 0; valid_bootloaders[i][0]; i++)
360 if (len > 0 && len != (long)valid_bootloaders[i][0])
361 continue;
363 crc32 = crc_32(addr, valid_bootloaders[i][0], 0xffffffff);
364 if (crc32 == valid_bootloaders[i][1])
365 return true;
368 return false;
371 static int get_section_address(int section)
373 if (section == SECT_RAMIMAGE)
374 return FLASH_RAMIMAGE_ENTRY;
375 else if (section == SECT_ROMIMAGE)
376 return FLASH_ROMIMAGE_ENTRY;
377 else
378 return -1;
381 int flash_rockbox(const char *filename, int section)
383 struct flash_header hdr;
384 char buf[64];
385 int pos, i, len, rc;
386 unsigned long checksum, sum;
387 unsigned char *p8;
388 uint16_t *p16;
390 if (get_section_address(section) < 0)
391 return -1;
393 p8 = (char *)BOOTLOADER_ENTRYPOINT;
394 if (!detect_valid_bootloader(p8, 0))
396 rb->splash(HZ*3, "Incompatible bootloader");
397 return -1;
400 if (!rb->detect_original_firmware())
402 if (!confirm("Update Rockbox flash image?"))
403 return -2;
405 else
407 if (!confirm("Erase original firmware?"))
408 return -3;
411 len = load_firmware_file(filename, &checksum);
412 if (len <= 0)
413 return len * 10;
415 pos = get_section_address(section);
417 /* Check if image relocation seems to be sane. */
418 if (section == SECT_ROMIMAGE)
420 uint32_t *p32 = (uint32_t *)audiobuf;
422 if (pos+sizeof(struct flash_header) != *p32)
424 rb->snprintf(buf, sizeof(buf), "Incorrect relocation: 0x%08lx/0x%08lx",
425 *p32, pos+sizeof(struct flash_header));
426 rb->splash(HZ*10, buf);
427 return -1;
432 /* Erase the program flash. */
433 for (i = 0; i + pos < BOOTLOADER_ENTRYPOINT && i < len + 32; i += SEC_SIZE)
435 /* Additional safety check. */
436 if (i + pos < SEC_SIZE)
437 return -1;
439 rb->snprintf(buf, sizeof(buf), "Erasing... %d%%",
440 (i+SEC_SIZE)*100/len);
441 rb->lcd_puts(0, 3, buf);
442 rb->lcd_update();
444 rc = cfi_erase_sector(FB + (i + pos)/2);
447 /* Write the magic and size. */
448 rb->memset(&hdr, 0, sizeof(struct flash_header));
449 hdr.magic = FLASH_MAGIC;
450 hdr.length = len;
451 // rb->strncpy(hdr.version, APPSVERSION, sizeof(hdr.version)-1);
452 p16 = (uint16_t *)&hdr;
454 rb->snprintf(buf, sizeof(buf), "Programming...");
455 rb->lcd_puts(0, 4, buf);
456 rb->lcd_update();
458 pos = get_section_address(section)/2;
459 for (i = 0; i < (long)sizeof(struct flash_header)/2; i++)
461 cfi_program_word(FB + pos, p16[i]);
462 pos++;
465 p16 = (uint16_t *)audiobuf;
466 for (i = 0; i < len/2 && pos + i < (BOOTLOADER_ENTRYPOINT/2); i++)
468 if (i % SEC_SIZE == 0)
470 rb->snprintf(buf, sizeof(buf), "Programming... %d%%",
471 (i+1)*100/(len/2));
472 rb->lcd_puts(0, 4, buf);
473 rb->lcd_update();
476 cfi_program_word(FB + pos + i, p16[i]);
479 /* Verify */
480 rb->snprintf(buf, sizeof(buf), "Verifying");
481 rb->lcd_puts(0, 5, buf);
482 rb->lcd_update();
484 p8 = (char *)get_section_address(section);
485 p8 += sizeof(struct flash_header);
486 sum = 0;
487 for (i = 0; i < len; i++)
488 sum += p8[i];
490 if (sum != checksum)
492 rb->splash(HZ*3, "Verify failed!");
493 /* Erase the magic sector so bootloader does not try to load
494 * rockbox from flash and crash. */
495 if (section == SECT_RAMIMAGE)
496 cfi_erase_sector(FB + FLASH_RAMIMAGE_ENTRY/2);
497 else
498 cfi_erase_sector(FB + FLASH_ROMIMAGE_ENTRY/2);
499 return -5;
502 rb->splash(HZ*2, "Success");
504 return 0;
507 void show_fatal_error(void)
509 rb->splash(HZ*30, "Disable idle poweroff, connect AC power and DON'T TURN PLAYER OFF!!");
510 rb->splash(HZ*30, "Contact Rockbox developers as soon as possible!");
511 rb->splash(HZ*30, "Your device won't be bricked unless you turn off the power");
512 rb->splash(HZ*30, "Don't use the device before further instructions from Rockbox developers");
515 int flash_bootloader(const char *filename)
517 char *bootsector;
518 int pos, i, len, rc;
519 unsigned long checksum, sum;
520 unsigned char *p8;
521 uint16_t *p16;
523 bootsector = audiobuf;
524 audiobuf += SEC_SIZE;
525 audiobuf_size -= SEC_SIZE;
527 if (!confirm("Update bootloader?"))
528 return -2;
530 len = load_firmware_file(filename, &checksum);
531 if (len <= 0)
532 return len * 10;
534 if (len > 0xFFFF)
536 rb->splash(HZ*3, "Too big bootloader");
537 return -1;
540 /* Verify the crc32 checksum also. */
541 if (!detect_valid_bootloader(audiobuf, len))
543 rb->splash(HZ*3, "Incompatible/Untested bootloader");
544 return -1;
547 rb->lcd_puts(0, 3, "Flashing...");
548 rb->lcd_update();
550 /* Backup the bootloader sector first. */
551 p8 = (char *)FB;
552 rb->memcpy(bootsector, p8, SEC_SIZE);
554 /* Erase the boot sector and write a proper reset vector. */
555 cfi_erase_sector(FB);
556 p16 = (uint16_t *)audiobuf;
557 for (i = 0; i < 8/2; i++)
558 cfi_program_word(FB + i, p16[i]);
560 /* And restore original content for original FW to function. */
561 p16 = (uint16_t *)bootsector;
562 for (i = 8/2; i < SEC_SIZE/2; i++)
563 cfi_program_word(FB + i, p16[i]);
565 /* Erase the bootloader flash section. */
566 for (i = BOOTLOADER_ENTRYPOINT/SEC_SIZE; i < 0x200; i++)
567 rc = cfi_erase_sector(FB + (SEC_SIZE/2) * i);
569 pos = BOOTLOADER_ENTRYPOINT/2;
570 p16 = (uint16_t *)audiobuf;
571 for (i = 0; i < len/2; i++)
572 cfi_program_word(FB + pos + i, p16[i]);
574 /* Verify */
575 p8 = (char *)BOOTLOADER_ENTRYPOINT;
576 sum = 0;
577 for (i = 0; i < len; i++)
578 sum += p8[i];
580 if (sum != checksum)
582 rb->splash(HZ*3, "Verify failed!");
583 show_fatal_error();
584 return -5;
587 p8 = (char *)FB;
588 for (i = 0; i < 8; i++)
590 if (p8[i] != audiobuf[i])
592 rb->splash(HZ*3, "Bootvector corrupt!");
593 show_fatal_error();
594 return -6;
598 rb->splash(HZ*2, "Success");
600 return 0;
603 int flash_original_fw(int len)
605 unsigned char reset_vector[8];
606 char buf[32];
607 int pos, i, rc;
608 unsigned char *p8;
609 uint16_t *p16;
611 (void)buf;
613 rb->lcd_puts(0, 3, "Critical section...");
614 rb->lcd_update();
616 p8 = (char *)FB;
617 rb->memcpy(reset_vector, p8, sizeof reset_vector);
619 /* Erase the boot sector and write back the reset vector. */
620 cfi_erase_sector(FB);
621 p16 = (uint16_t *)reset_vector;
622 for (i = 0; i < (long)sizeof(reset_vector)/2; i++)
623 cfi_program_word(FB + i, p16[i]);
625 rb->lcd_puts(0, 4, "Flashing orig. FW");
626 rb->lcd_update();
628 /* Erase the program flash. */
629 for (i = 1; i < BOOTLOADER_ERASEGUARD && (i-1)*4096 < len; i++)
631 rc = cfi_erase_sector(FB + (SEC_SIZE/2) * i);
632 rb->snprintf(buf, sizeof(buf), "Erase: 0x%03x (%d)", i, rc);
633 rb->lcd_puts(0, 5, buf);
634 rb->lcd_update();
637 rb->snprintf(buf, sizeof(buf), "Programming");
638 rb->lcd_puts(0, 6, buf);
639 rb->lcd_update();
641 pos = 0x00000008/2;
642 p16 = (uint16_t *)audiobuf;
643 for (i = 0; i < len/2 && pos + i < (BOOTLOADER_ENTRYPOINT/2); i++)
644 cfi_program_word(FB + pos + i, p16[i]);
646 rb->snprintf(buf, sizeof(buf), "Verifying");
647 rb->lcd_puts(0, 7, buf);
648 rb->lcd_update();
650 /* Verify reset vectors. */
651 p8 = (char *)FB;
652 for (i = 0; i < 8; i++)
654 if (p8[i] != reset_vector[i])
656 rb->splash(HZ*3, "Bootvector corrupt!");
657 show_fatal_error();
658 break;
662 /* Verify */
663 p8 = (char *)0x00000008;
664 for (i = 0; i < len; i++)
666 if (p8[i] != audiobuf[i])
668 rb->splash(HZ*3, "Verify failed!");
669 rb->snprintf(buf, sizeof buf, "at: 0x%08x", i);
670 rb->splash(HZ*10, buf);
671 return -5;
675 rb->splash(HZ*2, "Success");
677 return 0;
680 int load_original_bin(const char *filename)
682 unsigned long magic[2];
683 int len, rc;
684 int fd;
686 if (!confirm("Restore original firmware (bootloader will be kept)?"))
687 return -2;
689 fd = rb->open(filename, O_RDONLY);
690 if (fd < 0)
691 return -1;
693 len = rb->filesize(fd) - 0x228;
694 rb->lseek(fd, 0x220, SEEK_SET);
695 rb->read(fd, magic, 8);
696 if (magic[1] != 0x00000008 || len <= 0 || len > audiobuf_size)
698 rb->splash(HZ*2, "Not an original firmware file");
699 rb->close(fd);
700 return -1;
703 rc = rb->read(fd, audiobuf, len);
704 rb->close(fd);
706 if (rc != len)
708 rb->splash(HZ*2, "Read error");
709 return -2;
712 if (len % 2)
713 len++;
715 return flash_original_fw(len);
718 int load_romdump(const char *filename)
720 int len, rc;
721 int fd;
723 if (!confirm("Restore firmware section (bootloader will be kept)?"))
724 return -2;
726 fd = rb->open(filename, O_RDONLY);
727 if (fd < 0)
728 return -1;
730 len = rb->filesize(fd) - 8;
731 if (len <= 0)
732 return -1;
734 rb->lseek(fd, 8, SEEK_SET);
735 rc = rb->read(fd, audiobuf, len);
736 rb->close(fd);
738 if (rc != len)
740 rb->splash(HZ*2, "Read error");
741 return -2;
744 if (len % 2)
745 len++;
747 if (len > BOOTLOADER_ENTRYPOINT - 8)
748 len = BOOTLOADER_ENTRYPOINT - 8;
750 return flash_original_fw(len);
753 /* Kind of our main function, defines the application flow. */
754 void DoUserDialog(char* filename)
756 /* this can only work if Rockbox runs in DRAM, not flash ROM */
757 if ((uint16_t*)rb >= FB && (uint16_t*)rb < FB + 4096*1024) /* 4 MB max */
758 { /* we're running from flash */
759 rb->splash(HZ*3, "Not from ROM");
760 return; /* exit */
763 /* refuse to work if the power may fail meanwhile */
764 if (!rb->battery_level_safe())
766 rb->splash(HZ*3, "Battery too low!");
767 return; /* exit */
770 rb->lcd_setfont(FONT_SYSFIXED);
771 if (!show_info())
772 return ;
774 if (filename == NULL)
776 rb->splash(HZ*3, "Please use this plugin with \"Open with...\"");
777 return ;
780 audiobuf = rb->plugin_get_audio_buffer((size_t *)&audiobuf_size);
782 if (rb->strcasestr(filename, "/rockbox.iriver"))
783 flash_rockbox(filename, SECT_RAMIMAGE);
784 else if (rb->strcasestr(filename, "/rombox.iriver"))
785 flash_rockbox(filename, SECT_ROMIMAGE);
786 else if (rb->strcasestr(filename, "/bootloader.iriver"))
787 flash_bootloader(filename);
788 else if (rb->strcasestr(filename, "/ihp_120.bin"))
789 load_original_bin(filename);
790 else if (rb->strcasestr(filename, "/internal_rom_000000-1FFFFF.bin"))
791 load_romdump(filename);
792 else
793 rb->splash(HZ*3, "Unknown file type");
797 /***************** Plugin Entry Point *****************/
799 enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
801 int oldmode;
803 rb = api; /* copy to global api pointer */
805 /* now go ahead and have fun! */
806 oldmode = rb->system_memory_guard(MEMGUARD_NONE); /*disable memory guard */
807 DoUserDialog((char*) parameter);
808 rb->system_memory_guard(oldmode); /* re-enable memory guard */
810 return PLUGIN_OK;
813 #endif /* ifdef PLATFORM_ID */
814 #endif /* #ifndef SIMULATOR */