IAP C code example query

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

IAP C code example query

3,540 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Scribe on Sat Sep 08 15:42:38 MST 2012
Hi guys,

I'm looking at the recommended IAP C implementation and am a little confused as to why it recommends an array of longs yet the command itself is looking for an array of ints, is anyone able to shed some light on this? If anyone has an example of the code in use I would be most grateful.

--------
unsigned long command[5];
unsigned long result[4];

typedef void (*IAP)(unsigned int [],unsigned int[]);
--------

Any help much appreciated,
Many thanks
Labels (1)
0 Kudos
14 Replies

2,300 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by DiligentMinds.com on Sun Sep 09 13:21:24 MST 2012
I think you are right.  I just spent some time over on the Freescale website, and they have the same "issues" with telling anyone ANYTHING about how the 128-bit device UID is created-- and with the same "use all 128-bits to guarantee no collisions" caveat.  On the Freescale devices, the UID is stored on the factory programmed OTP section of the flash memory, and after a reset, hardware transfers the UID to (4) special read-only 32-bit registers that you can access with the help of some "#defines" in the CMSIS required header file for each chip.  So, they are doing the same thing NXP is doing with this.  The engineers posting on their forum are asking the same questions that we are, and are getting the same answers.

What would be GREAT is if NXP could give us a "suggested hash" function-- and, like any hash, there is still no guarantee of not having a collision, but maybe with their insider knowledge of how the UID is created, they can come up with a hash function that gives us a 64-bit, 32-bit, or 16-bit answer that has the "best chance" of not having collisions.

One person wrote that maybe a CRC algorithm would work best (over the CPU_ID and the DEVICE_UID).  This would be better than my "add'em all up" technique, but any way you cut it, a hash is still a hash, and there is always the possibility of collisions.

At the end of the day, if your application absolutely, positively, MUST have a unique ID of a size that is not 128-bits, then the internal NXP-supplied UID is useless to you, and you are going to have to create this yourself at manufacturing time (or add an external chip that has this programmed for you).  I like to keep my designs low-cost, so probably I will just go with my simple "add the 4 DWORDS together" hash to get the 32-bit ID I need.  Collisions will be rare and for my application they are not catastrophic.
0 Kudos

2,300 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by DiligentMinds.com on Sun Sep 09 12:51:50 MST 2012
Yes-- that's it.  I am using some example code from NXP (or is it Keil)-- and the "stdint.h" file is included somehow-- but I just can't for the moment see where that is...

In the conversation below, Zero corrected my code.  The example code for UID is wrong and the user-manual is not very clear, but the return from the ROM->IAP->UID code is actually 5 values-- not 4.  The first value returned is the "return code", which should be zero if all went well.  So, at the end of the day, the definition should be:

<code>
// possible return codes for the IAP ROM function
//
typedef enum IAP_STATUS_t {
   CMD_SUCCESS = 0,         // Command was executed successfully.
   INVALID_COMMAND = 1,     // Invalid command.
   SRC_ADDR_ERROR = 2,      // Source address is not on a word boundary.
   DST_ADDR_ERROR = 3,      // Destination address is not on a correct boundary.

   SRC_ADDR_NOT_MAPPED = 4, // Source address is not mapped in the memory map.
                            // Count value is taken in to consideration where
                            // applicable.

   DST_ADDR_NOT_MAPPED = 5, // Destination address is not mapped in the memory
                            // map. Count value is taken in to consideration
                            // where applicable.

   COUNT_ERROR = 6,         // Byte count is not multiple of 4 or is not a
                            // permitted value.

   INVALID_SECTOR = 7,      // Sector number is invalid.
   SECTOR_NOT_BLANK = 8,    // Sector is not blank.

   SECTOR_NOT_PREPARED_FOR_WRITE_OPERATION = 9, // Command to prepare sector for
                                                // write operation was not
                                                // executed.

   COMPARE_ERROR = 10,      // Source and destination data is not same.
   BUSY = 11,               // Flash programming hardware interface is busy.
} IAP_STATUS_t;

// In-Application-Programming [IAP]
//
// caller creates these as an auto variable, and populates the command array
// the result will be in the result array
// worst-case command size is 5, worst-case result is 5
//
// uint32_t IAP_command[5];
// uint32_t IAP_result[5];
//
// IAP usage:  IAP(IAP_command, IAP_result);
//
typedef void (*IAP_t)(uint32_t[5], uint32_t[5]);
const IAP_t IAP = (IAP_t)0x1FFF1FF1;
</code>

Note that in the above typedef, the '5' in each of the array brackets means NOTHING to the compiler, and these are ignored-- these are there just to make it clear to a human reading the code that an array of (up to) 5 DWORDS is needed.

and in my case, where I am using the IAP to return the device UID:

<code>
// get the device serial number
//
// returns 5 DWORDs into an array supplied by the caller
// the first element in the array is the return-code
//   (with return_code == 0 meaning success).
//
void IAP_get_device_UID (uint32_t UID_array[5])
   {
   uint32_t IAP_command = 58;

   IAP(&IAP_command, UID_array);
   }
</code>

Other IAP commands require parameters as well as the command, so the generic call would be:

<code>
IAP_STATUS_t IAP_do_something_cool (uint32_t IAP_result[5] <, add parameters you need> )
   {
   uint32_t IAP_command[5];  // (20-bytes on the stack)

   // set up the command and parameters for this particular command
   //
   IAP_command[0] = [whatever the command is];
   IAP_command[1] = [whatever this parameter is, if any];
   IAP_command[2] = [whatever this parameter is, if any];
   IAP_command[3] = [whatever this parameter is, if any];
   IAP_command[4] = [whatever this parameter is, if any];

   // call the IAP in the ROM
   //
   IAP(IAP_command, IAP_result);

   // also return the status this way for easy coding
   //
   return (IAP_STATUS_t)IAP_result[0];
   }
</code>
0 Kudos

2,300 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by jdurand on Sun Sep 09 12:35:18 MST 2012
I understand they want some encryption to find counterfeit devices, and they don't want sequential numbers because that would advertise production rates.

It's like credit card numbers, the far left digit is the type of card, a few more digits are the institution (bank, CU, etc.), the rest are pseudo-random to make it hard to guess a card number.  Adding the expiration date gives a few more digits.

This does present problems in making small, easy for the end user device ID numbers.

0 Kudos

2,300 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by DiligentMinds.com on Sun Sep 09 10:32:57 MST 2012
Now THAT is what I am talking about.  It's unfortunate that NXP wishes to remain secretive about this, but it really is an issue that needs to be addressed.  Most engineers only want the UID to be unique for THEIR family of devices-- (so it doesn't matter if MY device UID's collide with YOUR device's UID's-- unless we are collaborating on building compatible devices).  Most small devices don't need 128-bits for a UID-- in fact 16 or 32 bits is usually sufficient-- (sometimes 64, for USB for example)-- but I have never built a device that would need (and I wouldn't want) a 128-bit UID.

Here's the thing.  NXP advertises on their data-sheets for these LPC ARM-Cortex-Mx devices that there is a "Unique Device ID" available.  They live up to their word-- it *IS* there-- but what they DON'T tell you is that it is a 128-bit UID, and "we are not going to tell you anything about how we create it"-- so, if you need less than 128-bits, and your ID needs to be unique, well, you are just screwed.

In the thread that Zero provided, it shows that the first and third values (result[1] and result[3]) vary much more than the second and fourth values (result[2] and result[4])-- so it was suggested that only these be used to create some kind of hash.  Even so-- the message from NXP is: "If you don't use all 128-bits, we cannot guarantee collisions".

So, probably, what I am doing (just adding all 4 values together to get a "hopefully" unique 32-bit ID) appears to be the easiest and (arguably) best way to hash this UID into a smaller number of bits.  It DOES NOT guarantee that my devices will not have collisions though.

Way to go NXP...  What ELSE are you "not going to tell us"...?
0 Kudos

2,300 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by jdurand on Sun Sep 09 10:00:12 MST 2012
I don't suppose they suggest a way to use all 128 non-random, non-sequential bits to create smaller ID numbers with no chance of collision?

0 Kudos

2,300 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by DiligentMinds.com on Sun Sep 09 09:57:17 MST 2012
Ahhh--- yeah, that makes sense!  So, there 1 more 32-bit value.  Which is the more significant result[1] or result[4]?

Thanks for your help, BTW...
0 Kudos

2,300 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Ex-Zero on Sun Sep 09 09:36:54 MST 2012
Is that official enough?

NXP_USA:

Unfortunately the details of what go into the 128-bit GUID cannot be disclosed. It can be said, however, that the 128-bit GUIDs are not random, nor are they sequential. Thus to ensure there are no collisions with other devices, the full 128 bits should be used.

See #24 of http://www.lpcware.com/showthread.php?t=774&page=3
0 Kudos

2,300 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by jdurand on Sun Sep 09 09:34:20 MST 2012
On a clear disk you can seek forever.
0 Kudos

2,300 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Scribe on Sun Sep 09 08:27:54 MST 2012
Thank you very much for the detailed response DiligentMinds, I am indeed using the Keil MDK, however I've been working under the default c80 standard. Is there anything other than including the --c99 compiler flag that needs to be done to force the c99 standard? For example to recognise those variable types.

Many thanks

*EDIT* Ah I see I required stdint.h
0 Kudos

2,300 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Ex-Zero on Sun Sep 09 05:12:42 MST 2012
UID is of course Result0, Result1, Result2, Result3

since function return is ReturnCode,Result0,Result1,Result2,Result3.

I prefer to show UID as a nice 4x8 hex block:

printf("UID: %08X %08X %08X %08X\n",cc_id[0],cc_id[1],cc_id[2],cc_id[3]);

-> Command: Chip Number (hex)
<- 0404052C 53360836 4BFE7DEC F5000002

FlashMagic is unfortunately showing (a confusing) 4x? decimal block:

Serial Number: 67372332 1396049974 1274969580 4110417922
0 Kudos

2,300 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by DiligentMinds.com on Sat Sep 08 19:45:53 MST 2012
OK, well what Zero was saying (and the posting that was linked to) was for a different chip-- but it looks like all IAP calls are the same across all chips.  In that case, then the user-manual for the LPC11U24 is not very clear about this, and needs to be updated.  If the result array is indeed 5, then the proper definition for IAP_entry() should be:

typedef void (*IAP)(uint32_t[5], uint32_t[5]);
const IAP IAP_entry = (IAP)0x1FFF1FF1;

And then my function to get the UID should be:

// get the device serial number
//
// returns 5 DWORDs into an array supplied by the caller
// the first element in the array is the return-code
//   (with return_code == 0 meaning success).
//
void IAP_get_device_UID (uint32_t UID_array[5])
   {
   uint32_t IAP_command = 58;

   IAP_entry(&IAP_command, UID_array);
   }

Thank you Zero!

This still leaves the question-- if this is a 128-bit UID, then which is the most-significant number-- result[1] or result[4]?  Since everything else with this MPU is in little-endian format, I am going to assume (for the moment) that result[1] is the LSW, and result[4] is the MSW...

Regards,

Ken Peek

0 Kudos

2,300 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Ex-Zero on Sat Sep 08 19:41:19 MST 2012
UM10398:

21.7.9 ReadUID (IAP)

Table 306. IAP ReadUID command

Input Command code: 58 (decimal)
Return Code CMD_SUCCESS
Result Result0: The first 32-bit word (at the lowest address).
Result1: The second 32-bit word.
Result2: The third 32-bit word.
Result3: The fourth 32-bit word.
Description This command is used to read the unique ID.
0 Kudos

2,300 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by DiligentMinds.com on Sat Sep 08 19:12:23 MST 2012
At the end of the day, it doesn't matter.  An int and a long are the same size on this MPU.  The ROM code doesn't care-- it just wants 2 pointers to arrays that hold 4-byte-sized things.  There is no way for the ROM code to know if you passed it an int, a long, or an unsigned long (or int).  It is going to get it's command and send it's answer from/to those.

The two things must agree though in your code, or the compiler will (or should) complain that the parameter types don't match.  For that reason, I defined the two parameters as (void *)-- since a (void *) will take a pointer to anything, and I *think* the different commands take different arguments.  You could change these to the uint32_t type if you like, which will allow for better compile-time type checking, if that's all you pass to the IAP-- (otherwise, you might be doing some type-casting to put int's into the uint32_t array-- I don't know what you compiler will do with this.)

In embedded work we need to have absolute dictatorial control over what memory gets allocated where, so I like to use the C99 standard definitions (int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, etc.)-- which avoids all of this confusion.  (If you're not using Keil, you may have to include a header file for this to work).  When dealing with unions and structs-- usually you want to use the "__packed" modifier so the compiler doesn't pad these (unless you want it to).

Here's some code I have in my project for using the IAP ROM code to fetch the device Unique ID [UID]:

// In-Application-Programming [IAP]
//
// caller creates these as an auto variable, and populates the command array
// the result will be in the result array
// worst-case command size is 5, worst-case result is 4
//
// uint32_t IAP_command[5];
// uint32_t IAP_result[4];
//
// IAP usage:  IAP_entry (IAP_command, IAP_result);
//
typedef void (*IAP)(void *, void *);
IAP IAP_entry = (IAP)0x1FFF1FF1;

.
. (later in my code)
.

// get the 128-bit device serial number
// return 4 DWORDs into an array supplied by the caller
//
void IAP_get_device_UID (uint32_t UID_array[4])
   {
   uint32_t IAP_command = 58;

   IAP_entry(&IAP_command, UID_array);
   }

Notice here that I didn't use an array for the command, since there was only one DWORD needed.  This code is tested and works well.  Also, note that (depending on the device) the "magic number" that represents the IAP code entry point can vary-- so get this from YOUR device's user manual.  This code was written for the LPC11U24, but I'm fairly certain that it will work for any LPC1xxxx.  This is a great way to make sure your code can be re-used-- if you tried to get the device UID yourself, it could end up moving around based on the device die revision-- this way, you always get the right information.

With my device, the first number in the array element[0], is zero-- which makes me wonder if the most active number in this array isn't element[3].  I don't have a lot of devices to test-- maybe someone else could answer this.

With other IAP commands, you may need an actual command array, and then fill it up with the information needed before calling the IAP code.  The size needed for the command and response arrays varies with the command.  In their example code, they just made the two arrays global, and gave the worst-case sizes for each, but if these are global then the code has no chance of being re-entrant-- (which may or may not matter to you).

BTW-- I tested this code with the (void *) parameters changed to uint32_t[] parameters, and it works fine.  You can also make the parameters (uint32_t *).  The code will compile just fine and work just fine either way.  Probably, the uint32_t[] definition is the more readable version:

typedef void (*IAP)(uint32_t[], uint32_t[]);

With the above, you can see quite well that the function requires arrays as parameters.  So, define this however you like-- it will "just work"...

I hope this answers your questions!

Regards,

Ken Peek

*** EDIT *** EDIT *** EDIT *** EDIT *** EDIT *** EDIT *** EDIT ***

Good thing you asked this question.   Upon reflecting on the code snippet above, I came to the conclusion that the following would be better:

typedef void (*IAP)(uint32_t[5], uint32_t[4]);
const IAP IAP_entry = (IAP)0x1FFF1FF1;

I changed the parameters to the uint32_t[] type for readability.  The '5' and '4' in the array brackets does nothing-- it is just a reminder to the reader that these are the worst-case sizes needed.  In addition, I placed the "const" keyword in front of the instantiation.  This forces the function pointer into flash memory, with no RAM taken up and there is no flash memory needed to initialize the pointer in RAM.  This saves 8 bytes of Code, increases the RO-data by 4 bytes, saves 4 bytes of RW-data, and 4-bytes of ZI-data.  (This is with the Keil tool-chain).  So, write your definition like this, and you should have the most efficient implementation.  This code is tested and works fine.
0 Kudos

2,300 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Ex-Zero on Sat Sep 08 19:04:52 MST 2012
#1 A working sample is described here: http://knowledgebase.nxp.trimm.net/showthread.php?t=1594&page=2

#2 IAP is returning 'Return Code' + UID, so your first '0' is 'Return Code' for CMD_SUCCESS.
0 Kudos