int check_for_free_memory_corruption(const char * const title) { SERIAL_ECHO(title); char *ptr = END_OF_HEAP(), *sp = top_of_stack(); int n = sp - ptr; SERIAL_ECHOPAIR("\nfmc() n=", n); SERIAL_ECHOPAIR("\n&__brkval: ", hex_address(&__brkval)); SERIAL_ECHOPAIR("=", hex_address(__brkval)); SERIAL_ECHOPAIR("\n__bss_end: ", hex_address(&__bss_end)); SERIAL_ECHOPAIR(" sp=", hex_address(sp)); if (sp < ptr) { SERIAL_ECHOPGM(" sp < Heap "); // SET_INPUT_PULLUP(63); // if the developer has a switch wired up to their controller board // safe_delay(5); // this code can be enabled to pause the display as soon as the // while ( READ(63)) // malfunction is detected. It is currently defaulting to a switch // idle(); // being on pin-63 which is unassigend and available on most controller // safe_delay(20); // boards. // while ( !READ(63)) // idle(); safe_delay(20); #ifdef M100_FREE_MEMORY_DUMPER M100_dump_routine(" Memory corruption detected with sp<Heap\n", (char*)0x1B80, (char*)0x21FF); #endif } // Scan through the range looking for the biggest block of 0xE5's we can find int block_cnt = 0; for (int i = 0; i < n; i++) { if (ptr[i] == TEST_BYTE) { int16_t j = count_test_bytes(ptr + i); if (j > 8) { // SERIAL_ECHOPAIR("Found ", j); // SERIAL_ECHOLNPAIR(" bytes free at ", hex_address(ptr + i)); i += j; block_cnt++; SERIAL_ECHOPAIR(" (", block_cnt); SERIAL_ECHOPAIR(") found=", j); SERIAL_ECHOPGM(" "); } } } SERIAL_ECHOPAIR(" block_found=", block_cnt); if (block_cnt != 1 || __brkval != 0x0000) SERIAL_ECHOLNPGM("\nMemory Corruption detected in free memory area."); if (block_cnt == 0) // Make sure the special case of no free blocks shows up as an block_cnt = -1; // error to the calling code! SERIAL_ECHOPGM(" return="); if (block_cnt == 1) { SERIAL_CHAR('0'); // if the block_cnt is 1, nothing has broken up the free memory SERIAL_EOL(); // area and it is appropriate to say 'no corruption'. return 0; } SERIAL_ECHOLNPGM("true"); return block_cnt; }
/** * M100: Free Memory Check */ void gcode_M100() { SERIAL_ECHOPAIR("\n__brkval : ", hex_address(__brkval)); SERIAL_ECHOPAIR("\n__bss_end : ", hex_address(&__bss_end)); char *ptr = END_OF_HEAP(), *sp = top_of_stack(); SERIAL_ECHOPAIR("\nstart of free space : ", hex_address(ptr)); SERIAL_ECHOLNPAIR("\nStack Pointer : ", hex_address(sp)); // Always init on the first invocation of M100 static bool m100_not_initialized = true; if (m100_not_initialized || parser.seen('I')) { m100_not_initialized = false; init_free_memory(ptr, sp - ptr); } #if ENABLED(M100_FREE_MEMORY_DUMPER) if (parser.seen('D')) return dump_free_memory(ptr, sp); #endif if (parser.seen('F')) return free_memory_pool_report(ptr, sp - ptr); #if ENABLED(M100_FREE_MEMORY_CORRUPTOR) if (parser.seen('C')) return corrupt_free_memory(ptr, parser.value_int()); #endif }
/** * M100 F * Return the number of free bytes in the memory pool, * with other vital statistics defining the pool. */ void free_memory_pool_report(char * const ptr, const int16_t size) { int16_t max_cnt = -1, block_cnt = 0; char *max_addr = NULL; // Find the longest block of test bytes in the buffer for (int16_t i = 0; i < size; i++) { char *addr = ptr + i; if (*addr == TEST_BYTE) { const int16_t j = count_test_bytes(addr); if (j > 8) { SERIAL_ECHOPAIR("Found ", j); SERIAL_ECHOLNPAIR(" bytes free at ", hex_address(addr)); if (j > max_cnt) { max_cnt = j; max_addr = addr; } i += j; block_cnt++; } } } if (block_cnt > 1) { SERIAL_ECHOLNPGM("\nMemory Corruption detected in free memory area."); SERIAL_ECHOPAIR("\nLargest free block is ", max_cnt); SERIAL_ECHOLNPAIR(" bytes at ", hex_address(max_addr)); } SERIAL_ECHOLNPAIR("check_for_free_memory_corruption() = ", check_for_free_memory_corruption("M100 F ")); }
/** * M100 C<num> * Corrupt <num> locations in the free memory pool and report the corrupt addresses. * This is useful to check the correctness of the M100 D and the M100 F commands. */ void corrupt_free_memory(char *ptr, const uint16_t size) { ptr += 8; const uint16_t near_top = top_of_stack() - ptr - 250, // -250 to avoid interrupt activity that's altered the stack. j = near_top / (size + 1); SERIAL_ECHOLNPGM("Corrupting free memory block.\n"); for (uint16_t i = 1; i <= size; i++) { char * const addr = ptr + i * j; *addr = i; SERIAL_ECHOPAIR("\nCorrupting address: ", hex_address(addr)); } SERIAL_EOL(); }
/** * M100 I * Init memory for the M100 tests. (Automatically applied on the first M100.) */ void init_free_memory(char *ptr, int16_t size) { SERIAL_ECHOLNPGM("Initializing free memory block.\n\n"); size -= 250; // -250 to avoid interrupt activity that's altered the stack. if (size < 0) { SERIAL_ECHOLNPGM("Unable to initialize.\n"); return; } ptr += 8; // move a few bytes away from the heap just because we don't want // to be altering memory that close to it. memset(ptr, TEST_BYTE, size); SERIAL_ECHO(size); SERIAL_ECHOLNPGM(" bytes of memory initialized.\n"); for (int16_t i = 0; i < size; i++) { if (ptr[i] != TEST_BYTE) { SERIAL_ECHOPAIR("? address : ", hex_address(ptr + i)); SERIAL_ECHOLNPAIR("=", hex_byte(ptr[i])); SERIAL_EOL(); } } }
// Populate all fields by parsing a single line of GCode // 58 bytes of SRAM are used to speed up seen/value void GCodeParser::parse(char *p) { reset(); // No codes to report // Skip spaces while (*p == ' ') ++p; // Skip N[-0-9] if included in the command line if (*p == 'N' && NUMERIC_SIGNED(p[1])) { #if ENABLED(FASTER_GCODE_PARSER) //set('N', p + 1); // (optional) Set the 'N' parameter value #endif p += 2; // skip N[-0-9] while (NUMERIC(*p)) ++p; // skip [0-9]* while (*p == ' ') ++p; // skip [ ]* } // *p now points to the current command, which should be G, M, or T command_ptr = p; // Get the command letter, which must be G, M, or T const char letter = *p++; // Nullify asterisk and trailing whitespace char *starpos = strchr(p, '*'); if (starpos) { --starpos; // * while (*starpos == ' ') --starpos; // spaces... starpos[1] = '\0'; } // Bail if the letter is not G, M, or T switch (letter) { case 'G': case 'M': case 'T': break; default: return; } // Skip spaces to get the numeric part while (*p == ' ') p++; // Bail if there's no command code number if (!NUMERIC(*p)) return; // Save the command letter at this point // A '?' signifies an unknown command command_letter = letter; // Get the code number - integer digits only codenum = 0; do { codenum *= 10, codenum += *p++ - '0'; } while (NUMERIC(*p)); // Allow for decimal point in command #if USE_GCODE_SUBCODES if (*p == '.') { p++; while (NUMERIC(*p)) subcode *= 10, subcode += *p++ - '0'; } #endif // Skip all spaces to get to the first argument, or nul while (*p == ' ') p++; // The command parameters (if any) start here, for sure! #if DISABLED(FASTER_GCODE_PARSER) command_args = p; // Scan for parameters in seen() #endif // Only use string_arg for these M codes if (letter == 'M') switch (codenum) { case 23: case 28: case 30: case 117: case 118: case 928: string_arg = p; return; default: break; } #if ENABLED(DEBUG_GCODE_PARSER) const bool debug = codenum == 800; #endif /** * Find all parameters, set flags and pointers for fast parsing * * Most codes ignore 'string_arg', but those that want a string will get the right pointer. * The following loop assigns the first "parameter" having no numeric value to 'string_arg'. * This allows M0/M1 with expire time to work: "M0 S5 You Win!" */ string_arg = NULL; while (char code = *p++) { // Get the next parameter. A NUL ends the loop // Special handling for M32 [P] !/path/to/file.g# // The path must be the last parameter if (code == '!' && letter == 'M' && codenum == 32) { string_arg = p; // Name starts after '!' char * const lb = strchr(p, '#'); // Already seen '#' as SD char (to pause buffering) if (lb) *lb = '\0'; // Safe to mark the end of the filename return; } // Arguments MUST be uppercase for fast GCode parsing #if ENABLED(FASTER_GCODE_PARSER) #define PARAM_TEST WITHIN(code, 'A', 'Z') #else #define PARAM_TEST true #endif if (PARAM_TEST) { while (*p == ' ') p++; // Skip spaces between parameters & values const bool has_num = DECIMAL_SIGNED(*p); // The parameter has a number [-+0-9.] #if ENABLED(DEBUG_GCODE_PARSER) if (debug) { SERIAL_ECHOPAIR("Got letter ", code); // DEBUG SERIAL_ECHOPAIR(" at index ", (int)(p - command_ptr - 1)); // DEBUG if (has_num) SERIAL_ECHOPGM(" (has_num)"); } #endif if (!has_num && !string_arg) { // No value? First time, keep as string_arg string_arg = p - 1; #if ENABLED(DEBUG_GCODE_PARSER) if (debug) SERIAL_ECHOPAIR(" string_arg: ", hex_address((void*)string_arg)); // DEBUG #endif } #if ENABLED(DEBUG_GCODE_PARSER) if (debug) SERIAL_EOL(); #endif #if ENABLED(FASTER_GCODE_PARSER) set(code, has_num ? p : NULL // Set parameter exists and pointer (NULL for no number) #if ENABLED(DEBUG_GCODE_PARSER) , debug #endif ); #endif } else if (!string_arg) { // Not A-Z? First time, keep as the string_arg string_arg = p - 1; #if ENABLED(DEBUG_GCODE_PARSER) if (debug) SERIAL_ECHOPAIR(" string_arg: ", hex_address((void*)string_arg)); // DEBUG #endif } if (!WITHIN(*p, 'A', 'Z')) { while (*p && NUMERIC(*p)) p++; // Skip over the value section of a parameter while (*p == ' ') p++; // Skip over all spaces } } }