Line 5: |
Line 5: |
| | | |
| == Registers == | | == Registers == |
− | Registers from 0x54500000 to 0x54501000 are used to configure values for the host interface (HOST1X). | + | Registers from 0x54500000 to 0x54501000 are used to configure the host interface (HOST1X). |
| | | |
− | Registers from 0x54501000 to 0x54502000 are a MMIO window for communicating with the Falcon microprocessor. From this range, the subset of registers from 0x54501400 to 0x54501FE8 are specific to the TSEC. | + | Registers from 0x54501000 to 0x54502000 are a MMIO window for communicating with the Falcon microprocessor. From this range, the subset of registers from 0x54501400 to 0x54501FE8 are specific to the TSEC and are subdivided into: |
| + | * 0x54501400 to 0x54501500: SCP (secure crypto processor?). |
| + | * 0x54501500 to 0x54501600: Unknown. |
| + | * 0x54501600 to 0x54501700: MCCIF (Memory Controller Client Interface). |
| + | * 0x54501700 to 0x54501800: DMA. |
| + | * 0x54501800 to 0x54501900: TEGRA (miscellaneous interfaces). |
| | | |
| {| class="wikitable" border="1" | | {| class="wikitable" border="1" |
Line 80: |
Line 85: |
| | [[#FALCON_IRQDEST|FALCON_IRQDEST]] | | | [[#FALCON_IRQDEST|FALCON_IRQDEST]] |
| | 0x5450101C | | | 0x5450101C |
| + | | 0x04 |
| + | |- |
| + | | FALCON_PERIODIC_PERIOD |
| + | | 0x54501020 |
| + | | 0x04 |
| + | |- |
| + | | FALCON_PERIODIC_TIME |
| + | | 0x54501024 |
| + | | 0x04 |
| + | |- |
| + | | FALCON_PERIODIC_ENABLE |
| + | | 0x54501028 |
| + | | 0x04 |
| + | |- |
| + | | FALCON_TIME_LOW |
| + | | 0x5450102C |
| + | | 0x04 |
| + | |- |
| + | | FALCON_TIME_HIGH |
| + | | 0x54501030 |
| + | | 0x04 |
| + | |- |
| + | | FALCON_WATCHDOG_TIME |
| + | | 0x54501034 |
| + | | 0x04 |
| + | |- |
| + | | FALCON_WATCHDOG_ENABLE |
| + | | 0x54501038 |
| | 0x04 | | | 0x04 |
| |- | | |- |
Line 106: |
Line 139: |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_SCRATCH2 | + | | FALCON_CMDCTX |
− | | 0x54501080 | + | | 0x54501058 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_SCRATCH3 | + | | FALCON_STATUS_MASK |
− | | 0x54501084 | + | | 0x5450105C |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_CGCTL | + | | FALCON_VM_SUPERVISOR |
− | | 0x545010A0 | + | | 0x54501060 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_ENGCTL | + | | FALCON_FIFO_DATA |
− | | 0x545010A4 | + | | 0x54501064 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | [[#FALCON_CPUCTL|FALCON_CPUCTL]] | + | | FALCON_FIFO_CMD |
− | | 0x54501100 | + | | 0x54501068 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | [[#FALCON_BOOTVEC|FALCON_BOOTVEC]] | + | | FALCON_FIFO_DATA_WR |
− | | 0x54501104 | + | | 0x5450106C |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_HWCFG | + | | FALCON_FIFO_OCCUPIED |
− | | 0x54501108 | + | | 0x54501070 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | [[#FALCON_DMACTL|FALCON_DMACTL]] | + | | FALCON_FIFO_ACK |
− | | 0x5450110C | + | | 0x54501074 |
| + | | 0x04 |
| + | |- |
| + | | FALCON_FIFO_LIMIT |
| + | | 0x54501078 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | [[#FALCON_DMATRFBASE|FALCON_DMATRFBASE]] | + | | FALCON_SUBENGINE_RESET |
− | | 0x54501110 | + | | 0x5450107C |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | [[#FALCON_DMATRFMOFFS|FALCON_DMATRFMOFFS]] | + | | FALCON_SCRATCH2 |
− | | 0x54501114 | + | | 0x54501080 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | [[#FALCON_DMATRFCMD|FALCON_DMATRFCMD]] | + | | FALCON_SCRATCH3 |
− | | 0x54501118 | + | | 0x54501084 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | [[#FALCON_DMATRFFBOFFS|FALCON_DMATRFFBOFFS]] | + | | FALCON_PM_TRIGGER |
− | | 0x5450111C | + | | 0x54501088 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_CPUCTL_ALIAS | + | | FALCON_PM_MODE |
− | | 0x54501130 | + | | 0x5450108C |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_IMFILLRNG1 | + | | FALCON_DEBUG1 |
− | | 0x54501154 | + | | 0x54501090 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_IMFILLCTL | + | | [[#FALCON_DEBUGINFO|FALCON_DEBUGINFO]] |
− | | 0x54501158 | + | | 0x54501094 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_EXTERRADDR | + | | FALCON_BREAKPOINT0 |
− | | 0x54501168 | + | | 0x54501098 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_EXTERRSTAT | + | | FALCON_BREAKPOINT1 |
− | | 0x5450116C | + | | 0x5450109C |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_CG2 | + | | FALCON_CGCTL |
− | | 0x5450117C | + | | 0x545010A0 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_CODE_INDEX | + | | FALCON_ENGCTL |
− | | 0x54501180 | + | | 0x545010A4 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_CODE | + | | FALCON_PM_SEL |
− | | 0x54501184 | + | | 0x545010A8 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_CODE_VIRT_ADDR | + | | FALCON_HOST_IO_INDEX |
− | | 0x54501188 | + | | 0x545010AC |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_DATA_INDEX0 | + | | [[#FALCON_CPUCTL|FALCON_CPUCTL]] |
− | | 0x545011C0 | + | | 0x54501100 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_DATA0 | + | | [[#FALCON_BOOTVEC|FALCON_BOOTVEC]] |
− | | 0x545011C4 | + | | 0x54501104 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_DATA_INDEX1 | + | | FALCON_HWCFG |
− | | 0x545011C8 | + | | 0x54501108 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_DATA1 | + | | [[#FALCON_DMACTL|FALCON_DMACTL]] |
− | | 0x545011CC | + | | 0x5450110C |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_DATA_INDEX2 | + | | [[#FALCON_DMATRFBASE|FALCON_DMATRFBASE]] |
− | | 0x545011D0 | + | | 0x54501110 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_DATA2 | + | | [[#FALCON_DMATRFMOFFS|FALCON_DMATRFMOFFS]] |
− | | 0x545011D4 | + | | 0x54501114 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_DATA_INDEX3 | + | | [[#FALCON_DMATRFCMD|FALCON_DMATRFCMD]] |
− | | 0x545011D8 | + | | 0x54501118 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_DATA3 | + | | [[#FALCON_DMATRFFBOFFS|FALCON_DMATRFFBOFFS]] |
− | | 0x545011DC | + | | 0x5450111C |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_DATA_INDEX4 | + | | FALCON_DMATRFSTAT |
− | | 0x545011E0 | + | | 0x54501120 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_DATA4 | + | | FALCON_CRYPTTRFSTAT |
− | | 0x545011E4 | + | | 0x54501124 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_DATA_INDEX5 | + | | FALCON_CPUSTAT |
− | | 0x545011E8 | + | | 0x54501128 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_DATA5 | + | | FALCON_HWCFG_ALIAS |
− | | 0x545011EC | + | | 0x5450112C |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_DATA_INDEX6 | + | | FALCON_CPUCTL_ALIAS |
− | | 0x545011F0 | + | | 0x54501130 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_DATA6 | + | | FALCON_TLB_CMD |
− | | 0x545011F4 | + | | 0x54501140 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_DATA_INDEX7 | + | | FALCON_TLB_CMD_RES |
− | | 0x545011F8 | + | | 0x54501144 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_DATA7 | + | | FALCON_BRANCH_HISTORY_CTRL |
− | | 0x545011FC | + | | 0x54501148 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_ICD_CMD | + | | FALCON_BRANCH_HISTORY_PC |
− | | 0x54501200 | + | | 0x5450114C |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_ICD_ADDR | + | | FALCON_IMFILLRNG0 |
− | | 0x54501204 | + | | 0x54501150 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_ICD_WDATA | + | | FALCON_IMFILLRNG1 |
− | | 0x54501208 | + | | 0x54501154 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_ICD_RDATA | + | | FALCON_IMFILLCTL |
− | | 0x5450120C | + | | 0x54501158 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | FALCON_SCTL | + | | FALCON_EXTERRWIN |
− | | 0x54501240 | + | | 0x54501160 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | TSEC_SCP_CTL_UNK0 | + | | FALCON_EXTERRCFG |
− | | 0x54501400 | + | | 0x54501164 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | TSEC_SCP_CTL_UNK1 | + | | FALCON_EXTERRADDR |
− | | 0x54501404 | + | | 0x54501168 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | [[#TSEC_SCP_CTL_STAT|TSEC_SCP_CTL_STAT]] | + | | FALCON_EXTERRSTAT |
− | | 0x54501408 | + | | 0x5450116C |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | TSEC_SCP_CTL_AUTH_MODE | + | | FALCON_CG2 |
− | | 0x5450140C | + | | 0x5450117C |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | TSEC_SCP_CTL_UNK2 | + | | FALCON_CODE_INDEX |
− | | 0x54501410 | + | | 0x54501180 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | [[#TSEC_SCP_CTL_PKEY|TSEC_SCP_CTL_PKEY]] | + | | FALCON_CODE |
− | | 0x54501418 | + | | 0x54501184 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | TSEC_SCP_CTL_UNK3 | + | | FALCON_CODE_VIRT_ADDR |
− | | 0x54501420 | + | | 0x54501188 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | TSEC_SCP_CTL_UNK4 | + | | FALCON_DATA_INDEX0 |
− | | 0x54501428 | + | | 0x545011C0 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | TSEC_SCP_CTL_UNK5 | + | | FALCON_DATA0 |
− | | 0x54501430 | + | | 0x545011C4 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | TSEC_SCP_UNK0 | + | | FALCON_DATA_INDEX1 |
− | | 0x54501454 | + | | 0x545011C8 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | TSEC_SCP_UNK1 | + | | FALCON_DATA1 |
− | | 0x54501458 | + | | 0x545011CC |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | TSEC_SCP_UNK2 | + | | FALCON_DATA_INDEX2 |
− | | 0x54501470 | + | | 0x545011D0 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | TSEC_SCP_UNK3 | + | | FALCON_DATA2 |
− | | 0x54501480 | + | | 0x545011D4 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | TSEC_SCP_UNK4 | + | | FALCON_DATA_INDEX3 |
− | | 0x54501490 | + | | 0x545011D8 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | TSEC_UNK0 | + | | FALCON_DATA3 |
− | | 0x54501500 | + | | 0x545011DC |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | TSEC_UNK1 | + | | FALCON_DATA_INDEX4 |
− | | 0x54501504 | + | | 0x545011E0 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | TSEC_UNK2 | + | | FALCON_DATA4 |
− | | 0x5450150C | + | | 0x545011E4 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | TSEC_UNK3 | + | | FALCON_DATA_INDEX5 |
− | | 0x54501510 | + | | 0x545011E8 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | TSEC_UNK4 | + | | FALCON_DATA5 |
− | | 0x54501514 | + | | 0x545011EC |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | TSEC_UNK5 | + | | FALCON_DATA_INDEX6 |
− | | 0x54501518 | + | | 0x545011F0 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | TSEC_UNK6 | + | | FALCON_DATA6 |
− | | 0x5450151C | + | | 0x545011F4 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | TSEC_UNK7 | + | | FALCON_DATA_INDEX7 |
− | | 0x54501528 | + | | 0x545011F8 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | TSEC_UNK8 | + | | FALCON_DATA7 |
− | | 0x5450152C | + | | 0x545011FC |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | TSEC_TFBIF_MCCIF_UNK0 | + | | FALCON_ICD_CMD |
− | | 0x54501600 | + | | 0x54501200 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | TSEC_TFBIF_MCCIF_FIFOCTRL | + | | FALCON_ICD_ADDR |
− | | 0x54501604 | + | | 0x54501204 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | TSEC_TFBIF_MCCIF_UNK1 | + | | FALCON_ICD_WDATA |
− | | 0x54501608 | + | | 0x54501208 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | TSEC_TFBIF_MCCIF_UNK2 | + | | FALCON_ICD_RDATA |
− | | 0x5450160C | + | | 0x5450120C |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | TSEC_TFBIF_UNK0 | + | | FALCON_SCTL |
− | | 0x54501630 | + | | 0x54501240 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | TSEC_TFBIF_UNK1 | + | | TSEC_SCP_UNK0 |
− | | 0x54501634 | + | | 0x54501400 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | TSEC_TFBIF_UNK2 | + | | TSEC_SCP_UNK1 |
− | | 0x54501640 | + | | 0x54501404 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | TSEC_TFBIF_UNK3 | + | | [[#TSEC_SCP_CTL_STAT|TSEC_SCP_CTL_STAT]] |
− | | 0x54501644 | + | | 0x54501408 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | TSEC_TFBIF_UNK4 | + | | TSEC_SCP_CTL_AUTH_MODE |
− | | 0x54501648 | + | | 0x5450140C |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | [[#TSEC_DMA_CMD|TSEC_DMA_CMD]] | + | | TSEC_SCP_UNK2 |
− | | 0x54501700 | + | | 0x54501410 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | [[#TSEC_DMA_ADDR|TSEC_DMA_ADDR]] | + | | [[#TSEC_SCP_CTL_PKEY|TSEC_SCP_CTL_PKEY]] |
− | | 0x54501704 | + | | 0x54501418 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | [[#TSEC_DMA_VAL|TSEC_DMA_VAL]] | + | | TSEC_SCP_UNK3 |
− | | 0x54501708 | + | | 0x54501420 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | [[#TSEC_DMA_UNK|TSEC_DMA_UNK]] | + | | TSEC_SCP_UNK4 |
− | | 0x5450170C | + | | 0x54501428 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | TSEC_TEGRA_UNK0 | + | | TSEC_SCP_UNK5 |
− | | 0x54501800 | + | | 0x54501430 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | TSEC_TEGRA_UNK1 | + | | TSEC_SCP_UNK6 |
− | | 0x54501824 | + | | 0x54501454 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | TSEC_TEGRA_UNK2 | + | | TSEC_SCP_UNK7 |
− | | 0x54501828 | + | | 0x54501458 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | TSEC_TEGRA_UNK3 | + | | TSEC_SCP_UNK8 |
− | | 0x5450182C | + | | 0x54501470 |
| | 0x04 | | | 0x04 |
| |- | | |- |
− | | [[#TSEC_TEGRA_CTL|TSEC_TEGRA_CTL]] | + | | TSEC_SCP_UNK9 |
− | | 0x54501838 | + | | 0x54501480 |
| | 0x04 | | | 0x04 |
− | |}
| |
− |
| |
− | === FALCON_IRQMSET ===
| |
− | Used for configuring Falcon's IRQs.
| |
− |
| |
− | === FALCON_IRQDEST ===
| |
− | Used for configuring Falcon's IRQs.
| |
− |
| |
− | === FALCON_SCRATCH0 ===
| |
− | MMIO register for reading/writing data to Falcon.
| |
− |
| |
− | === FALCON_SCRATCH1 ===
| |
− | MMIO register for reading/writing data to Falcon.
| |
− |
| |
− | === FALCON_ITFEN ===
| |
− | {| class="wikitable" border="1"
| |
− | ! Bits
| |
− | ! Description
| |
| |- | | |- |
− | | 0 | + | | TSEC_SCP_UNK10 |
− | | FALCON_ITFEN_CTXEN | + | | 0x54501490 |
| + | | 0x04 |
| + | |- |
| + | | TSEC_UNK0 |
| + | | 0x54501500 |
| + | | 0x04 |
| |- | | |- |
− | | 1 | + | | TSEC_UNK1 |
− | | FALCON_ITFEN_MTHDEN | + | | 0x54501504 |
− | |} | + | | 0x04 |
− | | |
− | Used for enabling/disabling Falcon interfaces.
| |
− | | |
− | === FALCON_IDLESTATE ===
| |
− | {| class="wikitable" border="1"
| |
− | ! Bits
| |
− | ! Description
| |
| |- | | |- |
− | | 0 | + | | TSEC_UNK2 |
− | | FALCON_IDLESTATE_FALCON_BUSY | + | | 0x5450150C |
− | |} | + | | 0x04 |
− | | |
− | Used for detecting if Falcon is busy or not.
| |
− | | |
− | === FALCON_CPUCTL ===
| |
− | {| class="wikitable" border="1"
| |
− | ! Bits
| |
− | ! Description
| |
| |- | | |- |
− | | 0 | + | | TSEC_UNK3 |
− | | FALCON_CPUCTL_IINVAL | + | | 0x54501510 |
| + | | 0x04 |
| |- | | |- |
− | | 1 | + | | TSEC_UNK4 |
− | | FALCON_CPUCTL_STARTCPU | + | | 0x54501514 |
| + | | 0x04 |
| |- | | |- |
− | | 2 | + | | TSEC_UNK5 |
− | | FALCON_CPUCTL_SRESET | + | | 0x54501518 |
| + | | 0x04 |
| |- | | |- |
− | | 3 | + | | TSEC_UNK6 |
− | | FALCON_CPUCTL_HRESET | + | | 0x5450151C |
| + | | 0x04 |
| |- | | |- |
− | | 4 | + | | TSEC_UNK7 |
− | | FALCON_CPUCTL_HALTED | + | | 0x54501528 |
| + | | 0x04 |
| |- | | |- |
− | | 5 | + | | TSEC_UNK8 |
− | | FALCON_CPUCTL_STOPPED | + | | 0x5450152C |
− | |} | + | | 0x04 |
− | | |
− | Used for signaling the Falcon CPU.
| |
− | | |
− | === FALCON_BOOTVEC ===
| |
− | Takes the Falcon's boot vector address.
| |
− | | |
− | === FALCON_DMACTL ===
| |
− | {| class="wikitable" border="1"
| |
− | ! Bits
| |
− | ! Description
| |
| |- | | |- |
− | | 0 | + | | TSEC_MCCIF_UNK0 |
− | | FALCON_DMACTL_REQUIRE_CTX | + | | 0x54501600 |
| + | | 0x04 |
| |- | | |- |
− | | 1 | + | | [[#TSEC_MCCIF_FIFOCTRL|TSEC_MCCIF_FIFOCTRL]] |
− | | FALCON_DMACTL_DMEM_SCRUBBING | + | | 0x54501604 |
| + | | 0x04 |
| |- | | |- |
− | | 2 | + | | TSEC_MCCIF_UNK1 |
− | | FALCON_DMACTL_IMEM_SCRUBBING | + | | 0x54501608 |
| + | | 0x04 |
| |- | | |- |
− | | 3-6 | + | | TSEC_MCCIF_UNK2 |
− | | FALCON_DMACTL_DMAQ_NUM | + | | 0x5450160C |
| + | | 0x04 |
| |- | | |- |
− | | 7 | + | | TSEC_MCCIF_UNK3 |
− | | FALCON_DMACTL_SECURE_STAT | + | | 0x54501630 |
− | |} | + | | 0x04 |
− | | |
− | Used for configuring the Falcon's DMA engine.
| |
− | | |
− | === FALCON_DMATRFBASE ===
| |
− | Takes the host's base address for transferring data to/from the Falcon (DMA).
| |
− | | |
− | === FALCON_DMATRFMOFFS ===
| |
− | Takes the offset for the host's source memory being transferred.
| |
− | | |
− | === FALCON_DMATRFCMD ===
| |
− | {| class="wikitable" border="1"
| |
− | ! Bits
| |
− | ! Description
| |
| |- | | |- |
− | | 0 | + | | [[#TSEC_MCCIF_FIFOCTRL1|TSEC_MCCIF_FIFOCTRL1]] |
− | | FALCON_DMATRFCMD_FULL | + | | 0x54501634 |
| + | | 0x04 |
| |- | | |- |
− | | 1 | + | | TSEC_MCCIF_UNK4 |
− | | FALCON_DMATRFCMD_IDLE | + | | 0x54501640 |
| + | | 0x04 |
| + | |- |
| + | | [[#TSEC_MCCIF_UNK5|TSEC_MCCIF_UNK5]] |
| + | | 0x54501644 |
| + | | 0x04 |
| |- | | |- |
− | | 2-3 | + | | [[#TSEC_MCCIF_UNK6|TSEC_MCCIF_UNK6]] |
− | | FALCON_DMATRFCMD_SEC | + | | 0x54501648 |
| + | | 0x04 |
| |- | | |- |
− | | 4 | + | | [[#TSEC_DMA_CMD|TSEC_DMA_CMD]] |
− | | FALCON_DMATRFCMD_IMEM | + | | 0x54501700 |
| + | | 0x04 |
| |- | | |- |
− | | 5 | + | | [[#TSEC_DMA_ADDR|TSEC_DMA_ADDR]] |
− | | FALCON_DMATRFCMD_WRITE | + | | 0x54501704 |
| + | | 0x04 |
| |- | | |- |
− | | 8-10 | + | | [[#TSEC_DMA_VAL|TSEC_DMA_VAL]] |
− | | FALCON_DMATRFCMD_SIZE | + | | 0x54501708 |
| + | | 0x04 |
| |- | | |- |
− | | 12-14 | + | | [[#TSEC_DMA_UNK|TSEC_DMA_UNK]] |
− | | FALCON_DMATRFCMD_CTXDMA | + | | 0x5450170C |
− | |} | + | | 0x04 |
− | | |
− | Used for configuring DMA transfers.
| |
− | | |
− | === FALCON_DMATRFFBOFFS ===
| |
− | Takes the offset for Falcon's target memory being transferred.
| |
− | | |
− | === TSEC_SCP_CTL_STAT ===
| |
− | {| class="wikitable" border="1"
| |
− | ! Bits
| |
− | ! Description
| |
| |- | | |- |
− | | 20 | + | | TSEC_TEGRA_UNK0 |
− | | TSEC_SCP_CTL_STAT_DEBUG_MODE | + | | 0x54501800 |
− | |} | + | | 0x04 |
− | | |
− | === TSEC_SCP_CTL_PKEY ===
| |
− | {| class="wikitable" border="1"
| |
− | ! Bits
| |
− | ! Description
| |
| |- | | |- |
− | | 0 | + | | TSEC_TEGRA_UNK1 |
− | | TSEC_SCP_CTL_PKEY_REQUEST_RELOAD | + | | 0x54501824 |
| + | | 0x04 |
| |- | | |- |
− | | 1 | + | | TSEC_TEGRA_UNK2 |
− | | TSEC_SCP_CTL_PKEY_LOADED | + | | 0x54501828 |
| + | | 0x04 |
| + | |- |
| + | | TSEC_TEGRA_UNK3 |
| + | | 0x5450182C |
| + | | 0x04 |
| + | |- |
| + | | [[#TSEC_TEGRA_CTL|TSEC_TEGRA_CTL]] |
| + | | 0x54501838 |
| + | | 0x04 |
| |} | | |} |
| | | |
− | === TSEC_DMA_CMD === | + | === FALCON_IRQMSET === |
| + | Used for configuring Falcon's IRQs. |
| + | |
| + | === FALCON_IRQDEST === |
| + | Used for configuring Falcon's IRQs. |
| + | |
| + | === FALCON_SCRATCH0 === |
| + | MMIO register for reading/writing data to Falcon. |
| + | |
| + | === FALCON_SCRATCH1 === |
| + | MMIO register for reading/writing data to Falcon. |
| + | |
| + | === FALCON_ITFEN === |
| {| class="wikitable" border="1" | | {| class="wikitable" border="1" |
| ! Bits | | ! Bits |
Line 593: |
Line 606: |
| |- | | |- |
| | 0 | | | 0 |
− | | TSEC_DMA_CMD_READ | + | | FALCON_ITFEN_CTXEN |
| |- | | |- |
| | 1 | | | 1 |
− | | TSEC_DMA_CMD_WRITE | + | | FALCON_ITFEN_MTHDEN |
| + | |} |
| + | |
| + | Used for enabling/disabling Falcon interfaces. |
| + | |
| + | === FALCON_IDLESTATE === |
| + | {| class="wikitable" border="1" |
| + | ! Bits |
| + | ! Description |
| |- | | |- |
− | | 4-7 | + | | 0 |
− | | TSEC_DMA_CMD_UNK | + | | FALCON_IDLESTATE_FALCON_BUSY |
− | |-
| |
− | | 12
| |
− | | TSEC_DMA_CMD_BUSY
| |
− | |-
| |
− | | 13
| |
− | | TSEC_DMA_CMD_ERROR
| |
− | |-
| |
− | | 31
| |
− | | TSEC_DMA_CMD_INIT
| |
| |} | | |} |
| | | |
− | A DMA read/write operation requires bits TSEC_DMA_CMD_INIT and TSEC_DMA_CMD_READ/TSEC_DMA_CMD_WRITE to be set in TSEC_DMA_CMD.
| + | Used for detecting if Falcon is busy or not. |
| | | |
− | During the transfer, the TSEC_DMA_CMD_BUSY bit is set.
| + | === FALCON_DEBUGINFO === |
| + | [6.0.0+] [[NV_services|nvservices]] sets this to 0x8005FF00 >> 8 (physical DRAM address inside the GPU UCODE carveout) before starting the nvhost_tsec firmware. |
| | | |
− | Accessing an invalid address causes bit TSEC_DMA_CMD_ERROR to be set.
| + | === FALCON_CPUCTL === |
− | | + | {| class="wikitable" border="1" |
− | === TSEC_DMA_ADDR === | + | ! Bits |
− | Takes the address for DMA transfers between TSEC and HOST1X (master and clients).
| |
− | | |
− | === TSEC_DMA_VAL ===
| |
− | Takes the value for DMA transfers between TSEC and HOST1X (master and clients).
| |
− | | |
− | === TSEC_DMA_UNK ===
| |
− | Always 0xFFF.
| |
− | | |
− | === TSEC_TEGRA_CTL ===
| |
− | {| class="wikitable" border="1" | |
− | ! Bits | |
| ! Description | | ! Description |
| |- | | |- |
− | | 16 | + | | 0 |
− | | TSEC_TEGRA_CTL_TKFI_KFUSE | + | | FALCON_CPUCTL_IINVAL |
| |- | | |- |
− | | 17 | + | | 1 |
− | | TSEC_TEGRA_CTL_TKFI_RESTART_FSM_KFUSE | + | | FALCON_CPUCTL_STARTCPU |
| |- | | |- |
− | | 24 | + | | 2 |
− | | TSEC_TEGRA_CTL_TMPI_FORCE_IDLE_INPUTS_I2C | + | | FALCON_CPUCTL_SRESET |
| |- | | |- |
− | | 25 | + | | 3 |
− | | TSEC_TEGRA_CTL_TMPI_RESTART_FSM_HOST1X | + | | FALCON_CPUCTL_HRESET |
| |- | | |- |
− | | 26 | + | | 4 |
− | | TSEC_TEGRA_CTL_TMPI_RESTART_FSM_APB | + | | FALCON_CPUCTL_HALTED |
| |- | | |- |
− | | 27 | + | | 5 |
− | | TSEC_TEGRA_CTL_TMPI_DISABLE_OUTPUT_I2C | + | | FALCON_CPUCTL_STOPPED |
| |} | | |} |
| | | |
− | = Boot Process =
| + | Used for signaling the Falcon CPU. |
− | TSEC is configured and initialized by the first bootloader during key generation.
| |
| | | |
− | [6.2.0+] TSEC is now configured at the end of the first bootloader's main function.
| + | === FALCON_BOOTVEC === |
| + | Takes the Falcon's boot vector address. |
| | | |
− | == Initialization == | + | === FALCON_DMACTL === |
− | During this stage several clocks are programmed.
| + | {| class="wikitable" border="1" |
− | // Program the HOST1X clock and resets
| + | ! Bits |
− | // Uses RST_DEVICES_L, CLK_OUT_ENB_L, CLK_SOURCE_HOST1X and CLK_L_HOST1X
| + | ! Description |
− | enable_host1x_clkrst(); | + | |- |
− | | + | | 0 |
− | // Program the TSEC clock and resets
| + | | FALCON_DMACTL_REQUIRE_CTX |
− | // Uses RST_DEVICES_U, CLK_OUT_ENB_U, CLK_SOURCE_TSEC and CLK_U_TSEC
| + | |- |
− | enable_tsec_clkrst();
| + | | 1 |
− |
| + | | FALCON_DMACTL_DMEM_SCRUBBING |
− | // Program the SOR_SAFE clock and resets
| + | |- |
− | // Uses RST_DEVICES_Y, CLK_OUT_ENB_Y and CLK_Y_SOR_SAFE
| + | | 2 |
− | enable_sor_safe_clkrst();
| + | | FALCON_DMACTL_IMEM_SCRUBBING |
− |
| + | |- |
− | // Program the SOR0 clock and resets
| + | | 3-6 |
− | // Uses RST_DEVICES_X, CLK_OUT_ENB_X and CLK_X_SOR0
| + | | FALCON_DMACTL_DMAQ_NUM |
− | enable_sor0_clkrst();
| + | |- |
− |
| + | | 7 |
− | // Program the SOR1 clock and resets
| + | | FALCON_DMACTL_SECURE_STAT |
− | // Uses RST_DEVICES_X, CLK_OUT_ENB_X, CLK_SOURCE_SOR1 and CLK_X_SOR1
| + | |} |
− | enable_sor1_clkrst();
| |
− |
| |
− | // Program the KFUSE clock resets
| |
− | // Uses RST_DEVICES_H, CLK_OUT_ENB_H and CLK_H_KFUSE
| |
− | enable_kfuse_clkrst();
| |
| | | |
− | == Configuration ==
| + | Used for configuring the Falcon's DMA engine. |
− | In this stage the Falcon IRQs, interfaces and DMA engine are configured.
| + | |
− | // Clear the Falcon DMA control register
| + | === FALCON_DMATRFBASE === |
− | *(u32 *)FALCON_DMACTL = 0;
| + | Takes the host's base address for transferring data to/from the Falcon (DMA). |
− |
| + | |
− | // Enable Falcon IRQs
| + | === FALCON_DMATRFMOFFS === |
− | *(u32 *)FALCON_IRQMSET = 0xFFF2;
| + | Takes the offset for the host's source memory being transferred. |
− |
| |
− | // Enable Falcon IRQs
| |
− | *(u32 *)FALCON_IRQDEST = 0xFFF0;
| |
− |
| |
− | // Enable Falcon interfaces
| |
− | *(u32 *)FALCON_ITFEN = 0x03;
| |
− |
| |
− | // Wait for Falcon's DMA engine to be idle
| |
− | wait_flcn_dma_idle();
| |
| | | |
− | == Firmware loading == | + | === FALCON_DMATRFCMD === |
− | The Falcon firmware code is stored in the first bootloader's data segment in IMEM.
| + | {| class="wikitable" border="1" |
− | // Set DMA transfer base address to 0x40011900 >> 0x08
| + | ! Bits |
− | *(u32 *)FALCON_DMATRFBASE = 0x400119;
| + | ! Description |
− |
| + | |- |
− | u32 trf_mode = 0; // A value of 0 sets FALCON_DMATRFCMD_IMEM
| + | | 0 |
− | u32 dst_offset = 0;
| + | | FALCON_DMATRFCMD_FULL |
− | u32 src_offset = 0;
| + | |- |
− | | + | | 1 |
− | // Load code into Falcon (0x100 bytes at a time) | + | | FALCON_DMATRFCMD_IDLE |
− | while (src_offset < 0xF00)
| + | |- |
− | {
| + | | 2-3 |
− | flcn_load_firm(trf_mode, src_offset, dst_offset);
| + | | FALCON_DMATRFCMD_SEC |
− | src_offset += 0x100;
| + | |- |
− | dst_offset += 0x100;
| + | | 4 |
− | }
| + | | FALCON_DMATRFCMD_IMEM |
− | | + | |- |
− | [6.2.0+] The transfer base address and size of the Falcon firmware code changed.
| + | | 5 |
− | // Set DMA transfer base address to 0x40010E00 >> 0x08
| + | | FALCON_DMATRFCMD_WRITE |
− | *(u32 *)FALCON_DMATRFBASE = 0x40010E;
| + | |- |
− |
| + | | 8-10 |
− | u32 trf_mode = 0; // A value of 0 sets FALCON_DMATRFCMD_IMEM
| + | | FALCON_DMATRFCMD_SIZE |
− | u32 dst_offset = 0;
| + | |- |
− | u32 src_offset = 0;
| + | | 12-14 |
− |
| + | | FALCON_DMATRFCMD_CTXDMA |
− | // Load code into Falcon (0x100 bytes at a time)
| + | |} |
− | while (src_offset < 0x2900)
| + | |
− | {
| + | Used for configuring DMA transfers. |
− | flcn_load_firm(trf_mode, src_offset, dst_offset);
| |
− | src_offset += 0x100;
| |
− | dst_offset += 0x100;
| |
− | }
| |
| | | |
− | == Firmware booting == | + | === FALCON_DMATRFFBOFFS === |
− | Falcon is booted up and the first bootloader waits for it to finish.
| + | Takes the offset for Falcon's target memory being transferred. |
− | // Set magic value in host1x scratch space
| + | |
− | *(u32 *)0x50003300 = 0x34C2E1DA;
| + | === TSEC_SCP_CTL_STAT === |
− | | + | {| class="wikitable" border="1" |
− | // Clear Falcon scratch1 MMIO | + | ! Bits |
− | *(u32 *)FALCON_SCRATCH1 = 0;
| + | ! Description |
− |
| + | |- |
− | // Set Falcon boot key version in scratch0 MMIO
| + | | 20 |
− | *(u32 *)FALCON_SCRATCH0 = 0x01;
| + | | TSEC_SCP_CTL_STAT_DEBUG_MODE |
− | | + | |} |
− | // Set Falcon's boot vector address | + | |
− | *(u32 *)FALCON_BOOTVEC = 0;
| + | === TSEC_SCP_CTL_PKEY === |
− |
| + | {| class="wikitable" border="1" |
− | // Signal Falcon's CPU
| + | ! Bits |
− | *(u32 *)FALCON_CPUCTL = 0x02;
| + | ! Description |
− |
| + | |- |
− | // Wait for Falcon's DMA engine to be idle | + | | 0 |
− | wait_flcn_dma_idle(); | + | | TSEC_SCP_CTL_PKEY_REQUEST_RELOAD |
− |
| + | |- |
− | u32 boot_res = 0;
| + | | 1 |
− |
| + | | TSEC_SCP_CTL_PKEY_LOADED |
− | // The bootloader allows the TSEC two seconds from this point to do its job
| + | |} |
− | u32 maximum_time = read_timer() + 2000000;
| + | |
− |
| + | === TSEC_MCCIF_FIFOCTRL === |
− | while (!boot_res)
| + | {| class="wikitable" border="1" |
− | {
| + | ! Bits |
− | // Read boot result from scratch1 MMIO
| + | ! Description |
− | boot_res = *(u32 *)FALCON_SCRATCH1;
| + | |- |
− |
| + | | 0 |
− | // Read from TIMERUS_CNTR_1US (microseconds from boot)
| + | | TSEC_MCCIF_FIFOCTRL_RCLK_OVERRIDE |
− | u32 current_time = read_timer();
| + | |- |
− |
| + | | 1 |
− | // Booting is taking too long
| + | | TSEC_MCCIF_FIFOCTRL_WCLK_OVERRIDE |
− | if (current_time > maximum_time)
| + | |- |
− | panic();
| + | | 2 |
− | }
| + | | TSEC_MCCIF_FIFOCTRL_WRCL_MCLE2X |
− |
| + | |- |
− | // Invalid boot result was returned
| + | | 3 |
− | if (boot_res != 0xB0B0B0B0)
| + | | TSEC_MCCIF_FIFOCTRL_RDMC_RDFAST |
− | panic();
| + | |- |
| + | | 4 |
| + | | TSEC_MCCIF_FIFOCTRL_WRMC_CLLE2X |
| + | |- |
| + | | 5 |
| + | | TSEC_MCCIF_FIFOCTRL_RDCL_RDFAST |
| + | |- |
| + | | 6 |
| + | | TSEC_MCCIF_FIFOCTRL_CCLK_OVERRIDE |
| + | |- |
| + | | 7 |
| + | | TSEC_MCCIF_FIFOCTRL_RCLK_OVR_MODE |
| + | |- |
| + | | 8 |
| + | | TSEC_MCCIF_FIFOCTRL_WCLK_OVR_MODE |
| + | |} |
| | | |
− | [6.2.0+] Falcon is booted up, but the first bootloader is left in an infinite loop.
| + | === TSEC_MCCIF_FIFOCTRL1 === |
− | // Set magic value in host1x scratch space
| + | {| class="wikitable" border="1" |
− | *(u32 *)0x50003300 = 0x34C2E1DA; | + | ! Bits |
− | | + | ! Description |
− | // Clear Falcon scratch1 MMIO
| + | |- |
− | *(u32 *)FALCON_SCRATCH1 = 0;
| + | | 0-15 |
− |
| + | | TSEC_MCCIF_FIFOCTRL1_SRD2MC_REORDER_DEPTH_LIMIT |
− | // Set Falcon boot key version in scratch0 MMIO
| + | |- |
− | *(u32 *)FALCON_SCRATCH0 = 0x01;
| + | | 16-31 |
− |
| + | | TSEC_MCCIF_FIFOCTRL1_SWR2MC_REORDER_DEPTH_LIMIT |
− | // Set Falcon's boot vector address
| + | |} |
− | *(u32 *)FALCON_BOOTVEC = 0;
| + | |
− |
| + | === TSEC_MCCIF_UNK5 === |
− | // Signal Falcon's CPU
| + | Used to control accesses to DRAM. |
− | *(u32 *)FALCON_CPUCTL = 0x02;
| + | |
− |
| + | [6.0.0+] The nvhost_tsec firmware sets this register to 0x10 or 0x111110 before reading memory from the GPU UCODE carveout. |
− | // Infinite loop
| + | |
− | deadlock();
| + | === TSEC_MCCIF_UNK6 === |
| + | Used to control accesses to DRAM. |
| + | |
| + | [6.0.0+] The nvhost_tsec firmware sets this register to (data_size << 4) before reading memory from the GPU UCODE carveout. |
| | | |
− | == Device key generation == | + | === TSEC_DMA_CMD === |
− | The TSEC device key is generated by reading SOR1 registers modified by the Falcon CPU.
| + | {| class="wikitable" border="1" |
− | // Clear magic value in host1x scratch space | + | ! Bits |
− | *(u32 *)0x50003300 = 0; | + | ! Description |
− |
| + | |- |
− | // Read TSEC device key
| + | | 0 |
− | u32 tsec_device_key[4];
| + | | TSEC_DMA_CMD_READ |
− | tsec_device_key[0] = *(u32 *)NV_SOR_DP_HDCP_BKSV_LSB;
| + | |- |
− | tsec_device_key[1] = *(u32 *)NV_SOR_TMDS_HDCP_BKSV_LSB;
| + | | 1 |
− | tsec_device_key[2] = *(u32 *)NV_SOR_TMDS_HDCP_CN_MSB;
| + | | TSEC_DMA_CMD_WRITE |
− | tsec_device_key[3] = *(u32 *)NV_SOR_TMDS_HDCP_CN_LSB;
| + | |- |
− |
| + | | 4-7 |
− | // Clear SOR1 registers
| + | | TSEC_DMA_CMD_UNK |
− | *(u32 *)NV_SOR_DP_HDCP_BKSV_LSB = 0;
| + | |- |
− | *(u32 *)NV_SOR_TMDS_HDCP_BKSV_LSB = 0;
| + | | 12 |
− | *(u32 *)NV_SOR_TMDS_HDCP_CN_MSB = 0;
| + | | TSEC_DMA_CMD_BUSY |
− | *(u32 *)NV_SOR_TMDS_HDCP_CN_LSB = 0;
| + | |- |
− |
| + | | 13 |
− | if (out_size < 0x10)
| + | | TSEC_DMA_CMD_ERROR |
− | out_size = 0x10;
| + | |- |
− |
| + | | 31 |
− | // Copy back the TSEC device key
| + | | TSEC_DMA_CMD_INIT |
− | memcpy(out_buf, tsec_device_key, out_size);
| + | |} |
| | | |
− | [6.2.0+] This is now done inside an encrypted TSEC payload.
| + | A DMA read/write operation requires bits TSEC_DMA_CMD_INIT and TSEC_DMA_CMD_READ/TSEC_DMA_CMD_WRITE to be set in TSEC_DMA_CMD. |
| | | |
− | == Cleanup ==
| + | During the transfer, the TSEC_DMA_CMD_BUSY bit is set. |
− | Clocks and resets are disabled before returning.
| |
− | // Deprogram KFUSE clock and resets
| |
− | // Uses RST_DEVICES_H, CLK_OUT_ENB_H and CLK_H_KFUSE
| |
− | disable_kfuse_clkrst();
| |
− |
| |
− | // Deprogram SOR1 clock and resets
| |
− | // Uses RST_DEVICES_X, CLK_OUT_ENB_X, CLK_SOURCE_SOR1 and CLK_X_SOR1
| |
− | disable_sor1_clkrst();
| |
− |
| |
− | // Deprogram SOR0 clock and resets
| |
− | // Uses RST_DEVICES_X, CLK_OUT_ENB_X and CLK_X_SOR0
| |
− | disable_sor0_clkrst();
| |
− |
| |
− | // Deprogram SOR_SAFE clock and resets
| |
− | // Uses RST_DEVICES_Y, CLK_OUT_ENB_Y and CLK_Y_SOR_SAFE
| |
− | disable_sor_safe_clkrst();
| |
− |
| |
− | // Deprogram TSEC clock and resets
| |
− | // Uses RST_DEVICES_U, CLK_OUT_ENB_U, CLK_SOURCE_TSEC and CLK_U_TSEC
| |
− | disable_tsec_clkrst();
| |
− |
| |
− | // Deprogram HOST1X clock and resets
| |
− | // Uses RST_DEVICES_L, CLK_OUT_ENB_L, CLK_SOURCE_HOST1X and CLK_L_HOST1X
| |
− | disable_host1x_clkrst();
| |
− |
| |
− | return;
| |
| | | |
− | = TSEC Firmware =
| + | Accessing an invalid address causes bit TSEC_DMA_CMD_ERROR to be set. |
− | The actual code loaded into TSEC is assembled in NVIDIA's proprietary fuc5 ISA using crypto extensions.
| |
− | Stored inside the first bootloader, this firmware binary is split into 4 blobs (names are unofficial): [[#Boot|Boot]] (unencrypted and unauthenticated code), [[#KeygenLdr|KeygenLdr]] (unencrypted and authenticated code), [[#Keygen|Keygen]] (encrypted and authenticated code) and [[#Key data|key data]].
| |
| | | |
− | [6.2.0+] There are now 6 blobs (names are unofficial): [[#Boot|Boot]] (unencrypted and unauthenticated code), [[#Loader|Loader]] (unencrypted and unauthenticated code), [[#KeygenLdr|KeygenLdr]] (unencrypted and authenticated code), [[#Keygen|Keygen]] (encrypted and authenticated code), [[#Payload|Payload]] (part unencrypted and unauthenticated code, part encrypted and authenticated code) and [[#Key data|key data]].
| + | === TSEC_DMA_ADDR === |
| + | Takes the address for DMA transfers between TSEC and HOST1X (master and clients). |
| | | |
− | Firmware can be disassembled with [http://envytools.readthedocs.io/en/latest/ envytools'] [https://github.com/envytools/envytools/tree/master/envydis envydis]:
| + | === TSEC_DMA_VAL === |
| + | Takes the value for DMA transfers between TSEC and HOST1X (master and clients). |
| | | |
− | <code>envydis -i tsec_fw.bin -m falcon -V fuc5 -F crypt</code>
| + | === TSEC_DMA_UNK === |
| + | Always 0xFFF. |
| | | |
− | Note that the instruction set has variable length instructions, and the disassembler is not very good at detecting locations it should start disassembling from. One needs to disassemble multiple sub-regions and join them together.
| + | === TSEC_TEGRA_CTL === |
− | | + | {| class="wikitable" border="1" |
− | == Boot == | + | ! Bits |
− | During this stage, [[#Key data|key data]] is loaded and [[#KeygenLdr|KeygenLdr]] is authenticated, loaded and executed.
| + | ! Description |
− | Before returning, this stage writes back to the host (using MMIO registers) and sets the device key used by the first bootloader.
| + | |- |
− | | + | | 16 |
− | [6.2.0+] During this stage, [[#Key data|key data]] is loaded and execution jumps to [[#Loader|Loader]].
| + | | TSEC_TEGRA_CTL_TKFI_KFUSE |
− | | + | |- |
− | === Initialization ===
| + | | 17 |
− | Falcon sets up it's own stack pointer.
| + | | TSEC_TEGRA_CTL_TKFI_RESTART_FSM_KFUSE |
− | // Read data segment size from IO space
| + | |- |
− | u32 data_seg_size = *(u32 *)UC_CAPS;
| + | | 24 |
− | data_seg_size >>= 0x09;
| + | | TSEC_TEGRA_CTL_TMPI_FORCE_IDLE_INPUTS_I2C |
− | data_seg_size &= 0x1FF;
| + | |- |
− | data_seg_size <<= 0x08;
| + | | 25 |
− |
| + | | TSEC_TEGRA_CTL_TMPI_RESTART_FSM_HOST1X |
− | // Set the stack pointer
| + | |- |
− | *(u32 *)sp = data_seg_size;
| + | | 26 |
| + | | TSEC_TEGRA_CTL_TMPI_RESTART_FSM_APB |
| + | |- |
| + | | 27 |
| + | | TSEC_TEGRA_CTL_TMPI_DISABLE_OUTPUT_I2C |
| + | |} |
| + | |
| + | = Boot Process = |
| + | TSEC is configured and initialized by the first bootloader during key generation. |
| | | |
− | === Main === | + | [6.2.0+] TSEC is now configured at the end of the first bootloader's main function. |
− | Falcon reads the [[#Key data|key data]], authenticates, loads and executes [[#KeygenLdr|KeygenLdr]] and finally sets the device key.
| + | |
− | u32 boot_base_addr = 0;
| + | == Initialization == |
− | u8 key_data_buf[0x7C]; | + | During this stage several clocks are programmed. |
| + | // Program the HOST1X clock and resets |
| + | // Uses RST_DEVICES_L, CLK_OUT_ENB_L, CLK_SOURCE_HOST1X and CLK_L_HOST1X |
| + | enable_host1x_clkrst(); |
| | | |
− | // Read the key data from memory | + | // Program the TSEC clock and resets |
− | u32 key_data_addr = 0x300; | + | // Uses RST_DEVICES_U, CLK_OUT_ENB_U, CLK_SOURCE_TSEC and CLK_U_TSEC |
− | u32 key_data_size = 0x7C;
| + | enable_tsec_clkrst(); |
− | read_code(key_data_buf, key_data_addr, key_data_size); | |
| | | |
− | // Read the next code segment into boot base | + | // Program the SOR_SAFE clock and resets |
− | u32 blob1_addr = 0x400; | + | // Uses RST_DEVICES_Y, CLK_OUT_ENB_Y and CLK_Y_SOR_SAFE |
− | u32 blob1_size = *(u32 *)(key_data_buf + 0x74); | + | enable_sor_safe_clkrst(); |
− | read_code(boot_base_addr, blob1_addr, blob1_size);
| |
| | | |
− | // Upload the next code segment into Falcon's CODE region | + | // Program the SOR0 clock and resets |
− | u32 blob1_virt_addr = 0x300; | + | // Uses RST_DEVICES_X, CLK_OUT_ENB_X and CLK_X_SOR0 |
− | bool use_secret = true;
| + | enable_sor0_clkrst(); |
− | upload_code(blob1_virt_addr, boot_base_addr, blob1_size, blob1_virt_addr, use_secret); | |
| | | |
− | u32 boot_res = 0; | + | // Program the SOR1 clock and resets |
− | bool is_done = false; | + | // Uses RST_DEVICES_X, CLK_OUT_ENB_X, CLK_SOURCE_SOR1 and CLK_X_SOR1 |
− | u32 time = 0; | + | enable_sor1_clkrst(); |
− | bool is_blob_dec = false;
| |
| | | |
− | while (!is_done) | + | // Program the KFUSE clock resets |
− | { | + | // Uses RST_DEVICES_H, CLK_OUT_ENB_H and CLK_H_KFUSE |
− | if (time > 4000000)
| + | enable_kfuse_clkrst(); |
− | {
| + | |
− | // Write boot failed (timeout) magic to FALCON_SCRATCH1
| + | == Configuration == |
− | boot_res = 0xC0C0C0C0;
| + | In this stage the Falcon IRQs, interfaces and DMA engine are configured. |
− | *(u32 *)FALCON_SCRATCH1 = boot_res;
| + | // Clear the Falcon DMA control register |
− |
| + | *(u32 *)FALCON_DMACTL = 0; |
− | break;
| |
− | }
| |
− |
| |
− | // Load key version from FALCON_SCRATCH0 (bootloader sends 0x01)
| |
− | u32 key_version = *(u32 *)FALCON_SCRATCH0;
| |
| | | |
− | if (key_version == 0x64)
| + | // Enable Falcon IRQs |
− | {
| + | *(u32 *)FALCON_IRQMSET = 0xFFF2; |
− | // Skip all next stages
| |
− | boot_res = 0xB0B0B0B0;
| |
− | *(u32 *)FALCON_SCRATCH1 = boot_res;
| |
− |
| |
− | break;
| |
− | }
| |
− | else
| |
− | {
| |
− | if (key_version > 0x03)
| |
− | boot_res = 0xD0D0D0D0; // Invalid key version
| |
− | else if (key_version == 0)
| |
− | boot_res = 0xB0B0B0B0; // No keys used
| |
− | else
| |
− | {
| |
− | u32 key_buf[0x7C];
| |
− |
| |
− | // Copy key data
| |
− | memcpy(key_buf, key_data_buf, 0x7C);
| |
| | | |
− | u32 crypt_reg_flag = 0x00060000;
| + | // Enable Falcon IRQs |
− | u32 blob1_hash_addr = key_buf + 0x20;
| + | *(u32 *)FALCON_IRQDEST = 0xFFF0; |
| | | |
− | // fuc5 crypt cauth instruction
| + | // Enable Falcon interfaces |
− | // Set auth_addr to 0x300 and auth_size to blob1_size
| + | *(u32 *)FALCON_ITFEN = 0x03; |
− | cauth((blob1_size << 0x10) | (0x300 >> 0x08));
| + | |
| + | // Wait for Falcon's DMA engine to be idle |
| + | wait_flcn_dma_idle(); |
| + | |
| + | == Firmware loading == |
| + | The Falcon firmware code is stored in the first bootloader's data segment in IMEM. |
| + | // Set DMA transfer base address to 0x40011900 >> 0x08 |
| + | *(u32 *)FALCON_DMATRFBASE = 0x400119; |
| | | |
− | // fuc5 crypt cxset instruction
| + | u32 trf_mode = 0; // A value of 0 sets FALCON_DMATRFCMD_IMEM |
− | // The next 2 xfer instructions will be overridden
| + | u32 dst_offset = 0; |
− | // and target changes from DMA to crypto
| + | u32 src_offset = 0; |
− | cxset(0x02);
| |
− |
| |
− | // Transfer data to crypto register c6
| |
− | xdst(0, (blob1_hash_addr | crypt_reg_flag));
| |
− |
| |
− | // Wait for all data loads/stores to finish
| |
− | xdwait();
| |
− |
| |
− | // Jump to KeygenLdr
| |
− | u32 keygenldr_res = exec_keygenldr(key_buf, key_version, is_blob_dec);
| |
− | is_blob_dec = true; // Set this to prevent decrypting again
| |
| | | |
− | // Set boot finish magic on success
| + | // Load code into Falcon (0x100 bytes at a time) |
− | if (keygenldr_res == 0)
| + | while (src_offset < 0xF00) |
− | boot_res = 0xB0B0B0B0
| + | { |
− | }
| + | flcn_load_firm(trf_mode, src_offset, dst_offset); |
− |
| + | src_offset += 0x100; |
− | // Write result to FALCON_SCRATCH1
| + | dst_offset += 0x100; |
− | *(u32 *)FALCON_SCRATCH1 = boot_res;
| |
− | | |
− | if (boot_res == 0xB0B0B0B0)
| |
− | is_done = true;
| |
− | } | |
− |
| |
− | time++;
| |
| } | | } |
| + | |
| + | [6.2.0+] The transfer base address and size of the Falcon firmware code changed. |
| + | // Set DMA transfer base address to 0x40010E00 >> 0x08 |
| + | *(u32 *)FALCON_DMATRFBASE = 0x40010E; |
| | | |
− | // Write TSEC device key to registers | + | u32 trf_mode = 0; // A value of 0 sets FALCON_DMATRFCMD_IMEM |
− | set_device_key(key_data_buf); | + | u32 dst_offset = 0; |
| + | u32 src_offset = 0; |
| | | |
− | return boot_res; | + | // Load code into Falcon (0x100 bytes at a time) |
− | | + | while (src_offset < 0x2900) |
− | [6.2.0+] Falcon reads the [[#Key data|key data]] and jumps to [[#Loader|Loader]].
| + | { |
− | u8 key_data_buf[0x84]; | + | flcn_load_firm(trf_mode, src_offset, dst_offset); |
| + | src_offset += 0x100; |
| + | dst_offset += 0x100; |
| + | } |
| + | |
| + | == Firmware booting == |
| + | Falcon is booted up and the first bootloader waits for it to finish. |
| + | // Set magic value in host1x scratch space |
| + | *(u32 *)0x50003300 = 0x34C2E1DA; |
| | | |
− | // Read the key data from memory | + | // Clear Falcon scratch1 MMIO |
− | u32 key_data_addr = 0x300; | + | *(u32 *)FALCON_SCRATCH1 = 0; |
− | u32 key_data_size = 0x84; | + | |
− | read_code(key_data_buf, key_data_addr, key_data_size); | + | // Set Falcon boot key version in scratch0 MMIO |
| + | *(u32 *)FALCON_SCRATCH0 = 0x01; |
| | | |
− | // Calculate the next blob's address | + | // Set Falcon's boot vector address |
− | u32 blob4_size = *(u32 *)(key_data_buf + 0x80); | + | *(u32 *)FALCON_BOOTVEC = 0; |
− | u32 blob0_size = *(u32 *)(key_data_buf + 0x70);
| |
− | u32 blob1_size = *(u32 *)(key_data_buf + 0x74);
| |
− | u32 blob2_size = *(u32 *)(key_data_buf + 0x78);
| |
− | u32 blob3_addr = ((((blob0_size + blob1_size) + 0x100) + blob2_size) + blob4_size);
| |
| | | |
− | // Jump to next blob | + | // Signal Falcon's CPU |
− | (void *)blob3_addr();
| + | *(u32 *)FALCON_CPUCTL = 0x02; |
− |
| |
− | return 0;
| |
− | | |
− | ==== set_device_key ====
| |
− | This method takes '''key_data_buf''' as argument and writes the TSEC key to SOR1 registers.
| |
− | // This is TSEC_MMIO + 0x1000 + (0x1C300 / 0x40)
| |
− | *(u32 *)TSEC_DMA_UNK = 0xFFF; | |
| | | |
− | // Read the key's words | + | // Wait for Falcon's DMA engine to be idle |
− | u32 key0 = *(u32 *)(key_data_buf + 0x00); | + | wait_flcn_dma_idle(); |
− | u32 key1 = *(u32 *)(key_data_buf + 0x04);
| |
− | u32 key2 = *(u32 *)(key_data_buf + 0x08);
| |
− | u32 key3 = *(u32 *)(key_data_buf + 0x0C);
| |
| | | |
− | u32 result = 0; | + | u32 boot_res = 0; |
| | | |
− | // Write to SOR1 register | + | // The bootloader allows the TSEC two seconds from this point to do its job |
− | result = tsec_dma_write(NV_SOR_DP_HDCP_BKSV_LSB, key0); | + | u32 maximum_time = read_timer() + 2000000; |
| | | |
− | // Failed to write | + | while (!boot_res) |
− | if (result)
| + | { |
− | return result; | + | // Read boot result from scratch1 MMIO |
− |
| + | boot_res = *(u32 *)FALCON_SCRATCH1; |
− | // Write to SOR1 register
| + | |
− | result = tsec_dma_write(NV_SOR_TMDS_HDCP_BKSV_LSB, key1);
| + | // Read from TIMERUS_CNTR_1US (microseconds from boot) |
| + | u32 current_time = read_timer(); |
| + | |
| + | // Booting is taking too long |
| + | if (current_time > maximum_time) |
| + | panic(); |
| + | } |
| | | |
− | // Failed to write | + | // Invalid boot result was returned |
− | if (result) | + | if (boot_res != 0xB0B0B0B0) |
− | return result; | + | panic(); |
| + | |
| + | [6.2.0+] Falcon is booted up, but the first bootloader is left in an infinite loop. |
| + | // Set magic value in host1x scratch space |
| + | *(u32 *)0x50003300 = 0x34C2E1DA; |
| | | |
− | // Write to SOR1 register | + | // Clear Falcon scratch1 MMIO |
− | result = tsec_dma_write(NV_SOR_TMDS_HDCP_CN_MSB, key2); | + | *(u32 *)FALCON_SCRATCH1 = 0; |
| | | |
− | // Failed to write | + | // Set Falcon boot key version in scratch0 MMIO |
− | if (result) | + | *(u32 *)FALCON_SCRATCH0 = 0x01; |
− | return result;
| |
| | | |
− | // Write to SOR1 register | + | // Set Falcon's boot vector address |
− | result = tsec_dma_write(NV_SOR_TMDS_HDCP_CN_LSB, key3); | + | *(u32 *)FALCON_BOOTVEC = 0; |
| | | |
− | // Failed to write | + | // Signal Falcon's CPU |
− | if (result) | + | *(u32 *)FALCON_CPUCTL = 0x02; |
− | return result;
| |
| | | |
− | return result; | + | // Infinite loop |
| + | deadlock(); |
| | | |
− | ===== tsec_dma_write ===== | + | == TSEC key generation == |
− | This method takes '''addr''' and '''value''' as arguments and performs a DMA write using TSEC MMIO.
| + | The TSEC key is generated by reading SOR1 registers modified by the Falcon CPU. |
− | u32 result = 0; | + | // Clear magic value in host1x scratch space |
| + | *(u32 *)0x50003300 = 0; |
| | | |
− | // Wait for TSEC DMA engine | + | // Read TSEC key |
− | // This waits for bit 0x0C in TSEC_DMA_CMD to be 0 | + | u32 tsec_key[4]; |
− | result = wait_tsec_dma(); | + | tsec_key[0] = *(u32 *)NV_SOR_DP_HDCP_BKSV_LSB; |
| + | tsec_key[1] = *(u32 *)NV_SOR_TMDS_HDCP_BKSV_LSB; |
| + | tsec_key[2] = *(u32 *)NV_SOR_TMDS_HDCP_CN_MSB; |
| + | tsec_key[3] = *(u32 *)NV_SOR_TMDS_HDCP_CN_LSB; |
| | | |
− | // Wait failed | + | // Clear SOR1 registers |
− | if (result) | + | *(u32 *)NV_SOR_DP_HDCP_BKSV_LSB = 0; |
− | return 1;
| + | *(u32 *)NV_SOR_TMDS_HDCP_BKSV_LSB = 0; |
| + | *(u32 *)NV_SOR_TMDS_HDCP_CN_MSB = 0; |
| + | *(u32 *)NV_SOR_TMDS_HDCP_CN_LSB = 0; |
| | | |
− | // Set the destination address | + | if (out_size < 0x10) |
− | // This is TSEC_MMIO + 0x1000 + (0x1C100 / 0x40)
| + | out_size = 0x10; |
− | *(u32 *)TSEC_DMA_ADDR = addr;
| |
| | | |
− | // Set the value | + | // Copy back the TSEC key |
− | // This is TSEC_MMIO + 0x1000 + (0x1C200 / 0x40) | + | memcpy(out_buf, tsec_key, out_size); |
− | *(u32 *)TSEC_DMA_VAL = value; | + | |
| + | [6.2.0+] This is now done inside an encrypted TSEC payload. |
| + | |
| + | == Cleanup == |
| + | Clocks and resets are disabled before returning. |
| + | // Deprogram KFUSE clock and resets |
| + | // Uses RST_DEVICES_H, CLK_OUT_ENB_H and CLK_H_KFUSE |
| + | disable_kfuse_clkrst(); |
| | | |
− | // Start transfer? | + | // Deprogram SOR1 clock and resets |
− | // This is TSEC_MMIO + 0x1000 + (0x1C000 / 0x40) | + | // Uses RST_DEVICES_X, CLK_OUT_ENB_X, CLK_SOURCE_SOR1 and CLK_X_SOR1 |
− | *(u32 *)TSEC_DMA_CMD = 0x800000F2; | + | disable_sor1_clkrst(); |
| | | |
− | // Wait for TSEC DMA engine | + | // Deprogram SOR0 clock and resets |
− | // This waits for bit 0x0C in TSEC_DMA_CMD to be 0 | + | // Uses RST_DEVICES_X, CLK_OUT_ENB_X and CLK_X_SOR0 |
− | result = wait_tsec_dma(); | + | disable_sor0_clkrst(); |
| | | |
− | // Wait failed | + | // Deprogram SOR_SAFE clock and resets |
− | if (result) | + | // Uses RST_DEVICES_Y, CLK_OUT_ENB_Y and CLK_Y_SOR_SAFE |
− | return 1;
| + | disable_sor_safe_clkrst(); |
| + | |
| + | // Deprogram TSEC clock and resets |
| + | // Uses RST_DEVICES_U, CLK_OUT_ENB_U, CLK_SOURCE_TSEC and CLK_U_TSEC |
| + | disable_tsec_clkrst(); |
| + | |
| + | // Deprogram HOST1X clock and resets |
| + | // Uses RST_DEVICES_L, CLK_OUT_ENB_L, CLK_SOURCE_HOST1X and CLK_L_HOST1X |
| + | disable_host1x_clkrst(); |
| | | |
− | return 0; | + | return; |
| + | |
| + | = TSEC Firmware = |
| + | The actual code loaded into TSEC is assembled in NVIDIA's proprietary fuc5 ISA using crypto extensions. |
| + | Stored inside the first bootloader, this firmware binary is split into 4 blobs (names are unofficial): [[#Boot|Boot]] (unencrypted and unauthenticated code), [[#KeygenLdr|KeygenLdr]] (unencrypted and authenticated code), [[#Keygen|Keygen]] (encrypted and authenticated code) and [[#Key data|key data]]. |
| | | |
− | == KeygenLdr ==
| + | [6.2.0+] There are now 6 blobs (names are unofficial): [[#Boot|Boot]] (unencrypted and unauthenticated code), [[#Loader|Loader]] (unencrypted and unauthenticated code), [[#KeygenLdr|KeygenLdr]] (unencrypted and authenticated code), [[#Keygen|Keygen]] (encrypted and authenticated code), [[#Payload|Payload]] (part unencrypted and unauthenticated code, part encrypted and authenticated code) and [[#Key data|key data]]. |
− | This stage is responsible for reconfiguring the Falcon's crypto co-processor and loading, decrypting, authenticating and executing [[#Keygen|Keygen]].
| |
| | | |
− | === Main === | + | Firmware can be disassembled with [http://envytools.readthedocs.io/en/latest/ envytools'] [https://github.com/envytools/envytools/tree/master/envydis envydis]: |
− | // Clear interrupt flags | + | |
− | *(u8 *)flags_ie0 = 0; | + | <code>envydis -i tsec_fw.bin -m falcon -V fuc5 -F crypt</code> |
− | *(u8 *)flags_ie1 = 0; | + | |
− | *(u8 *)flags_ie2 = 0; | + | Note that the instruction set has variable length instructions, and the disassembler is not very good at detecting locations it should start disassembling from. One needs to disassemble multiple sub-regions and join them together. |
− | | + | |
− | // fuc5 crypt cxset instruction
| + | == Boot == |
− | // Clear overrides?
| + | During this stage, [[#Key data|key data]] is loaded and [[#KeygenLdr|KeygenLdr]] is authenticated, loaded and executed. |
− | cxset(0x80);
| + | Before returning, this stage writes back to the host (using MMIO registers) and sets the key used by the first bootloader. |
| + | |
| + | [6.2.0+] During this stage, [[#Key data|key data]] is loaded and execution jumps to [[#Loader|Loader]]. |
| + | |
| + | === Initialization === |
| + | Falcon sets up it's own stack pointer. |
| + | // Read data segment size from IO space |
| + | u32 data_seg_size = *(u32 *)UC_CAPS; |
| + | data_seg_size >>= 0x09; |
| + | data_seg_size &= 0x1FF; |
| + | data_seg_size <<= 0x08; |
| | | |
− | // fuc5 crypt cauth instruction | + | // Set the stack pointer |
− | // Clear bit 0x13 in cauth | + | *(u32 *)sp = data_seg_size; |
− | cauth(cauth_old & ~(1 << 0x13)); | + | |
| + | === Main === |
| + | Falcon reads the [[#Key data|key data]] and then authenticates, loads and executes [[#KeygenLdr|KeygenLdr]] which sets the TSEC key. |
| + | u32 boot_base_addr = 0; |
| + | u8 key_data_buf[0x7C]; |
| | | |
− | // Set the target port for memory transfers | + | // Read the key data from memory |
− | xtargets(0); | + | u32 key_data_addr = 0x300; |
| + | u32 key_data_size = 0x7C; |
| + | read_code(key_data_buf, key_data_addr, key_data_size); |
| | | |
− | // Wait for all data loads/stores to finish | + | // Read the next code segment into boot base |
− | xdwait(); | + | u32 blob1_addr = 0x400; |
| + | u32 blob1_size = *(u32 *)(key_data_buf + 0x74); |
| + | read_code(boot_base_addr, blob1_addr, blob1_size); |
| | | |
− | // Wait for all code loads to finish | + | // Upload the next code segment into Falcon's CODE region |
− | xcwait(); | + | u32 blob1_virt_addr = 0x300; |
| + | bool use_secret = true; |
| + | upload_code(blob1_virt_addr, boot_base_addr, blob1_size, blob1_virt_addr, use_secret); |
| | | |
− | // fuc5 crypt cxset instruction | + | u32 boot_res = 0; |
− | // The next 2 xfer instructions will be overridden | + | bool is_done = false; |
− | // and target changes from DMA to crypto | + | u32 time = 0; |
− | cxset(0x02); | + | bool is_blob_dec = false; |
| | | |
− | // Transfer data to crypto register c0 | + | while (!is_done) |
− | // This should clear any leftover data
| + | { |
− | xdst(0, 0);
| + | if (time > 4000000) |
| + | { |
| + | // Write boot failed (timeout) magic to FALCON_SCRATCH1 |
| + | boot_res = 0xC0C0C0C0; |
| + | *(u32 *)FALCON_SCRATCH1 = boot_res; |
| + | |
| + | break; |
| + | } |
| + | |
| + | // Load key version from FALCON_SCRATCH0 (bootloader sends 0x01) |
| + | u32 key_version = *(u32 *)FALCON_SCRATCH0; |
| | | |
− | // Wait for all data loads/stores to finish
| + | if (key_version == 0x64) |
− | xdwait();
| + | { |
− |
| + | // Skip all next stages |
− | // Clear all crypto registers, except c6 which is used for auth
| + | boot_res = 0xB0B0B0B0; |
− | cxor(c0, c0);
| + | *(u32 *)FALCON_SCRATCH1 = boot_res; |
− | cmov(c1, c0);
| + | |
− | cmov(c2, c0);
| + | break; |
− | cmov(c3, c0);
| + | } |
− | cmov(c4, c0);
| + | else |
− | cmov(c5, c0);
| + | { |
− | cmov(c7, c0);
| + | if (key_version > 0x03) |
| + | boot_res = 0xD0D0D0D0; // Invalid key version |
| + | else if (key_version == 0) |
| + | boot_res = 0xB0B0B0B0; // No keys used |
| + | else |
| + | { |
| + | u32 key_buf[0x7C]; |
| + | |
| + | // Copy key data |
| + | memcpy(key_buf, key_data_buf, 0x7C); |
| | | |
− | // Clear TSEC_TEGRA_CTL_TKFI_KFUSE
| + | u32 crypt_reg_flag = 0x00060000; |
− | // This is TSEC_MMIO + 0x1000 + (0x20E00 / 0x40)
| + | u32 blob1_hash_addr = key_buf + 0x20; |
− | *(u32 *)TSEC_TEGRA_CTL &= 0xEFFFF;
| |
| | | |
− | // Set TSEC_SCP_CTL_PKEY_REQUEST_RELOAD
| + | // fuc5 crypt cauth instruction |
− | // This is TSEC_MMIO + 0x1000 + (0x10600 / 0x40)
| + | // Set auth_addr to 0x300 and auth_size to blob1_size |
− | *(u32 *)TSEC_SCP_CTL_PKEY |= 0x01;
| + | cauth((blob1_size << 0x10) | (0x300 >> 0x08)); |
| | | |
− | u32 is_pkey_loaded = 0;
| + | // fuc5 crypt cxset instruction |
− |
| + | // The next 2 xfer instructions will be overridden |
− | // Wait for TSEC_SCP_CTL_PKEY_LOADED
| + | // and target changes from DMA to crypto |
− | while (!is_pkey_loaded)
| + | cxset(0x02); |
− | is_pkey_loaded = (*(u32 *)TSEC_SCP_CTL_PKEY & 0x02);
| + | |
| + | // Transfer data to crypto register c6 |
| + | xdst(0, (blob1_hash_addr | crypt_reg_flag)); |
| + | |
| + | // Wait for all data loads/stores to finish |
| + | xdwait(); |
| + | |
| + | // Jump to KeygenLdr |
| + | u32 keygenldr_res = exec_keygenldr(key_buf, key_version, is_blob_dec); |
| + | is_blob_dec = true; // Set this to prevent decrypting again |
| | | |
− | // Read data segment size from IO space
| + | // Set boot finish magic on success |
− | u32 data_seg_size = *(u32 *)UC_CAPS;
| + | if (keygenldr_res == 0) |
− | data_seg_size >>= 0x09;
| + | boot_res = 0xB0B0B0B0 |
− | data_seg_size &= 0x1FF;
| + | } |
− | data_seg_size <<= 0x08;
| + | |
| + | // Write result to FALCON_SCRATCH1 |
| + | *(u32 *)FALCON_SCRATCH1 = boot_res; |
| | | |
− | // Check stack bounds
| + | if (boot_res == 0xB0B0B0B0) |
− | if ((*(u32 *)sp >= data_seg_size) || (*(u32 *)sp < 0x800))
| + | is_done = true; |
− | exit();
| + | } |
| | | |
− | // Decrypt and load Keygen stage
| + | time++; |
− | load_keygen(key_buf, key_version, is_blob_dec); | + | } |
| | | |
− | // Partially unknown fuc5 instruction | + | // Overwrite the TSEC key in SOR1 registers |
− | // Likely forces a change of permissions | + | // This has no effect because the KeygenLdr locks out the TSEC DMA engine |
− | cchmod(c0, c0); | + | tsec_set_key(key_data_buf); |
| | | |
− | // Clear all crypto registers and propagate permissions | + | return boot_res; |
− | cxor(c0, c0);
| + | |
− | cxor(c1, c1);
| + | [6.2.0+] Falcon reads the [[#Key data|key data]] and jumps to [[#Loader|Loader]]. |
− | cxor(c2, c2);
| + | u8 key_data_buf[0x84]; |
− | cxor(c3, c3);
| |
− | cxor(c4, c4);
| |
− | cxor(c5, c5);
| |
− | cxor(c6, c6);
| |
− | cxor(c7, c7); | |
| | | |
− | // Exit Authenticated Mode | + | // Read the key data from memory |
− | // This is TSEC_MMIO + 0x1000 + (0x10300 / 0x40) | + | u32 key_data_addr = 0x300; |
− | *(u32 *)TSEC_SCP_CTL_AUTH_MODE = 0;
| + | u32 key_data_size = 0x84; |
− | | + | read_code(key_data_buf, key_data_addr, key_data_size); |
− | return;
| |
− | | |
− | ==== load_keygen ====
| |
− | u32 res = 0; | |
| | | |
− | u32 boot_base_addr = 0; | + | // Calculate the next blob's address |
− | u32 blob0_addr = 0; | + | u32 blob4_size = *(u32 *)(key_data_buf + 0x80); |
− | u32 blob0_size = *(u32 *)(key_buf + 0x70); | + | u32 blob0_size = *(u32 *)(key_data_buf + 0x70); |
| + | u32 blob1_size = *(u32 *)(key_data_buf + 0x74); |
| + | u32 blob2_size = *(u32 *)(key_data_buf + 0x78); |
| + | u32 blob3_addr = ((((blob0_size + blob1_size) + 0x100) + blob2_size) + blob4_size); |
| | | |
− | // Load blob0 code again | + | // Jump to next blob |
− | read_code(boot_base_addr, blob0_addr, blob0_size); | + | (void *)blob3_addr(); |
| + | |
| + | return 0; |
| + | |
| + | ==== tsec_set_key ==== |
| + | This method takes '''key_data_buf''' (a 16 bytes buffer) as argument and writes its contents to SOR1 registers. |
| + | // This is TSEC_MMIO + 0x1000 + (0x1C300 / 0x40) |
| + | *(u32 *)TSEC_DMA_UNK = 0xFFF; |
| | | |
− | // Generate "CODE_SIG_01" key into c4 crypto register | + | // Read the key's words |
− | gen_usr_key(0, 0); | + | u32 key0 = *(u32 *)(key_data_buf + 0x00); |
| + | u32 key1 = *(u32 *)(key_data_buf + 0x04); |
| + | u32 key2 = *(u32 *)(key_data_buf + 0x08); |
| + | u32 key3 = *(u32 *)(key_data_buf + 0x0C); |
| | | |
− | // Encrypt buffer with c4 | + | u32 result = 0; |
− | u8 sig_key[0x10];
| |
− | enc_buf(sig_key, blob0_size);
| |
| | | |
− | u32 src_addr = boot_base_addr; | + | // Write to SOR1 register |
− | u32 src_size = blob0_size;
| + | result = tsec_dma_write(NV_SOR_DP_HDCP_BKSV_LSB, key0); |
− | u32 iv_addr = sig_key;
| |
− | u32 dst_addr = sig_key;
| |
− | u32 mode = 0x02; // AES-CMAC
| |
− | u32 version = 0; | |
| | | |
− | // Do AES-CMAC over blob0 code | + | // Failed to write |
− | do_crypto(src_addr, src_size, iv_addr, dst_addr, mode, version); | + | if (result) |
| + | return result; |
| | | |
− | // Compare the hashes | + | // Write to SOR1 register |
− | if (memcmp(dst_addr, key_buf + 0x10, 0x10)) | + | result = tsec_dma_write(NV_SOR_TMDS_HDCP_BKSV_LSB, key1); |
− | {
| |
− | res = 0xDEADBEEF;
| |
− | return res;
| |
− | }
| |
| | | |
− | u32 blob1_size = *(u32 *)(key_buf + 0x74); | + | // Failed to write |
| + | if (result) |
| + | return result; |
| + | |
| + | // Write to SOR1 register |
| + | result = tsec_dma_write(NV_SOR_TMDS_HDCP_CN_MSB, key2); |
| | | |
− | // Decrypt Keygen blob if needed | + | // Failed to write |
− | if (!is_blob_dec) | + | if (result) |
− | {
| + | return result; |
− | // Read Stage2's size from key buffer | |
− | u32 blob2_size = *(u32 *)(key_buf + 0x78);
| |
| | | |
− | // Check stack bounds
| + | // Write to SOR1 register |
− | if (*(u32 *)sp > blob2_size)
| + | result = tsec_dma_write(NV_SOR_TMDS_HDCP_CN_LSB, key3); |
− | {
| |
− | u32 boot_base_addr = 0;
| |
− | u32 blob2_virt_addr = blob0_size + blob1_size;
| |
− | u32 blob2_addr = blob2_virt_addr + 0x100;
| |
− |
| |
− | // Read Keygen encrypted blob
| |
− | read_code(boot_base_addr, blob2_addr, blob2_size);
| |
| | | |
− | // Generate "CODE_ENC_01" key into c4 crypt register
| + | // Failed to write |
− | gen_usr_key(0x01, 0x01);
| + | if (result) |
− |
| + | return result; |
− | u32 src_addr = boot_base_addr;
| |
− | u32 src_size = blob2_size;
| |
− | u32 iv_addr = key_buf + 0x40;
| |
− | u32 dst_addr = boot_base_addr;
| |
− | u32 mode = 0; // AES-128-ECB
| |
− | u32 version = 0;
| |
− |
| |
− | // Decrypt Keygen blob
| |
− | do_crypto(src_addr, src_size, iv_addr, dst_addr, mode, version);
| |
− |
| |
− | // Upload the next code segment into Falcon's CODE region
| |
− | bool use_secret = true;
| |
− | upload_code(blob2_virt_addr, boot_base_addr, blob2_size, blob2_virt_addr, use_secret);
| |
| | | |
− | // Clear out the decrypted blob
| + | return result; |
− | memset(boot_base_addr, 0, blob2_size);
| + | |
− | }
| + | ===== tsec_dma_write ===== |
− | } | + | This method takes '''addr''' and '''value''' as arguments and performs a DMA write using TSEC MMIO. |
| + | u32 result = 0; |
| | | |
− | // fuc5 crypt cxset instruction | + | // Wait for TSEC DMA engine |
− | // The next 2 xfer instructions will be overridden | + | // This waits for bit 0x0C in TSEC_DMA_CMD to be 0 |
− | // and target changes from DMA to crypto
| + | result = wait_tsec_dma(); |
− | cxset(0x02); | |
| | | |
− | u32 crypt_reg_flag = 0x00060000; | + | // Wait failed |
− | u32 blob2_hash_addr = key_buf + 0x30; | + | if (result) |
| + | return 1; |
| | | |
− | // Transfer data to crypto register c6 | + | // Set the destination address |
− | xdst(0, (blob2_hash_addr | crypt_reg_flag));
| + | // This is TSEC_MMIO + 0x1000 + (0x1C100 / 0x40) |
− |
| + | *(u32 *)TSEC_DMA_ADDR = addr; |
− | // Wait for all data loads/stores to finish | |
− | xdwait(); | |
| | | |
− | // Save previous cauth value | + | // Set the value |
− | u32 c_old = cauth_old; | + | // This is TSEC_MMIO + 0x1000 + (0x1C200 / 0x40) |
| + | *(u32 *)TSEC_DMA_VAL = value; |
| | | |
− | // fuc5 crypt cauth instruction | + | // Start transfer? |
− | // Set auth_addr to blob2_virt_addr and auth_size to blob2_size | + | // This is TSEC_MMIO + 0x1000 + (0x1C000 / 0x40) |
− | cauth((blob2_virt_addr >> 0x08) | (blob2_size << 0x10)); | + | *(u32 *)TSEC_DMA_CMD = 0x800000F2; |
| | | |
− | u32 hovi_key_addr = 0; | + | // Wait for TSEC DMA engine |
| + | // This waits for bit 0x0C in TSEC_DMA_CMD to be 0 |
| + | result = wait_tsec_dma(); |
| | | |
− | // Select next stage key | + | // Wait failed |
− | if (key_version == 0x01) // Use HOVI_EKS_01 | + | if (result) |
− | hovi_key_addr = key_buf + 0x50;
| + | return 1; |
− | else if (key_version == 0x02) // Use HOVI_COMMON_01
| |
− | hovi_key_addr = key_buf + 0x60;
| |
− | else if (key_version == 0x03) // Use empty key
| |
− | hovi_key_addr = key_buf + 0x00;
| |
− | else
| |
− | res = 0xD0D0D0D0
| |
− |
| |
− | // Jump to Keygen
| |
− | if (hovi_key_addr)
| |
− | res = exec_keygen(hovi_key_addr, key_version);
| |
− |
| |
− | // Clear out key data
| |
− | memset(key_buf, 0, 0x7C);
| |
| | | |
− | // fuc5 crypt cauth instruction | + | return 0; |
− | // Restore previous cauth value
| + | |
− | cauth(c_old);
| + | == KeygenLdr == |
− |
| + | This stage is responsible for reconfiguring the Falcon's crypto co-processor and loading, decrypting, authenticating and executing [[#Keygen|Keygen]]. |
− | return res;
| |
| | | |
− | ===== gen_usr_key ===== | + | === Main === |
− | This method takes '''type''' and '''mode''' as arguments and generates a key.
| + | // Clear interrupt flags |
− | u8 seed_buf[0x10]; | + | *(u8 *)flags_ie0 = 0; |
| + | *(u8 *)flags_ie1 = 0; |
| + | *(u8 *)flags_ie2 = 0; |
| | | |
− | // Read a 16 bytes seed based on supplied type | + | // fuc5 crypt cxset instruction |
− | /* | + | // Clear overrides? |
− | Type 0: "CODE_SIG_01" + null padding
| + | cxset(0x80); |
− | Type 1: "CODE_ENC_01" + null padding
| |
− | */
| |
− | get_seed(seed_buf, type); | |
| | | |
− | // This will write the seed into crypto register c0 | + | // fuc5 crypt cauth instruction |
− | crypt_store(0, seed_buf); | + | // Clear bit 0x13 in cauth |
| + | cauth(cauth_old & ~(1 << 0x13)); |
| | | |
− | // fuc5 csecret instruction | + | // Set the target port for memory transfers |
− | // Load selected secret into crypto register c1 | + | xtargets(0); |
− | csecret(c1, 0x26);
| |
| | | |
− | // fuc5 ckeyreg instruction | + | // Wait for all data loads/stores to finish |
− | // Bind c1 register as the key for enc/dec operations
| + | xdwait(); |
− | ckeyreg(c1); | |
| | | |
− | // fuc5 cenc instruction | + | // Wait for all code loads to finish |
− | // Encrypt seed_buf (in c0) using keyreg value as key into c1 | + | xcwait(); |
− | cenc(c1, c0);
| |
| | | |
− | // fuc5 csigenc instruction | + | // fuc5 crypt cxset instruction |
− | // Encrypt c1 register with the auth signature stored in c6 | + | // The next 2 xfer instructions will be overridden |
− | csigenc(c1, c1); | + | // and target changes from DMA to crypto |
| + | cxset(0x02); |
| | | |
− | // Copy the result into c4 (will be used as key) | + | // Transfer data to crypto register c0 |
− | cmov(c4, c1); | + | // This should clear any leftover data |
| + | xdst(0, 0); |
| | | |
− | // Do key expansion (for decryption) | + | // Wait for all data loads/stores to finish |
− | if (mode != 0) | + | xdwait(); |
− | ckexp(c4, c4); // fuc5 ckexp instruction
| |
| | | |
− | return; | + | // Clear all crypto registers, except c6 which is used for auth |
− | | + | cxor(c0, c0); |
− | ===== enc_buffer =====
| + | cmov(c1, c0); |
− | This method takes '''buf''' (a 16 bytes buffer) and '''size''' as arguments and encrypts the supplied buffer.
| + | cmov(c2, c0); |
− | // Set first 3 words to null | + | cmov(c3, c0); |
− | *(u32 *)(buf + 0x00) = 0; | + | cmov(c4, c0); |
− | *(u32 *)(buf + 0x04) = 0; | + | cmov(c5, c0); |
− | *(u32 *)(buf + 0x08) = 0; | + | cmov(c7, c0); |
| | | |
− | // Swap halves (b16, b32 and b16 again) | + | // Clear TSEC_TEGRA_CTL_TKFI_KFUSE |
− | hswap(size); | + | // This is TSEC_MMIO + 0x1000 + (0x20E00 / 0x40) |
| + | *(u32 *)TSEC_TEGRA_CTL &= 0xEFFFF; |
| | | |
− | // Store the size as the last word | + | // Set TSEC_SCP_CTL_PKEY_REQUEST_RELOAD |
− | *(u32 *)(buf + 0x0C) = size; | + | // This is TSEC_MMIO + 0x1000 + (0x10600 / 0x40) |
| + | *(u32 *)TSEC_SCP_CTL_PKEY |= 0x01; |
| | | |
− | // This will write buf into crypto register c3 | + | u32 is_pkey_loaded = 0; |
− | crypt_store(0x03, buf);
| |
| | | |
− | // fuc5 ckeyreg instruction | + | // Wait for TSEC_SCP_CTL_PKEY_LOADED |
− | // Bind c4 register (from keygen) as the key for enc/dec operations | + | while (!is_pkey_loaded) |
− | ckeyreg(c4);
| + | is_pkey_loaded = (*(u32 *)TSEC_SCP_CTL_PKEY & 0x02); |
| | | |
− | // fuc5 cenc instruction | + | // Read data segment size from IO space |
− | // Encrypt buf (in c3) using keyreg value as key into c5 | + | u32 data_seg_size = *(u32 *)UC_CAPS; |
− | cenc(c5, c3); | + | data_seg_size >>= 0x09; |
| + | data_seg_size &= 0x1FF; |
| + | data_seg_size <<= 0x08; |
| | | |
− | // This will read into buf from crypto register c5 | + | // Check stack bounds |
− | crypt_load(0x05, buf); | + | if ((*(u32 *)sp >= data_seg_size) || (*(u32 *)sp < 0x800)) |
− |
| |
− | return;
| |
− | | |
− | ===== do_crypto ===== | |
− | This is the method responsible for all crypto operations performed during [[#KeygenLdr|KeygenLdr]]. It takes '''src_addr''', '''src_size''', '''iv_addr''', '''dst_addr''', '''mode''' and '''crypt_ver''' as arguments.
| |
− | // Check for invalid source data size
| |
− | if (!src_size || (src_size & 0x0F))
| |
| exit(); | | exit(); |
| | | |
− | // Check for invalid source data address | + | // Decrypt and load Keygen stage |
− | if (src_addr & 0x0F) | + | load_keygen(key_buf, key_version, is_blob_dec); |
− | exit();
| |
| | | |
− | // Check for invalid destination data address | + | // Partially unknown fuc5 instruction |
− | if (dst_addr & 0x0F) | + | // Likely forces a change of permissions |
− | exit();
| + | cchmod(c0, c0); |
| | | |
− | // Use IV if available | + | // Clear all crypto registers and propagate permissions |
− | if (iv_addr) | + | cxor(c0, c0); |
− | { | + | cxor(c1, c1); |
− | // This will write the iv_addr into crypto register c5
| + | cxor(c2, c2); |
− | crypt_store(0x05, iv_addr);
| + | cxor(c3, c3); |
− | } | + | cxor(c4, c4); |
− | else | + | cxor(c5, c5); |
− | { | + | cxor(c6, c6); |
− | // Clear c5 register (use null IV)
| + | cxor(c7, c7); |
− | cxor(c5, c5);
| |
− | } | |
| | | |
− | // Use key in c4 | + | // Exit Authenticated Mode |
− | ckeyreg(c4); | + | // This is TSEC_MMIO + 0x1000 + (0x10300 / 0x40) |
| + | *(u32 *)TSEC_SCP_CTL_AUTH_MODE = 0; |
| | | |
− | // AES-128-CBC decrypt | + | return; |
− | if (mode == 0x00)
| + | |
− | { | + | ==== load_keygen ==== |
− | // Create crypto script with 5 instructions
| + | u32 res = 0; |
− | cs0begin(0x05);
| + | |
− |
| + | u32 boot_base_addr = 0; |
− | cxsin(c3); // Read 0x10 bytes from crypto stream into c3
| + | u32 blob0_addr = 0; |
− | cdec(c2, c3); // Decrypt from c3 into c2
| + | u32 blob0_size = *(u32 *)(key_buf + 0x70); |
− | cxor(c5, c2); // XOR c2 with c5 and store in c5
| + | |
− | cxsout(c5); // Write 0x10 bytes into crypto stream from c5
| + | // Load blob0 code again |
− | cmov(c5, c3); // Move c3 into c5
| + | read_code(boot_base_addr, blob0_addr, blob0_size); |
− | } | + | |
− | else if (mode == 0x01) // AES-128-CBC encrypt | + | // Generate "CODE_SIG_01" key into c4 crypto register |
− | { | + | gen_usr_key(0, 0); |
− | // Create crypto script with 4 instructions
| + | |
− | cs0begin(0x04);
| + | // Encrypt buffer with c4 |
− |
| + | u8 sig_key[0x10]; |
− | cxsin(c3); // Read 0x10 bytes from crypto stream into c3
| + | enc_buf(sig_key, blob0_size); |
− | cxor(c3, c5); // XOR c5 with c3 and store in c3
| + | |
− | cenc(c5, c3); // Encrypt from c3 into c5 | + | u32 src_addr = boot_base_addr; |
− | cxsout(c5); // Write 0x10 bytes into crypto stream from c5 | + | u32 src_size = blob0_size; |
| + | u32 iv_addr = sig_key; |
| + | u32 dst_addr = sig_key; |
| + | u32 mode = 0x02; // AES-CMAC |
| + | u32 version = 0; |
| + | |
| + | // Do AES-CMAC over blob0 code |
| + | do_crypto(src_addr, src_size, iv_addr, dst_addr, mode, version); |
| + | |
| + | // Compare the hashes |
| + | if (memcmp(dst_addr, key_buf + 0x10, 0x10)) |
| + | { |
| + | res = 0xDEADBEEF; |
| + | return res; |
| } | | } |
− | else if (mode == 0x02) // AES-CMAC | + | |
| + | u32 blob1_size = *(u32 *)(key_buf + 0x74); |
| + | |
| + | // Decrypt Keygen blob if needed |
| + | if (!is_blob_dec) |
| { | | { |
− | // Create crypto script with 3 instructions
| + | // Read Stage2's size from key buffer |
− | cs0begin(0x03);
| + | u32 blob2_size = *(u32 *)(key_buf + 0x78); |
− |
| |
− | cxsin(c3); // Read 0x10 bytes from crypto stream into c3
| |
− | cxor(c5, c3); // XOR c5 with c3 and store in c3
| |
− | cenc(c5, c5); // Encrypt from c5 into c5
| |
− | }
| |
− | else if (mode == 0x03) // AES-128-ECB decrypt
| |
− | {
| |
− | // Create crypto script with 3 instructions
| |
− | cs0begin(0x03);
| |
− |
| |
− | cxsin(c3); // Read 0x10 bytes from crypto stream into c3
| |
− | cdec(c5, c3); // Decrypt from c3 into c5
| |
− | cxsout(c5); // Write 0x10 bytes into crypto stream from c5
| |
− | }
| |
− | else if (mode == 0x04) // AES-128-ECB encrypt
| |
− | {
| |
− | // Create crypto script with 3 instructions
| |
− | cs0begin(0x03);
| |
− |
| |
− | cxsin(c3); // Read 0x10 bytes from crypto stream into c3
| |
− | cenc(c5, c3); // Encrypt from c3 into c5
| |
− | cxsout(c5); // Write 0x10 bytes into crypto stream from c5
| |
− | }
| |
− | else
| |
− | return;
| |
| | | |
− | // Main loop
| + | // Check stack bounds |
− | while (src_size > 0)
| + | if (*(u32 *)sp > blob2_size) |
− | {
| + | { |
− | u32 blk_count = (src_size >> 0x04);
| + | u32 boot_base_addr = 0; |
− |
| + | u32 blob2_virt_addr = blob0_size + blob1_size; |
− | if (blk_count > 0x10)
| + | u32 blob2_addr = blob2_virt_addr + 0x100; |
− | blk_count = 0x10;
| + | |
− |
| + | // Read Keygen encrypted blob |
− | // Check size align
| + | read_code(boot_base_addr, blob2_addr, blob2_size); |
− | if (blk_count & (blk_count - 0x01))
| |
− | blk_count = 0x01;
| |
| | | |
− | u32 blk_size = (blk_count << 0x04);
| + | // Generate "CODE_ENC_01" key into c4 crypt register |
− |
| + | gen_usr_key(0x01, 0x01); |
− | u32 crypt_xfer_src = 0;
| + | |
− | u32 crypt_xfer_dst = 0;
| + | u32 src_addr = boot_base_addr; |
− |
| + | u32 src_size = blob2_size; |
− | if (block_size == 0x20)
| + | u32 iv_addr = key_buf + 0x40; |
− | {
| + | u32 dst_addr = boot_base_addr; |
− | crypt_xfer_src = (0x00030000 | src_addr);
| + | u32 mode = 0; // AES-128-ECB |
− | crypt_xfer_dst = (0x00030000 | dst_addr);
| + | u32 version = 0; |
− |
| + | |
− | // Execute crypto script 2 times (1 for each block)
| + | // Decrypt Keygen blob |
− | cs0exec(0x02);
| + | do_crypto(src_addr, src_size, iv_addr, dst_addr, mode, version); |
− | }
| + | |
− | if (block_size == 0x40)
| + | // Upload the next code segment into Falcon's CODE region |
− | {
| + | bool use_secret = true; |
− | crypt_xfer_src = (0x00040000 | src_addr);
| + | upload_code(blob2_virt_addr, boot_base_addr, blob2_size, blob2_virt_addr, use_secret); |
− | crypt_xfer_dst = (0x00040000 | dst_addr);
| + | |
− |
| + | // Clear out the decrypted blob |
− | // Execute crypto script 4 times (1 for each block)
| + | memset(boot_base_addr, 0, blob2_size); |
− | cs0exec(0x04);
| + | } |
− | }
| + | } |
− | if (block_size == 0x80)
| |
− | {
| |
− | crypt_xfer_src = (0x00050000 | src_addr);
| |
− | crypt_xfer_dst = (0x00050000 | dst_addr);
| |
− |
| |
− | // Execute crypto script 8 times (1 for each block)
| |
− | cs0exec(0x08);
| |
− | }
| |
− | if (block_size == 0x100)
| |
− | {
| |
− | crypt_xfer_src = (0x00060000 | src_addr);
| |
− | crypt_xfer_dst = (0x00060000 | dst_addr);
| |
− |
| |
− | // Execute crypto script 16 times (1 for each block)
| |
− | cs0exec(0x10);
| |
− | }
| |
− | else
| |
− | {
| |
− | crypt_xfer_src = (0x00020000 | src_addr);
| |
− | crypt_xfer_dst = (0x00020000 | dst_addr);
| |
− |
| |
− | // Execute crypto script 1 time (1 for each block)
| |
− | cs0exec(0x01);
| |
| | | |
− | // Ensure proper block size
| + | // fuc5 crypt cxset instruction |
− | block_size = 0x10;
| + | // The next 2 xfer instructions will be overridden |
− | }
| + | // and target changes from DMA to crypto |
| + | cxset(0x02); |
| + | |
| + | u32 crypt_reg_flag = 0x00060000; |
| + | u32 blob2_hash_addr = key_buf + 0x30; |
| | | |
− | // fuc5 crypt cxset instruction
| + | // Transfer data to crypto register c6 |
− | // The next xfer instruction will be overridden
| + | xdst(0, (blob2_hash_addr | crypt_reg_flag)); |
− | // and target changes from DMA to crypto input/output stream
| + | |
− | if (crypt_ver == 0x01)
| + | // Wait for all data loads/stores to finish |
− | cxset(0xA1); // Flag 0xA0 is (0x80 | 0x20)
| + | xdwait(); |
− | else
| |
− | cxset(0x21); // Flag 0x20 is external mem <-> crypto input/output stream
| |
| | | |
− | // Transfer data into the crypto input/output stream
| + | // Save previous cauth value |
− | xdst(crypt_xfer_src, crypt_xfer_src);
| + | u32 c_old = cauth_old; |
− |
| |
− | // AES-CMAC only needs one more xfer instruction
| |
− | if (mode == 0x02)
| |
− | {
| |
− | // fuc5 crypt cxset instruction
| |
− | // The next xfer instruction will be overridden
| |
− | // and target changes from DMA to crypto input/output stream
| |
− | if (crypt_ver == 0x01)
| |
− | cxset(0xA1); // Flag 0xA0 is (0x80 | 0x20)
| |
− | else
| |
− | cxset(0x21); // Flag 0x20 is external mem <-> crypto input/output stream
| |
− |
| |
− | // Wait for all data loads/stores to finish
| |
− | xdwait();
| |
− | }
| |
− | else // AES enc/dec needs 2 more xfer instructions
| |
− | {
| |
− | // fuc5 crypt cxset instruction
| |
− | // The next 2 xfer instructions will be overridden
| |
− | // and target changes from DMA to crypto input/output stream
| |
− | if (crypt_ver == 0x01)
| |
− | cxset(0xA2); // Flag 0xA0 is (0x80 | 0x20)
| |
− | else
| |
− | cxset(0x22); // Flag 0x20 is external mem <-> crypto input/output stream
| |
| | | |
− | // Transfer data from the crypto input/output stream
| + | // fuc5 crypt cauth instruction |
− | xdld(crypt_xfer_dst, crypt_xfer_dst);
| + | // Set auth_addr to blob2_virt_addr and auth_size to blob2_size |
− |
| + | cauth((blob2_virt_addr >> 0x08) | (blob2_size << 0x10)); |
− | // Wait for all data loads/stores to finish
| |
− | xdwait();
| |
| | | |
− | // Increase the destination address by block size
| + | u32 hovi_key_addr = 0; |
− | dst_addr += block_size;
| |
− | }
| |
− |
| |
− | // Increase the source address by block size
| |
− | src_addr += block_size;
| |
| | | |
− | // Decrease the source size by block size | + | // Select next stage key |
− | src_size -= block_size; | + | if (key_version == 0x01) // Use HOVI_EKS_01 |
− | } | + | hovi_key_addr = key_buf + 0x50; |
| + | else if (key_version == 0x02) // Use HOVI_COMMON_01 |
| + | hovi_key_addr = key_buf + 0x60; |
| + | else if (key_version == 0x03) // Use debug key (empty) |
| + | hovi_key_addr = key_buf + 0x00; |
| + | else |
| + | res = 0xD0D0D0D0 |
| + | |
| + | // Jump to Keygen |
| + | if (hovi_key_addr) |
| + | res = exec_keygen(hovi_key_addr, key_version); |
| + | |
| + | // Clear out key data |
| + | memset(key_buf, 0, 0x7C); |
| | | |
− | // AES-CMAC result is in c5 | + | // fuc5 crypt cauth instruction |
− | if (mode == 0x02) | + | // Restore previous cauth value |
− | {
| + | cauth(c_old); |
− | // This will read into dst_addr from crypto register c5
| |
− | crypt_load(0x05, dst_addr);
| |
− | }
| |
| | | |
− | return; | + | return res; |
| | | |
− | == Keygen == | + | ===== gen_usr_key ===== |
− | This stage is decrypted by [[#KeygenLdr|KeygenLdr]] using a key generated by encrypting a seed with an hardware secret. It will generate the final TSEC device key.
| + | This method takes '''type''' and '''mode''' as arguments and generates a key. |
− | | + | u8 seed_buf[0x10]; |
− | == Loader == | |
− | This stage starts by authenticating and executing [[#KeygenLdr|KeygenLdr]] which in turn authenticates, decrypts and executes [[#Keygen|Keygen]] (both blobs remain unchanged from previous firmware versions). | |
− | After the TSEC device key has been generated, execution returns to this stage which then parses and executes [[#Payload|Payload]].
| |
− | | |
− | === Main ===
| |
− | u8 key_data_buf[0x84];
| |
− | u8 tmp_key_data_buf[0x84]; | |
| | | |
− | // Read the key data from memory | + | // Read a 16 bytes seed based on supplied type |
− | u32 key_data_addr = 0x300; | + | /* |
− | u32 key_data_size = 0x84; | + | Type 0: "CODE_SIG_01" + null padding |
− | read_code(key_data_buf, key_data_addr, key_data_size); | + | Type 1: "CODE_ENC_01" + null padding |
| + | */ |
| + | get_seed(seed_buf, type); |
| | | |
− | // Read the KeygenLdr blob from memory | + | // This will write the seed into crypto register c0 |
− | u32 boot_base_addr = 0;
| + | crypt_store(0, seed_buf); |
− | u32 blob1_addr = 0x400;
| |
− | u32 blob1_size = *(u32 *)(key_data_buf + 0x74);
| |
− | read_code(boot_base_addr, blob1_addr, blob1_size);
| |
− |
| |
− | // Upload the next code segment into Falcon's CODE region
| |
− | u32 blob1_virt_addr = 0x300; | |
− | bool use_secret = true;
| |
− | upload_code(blob1_virt_addr, boot_base_addr, blob1_size, blob1_virt_addr, use_secret);
| |
| | | |
− | // Backup the key data | + | // fuc5 csecret instruction |
− | memcpy(tmp_key_data_buf, key_data_buf, 0x84); | + | // Load selected secret into crypto register c1 |
| + | csecret(c1, 0x26); |
| | | |
− | // Save previous cauth value | + | // fuc5 ckeyreg instruction |
− | u32 c_old = cauth_old; | + | // Bind c1 register as the key for enc/dec operations |
| + | ckeyreg(c1); |
| | | |
− | // fuc5 crypt cauth instruction | + | // fuc5 cenc instruction |
− | // Set auth_addr to 0x300 and auth_size to blob1_size | + | // Encrypt seed_buf (in c0) using keyreg value as key into c1 |
− | cauth((blob1_size << 0x10) | (0x300 >> 0x08)); | + | cenc(c1, c0); |
| | | |
− | // fuc5 crypt cxset instruction | + | // fuc5 csigenc instruction |
− | // The next 2 xfer instructions will be overridden | + | // Encrypt c1 register with the auth signature stored in c6 |
− | // and target changes from DMA to crypto | + | csigenc(c1, c1); |
− | cxset(0x02);
| |
| | | |
− | u32 crypt_reg_flag = 0x00060000; | + | // Copy the result into c4 (will be used as key) |
− | u32 blob1_hash_addr = tmp_key_data_buf + 0x20; | + | cmov(c4, c1); |
| | | |
− | // Transfer data to crypto register c6 | + | // Do key expansion (for decryption) |
− | xdst(0, (blob1_hash_addr | crypt_reg_flag)); | + | if (mode != 0) |
| + | ckexp(c4, c4); // fuc5 ckexp instruction |
| | | |
− | // Wait for all data loads/stores to finish | + | return; |
− | xdwait(); | + | |
| + | ===== enc_buffer ===== |
| + | This method takes '''buf''' (a 16 bytes buffer) and '''size''' as arguments and encrypts the supplied buffer. |
| + | // Set first 3 words to null |
| + | *(u32 *)(buf + 0x00) = 0; |
| + | *(u32 *)(buf + 0x04) = 0; |
| + | *(u32 *)(buf + 0x08) = 0; |
| | | |
− | u32 key_version = 0x01; | + | // Swap halves (b16, b32 and b16 again) |
− | bool is_blob_dec = false; | + | hswap(size); |
| | | |
− | // Jump to KeygenLdr | + | // Store the size as the last word |
− | u32 keygenldr_res = exec_keygenldr(tmp_key_data_buf, key_version, is_blob_dec); | + | *(u32 *)(buf + 0x0C) = size; |
| | | |
− | // Set boot finish magic on success | + | // This will write buf into crypto register c3 |
− | if (keygenldr_res == 0) | + | crypt_store(0x03, buf); |
− | keygenldr_res = 0xB0B0B0B0
| |
− |
| |
− | // Write result to FALCON_SCRATCH1
| |
− | *(u32 *)FALCON_SCRATCH1 = keygenldr_res;
| |
| | | |
− | if (keygenldr_res != 0xB0B0B0B0) | + | // fuc5 ckeyreg instruction |
− | return keygenldr_res;
| + | // Bind c4 register (from keygen) as the key for enc/dec operations |
| + | ckeyreg(c4); |
| | | |
− | // fuc5 crypt cauth instruction | + | // fuc5 cenc instruction |
− | // Restore previous cauth value | + | // Encrypt buf (in c3) using keyreg value as key into c5 |
− | cauth(c_old); | + | cenc(c5, c3); |
| | | |
− | u8 flcn_hdr_buf[0x18]; | + | // This will read into buf from crypto register c5 |
− | u8 flcn_os_hdr_buf[0x10]; | + | crypt_load(0x05, buf); |
| | | |
− | blob1_size = *(u32 *)(key_data_buf + 0x74); | + | return; |
− | u32 blob2_size = *(u32 *)(key_data_buf + 0x78); | + | |
− | u32 blob0_size = *(u32 *)(key_data_buf + 0x70);
| + | ===== do_crypto ===== |
| + | This is the method responsible for all crypto operations performed during [[#KeygenLdr|KeygenLdr]]. It takes '''src_addr''', '''src_size''', '''iv_addr''', '''dst_addr''', '''mode''' and '''crypt_ver''' as arguments. |
| + | // Check for invalid source data size |
| + | if (!src_size || (src_size & 0x0F)) |
| + | exit(); |
| | | |
− | // Read the Payload blob's Falcon header from memory | + | // Check for invalid source data address |
− | u32 blob4_flcn_hdr_addr = (((blob0_size + blob1_size) + 0x100) + blob2_size); | + | if (src_addr & 0x0F) |
− | read_code(flcn_hdr_buf, blob4_flcn_hdr_addr, 0x18);
| + | exit(); |
| | | |
− | blob1_size = *(u32 *)(key_data_buf + 0x74); | + | // Check for invalid destination data address |
− | blob2_size = *(u32 *)(key_data_buf + 0x78); | + | if (dst_addr & 0x0F) |
− | blob0_size = *(u32 *)(key_data_buf + 0x70);
| + | exit(); |
− | u32 flcn_hdr_size = *(u32 *)(flcn_hdr_buf + 0x0C);
| |
| | | |
− | // Read the Payload blob's Falcon OS header from memory | + | // Use IV if available |
− | u32 blob4_flcn_os_hdr_addr = ((((blob0_size + blob1_size) + 0x100) + blob2_size) + flcn_hdr_size); | + | if (iv_addr) |
− | read_code(flcn_os_hdr_buf, blob4_flcn_os_hdr_addr, 0x10); | + | { |
− | | + | // This will write the iv_addr into crypto register c5 |
− | blob1_size = *(u32 *)(key_data_buf + 0x74); | + | crypt_store(0x05, iv_addr); |
− | blob2_size = *(u32 *)(key_data_buf + 0x78); | + | } |
− | blob0_size = *(u32 *)(key_data_buf + 0x70);
| + | else |
− | u32 flcn_code_hdr_size = *(u32 *)(flcn_hdr_buf + 0x10);
| + | { |
− | u32 flcn_os_size = *(u32 *)(flcn_os_hdr_buf + 0x04); | + | // Clear c5 register (use null IV) |
| + | cxor(c5, c5); |
| + | } |
| | | |
− | // Read the Payload blob's Falcon OS image from memory | + | // Use key in c4 |
− | u32 blob4_flcn_os_addr = ((((blob0_size + blob1_size) + 0x100) + blob2_size) + flcn_code_hdr_size); | + | ckeyreg(c4); |
− | read_code(boot_base_addr, blob4_flcn_os_hdr_addr, flcn_os_size);
| |
| | | |
− | // Upload the Payload's Falcon OS image boot stub code segment into Falcon's CODE region | + | // AES-128-CBC decrypt |
− | u32 blob4_flcn_os_boot_virt_addr = 0; | + | if (mode == 0x00) |
− | u32 blob4_flcn_os_boot_size = 0x100; | + | { |
− | use_secret = false;
| + | // Create crypto script with 5 instructions |
− | upload_code(blob4_flcn_os_boot_virt_addr, boot_base_addr, blob4_flcn_os_boot_size, blob4_flcn_os_boot_virt_addr, use_secret);
| + | cs0begin(0x05); |
− |
| + | |
− | flcn_os_size = *(u32 *)(flcn_os_hdr_buf + 0x04);
| + | cxsin(c3); // Read 0x10 bytes from crypto stream into c3 |
− |
| + | cdec(c2, c3); // Decrypt from c3 into c2 |
− | // Upload the Payload blob's Falcon OS encrypted image code segment into Falcon's CODE region
| + | cxor(c5, c2); // XOR c2 with c5 and store in c5 |
− | u32 blob4_flcn_os_img_virt_addr = 0x100;
| + | cxsout(c5); // Write 0x10 bytes into crypto stream from c5 |
− | u32 blob4_flcn_os_img_size = (flcn_os_size - 0x100);
| + | cmov(c5, c3); // Move c3 into c5 |
− | use_secret = true;
| + | } |
− | upload_code(blob4_flcn_os_img_virt_addr, boot_base_addr + 0x100, blob4_flcn_os_img_size, blob4_flcn_os_img_virt_addr, use_secret);
| + | else if (mode == 0x01) // AES-128-CBC encrypt |
− |
| + | { |
− | // Wait for all code loads to finish
| + | // Create crypto script with 4 instructions |
− | xcwait();
| + | cs0begin(0x04); |
− |
| + | |
− | blob1_size = *(u32 *)(key_data_buf + 0x74);
| + | cxsin(c3); // Read 0x10 bytes from crypto stream into c3 |
− | blob2_size = *(u32 *)(key_data_buf + 0x78); | + | cxor(c3, c5); // XOR c5 with c3 and store in c3 |
− | blob0_size = *(u32 *)(key_data_buf + 0x70); | + | cenc(c5, c3); // Encrypt from c3 into c5 |
− | flcn_code_hdr_size = *(u32 *)(flcn_hdr_buf + 0x10); | + | cxsout(c5); // Write 0x10 bytes into crypto stream from c5 |
− | u32 flcn_os_code_size = *(u32 *)(flcn_os_hdr_buf + 0x08);
| + | } |
− |
| + | else if (mode == 0x02) // AES-CMAC |
− | // Read the Payload blob's falcon OS image's hash from memory
| |
− | u32 blob4_flcn_os_img_hash_addr = (((((blob0_size + blob1_size) + 0x100) + blob2_size) + flcn_code_hdr_size) + flcn_os_code_size);
| |
− | read_code(0, blob4_flcn_os_img_hash_addr, 0x10);
| |
− |
| |
− | // Read data segment size from IO space
| |
− | u32 data_seg_size = *(u32 *)UC_CAPS;
| |
− | data_seg_size >>= 0x03;
| |
− | data_seg_size &= 0x3FC0;
| |
− |
| |
− | u32 data_addr = 0x10;
| |
− |
| |
− | // Clear all data except the first 0x10 bytes (Payload blob's Falcon OS image's hash)
| |
− | for (int data_word_count = 0x04; data_word_count < data_seg_size; data_word_count++) | |
| { | | { |
− | *(u32 *)(data_addr) = 0; | + | // Create crypto script with 3 instructions |
− | data_addr += 0x04; | + | cs0begin(0x03); |
| + | |
| + | cxsin(c3); // Read 0x10 bytes from crypto stream into c3 |
| + | cxor(c5, c3); // XOR c5 with c3 and store in c3 |
| + | cenc(c5, c5); // Encrypt from c5 into c5 |
| } | | } |
| + | else if (mode == 0x03) // AES-128-ECB decrypt |
| + | { |
| + | // Create crypto script with 3 instructions |
| + | cs0begin(0x03); |
| + | |
| + | cxsin(c3); // Read 0x10 bytes from crypto stream into c3 |
| + | cdec(c5, c3); // Decrypt from c3 into c5 |
| + | cxsout(c5); // Write 0x10 bytes into crypto stream from c5 |
| + | } |
| + | else if (mode == 0x04) // AES-128-ECB encrypt |
| + | { |
| + | // Create crypto script with 3 instructions |
| + | cs0begin(0x03); |
| + | |
| + | cxsin(c3); // Read 0x10 bytes from crypto stream into c3 |
| + | cenc(c5, c3); // Encrypt from c3 into c5 |
| + | cxsout(c5); // Write 0x10 bytes into crypto stream from c5 |
| + | } |
| + | else |
| + | return; |
| | | |
− | // Clear all crypto registers | + | // Main loop |
− | cxor(c0, c0); | + | while (src_size > 0) |
− | cxor(c1, c1); | + | { |
− | cxor(c2, c2);
| + | u32 blk_count = (src_size >> 0x04); |
− | cxor(c3, c3);
| + | |
− | cxor(c4, c4);
| + | if (blk_count > 0x10) |
− | cxor(c5, c5);
| + | blk_count = 0x10; |
− | cxor(c6, c6);
| + | |
− | cxor(c7, c7);
| + | // Check size align |
− |
| + | if (blk_count & (blk_count - 0x01)) |
− | // Partially unknown fuc5 instruction
| + | blk_count = 0x01; |
− | // Likely forces a change of permissions
| |
− | cchmod(c0, c0);
| |
| | | |
− | // Jump to Payload
| + | u32 blk_size = (blk_count << 0x04); |
− | exec_payload();
| + | |
− |
| + | u32 crypt_xfer_src = 0; |
− | return 0xB0B0B0B0;
| + | u32 crypt_xfer_dst = 0; |
− | | + | |
− | == Payload == | + | if (block_size == 0x20) |
− | This stage prepares the stack then authenticates, decrypts and executes the Payload blob's Falcon OS image.
| + | { |
− | | + | crypt_xfer_src = (0x00030000 | src_addr); |
− | === Main === | + | crypt_xfer_dst = (0x00030000 | dst_addr); |
− | // Read data segment size from IO space
| + | |
− | u32 data_seg_size = *(u32 *)UC_CAPS;
| + | // Execute crypto script 2 times (1 for each block) |
− | data_seg_size >>= 0x01;
| + | cs0exec(0x02); |
− | data_seg_size &= 0xFF00;
| + | } |
− |
| + | if (block_size == 0x40) |
− | // Set the stack pointer
| + | { |
− | *(u32 *)sp = data_seg_size;
| + | crypt_xfer_src = (0x00040000 | src_addr); |
− |
| + | crypt_xfer_dst = (0x00040000 | dst_addr); |
− | // Jump to the Payload blob's Falcon OS image boot stub
| + | |
− | exec_flcn_os_boot();
| + | // Execute crypto script 4 times (1 for each block) |
− |
| + | cs0exec(0x04); |
− | // Halt execution
| + | } |
− | exit();
| + | if (block_size == 0x80) |
− |
| + | { |
− | return;
| + | crypt_xfer_src = (0x00050000 | src_addr); |
− | | + | crypt_xfer_dst = (0x00050000 | dst_addr); |
− | ==== exec_flcn_os_boot ==== | + | |
− | // Read the transfer base address from IO space
| + | // Execute crypto script 8 times (1 for each block) |
− | u32 xfer_ext_base_addr = *(u32 *)XFER_EXT_BASE;
| + | cs0exec(0x08); |
| + | } |
| + | if (block_size == 0x100) |
| + | { |
| + | crypt_xfer_src = (0x00060000 | src_addr); |
| + | crypt_xfer_dst = (0x00060000 | dst_addr); |
| + | |
| + | // Execute crypto script 16 times (1 for each block) |
| + | cs0exec(0x10); |
| + | } |
| + | else |
| + | { |
| + | crypt_xfer_src = (0x00020000 | src_addr); |
| + | crypt_xfer_dst = (0x00020000 | dst_addr); |
| + | |
| + | // Execute crypto script 1 time (1 for each block) |
| + | cs0exec(0x01); |
| | | |
− | // Copy transfer base address to data memory
| + | // Ensure proper block size |
− | u32 scratch_data_addr = 0x300;
| + | block_size = 0x10; |
− | *(u32 *)scratch_data_addr = xfer_ext_base_addr;
| + | } |
| | | |
− | // Set the transfer base address
| + | // fuc5 crypt cxset instruction |
− | xcbase(xfer_ext_base_addr);
| + | // The next xfer instruction will be overridden |
| + | // and target changes from DMA to crypto input/output stream |
| + | if (crypt_ver == 0x01) |
| + | cxset(0xA1); // Flag 0xA0 is (0x80 | 0x20) |
| + | else |
| + | cxset(0x21); // Flag 0x20 is external mem <-> crypto input/output stream |
| | | |
− | // fuc5 crypt cxset instruction
| + | // Transfer data into the crypto input/output stream |
− | // The next xfer instruction will be overridden
| + | xdst(crypt_xfer_src, crypt_xfer_src); |
− | // and target changes from DMA to crypto
| + | |
− | cxset(0x01);
| + | // AES-CMAC only needs one more xfer instruction |
− |
| + | if (mode == 0x02) |
− | u32 crypt_reg_flag = 0x00060000;
| + | { |
− | u32 blob4_flcn_os_img_hash_addr = 0;
| + | // fuc5 crypt cxset instruction |
− | | + | // The next xfer instruction will be overridden |
− | // Transfer data to crypto register c6
| + | // and target changes from DMA to crypto input/output stream |
− | xdst(0, (blob4_flcn_os_img_hash_addr | crypt_reg_flag));
| + | if (crypt_ver == 0x01) |
| + | cxset(0xA1); // Flag 0xA0 is (0x80 | 0x20) |
| + | else |
| + | cxset(0x21); // Flag 0x20 is external mem <-> crypto input/output stream |
| + | |
| + | // Wait for all data loads/stores to finish |
| + | xdwait(); |
| + | } |
| + | else // AES enc/dec needs 2 more xfer instructions |
| + | { |
| + | // fuc5 crypt cxset instruction |
| + | // The next 2 xfer instructions will be overridden |
| + | // and target changes from DMA to crypto input/output stream |
| + | if (crypt_ver == 0x01) |
| + | cxset(0xA2); // Flag 0xA0 is (0x80 | 0x20) |
| + | else |
| + | cxset(0x22); // Flag 0x20 is external mem <-> crypto input/output stream |
| | | |
− | // fuc5 crypt cxset instruction
| + | // Transfer data from the crypto input/output stream |
− | // The next xfer instruction will be overridden
| + | xdld(crypt_xfer_dst, crypt_xfer_dst); |
− | // and target changes from DMA to crypto
| + | |
− | cxset(0x01);
| + | // Wait for all data loads/stores to finish |
| + | xdwait(); |
| | | |
− | // Wait for all data loads/stores to finish
| + | // Increase the destination address by block size |
− | xdwait();
| + | dst_addr += block_size; |
| + | } |
| + | |
| + | // Increase the source address by block size |
| + | src_addr += block_size; |
| | | |
− | cmov(c7, c6);
| + | // Decrease the source size by block size |
− | cxor(c7, c7); | + | src_size -= block_size; |
| + | } |
| | | |
− | // fuc5 crypt cauth instruction | + | // AES-CMAC result is in c5 |
− | // Set auth_addr to 0x100, auth_size to 0x1300 and some unknown flags | + | if (mode == 0x02) |
− | cauth((0x02 << 0x10) | (0x01 << 0x10) | (0x1300 << 0x10) | (0x100 >> 0x08));
| + | { |
| + | // This will read into dst_addr from crypto register c5 |
| + | crypt_load(0x05, dst_addr); |
| + | } |
| | | |
− | // Clear interrupt flags
| + | return; |
− | *(u8 *)flags_ie0 = 0;
| |
− | *(u8 *)flags_ie1 = 0;
| |
− |
| |
− | // Jump to the Payload blob's Falcon OS image
| |
− | exec_flcn_os_img();
| |
− |
| |
− | return 0x0F0F0F0F; | |
| | | |
− | == Key data == | + | == Keygen == |
− | Small buffer stored after the [[#Boot|Boot]] blob and used across all stages. | + | This stage is decrypted by [[#KeygenLdr|KeygenLdr]] using a key generated by encrypting a seed with an hardware secret. It will generate the final TSEC key. |
| + | |
| + | == Loader == |
| + | This stage starts by authenticating and executing [[#KeygenLdr|KeygenLdr]] which in turn authenticates, decrypts and executes [[#Keygen|Keygen]] (both blobs remain unchanged from previous firmware versions). |
| + | After the TSEC key has been generated, execution returns to this stage which then parses and executes [[#Payload|Payload]]. |
| + | |
| + | === Main === |
| + | u8 key_data_buf[0x84]; |
| + | u8 tmp_key_data_buf[0x84]; |
| + | |
| + | // Read the key data from memory |
| + | u32 key_data_addr = 0x300; |
| + | u32 key_data_size = 0x84; |
| + | read_code(key_data_buf, key_data_addr, key_data_size); |
| + | |
| + | // Read the KeygenLdr blob from memory |
| + | u32 boot_base_addr = 0; |
| + | u32 blob1_addr = 0x400; |
| + | u32 blob1_size = *(u32 *)(key_data_buf + 0x74); |
| + | read_code(boot_base_addr, blob1_addr, blob1_size); |
| + | |
| + | // Upload the next code segment into Falcon's CODE region |
| + | u32 blob1_virt_addr = 0x300; |
| + | bool use_secret = true; |
| + | upload_code(blob1_virt_addr, boot_base_addr, blob1_size, blob1_virt_addr, use_secret); |
| + | |
| + | // Backup the key data |
| + | memcpy(tmp_key_data_buf, key_data_buf, 0x84); |
| + | |
| + | // Save previous cauth value |
| + | u32 c_old = cauth_old; |
| + | |
| + | // fuc5 crypt cauth instruction |
| + | // Set auth_addr to 0x300 and auth_size to blob1_size |
| + | cauth((blob1_size << 0x10) | (0x300 >> 0x08)); |
| + | |
| + | // fuc5 crypt cxset instruction |
| + | // The next 2 xfer instructions will be overridden |
| + | // and target changes from DMA to crypto |
| + | cxset(0x02); |
| + | |
| + | u32 crypt_reg_flag = 0x00060000; |
| + | u32 blob1_hash_addr = tmp_key_data_buf + 0x20; |
| + | |
| + | // Transfer data to crypto register c6 |
| + | xdst(0, (blob1_hash_addr | crypt_reg_flag)); |
| + | |
| + | // Wait for all data loads/stores to finish |
| + | xdwait(); |
| + | |
| + | u32 key_version = 0x01; |
| + | bool is_blob_dec = false; |
| + | |
| + | // Jump to KeygenLdr |
| + | u32 keygenldr_res = exec_keygenldr(tmp_key_data_buf, key_version, is_blob_dec); |
| + | |
| + | // Set boot finish magic on success |
| + | if (keygenldr_res == 0) |
| + | keygenldr_res = 0xB0B0B0B0 |
| + | |
| + | // Write result to FALCON_SCRATCH1 |
| + | *(u32 *)FALCON_SCRATCH1 = keygenldr_res; |
| + | |
| + | if (keygenldr_res != 0xB0B0B0B0) |
| + | return keygenldr_res; |
| + | |
| + | // fuc5 crypt cauth instruction |
| + | // Restore previous cauth value |
| + | cauth(c_old); |
| + | |
| + | u8 flcn_hdr_buf[0x18]; |
| + | u8 flcn_os_hdr_buf[0x10]; |
| + | |
| + | blob1_size = *(u32 *)(key_data_buf + 0x74); |
| + | u32 blob2_size = *(u32 *)(key_data_buf + 0x78); |
| + | u32 blob0_size = *(u32 *)(key_data_buf + 0x70); |
| + | |
| + | // Read the Payload blob's Falcon header from memory |
| + | u32 blob4_flcn_hdr_addr = (((blob0_size + blob1_size) + 0x100) + blob2_size); |
| + | read_code(flcn_hdr_buf, blob4_flcn_hdr_addr, 0x18); |
| + | |
| + | blob1_size = *(u32 *)(key_data_buf + 0x74); |
| + | blob2_size = *(u32 *)(key_data_buf + 0x78); |
| + | blob0_size = *(u32 *)(key_data_buf + 0x70); |
| + | u32 flcn_hdr_size = *(u32 *)(flcn_hdr_buf + 0x0C); |
| + | |
| + | // Read the Payload blob's Falcon OS header from memory |
| + | u32 blob4_flcn_os_hdr_addr = ((((blob0_size + blob1_size) + 0x100) + blob2_size) + flcn_hdr_size); |
| + | read_code(flcn_os_hdr_buf, blob4_flcn_os_hdr_addr, 0x10); |
| + | |
| + | blob1_size = *(u32 *)(key_data_buf + 0x74); |
| + | blob2_size = *(u32 *)(key_data_buf + 0x78); |
| + | blob0_size = *(u32 *)(key_data_buf + 0x70); |
| + | u32 flcn_code_hdr_size = *(u32 *)(flcn_hdr_buf + 0x10); |
| + | u32 flcn_os_size = *(u32 *)(flcn_os_hdr_buf + 0x04); |
| + | |
| + | // Read the Payload blob's Falcon OS image from memory |
| + | u32 blob4_flcn_os_addr = ((((blob0_size + blob1_size) + 0x100) + blob2_size) + flcn_code_hdr_size); |
| + | read_code(boot_base_addr, blob4_flcn_os_hdr_addr, flcn_os_size); |
| + | |
| + | // Upload the Payload's Falcon OS image boot stub code segment into Falcon's CODE region |
| + | u32 blob4_flcn_os_boot_virt_addr = 0; |
| + | u32 blob4_flcn_os_boot_size = 0x100; |
| + | use_secret = false; |
| + | upload_code(blob4_flcn_os_boot_virt_addr, boot_base_addr, blob4_flcn_os_boot_size, blob4_flcn_os_boot_virt_addr, use_secret); |
| + | |
| + | flcn_os_size = *(u32 *)(flcn_os_hdr_buf + 0x04); |
| + | |
| + | // Upload the Payload blob's Falcon OS encrypted image code segment into Falcon's CODE region |
| + | u32 blob4_flcn_os_img_virt_addr = 0x100; |
| + | u32 blob4_flcn_os_img_size = (flcn_os_size - 0x100); |
| + | use_secret = true; |
| + | upload_code(blob4_flcn_os_img_virt_addr, boot_base_addr + 0x100, blob4_flcn_os_img_size, blob4_flcn_os_img_virt_addr, use_secret); |
| + | |
| + | // Wait for all code loads to finish |
| + | xcwait(); |
| + | |
| + | blob1_size = *(u32 *)(key_data_buf + 0x74); |
| + | blob2_size = *(u32 *)(key_data_buf + 0x78); |
| + | blob0_size = *(u32 *)(key_data_buf + 0x70); |
| + | flcn_code_hdr_size = *(u32 *)(flcn_hdr_buf + 0x10); |
| + | u32 flcn_os_code_size = *(u32 *)(flcn_os_hdr_buf + 0x08); |
| + | |
| + | // Read the Payload blob's falcon OS image's hash from memory |
| + | u32 blob4_flcn_os_img_hash_addr = (((((blob0_size + blob1_size) + 0x100) + blob2_size) + flcn_code_hdr_size) + flcn_os_code_size); |
| + | read_code(0, blob4_flcn_os_img_hash_addr, 0x10); |
| + | |
| + | // Read data segment size from IO space |
| + | u32 data_seg_size = *(u32 *)UC_CAPS; |
| + | data_seg_size >>= 0x03; |
| + | data_seg_size &= 0x3FC0; |
| + | |
| + | u32 data_addr = 0x10; |
| + | |
| + | // Clear all data except the first 0x10 bytes (Payload blob's Falcon OS image's hash) |
| + | for (int data_word_count = 0x04; data_word_count < data_seg_size; data_word_count++) |
| + | { |
| + | *(u32 *)(data_addr) = 0; |
| + | data_addr += 0x04; |
| + | } |
| + | |
| + | // Clear all crypto registers |
| + | cxor(c0, c0); |
| + | cxor(c1, c1); |
| + | cxor(c2, c2); |
| + | cxor(c3, c3); |
| + | cxor(c4, c4); |
| + | cxor(c5, c5); |
| + | cxor(c6, c6); |
| + | cxor(c7, c7); |
| + | |
| + | // Partially unknown fuc5 instruction |
| + | // Likely forces a change of permissions |
| + | cchmod(c0, c0); |
| + | |
| + | // Jump to Payload |
| + | exec_payload(); |
| + | |
| + | return 0xB0B0B0B0; |
| + | |
| + | == Payload == |
| + | This stage prepares the stack then authenticates, decrypts and executes the Payload blob's Falcon OS image. |
| + | |
| + | === Main === |
| + | // Read data segment size from IO space |
| + | u32 data_seg_size = *(u32 *)UC_CAPS; |
| + | data_seg_size >>= 0x01; |
| + | data_seg_size &= 0xFF00; |
| + | |
| + | // Set the stack pointer |
| + | *(u32 *)sp = data_seg_size; |
| + | |
| + | // Jump to the Payload blob's Falcon OS image boot stub |
| + | exec_flcn_os_boot(); |
| + | |
| + | // Halt execution |
| + | exit(); |
| + | |
| + | return; |
| + | |
| + | ==== exec_flcn_os_boot ==== |
| + | // Read the transfer base address from IO space |
| + | u32 xfer_ext_base_addr = *(u32 *)XFER_EXT_BASE; |
| + | |
| + | // Copy transfer base address to data memory |
| + | u32 scratch_data_addr = 0x300; |
| + | *(u32 *)scratch_data_addr = xfer_ext_base_addr; |
| + | |
| + | // Set the transfer base address |
| + | xcbase(xfer_ext_base_addr); |
| + | |
| + | // fuc5 crypt cxset instruction |
| + | // The next xfer instruction will be overridden |
| + | // and target changes from DMA to crypto |
| + | cxset(0x01); |
| + | |
| + | u32 crypt_reg_flag = 0x00060000; |
| + | u32 blob4_flcn_os_img_hash_addr = 0; |
| + | |
| + | // Transfer data to crypto register c6 |
| + | xdst(0, (blob4_flcn_os_img_hash_addr | crypt_reg_flag)); |
| + | |
| + | // fuc5 crypt cxset instruction |
| + | // The next xfer instruction will be overridden |
| + | // and target changes from DMA to crypto |
| + | cxset(0x01); |
| + | |
| + | // Wait for all data loads/stores to finish |
| + | xdwait(); |
| + | |
| + | cmov(c7, c6); |
| + | cxor(c7, c7); |
| + | |
| + | // fuc5 crypt cauth instruction |
| + | // Set auth_addr to 0x100, auth_size to 0x1300, |
| + | // bit 16 (is_secret) and bit 17 (is_encrypted) |
| + | cauth((0x02 << 0x10) | (0x01 << 0x10) | (0x1300 << 0x10) | (0x100 >> 0x08)); |
| + | |
| + | // Clear interrupt flags |
| + | *(u8 *)flags_ie0 = 0; |
| + | *(u8 *)flags_ie1 = 0; |
| + | |
| + | // Jump to the Payload blob's Falcon OS image |
| + | exec_flcn_os_img(); |
| + | |
| + | return 0x0F0F0F0F; |
| + | |
| + | == Key data == |
| + | Small buffer stored after the [[#Boot|Boot]] blob and used across all stages. |
| | | |
| {| class="wikitable" border="1" | | {| class="wikitable" border="1" |
− | ! Offset | + | ! Offset |
− | ! Size | + | ! Size |
− | ! Description | + | ! Description |
| + | |- |
| + | | 0x00 |
| + | | 0x10 |
| + | | Debug key (empty) |
| + | |- |
| + | | 0x10 |
| + | | 0x10 |
| + | | blob0 ([[#Boot|Boot]]) auth hash |
| + | |- |
| + | | 0x20 |
| + | | 0x10 |
| + | | blob1 ([[#KeygenLdr|KeygenLdr]]) auth hash |
| + | |- |
| + | | 0x30 |
| + | | 0x10 |
| + | | blob2 ([[#Keygen|Keygen]]) auth hash |
| + | |- |
| + | | 0x40 |
| + | | 0x10 |
| + | | blob2 ([[#Keygen|Keygen]]) AES IV |
| + | |- |
| + | | 0x50 |
| + | | 0x10 |
| + | | HOVI EKS seed |
| + | |- |
| + | | 0x60 |
| + | | 0x10 |
| + | | HOVI COMMON seed |
| + | |- |
| + | | 0x70 |
| + | | 0x04 |
| + | | blob0 ([[#Boot|Boot]]) size |
| + | |- |
| + | | 0x74 |
| + | | 0x04 |
| + | | blob1 ([[#KeygenLdr|KeygenLdr]]) size |
| + | |- |
| + | | 0x78 |
| + | | 0x04 |
| + | | blob2 ([[#Keygen|Keygen]]) size |
| + | |- |
| + | | 0x7C |
| + | | 0x04 |
| + | | [6.2.0+] blob3 ([[#Loader|Loader]]) size |
| + | |- |
| + | | 0x80 |
| + | | 0x04 |
| + | | [6.2.0+] blob4 ([[#Payload|Payload]]) size |
| + | |} |
| + | |
| + | == Notes == |
| + | [https://wiki.0x04.net/wiki/Marcin_Ko%C5%9Bcielnicki mwk] shared additional info learned from RE of falcon processors over the years, which hasn't made it into envytools documentation yet: |
| + | |
| + | === cxset === |
| + | cxset instruction provides a way to change behavior of a variable amount of successively executed DMA-related instructions. |
| + | |
| + | for example: <code>000000de: f4 3c 02 cxset 0x2</code> |
| + | |
| + | can be read as: <code>dma_override(type=crypto_reg, count=2)</code> |
| + | |
| + | The argument to cxset specifies the type of behavior change in the top 3 bits, and the number of DMA-related instructions the effect lasts for in the lower 5 bits. |
| + | |
| + | ==== Override Types ==== |
| + | Unlisted values are unknown, but probably do something. |
| + | |
| + | {| class=wikitable |
| + | ! Value || Effect |
| + | |- |
| + | | 0b000 || falcon data mem <-> falcon $cX register |
| + | |- |
| + | | 0b001 || external mem <-> crypto input/output stream |
| + | |- |
| + | | 0b011 || falcon data mem <-> crypto input/output stream |
| + | |- |
| + | | 0b100 || unknown, but can be combined with other types |
| + | |} |
| + | |
| + | ==== DMA-Related Instructions ==== |
| + | At least the following instructions may have changed behavior, and count against the cxset "count" argument: <code>xdwait</code>, <code>xdst</code>, <code>xdld</code>. |
| + | |
| + | For example, if override type=0b000, then the "length" argument to <code>xdst</code> is instead treated as the index of the target $cX register. |
| + | |
| + | === Register ACLs === |
| + | Falcon tracks permission metadata about each crypto reg. Permissions include read/write ability per execution mode, as well as ability to use the reg for encrypt/decrypt, among other permissions. Permissions are propagated when registers are referenced by instructions (e.g. moving a value from read-protected $cX to $cY will result in $cY also being read-protected). |
| + | |
| + | === Authenticated Mode Entry/Exit === |
| + | Entry to Authenticated Mode always sets $pc to the address supplied in $cauth (ie the base of the signature-checked region). This takes effect when trying to branch to any address within the range covered by $cauth. Entry to Authenticated Mode (also called "Secure Mode") computes a MAC over the $cauth region and compares it to $c6 in order to perform the signature check. |
| + | |
| + | Exit from Authenticated Mode must poke a special register before leaving authenticated code pages and a failure to do this would result in the Falcon core halting. Every Falcon based unit (TSEC, NVDEC, VIC) must map this register in their engine-specific subset of registers. In TSEC's case, the register is TSEC_SCP_CTL_AUTH_MODE. |
| + | |
| + | === Unknown Instructions === |
| + | <code>00000000: f5 3c XY e0 cchmod $cY $cX</code> - likely forces a change of permissions. |
| + | |
| + | <code>00000000: f5 3c XY a8 c_unk0 $cY $cX</code> - unknown crypto operation. |
| + | |
| + | <code>00000000: f5 3c XY a9 c_unk1 $cY $cX</code> - unknown crypto operation. |
| + | |
| + | <code>00000000: f5 3c 0X 90 crng $cX</code> - seems to initialize a crypto register with random data. |
| + | |
| + | === Secrets === |
| + | Falcon's Authenticated Mode has access to 64 128-bit keys which are burned at factory. These keys can be loaded by using the $csecret instruction which takes the target crypto register and the key index as arguments. |
| + | |
| + | {| class=wikitable |
| + | ! Index || Notes |
| |- | | |- |
− | | 0x00 | + | | 0x00 || Used by the nvhost_tsec firmware. Debug mode only. |
− | | 0x10 | |
− | | Empty | |
| |- | | |- |
− | | 0x10 | + | | 0x03 || Used by the nvhost_tsec firmware. |
− | | 0x10 | |
− | | blob0 ([[#Boot|Boot]]) auth hash | |
| |- | | |- |
− | | 0x20 | + | | 0x04 || Used by the nvhost_tsec firmware. |
− | | 0x10 | |
− | | blob1 ([[#KeygenLdr|KeygenLdr]]) auth hash | |
| |- | | |- |
− | | 0x30 | + | | 0x05 || Used by the nvhost_tsec firmware. |
− | | 0x10 | |
− | | blob2 ([[#Keygen|Keygen]]) auth hash | |
− | |-
| |
− | | 0x40
| |
− | | 0x10
| |
− | | blob2 ([[#Keygen|Keygen]]) AES IV
| |
− | |-
| |
− | | 0x50
| |
− | | 0x10
| |
− | | HOVI EKS seed
| |
− | |-
| |
− | | 0x60
| |
− | | 0x10
| |
− | | HOVI COMMON seed
| |
− | |-
| |
− | | 0x70
| |
− | | 0x04
| |
− | | blob0 ([[#Boot|Boot]]) size
| |
| |- | | |- |
− | | 0x74 | + | | 0x07 || Used by the nvhost_tsec firmware. |
− | | 0x04 | |
− | | blob1 ([[#KeygenLdr|KeygenLdr]]) size | |
| |- | | |- |
− | | 0x78 | + | | 0x09 || Used by the nvhost_tsec firmware. |
− | | 0x04 | |
− | | blob2 ([[#Keygen|Keygen]]) size | |
| |- | | |- |
− | | 0x7C | + | | 0x0B || Used by the nvhost_tsec firmware. |
− | | 0x04 | |
− | | [6.2.0+] blob3 ([[#Loader|Loader]]) size | |
| |- | | |- |
− | | 0x80 | + | | 0x0F || Used by the nvhost_tsec firmware. |
− | | 0x04 | |
− | | [6.2.0+] blob4 ([[#Payload|Payload]]) size | |
− | |}
| |
− | | |
− | == Notes ==
| |
− | [https://wiki.0x04.net/wiki/Marcin_Ko%C5%9Bcielnicki mwk] shared additional info learned from RE of falcon processors over the years, which hasn't made it into envytools documentation yet:
| |
− | | |
− | === cxset ===
| |
− | cxset instruction provides a way to change behavior of a variable amount of successively executed DMA-related instructions.
| |
− | | |
− | for example: <code>000000de: f4 3c 02 cxset 0x2</code>
| |
− | | |
− | can be read as: <code>dma_override(type=crypto_reg, count=2)</code>
| |
− | | |
− | The argument to cxset specifies the type of behavior change in the top 3 bits, and the number of DMA-related instructions the effect lasts for in the lower 5 bits.
| |
− | | |
− | ==== Override Types ====
| |
− | Unlisted values are unknown, but probably do something.
| |
− | | |
− | {| class=wikitable
| |
− | ! Value || Effect
| |
| |- | | |- |
− | | 0b000 || falcon data mem <-> falcon $cX register | + | | 0x15 || Used by the nvhost_tsec firmware. |
| |- | | |- |
− | | 0b001 || external mem <-> crypto input/output stream | + | | 0x26 || Used by [[#KeygenLdr|KeygenLdr]]. |
| |- | | |- |
− | | 0b011 || falcon data mem <-> crypto input/output stream | + | | 0x3C || Used by the nvhost_tsec firmware. |
| |- | | |- |
− | | 0b100 || unknown, but can be combined with other types | + | | 0x3F || Used by the nvhost_tsec firmware. Potentially per-console. |
| |} | | |} |
− |
| |
− | ==== DMA-Related Instructions ====
| |
− | At least the following instructions may have changed behavior, and count against the cxset "count" argument: <code>xdwait</code>, <code>xdst</code>, <code>xdld</code>.
| |
− |
| |
− | For example, if override type=0b000, then the "length" argument to <code>xdst</code> is instead treated as the index of the target $cX register.
| |
− |
| |
− | === Register ACLs ===
| |
− | Falcon tracks permission metadata about each crypto reg. Permissions include read/write ability per execution mode, as well as ability to use the reg for encrypt/decrypt, among other permissions. Permissions are propagated when registers are referenced by instructions (e.g. moving a value from read-protected $cX to $cY will result in $cY also being read-protected).
| |
− |
| |
− | === Authenticated Mode Entry/Exit ===
| |
− | Entry to Authenticated Mode always sets $pc to the address supplied in $cauth (ie the base of the signature-checked region). This takes effect when trying to branch to any address within the range covered by $cauth. Entry to Authenticated Mode (also called "Secure Mode") computes a MAC over the $cauth region and compares it to $c6 in order to perform the signature check.
| |
− |
| |
− | Exit from Authenticated Mode must poke a special register before leaving authenticated code pages and a failure to do this would result in the Falcon core halting. Every Falcon based unit (TSEC, NVDEC, VIC) must map this register in their engine-specific subset of registers. In TSEC's case, the register is TSEC_SCP_CTL_AUTH_MODE.
| |
− |
| |
− | === Unknown Instructions ===
| |
− | <code>00000000: f5 3c XY e0 cchmod $cY $cX</code> - likely forces a change of permissions.
| |
− |
| |
− | <code>00000000: f5 3c XY a8 c_unk0 $cY $cX</code> - unknown crypto operation.
| |
− |
| |
− | <code>00000000: f5 3c XY a9 c_unk1 $cY $cX</code> - unknown crypto operation.
| |
− |
| |
− | <code>00000000: f5 3c 0X 90 crng $cX</code> - seems to initialize a crypto register with random data.
| |