-
Notifications
You must be signed in to change notification settings - Fork 72
/
rboot.c
586 lines (516 loc) · 16.7 KB
/
rboot.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
//////////////////////////////////////////////////
// rBoot open source boot loader for ESP8266.
// Copyright 2015 Richard A Burton
// richardaburton@gmail.com
// See license.txt for license terms.
//////////////////////////////////////////////////
#include "rboot-private.h"
#include <rboot-hex2a.h>
#ifndef UART_CLK_FREQ
// reset apb freq = 2x crystal freq: http://esp8266-re.foogod.com/wiki/Serial_UART
#define UART_CLK_FREQ (26000000 * 2)
#endif
static uint32_t check_image(uint32_t readpos) {
uint8_t buffer[BUFFER_SIZE];
uint8_t sectcount;
uint8_t sectcurrent;
uint8_t chksum = CHKSUM_INIT;
uint32_t loop;
uint32_t remaining;
uint32_t romaddr;
rom_header_new *header = (rom_header_new*)buffer;
section_header *section = (section_header*)buffer;
if (readpos == 0 || readpos == 0xffffffff) {
return 0;
}
// read rom header
if (SPIRead(readpos, header, sizeof(rom_header_new)) != 0) {
return 0;
}
// check header type
if (header->magic == ROM_MAGIC) {
// old type, no extra header or irom section to skip over
romaddr = readpos;
readpos += sizeof(rom_header);
sectcount = header->count;
} else if (header->magic == ROM_MAGIC_NEW1 && header->count == ROM_MAGIC_NEW2) {
// new type, has extra header and irom section first
romaddr = readpos + header->len + sizeof(rom_header_new);
#ifdef BOOT_IROM_CHKSUM
// we will set the real section count later, when we read the header
sectcount = 0xff;
// just skip the first part of the header
// rest is processed for the chksum
readpos += sizeof(rom_header);
#else
// skip the extra header and irom section
readpos = romaddr;
// read the normal header that follows
if (SPIRead(readpos, header, sizeof(rom_header)) != 0) {
return 0;
}
sectcount = header->count;
readpos += sizeof(rom_header);
#endif
} else {
return 0;
}
// test each section
for (sectcurrent = 0; sectcurrent < sectcount; sectcurrent++) {
// read section header
if (SPIRead(readpos, section, sizeof(section_header)) != 0) {
return 0;
}
readpos += sizeof(section_header);
// get section address and length
remaining = section->length;
while (remaining > 0) {
// work out how much to read, up to BUFFER_SIZE
uint32_t readlen = (remaining < BUFFER_SIZE) ? remaining : BUFFER_SIZE;
// read the block
if (SPIRead(readpos, buffer, readlen) != 0) {
return 0;
}
// increment next read position
readpos += readlen;
// decrement remaining count
remaining -= readlen;
// add to chksum
for (loop = 0; loop < readlen; loop++) {
chksum ^= buffer[loop];
}
}
#ifdef BOOT_IROM_CHKSUM
if (sectcount == 0xff) {
// just processed the irom section, now
// read the normal header that follows
if (SPIRead(readpos, header, sizeof(rom_header)) != 0) {
return 0;
}
sectcount = header->count + 1;
readpos += sizeof(rom_header);
}
#endif
}
// round up to next 16 and get checksum
readpos = readpos | 0x0f;
if (SPIRead(readpos, buffer, 1) != 0) {
return 0;
}
// compare calculated and stored checksums
if (buffer[0] != chksum) {
return 0;
}
return romaddr;
}
#if defined (BOOT_GPIO_ENABLED) || defined(BOOT_GPIO_SKIP_ENABLED)
#if BOOT_GPIO_NUM > 16
#error "Invalid BOOT_GPIO_NUM value (disable BOOT_GPIO_ENABLED to disable this feature)"
#endif
// sample gpio code for gpio16
#define ETS_UNCACHED_ADDR(addr) (addr)
#define READ_PERI_REG(addr) (*((volatile uint32_t *)ETS_UNCACHED_ADDR(addr)))
#define WRITE_PERI_REG(addr, val) (*((volatile uint32_t *)ETS_UNCACHED_ADDR(addr))) = (uint32_t)(val)
#define PERIPHS_RTC_BASEADDR 0x60000700
#define REG_RTC_BASE PERIPHS_RTC_BASEADDR
#define RTC_GPIO_OUT (REG_RTC_BASE + 0x068)
#define RTC_GPIO_ENABLE (REG_RTC_BASE + 0x074)
#define RTC_GPIO_IN_DATA (REG_RTC_BASE + 0x08C)
#define RTC_GPIO_CONF (REG_RTC_BASE + 0x090)
#define PAD_XPD_DCDC_CONF (REG_RTC_BASE + 0x0A0)
static uint32_t get_gpio16(void) {
// set output level to 1
WRITE_PERI_REG(RTC_GPIO_OUT, (READ_PERI_REG(RTC_GPIO_OUT) & (uint32_t)0xfffffffe) | (uint32_t)(1));
// read level
WRITE_PERI_REG(PAD_XPD_DCDC_CONF, (READ_PERI_REG(PAD_XPD_DCDC_CONF) & 0xffffffbc) | (uint32_t)0x1); // mux configuration for XPD_DCDC and rtc_gpio0 connection
WRITE_PERI_REG(RTC_GPIO_CONF, (READ_PERI_REG(RTC_GPIO_CONF) & (uint32_t)0xfffffffe) | (uint32_t)0x0); //mux configuration for out enable
WRITE_PERI_REG(RTC_GPIO_ENABLE, READ_PERI_REG(RTC_GPIO_ENABLE) & (uint32_t)0xfffffffe); //out disable
return (READ_PERI_REG(RTC_GPIO_IN_DATA) & 1);
}
// support for "normal" GPIOs (other than 16)
#define REG_GPIO_BASE 0x60000300
#define GPIO_IN_ADDRESS (REG_GPIO_BASE + 0x18)
#define GPIO_ENABLE_OUT_ADDRESS (REG_GPIO_BASE + 0x0c)
#define REG_IOMUX_BASE 0x60000800
#define IOMUX_PULLUP_MASK (1<<7)
#define IOMUX_FUNC_MASK 0x0130
const uint8_t IOMUX_REG_OFFS[] = {0x34, 0x18, 0x38, 0x14, 0x3c, 0x40, 0x1c, 0x20, 0x24, 0x28, 0x2c, 0x30, 0x04, 0x08, 0x0c, 0x10};
const uint8_t IOMUX_GPIO_FUNC[] = {0x00, 0x30, 0x00, 0x30, 0x00, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30};
static int get_gpio(int gpio_num) {
// disable output buffer if set
uint32_t old_out = READ_PERI_REG(GPIO_ENABLE_OUT_ADDRESS);
uint32_t new_out = old_out & ~ (1<<gpio_num);
WRITE_PERI_REG(GPIO_ENABLE_OUT_ADDRESS, new_out);
// set GPIO function, enable soft pullup
uint32_t iomux_reg = REG_IOMUX_BASE + IOMUX_REG_OFFS[gpio_num];
uint32_t old_iomux = READ_PERI_REG(iomux_reg);
uint32_t gpio_func = IOMUX_GPIO_FUNC[gpio_num];
uint32_t new_iomux = (old_iomux & ~IOMUX_FUNC_MASK) | gpio_func | IOMUX_PULLUP_MASK;
WRITE_PERI_REG(iomux_reg, new_iomux);
// allow soft pullup to take effect if line was floating
ets_delay_us(10);
int result = READ_PERI_REG(GPIO_IN_ADDRESS) & (1<<gpio_num);
// set iomux & GPIO output mode back to initial values
WRITE_PERI_REG(iomux_reg, old_iomux);
WRITE_PERI_REG(GPIO_ENABLE_OUT_ADDRESS, old_out);
return (result ? 1 : 0);
}
// return '1' if we should do a gpio boot
static int perform_gpio_boot(rboot_config *romconf) {
if (romconf->mode & MODE_GPIO_ROM == 0) {
return 0;
}
// pin low == GPIO boot
if (BOOT_GPIO_NUM == 16) {
return (get_gpio16() == 0);
} else {
return (get_gpio(BOOT_GPIO_NUM) == 0);
}
}
#endif
#ifdef BOOT_RTC_ENABLED
uint32_t system_rtc_mem(int32_t addr, void *buff, int32_t length, uint32_t mode) {
int32_t blocks;
// validate reading a user block
if (addr < 64) return 0;
if (buff == 0) return 0;
// validate 4 byte aligned
if (((uint32_t)buff & 0x3) != 0) return 0;
// validate length is multiple of 4
if ((length & 0x3) != 0) return 0;
// check valid length from specified starting point
if (length > (0x300 - (addr * 4))) return 0;
// copy the data
for (blocks = (length >> 2) - 1; blocks >= 0; blocks--) {
volatile uint32_t *ram = ((uint32_t*)buff) + blocks;
volatile uint32_t *rtc = ((uint32_t*)0x60001100) + addr + blocks;
if (mode == RBOOT_RTC_WRITE) {
*rtc = *ram;
} else {
*ram = *rtc;
}
}
return 1;
}
#endif
#ifdef BOOT_BAUDRATE
static enum rst_reason get_reset_reason(void) {
// reset reason is stored @ offset 0 in system rtc memory
volatile uint32_t *rtc = (uint32_t*)0x60001100;
return *rtc;
}
#endif
#if defined(BOOT_CONFIG_CHKSUM) || defined(BOOT_RTC_ENABLED)
// calculate checksum for block of data
// from start up to (but excluding) end
static uint8_t calc_chksum(uint8_t *start, uint8_t *end) {
uint8_t chksum = CHKSUM_INIT;
while(start < end) {
chksum ^= *start;
start++;
}
return chksum;
}
#endif
#ifndef BOOT_CUSTOM_DEFAULT_CONFIG
// populate the user fields of the default config
// created on first boot or in case of corruption
static uint8_t default_config(rboot_config *romconf, uint32_t flashsize) {
romconf->count = 2;
romconf->roms[0] = SECTOR_SIZE * (BOOT_CONFIG_SECTOR + 1);
romconf->roms[1] = (flashsize / 2) + (SECTOR_SIZE * (BOOT_CONFIG_SECTOR + 1));
#ifdef BOOT_GPIO_ENABLED
romconf->mode = MODE_GPIO_ROM;
#endif
#ifdef BOOT_GPIO_SKIP_ENABLED
romconf->mode = MODE_GPIO_SKIP;
#endif
}
#endif
// prevent this function being placed inline with main
// to keep main's stack size as small as possible
// don't mark as static or it'll be optimised out when
// using the assembler stub
uint32_t NOINLINE find_image(void) {
uint8_t flag;
uint32_t loadAddr;
uint32_t flashsize;
int32_t romToBoot;
uint8_t updateConfig = 0;
uint8_t buffer[SECTOR_SIZE];
#ifdef BOOT_GPIO_ENABLED
uint8_t gpio_boot = 0;
#endif
#if defined (BOOT_GPIO_ENABLED) || defined(BOOT_GPIO_SKIP_ENABLED)
uint8_t sec;
#endif
#ifdef BOOT_RTC_ENABLED
rboot_rtc_data rtc;
uint8_t temp_boot = 0;
#endif
rboot_config *romconf = (rboot_config*)buffer;
rom_header *header = (rom_header*)buffer;
#ifdef BOOT_BAUDRATE
// soft reset doesn't reset PLL/divider, so leave as configured
if (get_reset_reason() != REASON_SOFT_RESTART) {
uart_div_modify( 0, UART_CLK_FREQ / BOOT_BAUDRATE);
}
#endif
#if defined BOOT_DELAY_MICROS && BOOT_DELAY_MICROS > 0
// delay to slow boot (help see messages when debugging)
ets_delay_us(BOOT_DELAY_MICROS);
#endif
ets_printf("\r\nrBoot v1.4.2 - richardaburton@gmail.com\r\n");
// read rom header
SPIRead(0, header, sizeof(rom_header));
// print and get flash size
ets_printf("Flash Size: ");
flag = header->flags2 >> 4;
if (flag == 0) {
ets_printf("4 Mbit\r\n");
flashsize = 0x80000;
} else if (flag == 1) {
ets_printf("2 Mbit\r\n");
flashsize = 0x40000;
} else if (flag == 2) {
ets_printf("8 Mbit\r\n");
flashsize = 0x100000;
} else if (flag == 3 || flag == 5) {
ets_printf("16 Mbit\r\n");
#ifdef BOOT_BIG_FLASH
flashsize = 0x200000;
#else
flashsize = 0x100000; // limit to 8Mbit
#endif
} else if (flag == 4 || flag == 6) {
ets_printf("32 Mbit\r\n");
#ifdef BOOT_BIG_FLASH
flashsize = 0x400000;
#else
flashsize = 0x100000; // limit to 8Mbit
#endif
} else if (flag == 8) {
ets_printf("64 Mbit\r\n");
#ifdef BOOT_BIG_FLASH
flashsize = 0x800000;
#else
flashsize = 0x100000; // limit to 8Mbit
#endif
} else if (flag == 9) {
ets_printf("128 Mbit\r\n");
#ifdef BOOT_BIG_FLASH
flashsize = 0x1000000;
#else
flashsize = 0x100000; // limit to 8Mbit
#endif
} else {
ets_printf("unknown\r\n");
// assume at least 4mbit
flashsize = 0x80000;
}
// print spi mode
ets_printf("Flash Mode: ");
if (header->flags1 == 0) {
ets_printf("QIO\r\n");
} else if (header->flags1 == 1) {
ets_printf("QOUT\r\n");
} else if (header->flags1 == 2) {
ets_printf("DIO\r\n");
} else if (header->flags1 == 3) {
ets_printf("DOUT\r\n");
} else {
ets_printf("unknown\r\n");
}
// print spi speed
ets_printf("Flash Speed: ");
flag = header->flags2 & 0x0f;
if (flag == 0) ets_printf("40 MHz\r\n");
else if (flag == 1) ets_printf("26.7 MHz\r\n");
else if (flag == 2) ets_printf("20 MHz\r\n");
else if (flag == 0x0f) ets_printf("80 MHz\r\n");
else ets_printf("unknown\r\n");
// print enabled options
#ifdef BOOT_BIG_FLASH
ets_printf("rBoot Option: Big flash\r\n");
#endif
#ifdef BOOT_CONFIG_CHKSUM
ets_printf("rBoot Option: Config chksum\r\n");
#endif
#ifdef BOOT_GPIO_ENABLED
ets_printf("rBoot Option: GPIO rom mode (%d)\r\n", BOOT_GPIO_NUM);
#endif
#ifdef BOOT_GPIO_SKIP_ENABLED
ets_printf("rBoot Option: GPIO skip mode (%d)\r\n", BOOT_GPIO_NUM);
#endif
#ifdef BOOT_RTC_ENABLED
ets_printf("rBoot Option: RTC data\r\n");
#endif
#ifdef BOOT_IROM_CHKSUM
ets_printf("rBoot Option: irom chksum\r\n");
#endif
ets_printf("\r\n");
// read boot config
SPIRead(BOOT_CONFIG_SECTOR * SECTOR_SIZE, buffer, SECTOR_SIZE);
// fresh install or old version?
if (romconf->magic != BOOT_CONFIG_MAGIC || romconf->version != BOOT_CONFIG_VERSION
#ifdef BOOT_CONFIG_CHKSUM
|| romconf->chksum != calc_chksum((uint8_t*)romconf, (uint8_t*)&romconf->chksum)
#endif
) {
// create a default config for a standard 2 rom setup
ets_printf("Writing default boot config.\r\n");
ets_memset(romconf, 0x00, sizeof(rboot_config));
romconf->magic = BOOT_CONFIG_MAGIC;
romconf->version = BOOT_CONFIG_VERSION;
default_config(romconf, flashsize);
#ifdef BOOT_CONFIG_CHKSUM
romconf->chksum = calc_chksum((uint8_t*)romconf, (uint8_t*)&romconf->chksum);
#endif
// write new config sector
SPIEraseSector(BOOT_CONFIG_SECTOR);
SPIWrite(BOOT_CONFIG_SECTOR * SECTOR_SIZE, buffer, SECTOR_SIZE);
}
// try rom selected in the config, unless overriden by gpio/temp boot
romToBoot = romconf->current_rom;
#ifdef BOOT_RTC_ENABLED
// if rtc data enabled, check for valid data
if (system_rtc_mem(RBOOT_RTC_ADDR, &rtc, sizeof(rboot_rtc_data), RBOOT_RTC_READ) &&
(rtc.chksum == calc_chksum((uint8_t*)&rtc, (uint8_t*)&rtc.chksum))) {
if (rtc.next_mode & MODE_TEMP_ROM) {
if (rtc.temp_rom >= romconf->count) {
ets_printf("Invalid temp rom selected.\r\n");
return 0;
}
ets_printf("Booting temp rom.\r\n");
temp_boot = 1;
romToBoot = rtc.temp_rom;
}
}
#endif
#if defined(BOOT_GPIO_ENABLED) || defined (BOOT_GPIO_SKIP_ENABLED)
if (perform_gpio_boot(romconf)) {
#if defined(BOOT_GPIO_ENABLED)
if (romconf->gpio_rom >= romconf->count) {
ets_printf("Invalid GPIO rom selected.\r\n");
return 0;
}
ets_printf("Booting GPIO-selected rom.\r\n");
romToBoot = romconf->gpio_rom;
gpio_boot = 1;
#elif defined(BOOT_GPIO_SKIP_ENABLED)
romToBoot = romconf->current_rom + 1;
if (romToBoot >= romconf->count) {
romToBoot = 0;
}
romconf->current_rom = romToBoot;
#endif
updateConfig = 1;
if (romconf->mode & MODE_GPIO_ERASES_SDKCONFIG) {
ets_printf("Erasing SDK config sectors before booting.\r\n");
for (sec = 1; sec < 5; sec++) {
SPIEraseSector((flashsize / SECTOR_SIZE) - sec);
}
}
}
#endif
// check valid rom number
// gpio/temp boots will have already validated this
if (romconf->current_rom >= romconf->count) {
// if invalid rom selected try rom 0
ets_printf("Invalid rom selected, defaulting to 0.\r\n");
romToBoot = 0;
romconf->current_rom = 0;
updateConfig = 1;
}
// check rom is valid
loadAddr = check_image(romconf->roms[romToBoot]);
#ifdef BOOT_GPIO_ENABLED
if (gpio_boot && loadAddr == 0) {
// don't switch to backup for gpio-selected rom
ets_printf("GPIO boot rom (%d) is bad.\r\n", romToBoot);
return 0;
}
#endif
#ifdef BOOT_RTC_ENABLED
if (temp_boot && loadAddr == 0) {
// don't switch to backup for temp rom
ets_printf("Temp boot rom (%d) is bad.\r\n", romToBoot);
// make sure rtc temp boot mode doesn't persist
rtc.next_mode = MODE_STANDARD;
rtc.chksum = calc_chksum((uint8_t*)&rtc, (uint8_t*)&rtc.chksum);
system_rtc_mem(RBOOT_RTC_ADDR, &rtc, sizeof(rboot_rtc_data), RBOOT_RTC_WRITE);
return 0;
}
#endif
// check we have a good rom
while (loadAddr == 0) {
ets_printf("Rom %d at %x is bad.\r\n", romToBoot, romconf->roms[romToBoot]);
// for normal mode try each previous rom
// until we find a good one or run out
updateConfig = 1;
romToBoot--;
if (romToBoot < 0) romToBoot = romconf->count - 1;
if (romToBoot == romconf->current_rom) {
// tried them all and all are bad!
ets_printf("No good rom available.\r\n");
return 0;
}
loadAddr = check_image(romconf->roms[romToBoot]);
}
// re-write config, if required
if (updateConfig) {
romconf->current_rom = romToBoot;
#ifdef BOOT_CONFIG_CHKSUM
romconf->chksum = calc_chksum((uint8_t*)romconf, (uint8_t*)&romconf->chksum);
#endif
SPIEraseSector(BOOT_CONFIG_SECTOR);
SPIWrite(BOOT_CONFIG_SECTOR * SECTOR_SIZE, buffer, SECTOR_SIZE);
}
#ifdef BOOT_RTC_ENABLED
// set rtc boot data for app to read
rtc.magic = RBOOT_RTC_MAGIC;
rtc.next_mode = MODE_STANDARD;
rtc.last_mode = MODE_STANDARD;
if (temp_boot) rtc.last_mode |= MODE_TEMP_ROM;
#ifdef BOOT_GPIO_ENABLED
if (gpio_boot) rtc.last_mode |= MODE_GPIO_ROM;
#endif
rtc.last_rom = romToBoot;
rtc.temp_rom = 0;
rtc.chksum = calc_chksum((uint8_t*)&rtc, (uint8_t*)&rtc.chksum);
system_rtc_mem(RBOOT_RTC_ADDR, &rtc, sizeof(rboot_rtc_data), RBOOT_RTC_WRITE);
#endif
ets_printf("Booting rom %d at %x, load addr %x.\r\n", romToBoot, romconf->roms[romToBoot], loadAddr);
// copy the loader to top of iram
ets_memcpy((void*)_text_addr, _text_data, _text_len);
// return address to load from
return loadAddr;
}
#ifdef BOOT_NO_ASM
// small stub method to ensure minimum stack space used
void call_user_start(void) {
uint32_t addr;
stage2a *loader;
addr = find_image();
if (addr != 0) {
loader = (stage2a*)entry_addr;
loader(addr);
}
}
#else
// assembler stub uses no stack space
// works with gcc
void call_user_start(void) {
__asm volatile (
"mov a15, a0\n" // store return addr, hope nobody wanted a15!
"call0 find_image\n" // find a good rom to boot
"mov a0, a15\n" // restore return addr
"bnez a2, 1f\n" // ?success
"ret\n" // no, return
"1:\n" // yes...
"movi a3, entry_addr\n" // get pointer to entry_addr
"l32i a3, a3, 0\n" // get value of entry_addr
"jx a3\n" // now jump to it
);
}
#endif