1 //*****************************************************************************
2 //
3 //! @file am_hal_security.c
4 //!
5 //! @brief Functions for on-chip security features
6 //!
7 //! @addtogroup security Security - On-Chip Security Functionality
8 //! @ingroup apollo3_hal
9 //! @{
10 //
11 //*****************************************************************************
12
13 //*****************************************************************************
14 //
15 // Copyright (c) 2024, Ambiq Micro, Inc.
16 // All rights reserved.
17 //
18 // Redistribution and use in source and binary forms, with or without
19 // modification, are permitted provided that the following conditions are met:
20 //
21 // 1. Redistributions of source code must retain the above copyright notice,
22 // this list of conditions and the following disclaimer.
23 //
24 // 2. Redistributions in binary form must reproduce the above copyright
25 // notice, this list of conditions and the following disclaimer in the
26 // documentation and/or other materials provided with the distribution.
27 //
28 // 3. Neither the name of the copyright holder nor the names of its
29 // contributors may be used to endorse or promote products derived from this
30 // software without specific prior written permission.
31 //
32 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
33 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
34 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
35 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
36 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
37 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
38 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
39 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
40 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
41 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
42 // POSSIBILITY OF SUCH DAMAGE.
43 //
44 // This is part of revision release_sdk_3_2_0-dd5f40c14b of the AmbiqSuite Development Package.
45 //
46 //*****************************************************************************
47 #include <stdint.h>
48 #include <stdbool.h>
49 #include "am_mcu_apollo.h"
50
51 //*****************************************************************************
52 // Local defines.
53 //*****************************************************************************
54 //
55 //! ENABLE_EXTMEM_CRC
56 //! By default, the CRC engine can only operate on data located in internal
57 //! memory (i.e. flash or SRAM). This define enables am_hal_crc() to support
58 //! external memories, but requires a small amount of global SRAM allocated for
59 //! that purpose. If it is not desired to support this feature, set to 0.
60 //
61 #define ENABLE_EXTMEM_CRC 1
62
63 //
64 //! Maximum iterations for hardware CRC to finish
65 //
66 #define MAX_CRC_WAIT 100000
67
68 #define AM_HAL_SECURITY_LOCKSTAT_CUSTOMER 0x1
69 #define AM_HAL_SECURITY_LOCKSTAT_RECOVERY 0x40000000
70
71 //*****************************************************************************
72 //
73 // Globals
74 //
75 //*****************************************************************************
76 #if ENABLE_EXTMEM_CRC
77 //
78 //! Set up a small global buffer that can be used am_hal_crc32() when
79 //! computing CRCs on external memory.
80 //
81 #define CRC_XFERBUF_SZ (512) // Reserve 512 bytes for the buffer
82 static uint32_t g_CRC_buffer[CRC_XFERBUF_SZ / 4];
83 #endif // ENABLE_EXTMEM_CRC
84
85 //
86 // Assign ptr variables to avoid an issue with GCC reading from location 0x0.
87 //
88 const volatile uint32_t *g_pFlash0 = (uint32_t*)(AM_HAL_SBL_ADDRESS + 0);
89 const volatile uint32_t *g_pFlash4 = (uint32_t*)(AM_HAL_SBL_ADDRESS + 4);
90
91 //*****************************************************************************
92 //
93 //! @brief Hardcoded function - to Run supplied main program
94 //!
95 //! @param r0 = vtor - address of the vector table
96 //!
97 //! @return Returns None
98 //
99 //*****************************************************************************
100 #if (defined (__ARMCC_VERSION)) && (__ARMCC_VERSION < 6000000)
101 static __asm void
bl_run_main(uint32_t * vtor)102 bl_run_main(uint32_t *vtor)
103 {
104 //
105 // Store the vector table pointer of the new image into VTOR.
106 //
107 movw r3, #0xED08
108 movt r3, #0xE000
109 str r0, [r3, #0]
110
111 //
112 // Load the new stack pointer into R1 and the new reset vector into R2.
113 //
114 ldr r3, [r0, #0]
115 ldr r2, [r0, #4]
116
117 //
118 // Set the stack pointer for the new image.
119 //
120 mov sp, r3
121
122 //
123 // Jump to the new reset vector.
124 //
125 bx r2
126 }
127 #elif (defined (__ARMCC_VERSION)) && (__ARMCC_VERSION >= 6000000)
128 __attribute__((naked))
129 static void
bl_run_main(uint32_t * vtor)130 bl_run_main(uint32_t *vtor)
131 {
132 __asm
133 (
134 " movw r3, #0xED08\n\t" // Store the vector table pointer of the new image into VTOR.
135 " movt r3, #0xE000\n\t"
136 " str r0, [r3, #0]\n\t"
137 " ldr r3, [r0, #0]\n\t" // Load the new stack pointer into R1 and the new reset vector into R2.
138 " ldr r2, [r0, #4]\n\t"
139 " mov sp, r3\n\t" // Set the stack pointer for the new image.
140 " bx r2\n\t" // Jump to the new reset vector.
141 );
142 }
143 #elif defined(__GNUC_STDC_INLINE__)
144 __attribute__((naked))
145 static void
bl_run_main(uint32_t * vtor)146 bl_run_main(uint32_t *vtor)
147 {
148 __asm
149 (
150 " movw r3, #0xED08\n\t" // Store the vector table pointer of the new image into VTOR.
151 " movt r3, #0xE000\n\t"
152 " str r0, [r3, #0]\n\t"
153 " ldr r3, [r0, #0]\n\t" // Load the new stack pointer into R1 and the new reset vector into R2.
154 " ldr r2, [r0, #4]\n\t"
155 " mov sp, r3\n\t" // Set the stack pointer for the new image.
156 " bx r2\n\t" // Jump to the new reset vector.
157 );
158 }
159 #elif defined(__IAR_SYSTEMS_ICC__)
160 __stackless static inline void
bl_run_main(uint32_t * vtor)161 bl_run_main(uint32_t *vtor)
162 {
163 __asm volatile (
164 " movw r3, #0xED08\n" // Store the vector table pointer of the new image into VTOR.
165 " movt r3, #0xE000\n"
166 " str r0, [r3, #0]\n"
167 " ldr r3, [r0, #0]\n" // Load the new stack pointer into R1 and the new reset vector into R2.
168 " ldr r2, [r0, #4]\n"
169 " mov sp, r3\n" // Set the stack pointer for the new image.
170 " bx r2\n" // Jump to the new reset vector.
171 );
172 }
173 #else
174 #error Compiler is unknown, please contact Ambiq support team
175 #endif
176
177 //
178 // Pre- SBLv2 known versions that do not support callback
179 //
180 static uint32_t sblPreV2[][4] =
181 {
182 //
183 // flash0, flash4, sblVersion, sblVersionAddInfo
184 //
185 {0xA3007860, 0x2E2638FB, 0 , 0},
186 {0xA3007E14, 0x5EE4E461, 1 , 0},
187 {0xA3008290, 0xB49CECD5, 2 , 0},
188 };
189
190 //*****************************************************************************
191 //
192 // @brief Get Device Security Info
193 //
194 // @param pSecInfo - Pointer to structure for returned security info
195 //
196 // This will retrieve the security information for the device
197 //
198 // @return Returns AM_HAL_STATUS_SUCCESS on success
199 //
200 //*****************************************************************************
201 uint32_t
am_hal_security_get_info(am_hal_security_info_t * pSecInfo)202 am_hal_security_get_info(am_hal_security_info_t *pSecInfo)
203 {
204 if ( !pSecInfo )
205 {
206 return AM_HAL_STATUS_INVALID_ARG;
207 }
208
209 pSecInfo->info0Version = AM_REGVAL(0x50020040);
210 pSecInfo->bInfo0Valid = MCUCTRL->SHADOWVALID_b.INFO0_VALID;
211
212 if ( MCUCTRL->BOOTLOADER_b.SECBOOTFEATURE )
213 {
214 uint32_t ux, flash0, flash4;
215
216 //
217 // Check if we're running pre-SBLv2
218 //
219 flash0 = *g_pFlash0;
220 flash4 = *g_pFlash4;
221
222 //
223 // Check if SBL is installed
224 //
225 if ( (flash0 >> 24) != AM_IMAGE_MAGIC_SBL )
226 {
227 return AM_HAL_STATUS_FAIL;
228 }
229
230 for ( ux = 0; ux < sizeof(sblPreV2) / sizeof(sblPreV2[0]); ux++ )
231 {
232 if ((sblPreV2[ux][0] == flash0) && (sblPreV2[ux][1] == flash4))
233 {
234 // This is a device prior to SBLv2
235 pSecInfo->sblVersion = sblPreV2[ux][2];
236 pSecInfo->sblVersionAddInfo = sblPreV2[ux][3];
237 break;
238 }
239 }
240
241 if ( ux == (sizeof(sblPreV2) / sizeof(sblPreV2[0])) )
242 {
243 //
244 // SBLv2 or beyond
245 // Use SBL jump table function
246 //
247 uint32_t status;
248 uint32_t sblVersion;
249 uint32_t (*pFuncVersion)(uint32_t *) = (uint32_t (*)(uint32_t *))(AM_HAL_SBL_ADDRESS + 0x1D1);
250 status = pFuncVersion(&sblVersion);
251
252 if (status != AM_HAL_STATUS_SUCCESS)
253 {
254 return status;
255 }
256
257 pSecInfo->sblVersion = sblVersion & 0x7FFF;
258 pSecInfo->sblVersionAddInfo = sblVersion >> 15;
259 }
260 }
261 else
262 {
263 return AM_HAL_STATUS_FAIL;
264 }
265
266 return AM_HAL_STATUS_SUCCESS;
267
268 } // am_hal_security_get_info()
269
270 //*****************************************************************************
271 //
272 // @brief Set the key for specified lock
273 //
274 // @param lockType - The lock type to be operated upon
275 // @param pKey - Pointer to 128b key value
276 //
277 // This will program the lock registers for the specified lock and key
278 //
279 // @return Returns AM_HAL_STATUS_SUCCESS on success
280 //
281 //*****************************************************************************
282 uint32_t
am_hal_security_set_key(am_hal_security_locktype_t lockType,am_hal_security_128bkey_t * pKey)283 am_hal_security_set_key(am_hal_security_locktype_t lockType, am_hal_security_128bkey_t *pKey)
284 {
285 #ifndef AM_HAL_DISABLE_API_VALIDATION
286 if (pKey == NULL)
287 {
288 return AM_HAL_STATUS_INVALID_ARG;
289 }
290 switch (lockType)
291 {
292 case AM_HAL_SECURITY_LOCKTYPE_CUSTOMER:
293 case AM_HAL_SECURITY_LOCKTYPE_RECOVERY:
294 break;
295 default:
296 return AM_HAL_STATUS_INVALID_ARG;
297 }
298 #endif // AM_HAL_DISABLE_API_VALIDATION
299
300 #if defined(__GNUC_STDC_INLINE__)
301 //
302 // The GCC compiler flags the following accesses to key1, key2, and key3 as
303 // "may be used uninitialized in this function". Online comments suggest that
304 // this may be a compiler bug because how would it possibly know that they're
305 // uninitialized? Ignore this warning with this ugly workaround.
306 //
307 #pragma GCC diagnostic push
308 #pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
309 #endif
310 SECURITY->LOCKCTRL = lockType;
311 SECURITY->KEY0 = pKey->keys.key0;
312 SECURITY->KEY1 = pKey->keys.key1;
313 SECURITY->KEY2 = pKey->keys.key2;
314 SECURITY->KEY3 = pKey->keys.key3;
315 #if defined(__GNUC_STDC_INLINE__)
316 #pragma GCC diagnostic pop
317 #endif
318
319 return AM_HAL_STATUS_SUCCESS;
320 } // am_hal_security_set_key()
321
322 //*****************************************************************************
323 //
324 // @brief Get the current status of the specified lock
325 //
326 // @param lockType - The lock type to be operated upon
327 // @param pbUnlockStatus - Pointer to return variable with lock status
328 //
329 // This will get the lock status for specified lock - true implies unlocked
330 // Note that except for customer lock, other locks are self-locking on status read
331 //
332 // @return Returns AM_HAL_STATUS_SUCCESS on success
333 //
334 //*****************************************************************************
335 uint32_t
am_hal_security_get_lock_status(am_hal_security_locktype_t lockType,bool * pbUnlockStatus)336 am_hal_security_get_lock_status(am_hal_security_locktype_t lockType, bool *pbUnlockStatus)
337 {
338 uint32_t unlockMask;
339 #ifndef AM_HAL_DISABLE_API_VALIDATION
340 if (pbUnlockStatus == NULL)
341 {
342 return AM_HAL_STATUS_INVALID_ARG;
343 }
344 #endif // AM_HAL_DISABLE_API_VALIDATION
345 switch (lockType)
346 {
347 case AM_HAL_SECURITY_LOCKTYPE_CUSTOMER:
348 unlockMask = AM_HAL_SECURITY_LOCKSTAT_CUSTOMER;
349 break;
350 case AM_HAL_SECURITY_LOCKTYPE_RECOVERY:
351 unlockMask = AM_HAL_SECURITY_LOCKSTAT_RECOVERY;
352 break;
353 default:
354 return AM_HAL_STATUS_INVALID_ARG;
355 }
356
357 *pbUnlockStatus = SECURITY->LOCKSTAT & unlockMask;
358
359 return AM_HAL_STATUS_SUCCESS;
360
361 } // am_hal_security_get_lock_status()
362
363 //*****************************************************************************
364 //
365 // @brief Initialize CRC32 engine
366 //
367 // This will initialize the hardware engine to compute CRC32 on an arbitrary data payload
368 //
369 // @return Returns AM_HAL_STATUS_SUCCESS on success
370 //
371 //*****************************************************************************
372 uint32_t
am_hal_crc32_init(void)373 am_hal_crc32_init(void)
374 {
375 if (SECURITY->CTRL_b.ENABLE)
376 {
377 return AM_HAL_STATUS_IN_USE;
378 }
379
380 //
381 // Program the CRC engine to compute the crc
382 //
383 SECURITY->RESULT = 0xFFFFFFFF;
384 SECURITY->CTRL_b.FUNCTION = SECURITY_CTRL_FUNCTION_CRC32;
385
386 return AM_HAL_STATUS_SUCCESS;
387 } // am_hal_crc32_init()
388
389 //*****************************************************************************
390 //
391 // @brief Accumulate CRC32 for a specified payload
392 //
393 // @param ui32StartAddr - The start address of the payload
394 // @param ui32SizeBytes - The length of payload in bytes
395 // @param pui32Crc - Pointer to accumulated CRC
396 //
397 // This will use the hardware engine to compute CRC32 on an arbitrary data payload
398 //
399 // @return Returns AM_HAL_STATUS_SUCCESS on success
400 //
401 //*****************************************************************************
402 uint32_t
am_hal_crc32_accum(uint32_t ui32StartAddr,uint32_t ui32SizeBytes,uint32_t * pui32Crc)403 am_hal_crc32_accum(uint32_t ui32StartAddr, uint32_t ui32SizeBytes, uint32_t *pui32Crc)
404 {
405 uint32_t status, ui32CRC32;
406 bool bInternal;
407
408 #ifndef AM_HAL_DISABLE_API_VALIDATION
409 if (pui32Crc == NULL)
410 {
411 return AM_HAL_STATUS_INVALID_ARG;
412 }
413
414 //
415 // Make sure size is multiple of 4 bytes
416 //
417 if (ui32SizeBytes & 0x3)
418 {
419 return AM_HAL_STATUS_INVALID_ARG;
420 }
421 #endif // AM_HAL_DISABLE_API_VALIDATION
422
423 status = AM_HAL_STATUS_OUT_OF_RANGE; // Default status
424
425 //
426 // Determine whether the startaddr is in internal flash or SRAM.
427 //
428 bInternal = ISADDRFLASH(ui32StartAddr) || ISADDRSRAM(ui32StartAddr);
429
430 if ( bInternal )
431 {
432 SECURITY->SRCADDR = ui32StartAddr;
433 SECURITY->LEN_b.LEN = (ui32SizeBytes >> SECURITY_LEN_LEN_Pos);
434 // Start the CRC
435 SECURITY->CTRL_b.ENABLE = 1;
436
437 //
438 // Wait for CRC to finish
439 //
440 status = am_hal_flash_delay_status_change(MAX_CRC_WAIT,
441 (uint32_t)&SECURITY->CTRL, SECURITY_CTRL_ENABLE_Msk, 0);
442
443
444 if ( (status == AM_HAL_STATUS_SUCCESS) && !SECURITY->CTRL_b.CRCERROR )
445 {
446 *pui32Crc = SECURITY->RESULT;
447 }
448 else if ( SECURITY->CTRL_b.CRCERROR )
449 {
450 status = AM_HAL_STATUS_HW_ERR;
451 }
452 else
453 {
454 //
455 // Error from status_change function.
456 // Return the CRC value we do have, but return an error.
457 //
458 }
459
460 return status;
461 }
462
463 #if ENABLE_EXTMEM_CRC
464 uint32_t ui32XferSize, ui32cnt;
465 uint32_t *pui32Buf, *pui32Data;
466
467 //
468 // If we're here, the source data resides in non-internal memory (that is,
469 // not flash or SRAM).
470 //
471 // Begin the loop for computing the CRC of the external memory. The data
472 // will first be copied to the SRAM buffer.
473 //
474 // Program the parts of the CRC engine that will not need to change
475 // inside the loop: SRCADDR.
476 // While inside the loop, only the LEN will need to be provided.
477 //
478 ui32CRC32 = *pui32Crc;
479 SECURITY->SRCADDR = (uint32_t)&g_CRC_buffer[0];
480 pui32Data = (uint32_t*)ui32StartAddr;
481 while ( ui32SizeBytes )
482 {
483 //
484 // First copy a chunk of payload data to SRAM where the CRC engine
485 // can operate on it.
486 //
487 ui32XferSize = (ui32SizeBytes >= CRC_XFERBUF_SZ) ?
488 CRC_XFERBUF_SZ : ui32SizeBytes;
489 ui32SizeBytes -= ui32XferSize;
490 ui32cnt = ui32XferSize / 4;
491 pui32Buf = &g_CRC_buffer[0];
492 while ( ui32cnt-- )
493 {
494 *pui32Buf++ = *pui32Data++;
495 }
496
497 //
498 // Program the CRC engine's LEN parameter.
499 // All other parameters were preprogrammed: SRCADDR, FUNCTION, RESULT.
500 //
501 SECURITY->LEN_b.LEN = (ui32XferSize >> SECURITY_LEN_LEN_Pos);
502
503 //
504 // Start the CRC
505 //
506 SECURITY->CTRL_b.ENABLE = 1;
507
508 //
509 // Wait for CRC to finish
510 //
511 status = am_hal_flash_delay_status_change(MAX_CRC_WAIT,
512 (uint32_t)&SECURITY->CTRL, SECURITY_CTRL_ENABLE_Msk, 0);
513
514 if ( (status == AM_HAL_STATUS_SUCCESS) && !SECURITY->CTRL_b.CRCERROR )
515 {
516 ui32CRC32 = SECURITY->RESULT;
517 }
518 else if ( SECURITY->CTRL_b.CRCERROR )
519 {
520 return AM_HAL_STATUS_HW_ERR;
521 }
522 else
523 {
524 //
525 // Error from status_change function.
526 // Return the (partial) CRC value we do have, but return an error.
527 //
528 break;
529 }
530 }
531
532 //
533 // Return result to caller
534 //
535 *pui32Crc = ui32CRC32;
536 #endif // ENABLE_EXTMEM_CRC
537
538 return status;
539
540 } // am_hal_crc32_accum()
541
542 //*****************************************************************************
543 //
544 // @brief Compute CRC32 for a specified payload
545 //
546 // @param ui32StartAddr - The start address of the payload.
547 // @param ui32SizeBytes - The length of payload in bytes.
548 // @param pui32Crc - Pointer to variable to return the computed CRC.
549 //
550 // This function uses the hardware engine to compute CRC32 on an arbitrary data
551 // payload. The payload can reside in any contiguous memory including external
552 // memory.
553 //
554 // @return Returns AM_HAL_STATUS_SUCCESS on success
555 //
556 //*****************************************************************************
557 uint32_t
am_hal_crc32(uint32_t ui32StartAddr,uint32_t ui32SizeBytes,uint32_t * pui32Crc)558 am_hal_crc32(uint32_t ui32StartAddr, uint32_t ui32SizeBytes, uint32_t *pui32Crc)
559 {
560 uint32_t status;
561
562 status = am_hal_crc32_init();
563
564 if (status == AM_HAL_STATUS_SUCCESS)
565 {
566 status = am_hal_crc32_accum(ui32StartAddr, ui32SizeBytes, pui32Crc);
567 }
568
569 return status;
570
571 } // am_hal_crc32()
572
573 //*****************************************************************************
574 //
575 // @brief Helper function to Perform exit operations for a secondary bootloader
576 //
577 // @param pImage - The address of the image to give control to
578 //
579 // This function does the necessary security operations while exiting from a
580 // a secondary bootloader program. If still open, it locks the info0 key region,
581 // as well as further updates to the flash protection register.
582 // It also checks if it needs to halt to honor a debugger request.
583 // If an image address is specified, control is transferred to the same on exit.
584 //
585 // @return Returns AM_HAL_STATUS_SUCCESS on success, if no image address specified
586 // If an image address is provided, a successful execution results in transfer to
587 // the image - and this function does not return.
588 //
589 //*****************************************************************************
590 uint32_t
am_hal_bootloader_exit(uint32_t * pImage)591 am_hal_bootloader_exit(uint32_t *pImage)
592 {
593 uint32_t status = AM_HAL_STATUS_SUCCESS;
594
595 //
596 // Lock the assets
597 //
598 if ( MCUCTRL->SHADOWVALID_b.INFO0_VALID &&
599 MCUCTRL->BOOTLOADER_b.PROTLOCK )
600 {
601 am_hal_security_128bkey_t keyVal;
602 uint32_t *pCustKey = (uint32_t *)0x50021A00;
603 bool bLockStatus;
604
605 //
606 // PROTLOCK Open
607 // This should also mean that Customer key is accessible
608 // Now lock the key by writing an incorrect value
609 //
610 keyVal.keyword[0] = ~pCustKey[0];
611 am_hal_security_set_key(AM_HAL_SECURITY_LOCKTYPE_CUSTOMER, &keyVal);
612
613 status = am_hal_security_get_lock_status(AM_HAL_SECURITY_LOCKTYPE_CUSTOMER, &bLockStatus);
614
615 if ((status != AM_HAL_STATUS_SUCCESS) || (bLockStatus))
616 {
617 return AM_HAL_STATUS_FAIL;
618 }
619
620 //
621 // Lock the protection register to prevent further region locking
622 // CAUTION!!! - Can not do RMW on BOOTLOADER register as all writable
623 // bits in this register are Write 1 to clear
624 //
625 MCUCTRL->BOOTLOADER = _VAL2FLD(MCUCTRL_BOOTLOADER_PROTLOCK, 1);
626
627 //
628 // Check if we need to halt (debugger request)
629 //
630 if (MCUCTRL->SCRATCH0 & 0x1)
631 {
632 //
633 // Debugger wants to halt
634 //
635 uint32_t dhcsr = AM_REGVAL(0xE000EDF0);
636
637 //
638 // Clear the flag in Scratch register
639 //
640 MCUCTRL->SCRATCH0 &= ~0x1;
641
642 //
643 // Halt the core
644 //
645 dhcsr = ((uint32_t)0xA05F << 16) | (dhcsr & 0xFFFF) | 0x3;
646 AM_REGVAL(0xE000EDF0) = dhcsr;
647
648 //
649 // Resume from halt
650 //
651 }
652 }
653
654 //
655 // Give control to supplied image
656 //
657 if (pImage)
658 {
659 bl_run_main(pImage);
660
661 //
662 // Does not return
663 //
664 }
665
666 return status;
667
668 } // am_hal_bootloader_exit()
669 //*****************************************************************************
670 //
671 // End Doxygen group.
672 //! @}
673 //
674 //*****************************************************************************
675