source: arduino-1-6-7/trunk/fuentes/arduino-ide-amd64/hardware/arduino/avr/bootloaders/optiboot/optiboot.c @ 4837

Last change on this file since 4837 was 4837, checked in by daduve, 2 years ago

Adding new version

File size: 21.8 KB
Line 
1/**********************************************************/
2/* Optiboot bootloader for Arduino                        */
3/*                                                        */
4/* http://optiboot.googlecode.com                         */
5/*                                                        */
6/* Arduino-maintained version : See README.TXT            */
7/* http://code.google.com/p/arduino/                      */
8/*                                                        */
9/* Heavily optimised bootloader that is faster and        */
10/* smaller than the Arduino standard bootloader           */
11/*                                                        */
12/* Enhancements:                                          */
13/*   Fits in 512 bytes, saving 1.5K of code space         */
14/*   Background page erasing speeds up programming        */
15/*   Higher baud rate speeds up programming               */
16/*   Written almost entirely in C                         */
17/*   Customisable timeout with accurate timeconstant      */
18/*   Optional virtual UART. No hardware UART required.    */
19/*   Optional virtual boot partition for devices without. */
20/*                                                        */
21/* What you lose:                                         */
22/*   Implements a skeleton STK500 protocol which is       */
23/*     missing several features including EEPROM          */
24/*     programming and non-page-aligned writes            */
25/*   High baud rate breaks compatibility with standard    */
26/*     Arduino flash settings                             */
27/*                                                        */
28/* Fully supported:                                       */
29/*   ATmega168 based devices  (Diecimila etc)             */
30/*   ATmega328P based devices (Duemilanove etc)           */
31/*                                                        */
32/* Alpha test                                             */
33/*   ATmega1280 based devices (Arduino Mega)              */
34/*                                                        */
35/* Work in progress:                                      */
36/*   ATmega644P based devices (Sanguino)                  */
37/*   ATtiny84 based devices (Luminet)                     */
38/*                                                        */
39/* Does not support:                                      */
40/*   USB based devices (eg. Teensy)                       */
41/*                                                        */
42/* Assumptions:                                           */
43/*   The code makes several assumptions that reduce the   */
44/*   code size. They are all true after a hardware reset, */
45/*   but may not be true if the bootloader is called by   */
46/*   other means or on other hardware.                    */
47/*     No interrupts can occur                            */
48/*     UART and Timer 1 are set to their reset state      */
49/*     SP points to RAMEND                                */
50/*                                                        */
51/* Code builds on code, libraries and optimisations from: */
52/*   stk500boot.c          by Jason P. Kyle               */
53/*   Arduino bootloader    http://www.arduino.cc          */
54/*   Spiff's 1K bootloader http://spiffie.org/know/arduino_1k_bootloader/bootloader.shtml */
55/*   avr-libc project      http://nongnu.org/avr-libc     */
56/*   Adaboot               http://www.ladyada.net/library/arduino/bootloader.html */
57/*   AVR305                Atmel Application Note         */
58/*                                                        */
59/* This program is free software; you can redistribute it */
60/* and/or modify it under the terms of the GNU General    */
61/* Public License as published by the Free Software       */
62/* Foundation; either version 2 of the License, or        */
63/* (at your option) any later version.                    */
64/*                                                        */
65/* This program is distributed in the hope that it will   */
66/* be useful, but WITHOUT ANY WARRANTY; without even the  */
67/* implied warranty of MERCHANTABILITY or FITNESS FOR A   */
68/* PARTICULAR PURPOSE.  See the GNU General Public        */
69/* License for more details.                              */
70/*                                                        */
71/* You should have received a copy of the GNU General     */
72/* Public License along with this program; if not, write  */
73/* to the Free Software Foundation, Inc.,                 */
74/* 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA */
75/*                                                        */
76/* Licence can be viewed at                               */
77/* http://www.fsf.org/licenses/gpl.txt                    */
78/*                                                        */
79/**********************************************************/
80
81
82/**********************************************************/
83/*                                                        */
84/* Optional defines:                                      */
85/*                                                        */
86/**********************************************************/
87/*                                                        */
88/* BIG_BOOT:                                              */
89/* Build a 1k bootloader, not 512 bytes. This turns on    */
90/* extra functionality.                                   */
91/*                                                        */
92/* BAUD_RATE:                                             */
93/* Set bootloader baud rate.                              */
94/*                                                        */
95/* LUDICROUS_SPEED:                                       */
96/* 230400 baud :-)                                        */
97/*                                                        */
98/* SOFT_UART:                                             */
99/* Use AVR305 soft-UART instead of hardware UART.         */
100/*                                                        */
101/* LED_START_FLASHES:                                     */
102/* Number of LED flashes on bootup.                       */
103/*                                                        */
104/* LED_DATA_FLASH:                                        */
105/* Flash LED when transferring data. For boards without   */
106/* TX or RX LEDs, or for people who like blinky lights.   */
107/*                                                        */
108/* SUPPORT_EEPROM:                                        */
109/* Support reading and writing from EEPROM. This is not   */
110/* used by Arduino, so off by default.                    */
111/*                                                        */
112/* TIMEOUT_MS:                                            */
113/* Bootloader timeout period, in milliseconds.            */
114/* 500,1000,2000,4000,8000 supported.                     */
115/*                                                        */
116/**********************************************************/
117
118/**********************************************************/
119/* Version Numbers!                                       */
120/*                                                        */
121/* Arduino Optiboot now includes this Version number in   */
122/* the source and object code.                            */
123/*                                                        */
124/* Version 3 was released as zip from the optiboot        */
125/*  repository and was distributed with Arduino 0022.     */
126/* Version 4 starts with the arduino repository commit    */
127/*  that brought the arduino repository up-to-date with   */
128/* the optiboot source tree changes since v3.             */
129/*                                                        */
130/**********************************************************/
131
132/**********************************************************/
133/* Edit History:                                          */
134/*                                                        */
135/* 4.4 WestfW: add initialization of address to keep      */
136/*             the compiler happy.  Change SC'ed targets. */
137/*             Return the SW version via READ PARAM       */
138/* 4.3 WestfW: catch framing errors in getch(), so that   */
139/*             AVRISP works without HW kludges.           */
140/*  http://code.google.com/p/arduino/issues/detail?id=368n*/
141/* 4.2 WestfW: reduce code size, fix timeouts, change     */
142/*             verifySpace to use WDT instead of appstart */
143/* 4.1 WestfW: put version number in binary.              */
144/**********************************************************/
145
146#define OPTIBOOT_MAJVER 4
147#define OPTIBOOT_MINVER 4
148
149#define MAKESTR(a) #a
150#define MAKEVER(a, b) MAKESTR(a*256+b)
151
152asm("  .section .version\n"
153    "optiboot_version:  .word " MAKEVER(OPTIBOOT_MAJVER, OPTIBOOT_MINVER) "\n"
154    "  .section .text\n");
155
156#include <inttypes.h>
157#include <avr/io.h>
158#include <avr/pgmspace.h>
159
160// <avr/boot.h> uses sts instructions, but this version uses out instructions
161// This saves cycles and program memory.
162#include "boot.h"
163
164
165// We don't use <avr/wdt.h> as those routines have interrupt overhead we don't need.
166
167#include "pin_defs.h"
168#include "stk500.h"
169
170#ifndef LED_START_FLASHES
171#define LED_START_FLASHES 0
172#endif
173
174#ifdef LUDICROUS_SPEED
175#define BAUD_RATE 230400L
176#endif
177
178/* set the UART baud rate defaults */
179#ifndef BAUD_RATE
180#if F_CPU >= 8000000L
181#define BAUD_RATE   115200L // Highest rate Avrdude win32 will support
182#elsif F_CPU >= 1000000L
183#define BAUD_RATE   9600L   // 19200 also supported, but with significant error
184#elsif F_CPU >= 128000L
185#define BAUD_RATE   4800L   // Good for 128kHz internal RC
186#else
187#define BAUD_RATE 1200L     // Good even at 32768Hz
188#endif
189#endif
190
191/* Switch in soft UART for hard baud rates */
192#if (F_CPU/BAUD_RATE) > 280 // > 57600 for 16MHz
193#ifndef SOFT_UART
194#define SOFT_UART
195#endif
196#endif
197
198/* Watchdog settings */
199#define WATCHDOG_OFF    (0)
200#define WATCHDOG_16MS   (_BV(WDE))
201#define WATCHDOG_32MS   (_BV(WDP0) | _BV(WDE))
202#define WATCHDOG_64MS   (_BV(WDP1) | _BV(WDE))
203#define WATCHDOG_125MS  (_BV(WDP1) | _BV(WDP0) | _BV(WDE))
204#define WATCHDOG_250MS  (_BV(WDP2) | _BV(WDE))
205#define WATCHDOG_500MS  (_BV(WDP2) | _BV(WDP0) | _BV(WDE))
206#define WATCHDOG_1S     (_BV(WDP2) | _BV(WDP1) | _BV(WDE))
207#define WATCHDOG_2S     (_BV(WDP2) | _BV(WDP1) | _BV(WDP0) | _BV(WDE))
208#ifndef __AVR_ATmega8__
209#define WATCHDOG_4S     (_BV(WDP3) | _BV(WDE))
210#define WATCHDOG_8S     (_BV(WDP3) | _BV(WDP0) | _BV(WDE))
211#endif
212
213/* Function Prototypes */
214/* The main function is in init9, which removes the interrupt vector table */
215/* we don't need. It is also 'naked', which means the compiler does not    */
216/* generate any entry or exit code itself. */
217int main(void) __attribute__ ((naked)) __attribute__ ((section (".init9")));
218void putch(char);
219uint8_t getch(void);
220static inline void getNch(uint8_t); /* "static inline" is a compiler hint to reduce code size */
221void verifySpace();
222static inline void flash_led(uint8_t);
223uint8_t getLen();
224static inline void watchdogReset();
225void watchdogConfig(uint8_t x);
226#ifdef SOFT_UART
227void uartDelay() __attribute__ ((naked));
228#endif
229void appStart() __attribute__ ((naked));
230
231#if defined(__AVR_ATmega168__)
232#define RAMSTART (0x100)
233#define NRWWSTART (0x3800)
234#elif defined(__AVR_ATmega328P__) || defined(__AVR_ATmega328__)
235#define RAMSTART (0x100)
236#define NRWWSTART (0x7000)
237#elif defined (__AVR_ATmega644P__)
238#define RAMSTART (0x100)
239#define NRWWSTART (0xE000)
240#elif defined(__AVR_ATtiny84__)
241#define RAMSTART (0x100)
242#define NRWWSTART (0x0000)
243#elif defined(__AVR_ATmega1280__)
244#define RAMSTART (0x200)
245#define NRWWSTART (0xE000)
246#elif defined(__AVR_ATmega8__) || defined(__AVR_ATmega88__)
247#define RAMSTART (0x100)
248#define NRWWSTART (0x1800)
249#endif
250
251/* C zero initialises all global variables. However, that requires */
252/* These definitions are NOT zero initialised, but that doesn't matter */
253/* This allows us to drop the zero init code, saving us memory */
254#define buff    ((uint8_t*)(RAMSTART))
255#ifdef VIRTUAL_BOOT_PARTITION
256#define rstVect (*(uint16_t*)(RAMSTART+SPM_PAGESIZE*2+4))
257#define wdtVect (*(uint16_t*)(RAMSTART+SPM_PAGESIZE*2+6))
258#endif
259
260/* main program starts here */
261int main(void) {
262  uint8_t ch;
263
264  /*
265   * Making these local and in registers prevents the need for initializing
266   * them, and also saves space because code no longer stores to memory.
267   * (initializing address keeps the compiler happy, but isn't really
268   *  necessary, and uses 4 bytes of flash.)
269   */
270  register uint16_t address = 0;
271  register uint8_t  length;
272
273  // After the zero init loop, this is the first code to run.
274  //
275  // This code makes the following assumptions:
276  //  No interrupts will execute
277  //  SP points to RAMEND
278  //  r1 contains zero
279  //
280  // If not, uncomment the following instructions:
281  // cli();
282  asm volatile ("clr __zero_reg__");
283#ifdef __AVR_ATmega8__
284  SP=RAMEND;  // This is done by hardware reset
285#endif
286
287  // Adaboot no-wait mod
288  ch = MCUSR;
289  MCUSR = 0;
290  if (!(ch & _BV(EXTRF))) appStart();
291
292#if LED_START_FLASHES > 0
293  // Set up Timer 1 for timeout counter
294  TCCR1B = _BV(CS12) | _BV(CS10); // div 1024
295#endif
296#ifndef SOFT_UART
297#ifdef __AVR_ATmega8__
298  UCSRA = _BV(U2X); //Double speed mode USART
299  UCSRB = _BV(RXEN) | _BV(TXEN);  // enable Rx & Tx
300  UCSRC = _BV(URSEL) | _BV(UCSZ1) | _BV(UCSZ0);  // config USART; 8N1
301  UBRRL = (uint8_t)( (F_CPU + BAUD_RATE * 4L) / (BAUD_RATE * 8L) - 1 );
302#else
303  UCSR0A = _BV(U2X0); //Double speed mode USART0
304  UCSR0B = _BV(RXEN0) | _BV(TXEN0);
305  UCSR0C = _BV(UCSZ00) | _BV(UCSZ01);
306  UBRR0L = (uint8_t)( (F_CPU + BAUD_RATE * 4L) / (BAUD_RATE * 8L) - 1 );
307#endif
308#endif
309
310  // Set up watchdog to trigger after 500ms
311  watchdogConfig(WATCHDOG_1S);
312
313  /* Set LED pin as output */
314  LED_DDR |= _BV(LED);
315
316#ifdef SOFT_UART
317  /* Set TX pin as output */
318  UART_DDR |= _BV(UART_TX_BIT);
319#endif
320
321#if LED_START_FLASHES > 0
322  /* Flash onboard LED to signal entering of bootloader */
323  flash_led(LED_START_FLASHES * 2);
324#endif
325
326  /* Forever loop */
327  for (;;) {
328    /* get character from UART */
329    ch = getch();
330
331    if(ch == STK_GET_PARAMETER) {
332      unsigned char which = getch();
333      verifySpace();
334      if (which == 0x82) {
335        /*
336         * Send optiboot version as "minor SW version"
337         */
338        putch(OPTIBOOT_MINVER);
339      } else if (which == 0x81) {
340          putch(OPTIBOOT_MAJVER);
341      } else {
342        /*
343         * GET PARAMETER returns a generic 0x03 reply for
344         * other parameters - enough to keep Avrdude happy
345         */
346        putch(0x03);
347      }
348    }
349    else if(ch == STK_SET_DEVICE) {
350      // SET DEVICE is ignored
351      getNch(20);
352    }
353    else if(ch == STK_SET_DEVICE_EXT) {
354      // SET DEVICE EXT is ignored
355      getNch(5);
356    }
357    else if(ch == STK_LOAD_ADDRESS) {
358      // LOAD ADDRESS
359      uint16_t newAddress;
360      newAddress = getch();
361      newAddress = (newAddress & 0xff) | (getch() << 8);
362#ifdef RAMPZ
363      // Transfer top bit to RAMPZ
364      RAMPZ = (newAddress & 0x8000) ? 1 : 0;
365#endif
366      newAddress += newAddress; // Convert from word address to byte address
367      address = newAddress;
368      verifySpace();
369    }
370    else if(ch == STK_UNIVERSAL) {
371      // UNIVERSAL command is ignored
372      getNch(4);
373      putch(0x00);
374    }
375    /* Write memory, length is big endian and is in bytes */
376    else if(ch == STK_PROG_PAGE) {
377      // PROGRAM PAGE - we support flash programming only, not EEPROM
378      uint8_t *bufPtr;
379      uint16_t addrPtr;
380
381      getch();                  /* getlen() */
382      length = getch();
383      getch();
384
385      // If we are in RWW section, immediately start page erase
386      if (address < NRWWSTART) __boot_page_erase_short((uint16_t)(void*)address);
387
388      // While that is going on, read in page contents
389      bufPtr = buff;
390      do *bufPtr++ = getch();
391      while (--length);
392
393      // If we are in NRWW section, page erase has to be delayed until now.
394      // Todo: Take RAMPZ into account
395      if (address >= NRWWSTART) __boot_page_erase_short((uint16_t)(void*)address);
396
397      // Read command terminator, start reply
398      verifySpace();
399
400      // If only a partial page is to be programmed, the erase might not be complete.
401      // So check that here
402      boot_spm_busy_wait();
403
404#ifdef VIRTUAL_BOOT_PARTITION
405      if ((uint16_t)(void*)address == 0) {
406        // This is the reset vector page. We need to live-patch the code so the
407        // bootloader runs.
408        //
409        // Move RESET vector to WDT vector
410        uint16_t vect = buff[0] | (buff[1]<<8);
411        rstVect = vect;
412        wdtVect = buff[8] | (buff[9]<<8);
413        vect -= 4; // Instruction is a relative jump (rjmp), so recalculate.
414        buff[8] = vect & 0xff;
415        buff[9] = vect >> 8;
416
417        // Add jump to bootloader at RESET vector
418        buff[0] = 0x7f;
419        buff[1] = 0xce; // rjmp 0x1d00 instruction
420      }
421#endif
422
423      // Copy buffer into programming buffer
424      bufPtr = buff;
425      addrPtr = (uint16_t)(void*)address;
426      ch = SPM_PAGESIZE / 2;
427      do {
428        uint16_t a;
429        a = *bufPtr++;
430        a |= (*bufPtr++) << 8;
431        __boot_page_fill_short((uint16_t)(void*)addrPtr,a);
432        addrPtr += 2;
433      } while (--ch);
434
435      // Write from programming buffer
436      __boot_page_write_short((uint16_t)(void*)address);
437      boot_spm_busy_wait();
438
439#if defined(RWWSRE)
440      // Reenable read access to flash
441      boot_rww_enable();
442#endif
443
444    }
445    /* Read memory block mode, length is big endian.  */
446    else if(ch == STK_READ_PAGE) {
447      // READ PAGE - we only read flash
448      getch();                  /* getlen() */
449      length = getch();
450      getch();
451
452      verifySpace();
453#ifdef VIRTUAL_BOOT_PARTITION
454      do {
455        // Undo vector patch in bottom page so verify passes
456        if (address == 0)       ch=rstVect & 0xff;
457        else if (address == 1)  ch=rstVect >> 8;
458        else if (address == 8)  ch=wdtVect & 0xff;
459        else if (address == 9) ch=wdtVect >> 8;
460        else ch = pgm_read_byte_near(address);
461        address++;
462        putch(ch);
463      } while (--length);
464#else
465#ifdef __AVR_ATmega1280__
466//      do putch(pgm_read_byte_near(address++));
467//      while (--length);
468      do {
469        uint8_t result;
470        __asm__ ("elpm %0,Z\n":"=r"(result):"z"(address));
471        putch(result);
472        address++;
473      }
474      while (--length);
475#else
476      do putch(pgm_read_byte_near(address++));
477      while (--length);
478#endif
479#endif
480    }
481
482    /* Get device signature bytes  */
483    else if(ch == STK_READ_SIGN) {
484      // READ SIGN - return what Avrdude wants to hear
485      verifySpace();
486      putch(SIGNATURE_0);
487      putch(SIGNATURE_1);
488      putch(SIGNATURE_2);
489    }
490    else if (ch == 'Q') {
491      // Adaboot no-wait mod
492      watchdogConfig(WATCHDOG_16MS);
493      verifySpace();
494    }
495    else {
496      // This covers the response to commands like STK_ENTER_PROGMODE
497      verifySpace();
498    }
499    putch(STK_OK);
500  }
501}
502
503void putch(char ch) {
504#ifndef SOFT_UART
505  while (!(UCSR0A & _BV(UDRE0)));
506  UDR0 = ch;
507#else
508  __asm__ __volatile__ (
509    "   com %[ch]\n" // ones complement, carry set
510    "   sec\n"
511    "1: brcc 2f\n"
512    "   cbi %[uartPort],%[uartBit]\n"
513    "   rjmp 3f\n"
514    "2: sbi %[uartPort],%[uartBit]\n"
515    "   nop\n"
516    "3: rcall uartDelay\n"
517    "   rcall uartDelay\n"
518    "   lsr %[ch]\n"
519    "   dec %[bitcnt]\n"
520    "   brne 1b\n"
521    :
522    :
523      [bitcnt] "d" (10),
524      [ch] "r" (ch),
525      [uartPort] "I" (_SFR_IO_ADDR(UART_PORT)),
526      [uartBit] "I" (UART_TX_BIT)
527    :
528      "r25"
529  );
530#endif
531}
532
533uint8_t getch(void) {
534  uint8_t ch;
535
536#ifdef LED_DATA_FLASH
537#ifdef __AVR_ATmega8__
538  LED_PORT ^= _BV(LED);
539#else
540  LED_PIN |= _BV(LED);
541#endif
542#endif
543
544#ifdef SOFT_UART
545  __asm__ __volatile__ (
546    "1: sbic  %[uartPin],%[uartBit]\n"  // Wait for start edge
547    "   rjmp  1b\n"
548    "   rcall uartDelay\n"          // Get to middle of start bit
549    "2: rcall uartDelay\n"              // Wait 1 bit period
550    "   rcall uartDelay\n"              // Wait 1 bit period
551    "   clc\n"
552    "   sbic  %[uartPin],%[uartBit]\n"
553    "   sec\n"
554    "   dec   %[bitCnt]\n"
555    "   breq  3f\n"
556    "   ror   %[ch]\n"
557    "   rjmp  2b\n"
558    "3:\n"
559    :
560      [ch] "=r" (ch)
561    :
562      [bitCnt] "d" (9),
563      [uartPin] "I" (_SFR_IO_ADDR(UART_PIN)),
564      [uartBit] "I" (UART_RX_BIT)
565    :
566      "r25"
567);
568#else
569  while(!(UCSR0A & _BV(RXC0)))
570    ;
571  if (!(UCSR0A & _BV(FE0))) {
572      /*
573       * A Framing Error indicates (probably) that something is talking
574       * to us at the wrong bit rate.  Assume that this is because it
575       * expects to be talking to the application, and DON'T reset the
576       * watchdog.  This should cause the bootloader to abort and run
577       * the application "soon", if it keeps happening.  (Note that we
578       * don't care that an invalid char is returned...)
579       */
580    watchdogReset();
581  }
582 
583  ch = UDR0;
584#endif
585
586#ifdef LED_DATA_FLASH
587#ifdef __AVR_ATmega8__
588  LED_PORT ^= _BV(LED);
589#else
590  LED_PIN |= _BV(LED);
591#endif
592#endif
593
594  return ch;
595}
596
597#ifdef SOFT_UART
598// AVR350 equation: #define UART_B_VALUE (((F_CPU/BAUD_RATE)-23)/6)
599// Adding 3 to numerator simulates nearest rounding for more accurate baud rates
600#define UART_B_VALUE (((F_CPU/BAUD_RATE)-20)/6)
601#if UART_B_VALUE > 255
602#error Baud rate too slow for soft UART
603#endif
604
605void uartDelay() {
606  __asm__ __volatile__ (
607    "ldi r25,%[count]\n"
608    "1:dec r25\n"
609    "brne 1b\n"
610    "ret\n"
611    ::[count] "M" (UART_B_VALUE)
612  );
613}
614#endif
615
616void getNch(uint8_t count) {
617  do getch(); while (--count);
618  verifySpace();
619}
620
621void verifySpace() {
622  if (getch() != CRC_EOP) {
623    watchdogConfig(WATCHDOG_16MS);    // shorten WD timeout
624    while (1)                         // and busy-loop so that WD causes
625      ;                               //  a reset and app start.
626  }
627  putch(STK_INSYNC);
628}
629
630#if LED_START_FLASHES > 0
631void flash_led(uint8_t count) {
632  do {
633    TCNT1 = -(F_CPU/(1024*16));
634    TIFR1 = _BV(TOV1);
635    while(!(TIFR1 & _BV(TOV1)));
636#ifdef __AVR_ATmega8__
637    LED_PORT ^= _BV(LED);
638#else
639    LED_PIN |= _BV(LED);
640#endif
641    watchdogReset();
642  } while (--count);
643}
644#endif
645
646// Watchdog functions. These are only safe with interrupts turned off.
647void watchdogReset() {
648  __asm__ __volatile__ (
649    "wdr\n"
650  );
651}
652
653void watchdogConfig(uint8_t x) {
654  WDTCSR = _BV(WDCE) | _BV(WDE);
655  WDTCSR = x;
656}
657
658void appStart() {
659  watchdogConfig(WATCHDOG_OFF);
660  __asm__ __volatile__ (
661#ifdef VIRTUAL_BOOT_PARTITION
662    // Jump to WDT vector
663    "ldi r30,4\n"
664    "clr r31\n"
665#else
666    // Jump to RST vector
667    "clr r30\n"
668    "clr r31\n"
669#endif
670    "ijmp\n"
671  );
672}
Note: See TracBrowser for help on using the repository browser.