Skip to content

Add manner for grabbing unique Board ID/Serial Number #462

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Andon-A opened this issue Nov 27, 2017 · 22 comments
Closed

Add manner for grabbing unique Board ID/Serial Number #462

Andon-A opened this issue Nov 27, 2017 · 22 comments

Comments

@Andon-A
Copy link

Andon-A commented Nov 27, 2017

Pulling a unique ID would be useful for an example I have in mind for #345 and @tannewt mentioned in Discord that a Serial Number was pulled for the USB code and that it may be able to be added somewhere.

Personal preference is board.id() but putting it in os along with os.uname() would make sense as well. Perhaps even more sense from a programming standpoint since that's where you get other board information from.

@sommersoft
Copy link

Looking to jump into contributing on this one. But, as this would be my first on this MASSIVE project, I have a couple questions.

  1. Which branch would be the best to reference & fork? I know the updates since asf4 have changed a few things, so don't want to start on outdated footing. I'm assuming master, but we all know what happens when I assume.

  2. Are we looking at SAMD21 only, or for all ports?

In some preliminary research, I found the reference that @tannewt mentioned for Serial Number in USB. Page 44 of the SAMD21 datasheet says that its read from NVM:

10.3.3. Serial Number Each device has a unique 128-bit serial number which is a concatenation of four 32-bit words contained at the following addresses:
Word 0: 0x0080A00C
Word 1: 0x0080A040
Word 2: 0x0080A044
Word 3: 0x0080A048

Looks like it would be easiest to have the base call in common_hal/microcontroller and extend the call to os or board.

@dhalbert
Copy link
Collaborator

dhalbert commented Dec 9, 2017

Thanks for looking at this! microcontroller would be a good place to put a read-only attribute, returned as bytes, probably. That’s where I would put it rather than os or board. Work on the master branch. We’d want SAMD21 and ‘51. If ESP8266 and nRF have a similar uid, they should be supported too.

@Andon-A
Copy link
Author

Andon-A commented Dec 10, 2017

Personally, I'd prefer it returning the four words as a list.

I would also say that microcontroller would be a decent spot to put such a function return, but os does already return some board and microcontroller information, IE os.uname()

@sommersoft
Copy link

I agree that the outside call would be best in os.uname().

As I've gotten further into this, the first hurdle is getting NVM functions (read at least) working. I didn't realize it wasn't working yet...know before you go! haha. It seems that that the few people who were interested in the NVM to this point, wanted the EEPROM simulator...and have been mostly unsuccessful.

@dhalbert
Copy link
Collaborator

dhalbert commented Dec 10, 2017

os.uname() returns this:

Returns information identifying the current operating system. The return value is an object with five attributes:

sysname - operating system name
nodename - name of machine on network (implementation-defined)
release - operating system release
version - operating system version
machine - hardware identifier

Regular CPython returns something like:
posix.uname_result(sysname='Linux', nodename='salmonx', release='4.10.0-42-generic', version='#46~16.04.1-Ubuntu SMP Mon Dec 4 15:57:59 UTC 2017', machine='x86_64')
machine designates the type of machine, not something unique.

Our general philosophy is to match the regular Python's libraries as much as we can, without enhancing or changing their API's, so that code can be ported more easily. If you can suggest an existing API call that returns a unique ID for the current machine, that would be great, but I'm not sure there is one. There are some ways to get a MAC address but they're not straightforward.

The board API right now just returns pins, since it's more about the whole board and its configuration, not the CPU chip itself. We have been putting chip-specific stuff in microcontroller.cpu like .temperature and .frequency and .nvm.

@dhalbert
Copy link
Collaborator

dhalbert commented Dec 10, 2017

It seems that that the few people who were interested in the NVM to this point, wanted the EEPROM simulator...and have been mostly unsuccessful.

It's implemented only on the Express boards right now due to space considerations.

@Andon-A
Copy link
Author

Andon-A commented Dec 10, 2017

@sommersoft I was thinking more of an os.id() than tacking it on to os.uname()

@sommersoft
Copy link

Our general philosophy is to match the regular Python's libraries as much as we can, without enhancing or changing their API's, so that code can be ported more easily. If you can suggest an existing API call that returns a unique ID for the current machine, that would be great, but I'm not sure there is one. There are some ways to get a MAC address but they're not straightforward.

Yeah, uuid (MAC and other unique id's) doesn't really fit this case either, and definitely wouldn't be recursive. microcontroller.cpu.uid sounds like the best option, all things considered.

It's implemented only on the Express boards right now due to space considerations.

Well, now I have a reason to buy a CPX. Now that they're out of stock, of course. Until then I'll see what kind of juice I can squeeze out of the Trinket M0. If any. Still a ways off before running anything anyway (at least at my pace).

Also, the ESP8266 only has a CHIP_ID, which is the ending 24 bits of the MAC. Not "unique" per se, but closest you can get on that chip. I can work that, but if we make the .uid output bytes that coule make API portability an issue, yes? Using a list would be easier, since you could just write it to the first position (or all 4) as an int. (Arduino ESP8266 system_get_chip_id returns int; have to look at CPy's function)

nRF...out of my reach for now.

@Andon-A
Copy link
Author

Andon-A commented Dec 10, 2017

A Feather M0 Express would also work.

For the ESP8266, I think writing it as an int to all four positions on a list would work to allow anything that cares about such things to pull it. No, pulling the same thing four times isn't going to give the same results as an actual SN that's four different ints, but it would let the code work at least.

@dhalbert
Copy link
Collaborator

dhalbert commented Dec 10, 2017

@Andon-A

I'd prefer it returning the four words as a list.

What do you think your use cases would be for the UID? The basic integer type is 31 bits (including sign bit). Only some CircuitPython impls are going to provide long ints due to code space issues. So just a list of integers won't always work. Would an array.array('L') work for you?

@tannewt
Copy link
Member

tannewt commented Dec 10, 2017

I'd prefer the ID be bytes because its immutable. Its also very similar to a list of ints which are 0-255 each. Its a sequence so you can do indexing and such.

@Andon-A
Copy link
Author

Andon-A commented Dec 10, 2017

@dhalbert To be extremely specific, what I intend on doing is making an example for how Python can be modified without the need to recompile. To accomplish this, I wanted to have a way to have the device create a program unique to it, using the device ID. I would prefer a device ID call that returned the same data type and size regardless of MC

@sommersoft
Copy link

sommersoft commented Dec 10, 2017

What do you think your use cases would be for the UID? The basic integer type is 31 bits (including sign bit). Only some CircuitPython impls are going to provide long ints due to code space issues. So just a list of integers won't always work.

I'd prefer the ID be bytes because its immutable. Its also very similar to a list of ints which are 0-255 each. Its a sequence so you can do indexing and such.

As far as I can tell through digging in each boards' structures so far, the ESP8266 is the only one that currently even handles a uid (in modmachine.c line 231):

STATIC const mp_rom_map_elem_t   machine_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_umachine) },
####  some table defs removed for conciseness on GitHub ####
{ MP_ROM_QSTR(MP_QSTR_unique_id), MP_ROM_PTR(&machine_unique_id_obj) },   <----------------------
####  some more table defs removed for conciseness on GitHub ####

As you can see, they set it up as a QSTR, so is stored as bytes in that impl. I think that's right; I'm still figuring out the data structures and CPy intricacies. I'm still hunting to see if the machine_unique_id_obj is even populated at board startup.

EDIT # 3: Found it...scrolled right past it in modmachine.c line 81:

STATIC mp_obj_t machine_unique_id(void) {
    uint32_t id = system_get_chip_id();
    return mp_obj_new_bytes((byte*)&id, sizeof(id));
}
STATIC MP_DEFINE_CONST_FUN_OBJ_0(machine_unique_id_obj, machine_unique_id);

With regards to the size of the SAMD21 & 51 uid, with it being 128 bits and coupled with the fact that NVM funcs are only on Express boards due to size as well...won't there be code space issues no matter how the value is stored? I'm not advocating for any certain datatype; you guys own this project way more than I do so if you want bytes you'll get bytes. As @tannewt pointed out, indexing is available so that more or less takes care of portability and usability. Just think its worth mentioning that unless we can expand NVM to all SAMD boards, this will only apply to Express and ESP8266 (can't speak to nRF atm).


Now for more background information...

LAST EDIT: For the SAMD51, uid is almost identical to SAMD21; 128bits (4x 32bits) and in Flash. Addresses are: 0x008061FC, 0x00806010, 0x00806014, 0x00806018.


For the nRF52832, uid is 64 bit (2x 32 bit) stored in the Factory Information Control Registers, at addresses 0x060 & 0x064. They're know as DEVICEID[0] & DEVICEID[1], respectively. Nordic's documentation is annoying (sidenote). I did read a forum post on Nordic that said DEVICEID[0] holds the HWID, and DEVICEID[1] holds the actual uid.

Here is a link to Nordic's documentation: http://infocenter.nordicsemi.com/topic/com.nordic.infocenter.nrf52832.ps.v1.1/ficr.html?cp=2_1_0_12#concept_xb5_mpx_vr

Here is a link to the forum post I found on the subject: (EDIT: just noticed that this is referencing nRF 51x; I'll confirm if nRF52 is the same later)
https://devzone.nordicsemi.com/question/1691/how-to-get-nrf51822-serial-number-and-hw-id-through-segger/

Between domestics and football, this is as far as I've gotten today. Apologize for the huge post...

@tannewt
Copy link
Member

tannewt commented Dec 12, 2017

@sommersoft thank you for the hugely informative post! Could you explain how the nvm module fits into your plans? I was thinking the uid would be accessible from microcontroller instead.

The uid is scattered around memory on the SAMDs. The USB code uses it here.

@sommersoft
Copy link

sommersoft commented Dec 12, 2017

Ok..so that was part of my fear with that giant post. Info got a little fragmented. I'll see if I can give a clean explanation of where I THINK I am. (Found one mistake from yesterday. I was looking at the SAM_G_51 datasheet. So that info is garbage; I will correct it. SAMD51 uid is identical to SAMD21...just @ different addresses.)


  • SAMD21: 128bit (4x 32bit) uid is stored in Flash (nvm) @ these addresses: 0x0080A00C, 0x0080A040, 0x0080A044, 0x0080A048.

  • SAMD51: 128bit (4x 32bit) uid is stored in Flash (nvm) @ these addresses: 0x008061FC, 0x00806010, 0x00806014, 0x00806018.

  • nrf52: 64bit (2x 32bit) uid is stored in Factory Info Control Registers @ these addresses: DEVICEID[0] (0x060) & DEVICEID[1] (0x064). Still looking at how to read this.

  • ESP8266: 24bit uid is ending values of MAC; this one is actually already built-in. Use machine.unique_id(); returns bytes string.


Now, the reason I'm saying that the nvm needs to be working, is that we have to read the Flash to get the uid for the SAMD21/SAMD51. The read-only microcontroller attribute will be set by, say, nmv.read(uid). (ESP8266 could just stay odd-man-out with machine).

Now that I have all the "what's where" info, I'm going to layout a project on my fork and start working it.


After looking at the USB code you referenced, it looks like that particular function is only setting the address locations of where the uid is stored, not actually reading the uid. I'm still tracing back (at the asf4/samdX1/usb level now) how those uid address locations are actually used. I've read that the USB protocol uses controller serial numbers, but that it isn't required (or is it?). I don't have much experience with USB beyond a user perspective. At any rate, if the USB does in fact read the uid, makes sense to expose it...

@dhalbert
Copy link
Collaborator

dhalbert commented Dec 12, 2017

@sommersoft Thanks for doing all that research! The USB code does read the values, 4 bits at a time, on this line:

uint8_t nibble = (*(addresses[i]) >> j * 4) & 0xf;

You don't need the nvm module for this. That module is written specifically to provide a small EEPROM-like space to store persistent data. It isn't meant as a general-purpose way of reading flash.

@sommersoft
Copy link

sommersoft commented Dec 12, 2017

So, this would be some of my inexperience showing with "foundational" C & MCU programming. After looking back without my biases, I see what you're saying.

uint32_t* addresses[4] = {(uint32_t *) 0x0080A00C, (uint32_t *) 0x0080A040... is assigning pointers to the ACTUAL memory, not simply assigning the locations. That's what was tripping me up, since you would never do that in more computer-os based environments (at least for the things I've done). The rest of that routine is just converting to hex. Thanks @dhalbert for helping to open my eyes on this one.

Now, scrapping my nvm thoughts from memory. (<---that's for @Andon-A) This should be a lot simpler than I was trying to make it...

@sommersoft
Copy link

After 4 different approaches, and this week's holiday break, I went back to zero and applied KISS. Previous iterations of trying to just return autogen_usb_descriptor.h/serial_number either wouldn't compile or locked the bootloader up. How can pointers be so simple, yet so confusing?! At any rate, SAMD21 seems to be working:

# Trinket CPU demo
import microcontroller, time

while True:
    print ("^...Trinket M0 CPU Information...^")
    print ("Frequency: ", microcontroller.cpu.frequency)
    print ("Unique ID: ", microcontroller.cpu.uid)
    time.sleep(60)

result:

^...Trinket M0 CPU Information...^
Frequency:  48000000
Unique ID:  58280

I'm assuming SAMD51 will work as well, just can't verify.

The UID is short, obviously because serial_number is a uint8_t. Would it be desired to extend it to a long?

@tannewt
Copy link
Member

tannewt commented Jan 2, 2018

I'm suspicious of it being that short. IIRC is only unique if its 64 or 128 bits. Could you adapt it to return a bytes string instead of an int?

sommersoft added a commit to sommersoft/circuitpython that referenced this issue Jan 5, 2018
shared-bindings/microcontroller/Processor.c & .h: added UID lookup functionality for use with all ports. See Issue adafruit#462.
sommersoft added a commit to sommersoft/circuitpython that referenced this issue Jan 5, 2018
ports/atmel-samd/common-hal/microcontroller/Processor.c & .h: added UID lookup function for SAMD ports. See Issue adafruit#462.
@sommersoft
Copy link

SAMD uid function is now reporting longer (proper) unique numbers. Thanks to @tannewt for helping me get the shortness straightened out. And thanks to @Andon-A for doing uniqueness testing.

To keep things standard, I'm now working on adding this to ESP8266 by calling the existing machine.unique_id. Gotta get the esp vagrant toolchain up and running...

After that will come the nRF. I've got a board on the way, so hopefully we can get this issue closed out in a couple weeks.

@sommersoft
Copy link

sommersoft commented Jan 13, 2018

ESP8266 uid is now functioning the same as SAMD. machine.unique_id is still available. 2/3rds of the way there. nRF showed up a couple days ago, so that's the next backend to learn! 🔢

#4 ets_task(40100130, 3, 3fff8380, 4)
boot.py output:

Press any key to enter the REPL. Use CTRL-D to soft reset.

Adafruit CircuitPython 3.0.0-alpha.1-49-g068479d-dirty on 2018-01-13; ESP module with ESP8266
>>> import machine, microcontroller
>>> print(machine.unique_id())
b'\x02\xd8\xc6\x00'
>>> print(microcontroller.cpu.uid)
bytearray(b'\x02\xd8\xc6\x00')
>>>

@sommersoft
Copy link

nRF done! PR imminent...

sommersoft added a commit to sommersoft/circuitpython that referenced this issue Jan 16, 2018
shared-bindings/microcontroller/Processor.c & .h: added UID lookup functionality for use with all ports. See Issue adafruit#462.
sommersoft added a commit to sommersoft/circuitpython that referenced this issue Jan 16, 2018
ports/atmel-samd/common-hal/microcontroller/Processor.c & .h: added UID lookup function for SAMD ports. See Issue adafruit#462.
sommersoft added a commit to sommersoft/circuitpython that referenced this issue Jan 16, 2018
/ports/nrf/common-hal/microcontroller/Processor.c: add UID function. See Issue adafruit#462.
sommersoft added a commit to sommersoft/circuitpython that referenced this issue Jan 16, 2018
/shared-bindings/microcontroller/Processor.c: added a note to `microcontroller.cpu.uid` (Issue adafruit#462) that ``struct`` is not currently available on the nRF port.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants