From 8b1980ad450ea2b34af4b831a8c655ba08dee800 Mon Sep 17 00:00:00 2001
From: Angus Gratton <angus@redyak.com.au>
Date: Wed, 8 Nov 2023 10:37:04 +1100
Subject: [PATCH] samd: Use unique id for USB serial number.

Replaces the previous all-zeroes "TODO" serial number.

Requires refactoring the low-level unique_id routine out from modmachine.c.

This work was funded through GitHub Sponsors.

Signed-off-by: Angus Gratton <angus@redyak.com.au>
---
 ports/samd/modmachine.c   | 42 +++------------------------------------
 ports/samd/mpconfigport.h |  5 +++++
 ports/samd/samd_soc.c     | 36 +++++++++++++++++++++++++++++++++
 ports/samd/samd_soc.h     |  8 ++++++++
 ports/samd/usbd.c         |  9 +++++----
 5 files changed, 57 insertions(+), 43 deletions(-)

diff --git a/ports/samd/modmachine.c b/ports/samd/modmachine.c
index 7c3d96eb01..8c4808075e 100644
--- a/ports/samd/modmachine.c
+++ b/ports/samd/modmachine.c
@@ -92,45 +92,9 @@ STATIC mp_obj_t machine_freq(size_t n_args, const mp_obj_t *args) {
 STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_freq_obj, 0, 1, machine_freq);
 
 STATIC mp_obj_t machine_unique_id(void) {
-    // Each device has a unique 128-bit serial number which is a concatenation of four 32-bit
-    // words contained at the following addresses. The uniqueness of the serial number is
-    // guaranteed only when using all 128 bits.
-    // Atmel SAM D21E / SAM D21G / SAM D21J
-    // SMART ARM-Based Microcontroller
-    // DATASHEET
-    // 9.6 (SAMD51) or 9.3.3 (or 10.3.3 depending on which manual)(SAMD21) Serial Number
-    //
-    // EXAMPLE (SAMD21)
-    // ----------------
-    // OpenOCD:
-    // Word0:
-    // > at91samd21g18.cpu mdw 0x0080A00C 1
-    // 0x0080a00c: 6e27f15f
-    // Words 1-3:
-    // > at91samd21g18.cpu mdw 0x0080A040 3
-    // 0x0080a040: 50534b54 332e3120 ff091645
-    //
-    // MicroPython (this code and same order as shown in Arduino IDE)
-    // >>> binascii.hexlify(machine.unique_id())
-    // b'6e27f15f50534b54332e3120ff091645'
-
-    #if defined(MCU_SAMD21)
-    uint32_t *id_addresses[4] = {(uint32_t *)0x0080A00C, (uint32_t *)0x0080A040,
-                                 (uint32_t *)0x0080A044, (uint32_t *)0x0080A048};
-    #elif defined(MCU_SAMD51)
-    uint32_t *id_addresses[4] = {(uint32_t *)0x008061FC, (uint32_t *)0x00806010,
-                                 (uint32_t *)0x00806014, (uint32_t *)0x00806018};
-    #endif
-    uint8_t raw_id[16];
-
-    for (int i = 0; i < 4; i++) {
-        for (int k = 0; k < 4; k++) {
-            // 'Reverse' the read bytes into a 32 bit word (Consistent with Arduino)
-            raw_id[4 * i + k] = (*(id_addresses[i]) >> (24 - k * 8)) & 0xff;
-        }
-    }
-
-    return mp_obj_new_bytes((byte *)&raw_id, sizeof(raw_id));
+    samd_unique_id_t id;
+    samd_get_unique_id(&id);
+    return mp_obj_new_bytes((byte *)&id.bytes, sizeof(id.bytes));
 }
 STATIC MP_DEFINE_CONST_FUN_OBJ_0(machine_unique_id_obj, machine_unique_id);
 
diff --git a/ports/samd/mpconfigport.h b/ports/samd/mpconfigport.h
index 497ca9a870..3684457b48 100644
--- a/ports/samd/mpconfigport.h
+++ b/ports/samd/mpconfigport.h
@@ -67,6 +67,11 @@
 #ifndef MICROPY_HW_USB_CDC
 #define MICROPY_HW_USB_CDC (1)
 #endif
+// SAMD unique ID is 16 bytes (hex string == 32)
+#ifndef MICROPY_HW_USB_DESC_STR_MAX
+#define MICROPY_HW_USB_DESC_STR_MAX (32)
+#endif
+
 #endif
 
 // Control over Python builtins
diff --git a/ports/samd/samd_soc.c b/ports/samd/samd_soc.c
index ad5910301c..dcfff9dc22 100644
--- a/ports/samd/samd_soc.c
+++ b/ports/samd/samd_soc.c
@@ -153,3 +153,39 @@ void sercom_deinit_all(void) {
 }
 
 #endif
+
+void samd_get_unique_id(samd_unique_id_t *id) {
+    // Atmel SAM D21E / SAM D21G / SAM D21J
+    // SMART ARM-Based Microcontroller
+    // DATASHEET
+    // 9.6 (SAMD51) or 9.3.3 (or 10.3.3 depending on which manual)(SAMD21) Serial Number
+    //
+    // EXAMPLE (SAMD21)
+    // ----------------
+    // OpenOCD:
+    // Word0:
+    // > at91samd21g18.cpu mdw 0x0080A00C 1
+    // 0x0080a00c: 6e27f15f
+    // Words 1-3:
+    // > at91samd21g18.cpu mdw 0x0080A040 3
+    // 0x0080a040: 50534b54 332e3120 ff091645
+    //
+    // MicroPython (this code and same order as shown in Arduino IDE)
+    // >>> binascii.hexlify(machine.unique_id())
+    // b'6e27f15f50534b54332e3120ff091645'
+
+    #if defined(MCU_SAMD21)
+    uint32_t *id_addresses[4] = {(uint32_t *)0x0080A00C, (uint32_t *)0x0080A040,
+                                 (uint32_t *)0x0080A044, (uint32_t *)0x0080A048};
+    #elif defined(MCU_SAMD51)
+    uint32_t *id_addresses[4] = {(uint32_t *)0x008061FC, (uint32_t *)0x00806010,
+                                 (uint32_t *)0x00806014, (uint32_t *)0x00806018};
+    #endif
+
+    for (int i = 0; i < 4; i++) {
+        for (int k = 0; k < 4; k++) {
+            // 'Reverse' the read bytes into a 32 bit word (Consistent with Arduino)
+            id->bytes[4 * i + k] = (*(id_addresses[i]) >> (24 - k * 8)) & 0xff;
+        }
+    }
+}
diff --git a/ports/samd/samd_soc.h b/ports/samd/samd_soc.h
index 90a5a57ffa..707d2f8edf 100644
--- a/ports/samd/samd_soc.h
+++ b/ports/samd/samd_soc.h
@@ -30,6 +30,10 @@
 #include "sam.h"
 #include "clock_config.h"
 
+typedef struct _samd_unique_id_t {
+    uint8_t bytes[16];
+} samd_unique_id_t;
+
 extern Sercom *sercom_instance[];
 
 void samd_init(void);
@@ -40,6 +44,10 @@ void USB_Handler_wrapper(void);
 void sercom_enable(Sercom *spi, int state);
 void sercom_register_irq(int sercom_id, void (*sercom_irq_handler));
 
+// Each device has a unique 128-bit serial number. The uniqueness of the serial number is
+// guaranteed only when using all 128 bits.
+void samd_get_unique_id(samd_unique_id_t *id);
+
 #define SERCOM_IRQ_TYPE_UART  (0)
 #define SERCOM_IRQ_TYPE_SPI   (1)
 #define SERCOM_IRQ_TYPE_I2C   (2)
diff --git a/ports/samd/usbd.c b/ports/samd/usbd.c
index 90f2e1bfd9..2e3200ad7d 100644
--- a/ports/samd/usbd.c
+++ b/ports/samd/usbd.c
@@ -31,12 +31,13 @@
 #include "mp_usbd.h"
 #include "string.h"
 #include "tusb.h"
-
-#define SERIAL_TODO "000000000000" // TODO
+#include "samd_soc.h"
 
 void mp_usbd_port_get_serial_number(char *serial_buf) {
-    MP_STATIC_ASSERT(sizeof(SERIAL_TODO) <= MICROPY_HW_USB_DESC_STR_MAX);
-    strcpy(serial_buf, SERIAL_TODO);
+    samd_unique_id_t id;
+    samd_get_unique_id(&id);
+    MP_STATIC_ASSERT(sizeof(id.bytes) * 2 <= MICROPY_HW_USB_DESC_STR_MAX);
+    mp_usbd_hex_str(serial_buf, id.bytes, sizeof(id.bytes));
 }
 
 void USB_Handler_wrapper(void) {