SDK สำหรับ TESAIoT Dev Kit
คู่มืออ้างอิง API และ Tutorial (MTB & µPython)
Loading...
Searching...
No Matches
D1 — วินัยการเข้าถึงชิป: gate, lock, touch-hold

เป้าหมายของหัวข้อนี้

OPTIGA Trust M ถูกใช้ร่วมกันโดยทุก task ที่ต้องการข้อมูลรับรอง ลายเซ็นดิจิทัล หรือใบรับรอง (certificate) และยังใช้บล็อก I2C ร่วมกับตัวควบคุมการสัมผัส (touch) บน CM55 ด้วย มีกลไก 3 อย่างที่กันไม่ให้เรื่องนี้กลายเป็นการชนกันบนบัส และแต่ละอย่างมีกฎการจับคู่ที่แน่นอน:

กลไก API ตอบคำถามใด การจับคู่
Gate optiga_chip_enter() / optiga_chip_exit() "ขณะนี้มี task อื่นถือชิปอยู่หรือไม่" หนึ่ง exit ต่อหนึ่ง enter ที่สำเร็จ
Lock optiga_manager_lock() / optiga_manager_unlock() "manager ขึ้นแล้วหรือยัง และขอใช้ได้หรือไม่" หนึ่ง unlock ต่อหนึ่ง lock ที่สำเร็จ และเป็นชื่อเรียกซ้อนบน gate
Touch hold optiga_manager_touch_hold[_reason]() / optiga_manager_touch_release() "กัน CM55 ออกจากบล็อก I2C ที่ใช้ร่วมกัน" นับจำนวน และต้องเป็น 1:1 อย่างเคร่งครัดบนทุกเส้นทางขาออก

นอกจากนี้ยังมีคู่ acquire/release — optiga_manager_acquire() คืนค่า optiga_util_t * ที่ใช้สั่งงานชิป และ optiga_manager_release() เป็นชื่อเรียกซ้อนของ optiga_chip_exit()

ลายเซ็นของความล้มเหลวที่ต้องหัดจดจำคือ OPTIGA_COMMS_ERROR (0x0102): ตัวควบคุมการสัมผัสกับ secure element (ชิปนิรภัยแยกส่วน) ถูกสั่งงานบน SCB เดียวกันในเวลาเดียวกัน

ลำดับการทำงานจริงของเฟิร์มแวร์

enter เทียบกับ lock — ความจริงว่าแต่ละตัวตรวจอะไร

int optiga_verify_cert_key_pair(uint16_t cert_oid, uint16_t key_oid)
{
int rc;
/* Two tasks call this: the MQTT subscriber after installing a certificate,
* and the provisioning task when its wait ends. They share cert_pem - a 2 KB
* static - and the library's single global status. The gate is re-entrant, so
* the certificate read and the signature inside still take it, without
* deadlocking on this one. */
return -5; /* the chip is busy elsewhere; a verdict now would be a guess */
}
optiga_manager_touch_hold_reason("Checking certificate against its key");
rc = verify_cert_key_pair_locked(cert_oid, key_oid);
return rc;
}

optiga_chip_enter() เป็นแบบ re-entrant ต่อหนึ่ง task ของ FreeRTOS (การซ้อนชั้นไม่มีต้นทุน) และคืนค่า false ด้วยเหตุผลเดียวเท่านั้น คือมี task อื่นถือชิปอยู่ ซึ่งเป็นความล้มเหลวขั้นเด็ดขาด ให้คืนค่าความผิดพลาดออกไป ห้ามเดินหน้าต่อ สิ่งที่ฟังก์ชันนี้ ไม่ใช่ คือการตรวจสอบว่าเริ่มใช้งานแล้วหรือยัง เมื่อ manager ไม่เคยถูกเริ่มใช้งาน ฟังก์ชันนี้จงใจคืนค่า true (tesaiot_optiga_manager.c:157-169): "Reporting failure here is what made every call site fail open, because a caller could not tell 'no lock exists' from 'somebody else has it'." การสำเร็จเมื่อยังไม่มีอะไรอยู่เลย และล้มเหลวเฉพาะตอนมีการแย่งกัน ทำให้การปฏิเสธมีความหมายเพียงอย่างเดียว

optiga_manager_lock() เป็นตรงกันข้าม: มันคืนค่า false เมื่อไม่เคยถูกเริ่มใช้งาน จึงเป็นการทดสอบที่ถูกต้องสำหรับคำถาม "manager ขึ้นแล้วหรือยัง" การข้าม optiga_manager_init() ก่อน TLS ทำให้ CertificateVerify ล้มเหลวทุกครั้ง เพราะ trustm_ecdsa_sign() เริ่มต้นด้วย optiga_manager_lock() (บท C4, mqtt_mtls_setup.c:184-201) การสลับ enter กับ lock ผิดด้านคือสาเหตุที่มีบันทึกไว้ของข้อบกพร่องประเภทเข้าถึงชิปโดยไม่ผ่าน gate หลายกรณี

หนึ่ง exit ต่อหนึ่ง enter ที่สำเร็จ

{
/* Closing under another task's transaction is worse than deferring the
* close: the reference stays, and the next balanced close will do it. */
optiga_lib_print_message("close deferred: the chip is busy elsewhere",
OPTIGA_UTIL_SERVICE, OPTIGA_UTIL_SERVICE_COLOR);
return;
}
optiga_trust_close_application_gated();
}

ห้ามเรียก optiga_chip_exit() หลังจาก enter ที่ล้มเหลว — เส้นทางคืนค่าเร็วของ deferred-close มีอยู่เพื่อกิ่งนั้นโดยเฉพาะ gate คือตัวนับความลึกต่อหนึ่ง task ที่เป็นเจ้าของ และ exit ที่ไม่จับคู่จะทำให้ตัวนับเสียหายสำหรับทุกคน

lock → touch-hold → ทำงาน → release → unlock

/* ...context: inside trustm_ecdsa_sign() ... */
/* Lock OPTIGA mutex for thread-safe access */
printf("optiga_manager_lock failed\n");
return OPTIGA_LIB_BUSY;
}
/* Keep CM55 touch off SCB5 for the whole transaction — the secure element
* and the touch controller share the bus, and the TLS handshake signs after
* the mTLS setup has already resumed touch polling. */

นี่คือ trustm_ecdsa_sign() ซึ่งเป็นฟังก์ชันปลายทางของการจับมือ (handshake) ของ TLS ทุกครั้ง จับ lock ก่อน แล้วจึงจับ touch ไว้ตลอดทั้ง transaction — "the secure element and the touch controller share the bus, and the TLS handshake signs after the mTLS setup has already resumed touch polling."

/* ...context: inside trustm_ecdsa_sign(), the single exit path ... */

ปล่อย hold ก่อนปลด lock ลำดับเดียวกันนี้ถูกใช้ในการตรวจสอบโมเดลแบบเป็นขั้น (staged) ด้วย:

/* ...context: inside optiga_verify_staged_model() ... */
return STAGE_SIG_CHIP_ERROR;
}
/* Same bus discipline every other transaction here uses: the secure
* element and the touch controller share SCB5. */

รูปแบบ lock/unlock ที่ชัดเจนที่สุดอย่างในตำรา ซึ่งทุกเส้นทางคืนค่าเร็วถูกจับคู่ครบ อยู่ใน tesaiot_crypto.cเป็นเอกสารอ้างอิงที่ส่งมอบมา แต่ proj_cm33_ns ไม่ได้คอมไพล์ (ไม่อยู่ในรายการ SOURCES ใดเลย) ให้อ่านเป็นสำนวนการเขียน ไม่ใช่ในฐานะ call site จริง:

/* shipped reference, not compiled by proj_cm33_ns */
/* ...context: inside the TRNG random_generate helper ... */
if (!optiga_manager_is_initialized()) {
return TESAIOT_ERROR_NOT_INITIALIZED;
}
return TESAIOT_ERROR_TIMEOUT;
}
volatile optiga_lib_status_t optiga_status = OPTIGA_LIB_BUSY;
optiga_crypt_t *crypt = optiga_manager_create_crypt(crypto_callback,
(void *)&optiga_status);
if (!crypt) {
return TESAIOT_ERROR_OPTIGA;
}
optiga_lib_status_t result = optiga_crypt_random(crypt,
OPTIGA_RNG_TYPE_TRNG,
buffer,
length);
int rc = TESAIOT_ERROR_OPTIGA;
if (result == OPTIGA_LIB_SUCCESS) {
rc = crypto_wait_for_completion(&optiga_status);
}
optiga_crypt_destroy(crypt);

อีกหนึ่งกฎเรื่องลำดับจากไฟล์ตระกูลเดียวกัน: ห้ามเปิด application ของ OPTIGA ขณะที่ถือ lock อยู่ ให้เปิดก่อน แล้วจึงจับ lock (optiga_trust_helpers.c:4604-4622)

Acquire และ release

/* ...context: inside the certificate read helper ... */
// Acquire OPTIGA instance (thread-safe)
optiga_util_t *me_util = optiga_manager_acquire();
if (!me_util) {
printf("%s ERROR: OPTIGA instance not available\n", LABEL_SUBSCRIBER);
vPortFree(cert_der);
return CY_RSLT_TYPE_ERROR;
}
// Perform async read
optiga_lib_status = OPTIGA_LIB_BUSY;
optiga_lib_status_t status = optiga_util_read_data(me_util, oid, 0, cert_der, &cert_len);
if (OPTIGA_LIB_SUCCESS != status) {
printf("%s ERROR: OPTIGA read failed (0x%04X)\n", LABEL_SUBSCRIBER, status);
vPortFree(cert_der);
return CY_RSLT_TYPE_ERROR;
}
// Wait for read completion (max 2 seconds)
TickType_t start = xTaskGetTickCount();
TickType_t timeout = pdMS_TO_TICKS(2000);
while (optiga_lib_status == OPTIGA_LIB_BUSY && (xTaskGetTickCount() - start) < timeout) {
vTaskDelay(pdMS_TO_TICKS(100));
}
if (optiga_lib_status != OPTIGA_LIB_SUCCESS) {
printf("%s ERROR: OPTIGA read timeout/failed (0x%04X)\n", LABEL_SUBSCRIBER, optiga_lib_status);
vPortFree(cert_der);
return CY_RSLT_TYPE_ERROR;
}

optiga_manager_acquire() คืนค่า NULL จนกว่า optiga_manager_init() จะได้ทำงาน เมื่อได้ NULL ห้ามเรียก release() เพราะ implementation ออกจาก gate ไปแล้ว เมื่อได้ค่าที่ไม่ใช่ NULL ให้เรียก release() หนึ่งครั้งต่อหนึ่งเส้นทางขาออก รวมถึงเส้นทางความผิดพลาดด้วย — ฟังก์ชันที่ส่งมอบมามีทางออกสามทาง และปล่อยครบทั้งสามทาง รูปแบบแบบไม่ประสานเวลาจะตั้ง optiga_lib_status = OPTIGA_LIB_BUSY ก่อนเรียก แล้ววนถามหลังจากนั้น

hold แบบนับจำนวน ตลอดทั้งบทสนทนา

/* ...context: inside the Protected Update bundle ingest ... */
/* Keep CM55 touch off the bus for the whole ingest.
*
* The secure element and the touch controller share SCB5 on this board.
* The carried-over reference has no touchscreen and so no notion of this;
* dropped in here unchanged, its longest writes lose the bus mid-transfer.
* Observed 2026-08-06 on correlation bento-pu-1: the 8-byte trust anchor
* metadata write succeeded, and the 580-byte certificate write that follows
* failed with 0x0102 — OPTIGA_COMMS_ERROR, the transport layer, not the
* chip refusing anything — after 57 seconds of comms retries.
*
* Held across the entire function rather than per operation: the ingest is
* one long conversation with the chip, and releasing between steps would
* reopen the same window. Counted, so the signature path inside
* trustm_ecdsa_sign() nests without resuming polling early. Every exit runs
* through pu_done, which releases it. */

hold เป็นแบบนับจำนวน (s_touch_holds) การซ้อนชั้นภายในฟังก์ชันที่ถูกเรียกซึ่งจับ hold ด้วยจึงถูกต้องและจำเป็น hold ครั้งแรกจะหน่วง 50 ms เพื่อให้การรับส่งข้อมูลของ touch ที่ค้างอยู่ทำจนจบได้ ให้จับ hold ไว้ตลอด ทั้งบทสนทนากับชิป ไม่ใช่ทีละปฏิบัติการ: ตัว ingest นี้ผ่านความล้มเหลวนั้นมาแล้วด้วยราคาที่แพง เมื่อการเขียนใบรับรองขนาด 580 ไบต์เสียบัส SCB5 กลางการรับส่งข้อมูลและล้มเหลวด้วย 0x0102 ทุกเส้นทางขาออกถูกรวบให้ผ่านป้าย pu_done: เพียงป้ายเดียวซึ่งเป็นที่ปล่อย hold

wrapper ตระกูล *_held() ทั้ง 5 ตัว

static bool prov_open_held(void)
{
bool ok;
optiga_manager_touch_hold_reason("Opening the secure element");
return ok;
}
static void prov_close_held(void)
{
optiga_manager_touch_hold_reason("Closing the secure element");
}
static bool prov_make_csr_held(uint16_t key_oid)
{
bool ok;
optiga_manager_touch_hold_reason("Generating a key and signing the request");
ok = prov_make_csr(key_oid);
return ok;
}
static uint16_t prov_manifest_anchor_held(uint16_t oid)
{
uint16_t anchor;
optiga_manager_touch_hold_reason("Reading the secure element");
return anchor;
}
static int prov_publish_pu_held(const char *target, const char *anchor)
{
int rc;
optiga_manager_touch_hold_reason("Generating a key and signing the request");
rc = tesaiot_publish_protected_update(target, anchor, 1U, true);
return rc;
}

prov_open_held, prov_close_held, prov_make_csr_held, prov_manifest_anchor_held, prov_publish_pu_held: แต่ละตัวจับ hold พร้อมสตริงเหตุผล ทำงานหนึ่งอย่าง แล้วปล่อย คอมเมนต์เหนือกลุ่มนี้บันทึกเหตุผลที่ต้องมีไว้ว่า "of the eleven chip operations on this path only four" จับ touch เอง ส่วนการสร้างกุญแจและการลงลายเซ็น CSR ซึ่งเป็นสอง transaction ที่ยาวที่สุด กลับทำงานขณะที่ CM55 วนถาม FT5406 บน SCB เดียวกัน "which is what returns `OPTIGA_COMMS_ERROR (0x0102)` and then leaves the next call meeting `OPTIGA_UTIL_ERROR_INSTANCE_IN_USE (0x0305)`." และ: "Nothing holds across the 60 second wait for the platform: no chip traffic happens there, and freezing the screen for a minute would be its own bug."

สตริงเหตุผลไม่ใช่ของประดับ มันเดินทางไปยัง CM55 และถูกแสดงบนพาเนลระหว่างที่การสัมผัสถูกปิดอยู่ — "Opening the secure element", "Generating a key and signing the request", "Reading the secure element", "Closing the secure element"

ตัวอย่างเต็มที่ทุกเส้นทางคืนค่าเร็วถูกจับคู่ครบ

/* ...context: inside the synchronous credential read ... */
/* Pause CM55 touch to get exclusive SCB0 I2C access */
optiga_manager_touch_hold_reason("Reading stored credentials");
/* These take s_optiga_mutex but never entered the gate, so they were the
* third mechanism that did not know about the other two. */
xSemaphoreGive(s_optiga_mutex);
return false;
}
bool ok = false;
if (optiga_open()) {
uint16_t len = read_oid(oid, buf, buf_len);
printf("[HSM] cred_read_sync: slot %u OID 0x%04X → %u bytes\r\n",
slot, oid, len);
if (len > 0) {
if (out_len) *out_len = len;
ok = true;
}
optiga_close();
} else {
printf("[HSM] cred_read_sync: optiga_open FAILED (slot %u)\r\n", slot);
}
/* Resume CM55 touch polling */

จับ hold พร้อมเหตุผล แล้วเรียก optiga_chip_enter() เมื่อ enter ล้มเหลว ให้ ปล่อย hold และคืน mutex ก่อนคืนค่าออกไป mqtt_mtls_setup.c คือรูปแบบขยายของวินัยเดียวกันนี้ — ปล่อย 7 จุดต่อการจับ hold หนึ่งครั้ง

ข้อห้ามส่ง IPC_CMD_TOUCH_RESUME ดิบ

CM55 ปฏิบัติต่อ IPC_CMD_TOUCH_RESUME เป็น touch_disabled = false แบบไม่มีเงื่อนไข — มันไม่ใช่ตัวนับ (ipc_hsm_handler.c:1393-1405) การส่ง resume ดิบจาก task ใดก็ตามจะยกเลิก hold ที่ task อื่นกำลังพึ่งพาอยู่ และ touchpad_read() ครั้งถัดไปบน CM55 จะปิดแล้วเปิดบล็อก SCB ที่ใช้ร่วมกันใหม่ ทั้งที่ CM33_NS ยังมีการรับส่งข้อมูลค้างอยู่ นั่นคือ 0x0102 แบบเดียวกับที่การวนถามของ provisioning บนหน้านี้เองเคยก่อขึ้น "about 150 times per enrolment, at 400 ms intervals" ให้เรียกผ่าน optiga_manager_touch_hold_reason() / optiga_manager_touch_release() เสมอ และ ห้ามส่งคำสั่งดิบนั้นเด็ดขาด (ภาคผนวก X ข้อ 2)

ทีละขั้น

ขั้นตอนเหล่านี้ใช้หน้า HSM เพราะหน้านี้ใช้กลไกครบทั้ง 3 อย่าง สิ่งที่สังเกตได้คือพาเนลและ UART

ขั้นที่ 1 — ดู hold จากพาเนล

Home → HSM Security → Enrol Certificate (หรือสั่งอ่านข้อมูลรับรองด้วยวิธีใดก็ได้)

สิ่งที่ควรสังเกต การสัมผัสบนหน้าจอหยุดตอบสนอง และมีสตริงเหตุผลปรากฏขึ้น หนึ่งในสตริงข้างต้น เช่น Opening the secure element แล้วตามด้วย Generating a key and signing the request เมื่อ wrapper ปล่อย hold การสัมผัสจะกลับมา ไม่มีบรรทัด UART สำหรับ hold/release

ขั้นที่ 2 — ดู gate ปฏิเสธ

เริ่มการลงทะเบียน (enrolment) แล้วขณะที่สตริงเหตุผลของมันยังอยู่บนหน้าจอ ให้สั่งอ่านข้อมูลรับรองจากบอร์ดเดียวกัน (จะเป็นการกระทำที่สองบนหน้า HSM หรือเรียกโมดูล optiga จาก REPL บน mtb-mpy ก็ได้)

สิ่งที่ควรสังเกต ปฏิบัติการที่ 2 รายงานความล้มเหลวโดยไม่ได้แตะชิปเลย เพราะ gate คืนค่า false เนื่องจากมี task อื่นถืออยู่ บน UART นั้น ipc_hsm_cred_read_sync ไม่พิมพ์อะไรบนเส้นทางนั้น ส่วน mutex ที่หมดเวลาจะพิมพ์ [HSM] cred_read_sync: mutex timeout (slot u) (ipc_hsm_handler.c:2408) ปฏิบัติการแรกทำงานจนจบตามปกติ นี่คือ "การปฏิเสธมีความหมายเพียงอย่างเดียว": การแย่งกัน ไม่ใช่ "ยังไม่ได้เริ่มใช้งาน"

ขั้นที่ 3 — จดจำ 0x0102

ไม่ได้ขอให้ทำให้เกิดขึ้น เพราะโค้ดที่ส่งมอบมาถูกออกแบบให้ก่อกรณีนี้จาก UI ไม่ได้ แต่ให้จดจำไว้: ปฏิบัติการใดกับชิปที่คืนค่า OPTIGA_COMMS_ERROR (0x0102) และมักตามด้วย OPTIGA_UTIL_ERROR_INSTANCE_IN_USE (0x0305) ในการเรียก ครั้งถัดไป หมายความว่ามี transaction กับชิปทำงานขณะที่ CM55 กำลังวนถาม touch บน SCB เดียวกัน ให้มองหา hold ที่ขาดไป การส่ง IPC_CMD_TOUCH_RESUME ดิบ หรือการปล่อยที่ไม่จับคู่ซึ่งทำให้ตัวนับลงถึงศูนย์ก่อนเวลา บนเส้นทาง mTLS อาการคือ [PSA-Sign] ERROR: trustm_ecdsa_sign status=0x0102 (บท C4)

ขั้นที่ 4 — จดจำการปล่อยที่ไม่จับคู่

สิ่งที่ควรสังเกต พาเนลยังไม่ตอบสนองแม้ปฏิบัติการจบไปแล้ว — "an unbalanced release leaves the panel dead" ให้นับ hold และ release บนทุกเส้นทางขาออกของฟังก์ชันที่เพิ่งทำงานไป

กับดัก

กับดัก 1 — ใช้ optiga_chip_enter() เป็นการตรวจว่าเริ่มใช้งานแล้วหรือยัง
มันคืนค่า true เมื่อยังไม่มีอะไรถูกเริ่มใช้งาน ให้ใช้ optiga_manager_lock() สำหรับคำถาม "manager ขึ้นแล้วหรือยัง" (ภาคผนวก X ข้อ 3)
กับดัก 2 — เรียก exit() / release() หลังจาก enter() ล้มเหลว หรือหลัง acquire() คืน NULL
implementation ออกจาก gate ไปแล้ว การเรียก exit ซ้ำจะทำให้ตัวนับความลึกของ task ที่ถือชิปอยู่จริงเสียสมดุล
กับดัก 3 — ปล่อย touch ทีละปฏิบัติการแทนที่จะปล่อยเมื่อจบทั้งบทสนทนา
ระหว่างสองปฏิบัติการกับชิป ตัวควบคุมการสัมผัสจะได้บัสไป และปฏิบัติการถัดไปจะเจอ 0x0102 ให้จับ hold ไว้ตลอดทั้งบทสนทนา และรวบเส้นทางขาออกให้ผ่านป้ายที่ปล่อยเพียงป้ายเดียว
กับดัก 4 — ปลด lock ก่อนปล่อย hold
ลำดับขาออกคือ release แล้วจึง unlock ซึ่งเป็นเงาสะท้อนของ lock แล้วจึง hold ในขาเข้า
กับดัก 5 — จับ touch ค้างไว้ข้ามการรอเครือข่ายที่ยาวนาน
ในกระบวนการที่ส่งมอบมา ไม่มีอะไรจับ hold ค้างข้ามการรอแพลตฟอร์ม 60 s hold หนึ่งครั้งเท่ากับหน้าจอที่ค้างไปหนึ่งช่วง ให้จำกัดไว้เฉพาะช่วงที่มีการรับส่งข้อมูลกับชิป
กับดัก 6 — เรียก optiga_manager_init() ก่อน scheduler เริ่มทำงาน
มันไปถึง xTimerCreate() ภายใน critical section ที่ยังไม่มี scheduler (ipc_hsm_handler.c:1356-1364) ให้เรียกใน task context เท่านั้น ฟังก์ชันนี้เป็น idempotent (เรียกซ้ำได้โดยผลไม่เปลี่ยน) การเรียกจากมากกว่าหนึ่ง task จึงไม่มีปัญหา และ การเริ่มใช้งานเป็นคนละขั้นกับ optiga_trust_open_application()
กับดัก 7 — วาด optiga_chip_enter/exit เป็นขั้นตอนแยกในผังกระบวนการ
บนเส้นทางการลงทะเบียนและ mTLS ไม่มีอะไรเรียก 2 ฟังก์ชันนี้โดยตรง ทั้งคู่ถูกเข้าถึงผ่าน lock/unlock/acquire/release (tesaiot_optiga_manager.c:340-365) ให้แสดงเป็นชื่อเรียกซ้อน ไม่ใช่กล่องสองกล่อง

Variant

variant ที่ใช้ได้
mtb-mpy และ mtb-only

ฟังก์ชัน optiga_manager_* / optiga_chip_* ทั้ง 10 ตัวอยู่ใน libbento_hsm.a ซึ่งไม่ต้องใช้ symbol ของ MicroPython เลย ส่วน optiga_trust_helpers.c, tesaiot_pu_ingest.c และ ipc_hsm_handler.c ส่งมอบมาเป็นซอร์สและคอมไพล์ได้ทั้งสอง variant ส่วน IPC ฝั่ง touch ที่อยู่ข้างใต้ — ipc_hsm_touch_pause, ipc_hsm_touch_pause_reason, ipc_hsm_touch_resume — อยู่ในรายการ consumer_must_provide.txt ของ archive (บท D3) กล่าวคือเทมเพลตเป็นผู้จัดหาให้ และผู้ใช้ไลบรารีรายใดที่เปลี่ยน HSM handler ของเทมเพลตก็ต้องจัดหาเองเช่นกัน