diff --git a/docs_espressif/en/additionalfeatures.rst b/docs_espressif/en/additionalfeatures.rst index cf15d2ea1..d3491e18a 100644 --- a/docs_espressif/en/additionalfeatures.rst +++ b/docs_espressif/en/additionalfeatures.rst @@ -13,6 +13,7 @@ Additional IDE Features Docker container ESP-IDF Terminal EFuse Explorer + Flash Encryption Heap Tracing Hints viewer Install ESP-IDF Components diff --git a/docs_espressif/en/additionalfeatures/flash-encryption.rst b/docs_espressif/en/additionalfeatures/flash-encryption.rst new file mode 100644 index 000000000..39560bd05 --- /dev/null +++ b/docs_espressif/en/additionalfeatures/flash-encryption.rst @@ -0,0 +1,41 @@ +.. _flash_encryption: + +Flash Encryption +======================== + +Flash Encryption secures the device's flash memory contents. Once enabled, the firmware is uploaded in plaintext but becomes encrypted on the first boot, thus preventing unauthorized flash readouts. For more details, refer to the `ESP-IDF Flash Encryption documentation `_. + +Let's open an ESP-IDF project. For this tutorial, we will use the ``security/flash_encryption`` example. + +1. Navigate to **View** > **Command Palette** and search for the **ESP-IDF: Show Example Projects** command, then choose ``Use Current ESP-IDF (/path/to/esp-idf)``. If you don't see this option, please review the setup in the :ref:`Install ESP-IDF and Tools `. + +2. A window will open with a list of projects. Search for ``flash_encryption``. You will see a **Create project using example flash_encryption** button at the top and a description of the project below. Click the button, and the project will open in a new window. + +.. image:: ../../../media/tutorials/flash_encryption/flash-encryption.png + :alt: Flash Encryption example + +3. Configure the project by setting up the following: + + - Select the Port to Use + - Set the Espressif Device Target + - Set the Flashing Method to UART + +.. note:: + In case this step is not clear, take a look at the :ref:`Build the project `. + +4. Use the Command Palette with ``ESP-IDF: SDK Configuration editor (Menuconfig)`` to open the SDK Config Menu. Search for **flash encryption** and enable the following option: + +.. image:: ../../../media/tutorials/flash_encryption/flash-encryption2.png + :alt: Flash Encryption configuration + +.. important:: + Enabling flash encryption limits the options for further updates of the ESP32. Before using this feature, read the document and make sure to understand the implications. `ESP-IDF Flash Encryption documentation `_ + +5. Build the project. + +6. Flash the project. + +.. note:: + The first flash will upload the firmware without using the ``--encrypt`` flag. After flashing is complete, you will need to reset your device by pressing the reset button on the board. (The button may be labeled as "RESET", "RST", or "EN") + +7. Flash the firmware once again, this time if all the steps were followed correctly, the ``--encrypt`` flag will be automatically added. \ No newline at end of file diff --git a/l10n/bundle.l10n.es.json b/l10n/bundle.l10n.es.json index abfec92b3..56531d674 100644 --- a/l10n/bundle.l10n.es.json +++ b/l10n/bundle.l10n.es.json @@ -188,5 +188,13 @@ "Target {0} Set Successfully.": "Objetivo {0} configurado con éxito.", "Unknown error occurred while setting IDF target.": "Ocurrió un error desconocido al configurar el objetivo IDF.", "compile_commands.json is missing. This may cause errors with code analysis extensions.": "Falta compile_commands.json. Esto puede causar errores con las extensiones de análisis de código.", - "Generate compile_commands.json": "Generar compile_commands.json" + "Generate compile_commands.json": "Generar compile_commands.json", + "Cannot flash via JTAG method: {0}": "No se puede flashear mediante el método JTAG: {0}", + "{0}\n\nThe JTAG configuration may depend on hardware strapping. Please consult the ESP32 technical documentation for your specific model to ensure proper JTAG configuration before proceeding.": "{0}\n\nLa configuración de JTAG puede depender del acoplamiento del hardware. Por favor, consulte la documentación técnica del ESP32 para su modelo específico para asegurar la configuración correcta de JTAG antes de continuar.", + "JTAG is permanently disabled in hardware ({0} is set).": "JTAG está deshabilitado permanentemente en hardware (se ha establecido {0}).", + "USB-to-JTAG functionality is disabled ({0} is set).": "La funcionalidad USB a JTAG está deshabilitada (se ha establecido {0}).", + "JTAG is soft-disabled ({0} is set to an odd value: {1}).": "JTAG está deshabilitado por software ({0} se ha establecido con un valor impar: {1}).", + "JTAG selection may be affected by strapping configuration ({0} is set).": "La selección de JTAG puede verse afectada por la configuración de strapping (se ha establecido {0}).", + "JTAG is not disabled.": "JTAG no está deshabilitado.", + "IDF Version >= 4.3.x required to have e-fuse view": "Se requiere IDF Versión >= 4.3.x para tener vista de e-fuse" } diff --git a/l10n/bundle.l10n.pt.json b/l10n/bundle.l10n.pt.json index ab3854ca2..1c26c82c1 100644 --- a/l10n/bundle.l10n.pt.json +++ b/l10n/bundle.l10n.pt.json @@ -188,5 +188,13 @@ "Target {0} Set Successfully.": "Alvo {0} definido com sucesso.", "Unknown error occurred while setting IDF target.": "Ocorreu um erro desconhecido ao definir o alvo IDF.", "compile_commands.json is missing. This may cause errors with code analysis extensions.": "O arquivo compile_commands.json está faltando. Isso pode causar erros com extensões de análise de código.", - "Generate compile_commands.json": "Gerar compile_commands.json" + "Generate compile_commands.json": "Gerar compile_commands.json", + "Cannot flash via JTAG method: {0}": "Não é possível flashear pelo método JTAG: {0}", + "{0}\n\nThe JTAG configuration may depend on hardware strapping. Please consult the ESP32 technical documentation for your specific model to ensure proper JTAG configuration before proceeding.": "{0}\n\nA configuração JTAG pode depender da configuração do hardware. Por favor, consulte a documentação técnica do ESP32 para o seu modelo específico para garantir a configuração adequada do JTAG antes de prosseguir.", + "JTAG is permanently disabled in hardware ({0} is set).": "JTAG está permanentemente desativado no hardware ({0} está definido).", + "USB-to-JTAG functionality is disabled ({0} is set).": "A funcionalidade USB para JTAG está desativada ({0} está definido).", + "JTAG is soft-disabled ({0} is set to an odd value: {1}).": "JTAG está desativado por software ({0} está definido com um valor ímpar: {1}).", + "JTAG selection may be affected by strapping configuration ({0} is set).": "A seleção JTAG pode ser afetada pela configuração de strapping ({0} está definido).", + "JTAG is not disabled.": "JTAG não está desativado.", + "IDF Version >= 4.3.x required to have e-fuse view": "IDF Versão >= 4.3.x necessária para ter visualização de e-fuse" } diff --git a/l10n/bundle.l10n.ru.json b/l10n/bundle.l10n.ru.json index 0c21e0fb9..eb535953a 100644 --- a/l10n/bundle.l10n.ru.json +++ b/l10n/bundle.l10n.ru.json @@ -188,5 +188,13 @@ "Target {0} Set Successfully.": "Цель {0} успешно установлена.", "Unknown error occurred while setting IDF target.": "Произошла неизвестная ошибка при установке цели IDF.", "compile_commands.json is missing. This may cause errors with code analysis extensions.": "Отсутствует файл compile_commands.json. Это может вызвать ошибки в работе расширений для анализа кода.", - "Generate compile_commands.json": "Создать compile_commands.json" + "Generate compile_commands.json": "Создать compile_commands.json", + "Cannot flash via JTAG method: {0}": "Невозможно прошить методом JTAG: {0}", + "{0}\n\nThe JTAG configuration may depend on hardware strapping. Please consult the ESP32 technical documentation for your specific model to ensure proper JTAG configuration before proceeding.": "{0}\n\nКонфигурация JTAG может зависеть от аппаратной конфигурации. Пожалуйста, обратитесь к технической документации ESP32 для вашей конкретной модели, чтобы обеспечить правильную конфигурацию JTAG перед продолжением.", + "JTAG is permanently disabled in hardware ({0} is set).": "JTAG постоянно отключен на аппаратном уровне (установлен {0}).", + "USB-to-JTAG functionality is disabled ({0} is set).": "Функциональность USB-to-JTAG отключена (установлен {0}).", + "JTAG is soft-disabled ({0} is set to an odd value: {1}).": "JTAG программно отключен ({0} установлен на нечетное значение: {1}).", + "JTAG selection may be affected by strapping configuration ({0} is set).": "Выбор JTAG может зависеть от конфигурации стрэппинга (установлен {0}).", + "JTAG is not disabled.": "JTAG не отключен.", + "IDF Version >= 4.3.x required to have e-fuse view": "Требуется IDF версии >= 4.3.x для просмотра e-fuse" } diff --git a/l10n/bundle.l10n.zh-CN.json b/l10n/bundle.l10n.zh-CN.json index b46dd5fba..a36a0cfea 100644 --- a/l10n/bundle.l10n.zh-CN.json +++ b/l10n/bundle.l10n.zh-CN.json @@ -188,5 +188,13 @@ "Target {0} Set Successfully.": "目标 {0} 设置成功", "Unknown error occurred while setting IDF target.": "设置 IDF 目标时发生未知错误", "compile_commands.json is missing. This may cause errors with code analysis extensions.": "缺少 compile_commands.json 文件。这可能会导致代码分析扩展出错。", - "Generate compile_commands.json": "生成 compile_commands.json" + "Generate compile_commands.json": "生成 compile_commands.json", + "Cannot flash via JTAG method: {0}": "无法通过JTAG方法刷写:{0}", + "{0}\n\nThe JTAG configuration may depend on hardware strapping. Please consult the ESP32 technical documentation for your specific model to ensure proper JTAG configuration before proceeding.": "{0}\n\nJTAG配置可能取决于硬件配置。请在继续之前查阅您特定型号的ESP32技术文档,以确保正确的JTAG配置。", + "JTAG is permanently disabled in hardware ({0} is set).": "JTAG在硬件上被永久禁用(已设置{0})。", + "USB-to-JTAG functionality is disabled ({0} is set).": "USB-to-JTAG功能已禁用(已设置{0})。", + "JTAG is soft-disabled ({0} is set to an odd value: {1}).": "JTAG已软禁用({0}设置为奇数值:{1})。", + "JTAG selection may be affected by strapping configuration ({0} is set).": "JTAG选择可能受到硬件配置的影响(已设置{0})。", + "JTAG is not disabled.": "JTAG未被禁用。", + "IDF Version >= 4.3.x required to have e-fuse view": "需要 IDF 版本 >= 4.3.x 才能查看 e-fuse" } diff --git a/media/tutorials/flash_encryption/flash-encryption.png b/media/tutorials/flash_encryption/flash-encryption.png new file mode 100644 index 000000000..9dab201b0 Binary files /dev/null and b/media/tutorials/flash_encryption/flash-encryption.png differ diff --git a/media/tutorials/flash_encryption/flash-encryption2.png b/media/tutorials/flash_encryption/flash-encryption2.png new file mode 100644 index 000000000..5733ae737 Binary files /dev/null and b/media/tutorials/flash_encryption/flash-encryption2.png differ diff --git a/src/config.ts b/src/config.ts index 591a28282..94b81083e 100644 --- a/src/config.ts +++ b/src/config.ts @@ -113,6 +113,7 @@ export namespace ESP { export const IDF_VERSIONS = "https://dl.espressif.com/dl/esp-idf/idf_versions.js"; export const README = ESP.URL.GithubRepository + "/blob/master/README.md"; + export const FLASH_ENCRYPTION = "/security/flash-encryption.html"; } } diff --git a/src/efuse/index.ts b/src/efuse/index.ts index ee826068f..f1d5af8c5 100644 --- a/src/efuse/index.ts +++ b/src/efuse/index.ts @@ -22,7 +22,7 @@ import { readParameter } from "../idfConfiguration"; import { tmpdir } from "os"; import { readJson, unlink } from "fs-extra"; import { Logger } from "../logger/logger"; -import { Uri } from "vscode"; +import { Uri, l10n } from "vscode"; import { getVirtualEnvPythonPath } from "../pythonManager"; export type ESPEFuseSummary = { @@ -55,8 +55,30 @@ export class ESPEFuseManager { } async summary(): Promise { + const eFuseFields = await this.readSummary(); + + const resp = {}; + for (const name in eFuseFields) { + const fields = eFuseFields[name]; + if (!fields.category) { + const error = new Error( + l10n.t("IDF Version >= 4.3.x required to have e-fuse view") + ); + error.name = "IDF_VERSION_MIN_REQUIREMENT_ERROR"; + throw error; + } + if (!resp[fields.category]) { + resp[fields.category] = []; + } + resp[fields.category].push(fields); + } + return resp; + } + + async readSummary() { const tempFile = join(tmpdir(), "espefusejsondump.tmp"); const pythonPath = await getVirtualEnvPythonPath(this.workspace); + await spawn( pythonPath, [ @@ -71,26 +93,23 @@ export class ESPEFuseManager { ], {} ); + const eFuseFields = await readJson(tempFile); + unlink(tempFile, (err) => { - Logger.error("Failed to delete the tmp espfuse json file", err, "ESPEFuseManager summary"); - }); - const resp = {}; - for (const name in eFuseFields) { - const fields = eFuseFields[name]; - if (!fields.category) { - const error = new Error( - "IDF Version >= 4.3.x required to have e-fuse view" + if (err) { + Logger.error( + "Failed to delete the tmp espefuse json file", + err, + "readSummary", + { + tag: "ESPeFuse", + } ); - error.name = "IDF_VERSION_MIN_REQUIREMENT_ERROR"; - throw error; } - if (!resp[fields.category]) { - resp[fields.category] = []; - } - resp[fields.category].push(fields); - } - return resp; + }); + + return eFuseFields; } private get toolPath(): string { diff --git a/src/efuse/view/item_generator.ts b/src/efuse/view/item_generator.ts index 5ed6a6c3c..cab546f80 100644 --- a/src/efuse/view/item_generator.ts +++ b/src/efuse/view/item_generator.ts @@ -51,7 +51,10 @@ export function FieldsForCategory( return fields.map((v) => { const item = new ESPEFuseTreeDataItem(v.name); item.tooltip = v.writeable ? "writable" : "read only"; - item.description = v.value; + item.description = + typeof v.value === "boolean" || typeof v.value === "number" + ? JSON.stringify(v.value) + : v.value; item.iconPath = v.writeable ? ThemeIconFor("edit", "merge.currentHeaderBackground") : ThemeIconFor("book", "button.background"); diff --git a/src/espIdf/documentation/getDocsVersion.ts b/src/espIdf/documentation/getDocsVersion.ts index 89a21754d..9ef8f503b 100644 --- a/src/espIdf/documentation/getDocsVersion.ts +++ b/src/espIdf/documentation/getDocsVersion.ts @@ -19,7 +19,10 @@ import { basename, join } from "path"; import { DownloadManager } from "../../downloadManager"; import jsonic from "jsonic"; import { Logger } from "../../logger/logger"; -import { extensionContext } from "../../utils"; +import { extensionContext, getEspIdfFromCMake } from "../../utils"; +import * as vscode from "vscode"; +import * as idfConf from "../../idfConfiguration"; +import { getIdfTargetFromSdkconfig } from "../../workspaceConfig"; export interface IEspIdfDocVersion { name: string; @@ -66,7 +69,11 @@ export function getDocsLocaleLang() { const localeConf = JSON.parse(process.env.VSCODE_NLS_CONFIG); localeLang = localeConf.locale === "zh-CN" ? "zh_CN" : "en"; } catch (error) { - Logger.error("Error getting current vscode language", error, "getDocsVersion getDocsLocaleLang"); + Logger.error( + "Error getting current vscode language", + error, + "getDocsVersion getDocsLocaleLang" + ); } return localeLang; } @@ -112,3 +119,34 @@ export async function readObjectFromUrlFile(objectUrl: string) { return jsonic(objectMatches[0]); } } + +/** + * Retrieves the URL for the specified documentation part based on the ESP-IDF version and workspace. + * @param documentationPart - The documentation part to retrieve the URL for. + * @param workspace - The workspace URI. + * @returns The URL for the ESP-IDF specified documentation part. + */ +export async function getDocsUrl( + documentationPart: string, + workspace: vscode.Uri +) { + const espIdfPath = idfConf.readParameter( + "idf.espIdfPath", + workspace + ) as string; + + const adapterTargetName = await getIdfTargetFromSdkconfig(workspace); + const idfVersion = await getEspIdfFromCMake(espIdfPath); + const docVersions = await getDocsVersion(); + let docVersion = docVersions.find((docVer) => docVer.name === idfVersion); + if (!docVersion) { + docVersion = docVersions.find((docVer) => docVer.name === "latest"); + } + if (!docVersion) { + return; + } + const baseUrl = getDocsBaseUrl(docVersion.name, adapterTargetName); + const url = `${baseUrl}/${documentationPart}`; + + return url; +} diff --git a/src/extension.ts b/src/extension.ts index 79ec305a3..88365ea4b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -103,6 +103,12 @@ import { getNewProjectArgs } from "./newProject/newProjectInit"; import { NewProjectPanel } from "./newProject/newProjectPanel"; import { buildCommand } from "./build/buildCmd"; import { verifyCanFlash } from "./flash/flashCmd"; +import { + isFlashEncryptionEnabled, + FlashCheckResultType, + checkFlashEncryption, + isJtagDisabled, +} from "./flash/verifyFlashEncryption"; import { flashCommand } from "./flash/uartFlash"; import { jtagFlashCommand } from "./flash/jtagCmd"; import { createNewIdfMonitor } from "./espIdf/monitor/command"; @@ -136,6 +142,7 @@ import { getPreviousIdfSetups, } from "./setup/existingIdfSetups"; import { getEspRainmaker } from "./rainmaker/download/espRainmakerDownload"; +import { getDocsUrl } from "./espIdf/documentation/getDocsVersion"; import { UnitTest } from "./espIdf/unitTest/adapter"; import { buildFlashTestApp, @@ -1787,11 +1794,15 @@ export async function activate(context: vscode.ExtensionContext) { flash(false, ESP.FlashType.JTAG) ); registerIDFCommand("espIdf.flashDFU", () => flash(false, ESP.FlashType.DFU)); - registerIDFCommand("espIdf.flashUart", () => - flash(false, ESP.FlashType.UART) - ); + registerIDFCommand("espIdf.flashUart", async () => { + const isEncrypted = await isFlashEncryptionEnabled(workspaceRoot); + return flash(isEncrypted, ESP.FlashType.UART); + }); registerIDFCommand("espIdf.buildDFU", () => build(ESP.FlashType.DFU)); - registerIDFCommand("espIdf.flashDevice", flash); + registerIDFCommand("espIdf.flashDevice", async () => { + const isEncrypted = await isFlashEncryptionEnabled(workspaceRoot); + return flash(isEncrypted); + }); registerIDFCommand("espIdf.flashAndEncryptDevice", () => flash(true)); registerIDFCommand("espIdf.buildDevice", build); registerIDFCommand("espIdf.monitorDevice", createMonitor); @@ -3936,7 +3947,7 @@ const build = (flashType?: ESP.FlashType) => { }); }; const flash = ( - encryptPartition: boolean = false, + encryptPartitions: boolean = false, flashType?: ESP.FlashType ) => { PreCheck.perform([openFolderCheck], async () => { @@ -3970,7 +3981,7 @@ const flash = ( workspaceRoot ) as ESP.FlashType; } - if (await startFlashing(cancelToken, flashType, encryptPartition)) { + if (await startFlashing(cancelToken, flashType, encryptPartitions)) { OutputChannel.appendLine( "Flash has finished. You can monitor your device with 'ESP-IDF: Monitor command'" ); @@ -4052,6 +4063,7 @@ const buildFlashAndMonitor = async (runMonitor: boolean = true) => { notificationMode === idfConf.NotificationMode.Notifications ? vscode.ProgressLocation.Notification : vscode.ProgressLocation.Window; + await vscode.window.withProgress( { cancellable: true, @@ -4076,7 +4088,13 @@ const buildFlashAndMonitor = async (runMonitor: boolean = true) => { message: "Flashing project into device...", increment: 60, }); - canContinue = await startFlashing(cancelToken, flashType, false); + + let encryptPartitions = await isFlashEncryptionEnabled(workspaceRoot); + canContinue = await startFlashing( + cancelToken, + flashType, + encryptPartitions + ); if (!canContinue) { return; } @@ -4088,7 +4106,7 @@ const buildFlashAndMonitor = async (runMonitor: boolean = true) => { if (IDFMonitor.terminal) { IDFMonitor.terminal.sendText(ESP.CTRL_RBRACKET); } - createMonitor(); + await createMonitor(); } } ); @@ -4118,6 +4136,9 @@ async function selectFlashMethod() { vscode.ConfigurationTarget.WorkspaceFolder, workspaceRoot ); + vscode.window.showInformationMessage( + `Flash method changed to ${newFlashType}.` + ); return newFlashType; } @@ -4130,6 +4151,23 @@ async function startFlashing( flashType = await selectFlashMethod(); } + if (encryptPartitions) { + const encryptionValidationResult = await checkFlashEncryption( + flashType, + workspaceRoot + ); + if (!encryptionValidationResult.success) { + if ( + encryptionValidationResult.resultType === + FlashCheckResultType.ErrorEfuseNotSet + ) { + encryptPartitions = false; + } else { + return; + } + } + } + const port = idfConf.readParameter("idf.port", workspaceRoot); const flashBaudRate = idfConf.readParameter( "idf.flashBaudRate", @@ -4144,6 +4182,26 @@ async function startFlashing( } if (flashType === ESP.FlashType.JTAG) { + // Check if JTAG is disabled on the hardware + const eFuse = new ESPEFuseManager(workspaceRoot); + const eFuseSummary = await eFuse.readSummary(); + const jtagStatus = isJtagDisabled(eFuseSummary); + if (jtagStatus.disabled) { + Logger.errorNotify( + vscode.l10n.t("Cannot flash via JTAG method: {0}", jtagStatus.message), + new Error("JTAG Disabled"), + "extension startFlashing" + ); + return; + } else if (jtagStatus.requiresVerification) { + const message = vscode.l10n.t( + "{0}\n\nThe JTAG configuration may depend on hardware strapping. Please consult the ESP32 technical documentation for your specific model to ensure proper JTAG configuration before proceeding.", + jtagStatus.message + ); + Logger.warnNotify(message); + return; + } + const currOpenOcdVersion = await openOCDManager.version(); const openOCDVersionIsValid = PreCheck.openOCDVersionValidator( "v0.10.0-esp32-20201125", @@ -4188,21 +4246,47 @@ function createIdfTerminal() { }); } -function createMonitor() { +async function createMonitor() { PreCheck.perform([openFolderCheck], async () => { // Re route to ESP-IDF Web extension if using Codespaces or Browser if (vscode.env.uiKind === vscode.UIKind.Web) { vscode.commands.executeCommand(IDFWebCommandKeys.Monitor); return; } - const noReset = idfConf.readParameter( - "idf.monitorNoReset", - workspaceRoot - ) as boolean; + const noReset = await shouldDisableMonitorReset(); await createNewIdfMonitor(workspaceRoot, noReset); }); } +/** + * Determines if the monitor reset should be disabled. + * If flash encryption is enabled for release mode, we add --no-reset flag for monitoring + * because by default monitoring command resets the device which is not recommended. + * Reset should happen by Bootloader itself once it completes encrypting all artifacts. + * + * @returns {Promise} True if monitor reset should be disabled, false otherwise. + */ +const shouldDisableMonitorReset = async (): Promise => { + const configNoReset = idfConf.readParameter( + "idf.monitorNoReset", + workspaceRoot + ); + + if (configNoReset === true) { + return true; + } + + if (isFlashEncryptionEnabled(workspaceRoot)) { + const valueReleaseModeEnabled = await utils.getConfigValueFromSDKConfig( + "CONFIG_SECURE_FLASH_ENCRYPTION_MODE_RELEASE", + workspaceRoot + ); + return valueReleaseModeEnabled === "y"; + } + + return false; +}; + export function deactivate() { Telemetry.dispose(); if (IDFMonitor.terminal) { diff --git a/src/flash/verifyFlashEncryption.ts b/src/flash/verifyFlashEncryption.ts new file mode 100644 index 000000000..4fbc53a80 --- /dev/null +++ b/src/flash/verifyFlashEncryption.ts @@ -0,0 +1,310 @@ +import * as idfConf from "../idfConfiguration"; +import { Logger } from "../logger/logger"; +import { OutputChannel } from "../logger/outputChannel"; +import { ESP } from "../config"; +import { + showInfoNotificationWithLink, + showQuickPickWithCustomActions, +} from "../logger/utils"; +import { ConfserverProcess } from "../espIdf/menuconfig/confServerProcess"; +import { ESPEFuseManager } from "../efuse"; +import { getDocsUrl } from "../espIdf/documentation/getDocsVersion"; +import * as utils from "../utils"; +import * as vscode from "vscode"; +import { getIdfTargetFromSdkconfig } from "../workspaceConfig"; + +export enum FlashCheckResultType { + Success, + ErrorInvalidFlashType, + ErrorEfuseNotSet, + ErrorEncryptionArgsRequired, + GenericError, +} + +export interface FlashCheckResult { + success: boolean; + resultType?: FlashCheckResultType; +} + +export async function isFlashEncryptionEnabled(workspaceRoot: vscode.Uri) { + const flashEncryption = await utils.getConfigValueFromSDKConfig( + "CONFIG_FLASH_ENCRYPTION_ENABLED", + workspaceRoot + ); + return flashEncryption === "y"; +} + +export async function checkFlashEncryption( + flashType: ESP.FlashType, + workspaceRoot: vscode.Uri +): Promise { + Logger.info(`Using flash type: ${flashType}`, { tag: "Flash" }); + + try { + if (flashType !== ESP.FlashType.UART) { + const errorMessage = `Invalid flash type for partition encryption. Required: UART, Found: ${flashType}. \n Choose one of the actions presented in the top center quick pick menu and re-flash.`; + const error = new Error(errorMessage); + const customButtons = [ + { + label: "Change flash type to UART", + action: () => { + idfConf.writeParameter( + "idf.flashType", + "UART", + vscode.ConfigurationTarget.WorkspaceFolder, + workspaceRoot + ); + const saveMessage = vscode.l10n.t( + "Flashing method successfully changed to UART" + ); + Logger.infoNotify(saveMessage); + OutputChannel.appendLineAndShow(saveMessage, "Flash Encryption"); + }, + }, + { + label: "Disable Flash Encryption", + action: () => { + disableFlashEncryption(); + const saveMessage = vscode.l10n.t( + "Flash encryption has been disabled in the SDK configuration" + ); + Logger.infoNotify(saveMessage); + OutputChannel.appendLineAndShow(saveMessage, "Flash Encryption"); + }, + }, + ]; + + OutputChannel.appendLineAndShow(errorMessage, "Flash Encryption"); + Logger.errorNotify( + errorMessage, + error, + "verifyFlashEncryption !ESP.FlashType.UART", + { tag: "Flash Encryption" } + ); + await showQuickPickWithCustomActions( + "Pick one of the following actions to continue", + customButtons + ); + return { + success: false, + resultType: FlashCheckResultType.ErrorInvalidFlashType, + }; + } + + const valueEncryptionEnabled = await utils.getConfigValueFromBuild( + "SECURE_FLASH_ENC_ENABLED", + workspaceRoot + ); + if (!valueEncryptionEnabled) { + const errorMessage = + "Flash encryption is enabled in the SDK configuration, but the project has not been rebuilt with these settings. Please rebuild the project to apply the encryption settings before attempting to flash the device."; + const error = new Error(errorMessage); + OutputChannel.appendLineAndShow(errorMessage, "Flash Encryption"); + Logger.errorNotify( + errorMessage, + error, + "verifyFlashEncryption !valueEncryptionEnabled", + { tag: "Flash Encryption" } + ); + + return { + success: false, + resultType: FlashCheckResultType.ErrorEncryptionArgsRequired, + }; + } + + const idfTarget = await getIdfTargetFromSdkconfig(workspaceRoot); + const eFuse = new ESPEFuseManager(workspaceRoot); + + const notificationMode = idfConf.readParameter( + "idf.notificationMode", + workspaceRoot + ) as string; + const ProgressLocation = + notificationMode === idfConf.NotificationMode.All || + notificationMode === idfConf.NotificationMode.Notifications + ? vscode.ProgressLocation.Notification + : vscode.ProgressLocation.Window; + const data = await vscode.window.withProgress( + { + cancellable: true, + location: ProgressLocation, + title: "ESP-IDF: Checking encryption eFuse...", + }, + async ( + progress: vscode.Progress<{ + message?: string; + increment?: number; + }>, + cancelToken: vscode.CancellationToken + ) => { + return new Promise(async (resolve, reject) => { + // Register cancellation handler + cancelToken.onCancellationRequested(() => { + Logger.info("eFuse check cancelled by user", { + tag: "Flash Encryption", + }); + reject(new Error("Operation cancelled by user")); + }); + + try { + // Start the eFuse reading operation + progress.report({ message: "Reading eFuse data..." }); + const summary = await eFuse.readSummary(); + + if (cancelToken.isCancellationRequested) { + reject(new Error("Operation cancelled by user")); + return; + } + + resolve(summary); + } catch (error) { + Logger.errorNotify( + "Failed to read eFuse summary", + error, + "verifyFlashEncryption readSummary", + { tag: "Flash Encryption" } + ); + reject(error); + } + }); + } + ); + + // ESP32 boards have property FLASH_CRYPT_CNT + // All other boards have property SPI_BOOT_CRYPT_CNT + // The values of these properties can be: 0 or 1 for ESP32 + // Or "Disable", "Enable" for the rest of the boards + const fieldEncription = + idfTarget === "esp32" ? "FLASH_CRYPT_CNT" : "SPI_BOOT_CRYPT_CNT"; + + if (data && data[fieldEncription]) { + if ( + // eFuse is not set + data[fieldEncription] && + (data[fieldEncription].value === 0 || + data[fieldEncription].value == "Disable") + ) { + const documentationUrl = await getDocsUrl( + ESP.URL.Docs.FLASH_ENCRYPTION, + workspaceRoot + ); + const warnMessage = + "Encryption setup requires a two-step flashing process due to uninitialized eFuse BLOCK_KEY0. Please complete the current flash, reset your device, and then flash again. See documentation for details."; + showInfoNotificationWithLink(warnMessage, documentationUrl); + OutputChannel.appendLineAndShow(warnMessage, "Flash Encryption"); + Logger.info(warnMessage, { tag: "Flash Encryption" }); + return { + success: false, + resultType: FlashCheckResultType.ErrorEfuseNotSet, + }; + } + // eFuse is set + return { success: true }; + } else { + const errorMessage = `Could not find Encryption Key for ${idfTarget}.`; + OutputChannel.appendLineAndShow(errorMessage, "Flash Encryption"); + Logger.info(errorMessage, { tag: "Flash Encryption" }); + return { + success: false, + resultType: FlashCheckResultType.ErrorEfuseNotSet, + }; + } + } catch (error) { + if (error.message === "Operation cancelled by user") { + OutputChannel.appendLineAndShow( + "eFuse check cancelled by user", + "Flash Encryption" + ); + return { + success: false, + resultType: FlashCheckResultType.GenericError, + }; + } + + OutputChannel.appendLineAndShow(error.message); + Logger.errorNotify( + error.message, + error, + "verifyFlashEncryption checkFlashEncryption", + { tag: "Flash Encryption" } + ); + return { success: false, resultType: FlashCheckResultType.GenericError }; + } +} + +/** + * Disables flash encryption in SDK Configuration. + */ +export function disableFlashEncryption() { + const newValueRequest = `{"version": 2, "set": { "SECURE_FLASH_ENC_ENABLED": false }}\n`; + OutputChannel.appendLine(newValueRequest, "SDK Configuration Editor"); + ConfserverProcess.sendUpdatedValue(newValueRequest); + ConfserverProcess.saveGuiConfigValues(); +} + +/** + * Determines the status of JTAG based on eFuse summary data and provides a detailed result. + * @param {Object} eFuseSummary - The eFuse summary object containing various configuration fields, including JTAG-related ones. + * @returns {Object} An object with the following properties: + * - {boolean} disabled: Indicates whether JTAG is permanently or temporarily disabled. + * - {string} message: A descriptive message explaining the JTAG status or the conditions that may affect it. + * - {boolean} requiresVerification: Indicates if additional verification is recommended (e.g., due to configurable strapping options). + */ +export function isJtagDisabled( + eFuseSummary: any +): { disabled: boolean; message: string; requiresVerification: boolean } { + if (eFuseSummary.DIS_PAD_JTAG && eFuseSummary.DIS_PAD_JTAG.value === true) { + return { + disabled: true, + message: vscode.l10n.t( + "JTAG is permanently disabled in hardware (DIS_PAD_JTAG is set)." + ), + requiresVerification: false, + }; + } + + if (eFuseSummary.DIS_USB_JTAG && eFuseSummary.DIS_USB_JTAG.value === true) { + return { + disabled: true, + message: vscode.l10n.t( + "USB-to-JTAG functionality is disabled (DIS_USB_JTAG is set)." + ), + requiresVerification: false, + }; + } + + if ( + eFuseSummary.SOFT_DIS_JTAG && + typeof eFuseSummary.SOFT_DIS_JTAG.value === "number" && + eFuseSummary.SOFT_DIS_JTAG.value % 2 === 1 + ) { + return { + disabled: true, + message: vscode.l10n.t( + "JTAG is soft-disabled (SOFT_DIS_JTAG is set to an odd value: {0}).", + eFuseSummary.SOFT_DIS_JTAG.value.toString() + ), + requiresVerification: false, + }; + } + + if ( + eFuseSummary.STRAP_JTAG_SEL && + eFuseSummary.STRAP_JTAG_SEL.value === true + ) { + return { + disabled: false, + message: vscode.l10n.t( + "JTAG selection may be affected by strapping configuration (STRAP_JTAG_SEL is set)." + ), + requiresVerification: true, + }; + } + + return { + disabled: false, + message: vscode.l10n.t("JTAG is not disabled."), + requiresVerification: false, + }; +} diff --git a/src/logger/utils.ts b/src/logger/utils.ts index 972c0933a..2d4576381 100644 --- a/src/logger/utils.ts +++ b/src/logger/utils.ts @@ -18,8 +18,55 @@ export async function showInfoNotificationWithAction( infoMessage, buttonLabel ); - if (selectedOption === buttonLabel) { await Promise.resolve(action()); } } + +/** + * Shows an error notification with a button that opens a link when clicked. + * @param {string} infoMessage - The waning message to display. + * @param {string} [buttonLabel="Read Documentation"] - The label for the button (default: "Read Documentation") + * @param {string} linkUrl - The URL to open when the button is clicked. + * @returns {Promise} - A promise that resolves when the notification is shown. + */ +export async function showInfoNotificationWithLink( + infoMessage, + linkUrl, + buttonLabel = "Read documentation" +) { + const selectedOption = await vscode.window.showInformationMessage( + infoMessage, + buttonLabel + ); + + if (selectedOption === buttonLabel) { + vscode.env.openExternal(vscode.Uri.parse(linkUrl)); + } +} + +/** + * Shows a notification with one or two buttons that perform custom actions when clicked. + * @param {string} message - The message to display. + * @param {Array<{ label: string, action: () => void }>} buttons - An array of objects containing the label and action for each button. + * @returns {Promise} - A promise that resolves when the notification is shown. + */ +export async function showQuickPickWithCustomActions( + message: string, + buttons: { label: string; action: () => void }[] +): Promise { + const selectedOption = await vscode.window.showQuickPick( + buttons.map((button) => button.label), + { + placeHolder: message, + canPickMany: false, + ignoreFocusOut: true, + } + ); + const selectedButton = buttons.find( + (button) => button.label === selectedOption + ); + if (selectedButton) { + selectedButton.action(); + } +} diff --git a/src/utils.ts b/src/utils.ts index 4c86fed5b..129a66f64 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1390,3 +1390,27 @@ export function getUserShell() { // if no match, return null return null; } + +export async function getConfigValueFromBuild( + configKey: string, + workspacePath: vscode.Uri +): Promise { + const buildPath = idfConf.readParameter( + "idf.buildPath", + workspacePath + ) as string; + const jsonFilePath = path.join(buildPath, "config", "sdkconfig.json"); + try { + const data = await readFile(jsonFilePath, "utf-8"); + const config = JSON.parse(data); + if (config[configKey] !== undefined) { + // Key found, return the value assigned to it + return config[configKey]; + } else { + // Key not found, throw an error + throw new Error(`The key ${configKey} was not found in ${jsonFilePath}.`); + } + } catch (error) { + throw new Error(`Failed to read or parse the JSON file: ${error.message}`); + } +}