diff --git a/package.json b/package.json index 4dbc776..66b0a7a 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "serve": "serve --cors -p 9000" }, "dependencies": { + "jszip": "^3.10.1", "node-fetch": "^2.6.7", "xml2js": "^0.4.23" }, @@ -230,8 +231,13 @@ }, "svd-viewer.deviceConfig": { "type": "string", - "default": "device", - "description": "Debug configuration key to use to get the device" + "default": "deviceName", + "description": "Debug configuration key to use to get the device name" + }, + "svd-viewer.processorConfig": { + "type": "string", + "default": "processorName", + "description": "Debug configuration key to use to get the procerssor name" }, "svd-viewer.svdAddrGapThreshold": { "type": "number", @@ -240,6 +246,11 @@ "minimum": -1, "maximum": 32, "description": "If the gap between registers is less than this threshold (multiple of 8), combine into a single read from device. -1 means never combine registers and is very slow" + }, + "svd-viewer.packAssetUrl": { + "type": "string", + "default": "https://pack-asset-service.keil.arm.com", + "description": "Base URL for CMSIS pack assets" } } } diff --git a/src/browser/extension.ts b/src/browser/extension.ts index df00867..48cf516 100644 --- a/src/browser/extension.ts +++ b/src/browser/extension.ts @@ -1,13 +1,19 @@ +/** + * Copyright (C) 2023 Arm Limited + */ + import * as vscode from 'vscode'; import { PeripheralTreeProvider } from '../views/peripheral'; import { Commands } from '../commands'; import { DebugTracker } from '../debug-tracker'; import { SvdRegistry } from '../svd-registry'; +import { SvdResolver } from '../svd-resolver'; export const activate = async (context: vscode.ExtensionContext): Promise => { - const registry = new SvdRegistry(); const tracker = new DebugTracker(); - const peripheralTree = new PeripheralTreeProvider(tracker, registry); + const registry = new SvdRegistry(); + const resolver = new SvdResolver(registry); + const peripheralTree = new PeripheralTreeProvider(tracker, resolver); const commands = new Commands(peripheralTree); await tracker.activate(context); diff --git a/src/cmsis-pack/pack-utils.ts b/src/cmsis-pack/pack-utils.ts new file mode 100644 index 0000000..01d86b8 --- /dev/null +++ b/src/cmsis-pack/pack-utils.ts @@ -0,0 +1,53 @@ +/** + * Copyright (C) 2023 Arm Limited + */ + +import * as vscode from 'vscode'; + +const MAIN_DELIMITER = '::'; +const VERSION_DELIMITER = '@'; + +export interface Pack { + vendor: string; + pack: string; + version?: string; +} + +export const parsePackString = (packString: string): Pack | undefined => { + let parts = packString.split(MAIN_DELIMITER); + + if (parts.length < 2) { + return undefined; + } + + const vendor = parts[0]; + let pack = parts[1]; + + parts = pack.split(VERSION_DELIMITER); + let version: string | undefined; + + if (parts.length > 1) { + pack = parts[0]; + version = parts[1]; + } + + return { + vendor, + pack, + version + }; +}; + +export const pdscFromPack = (basePath: string, pack: Pack): vscode.Uri => { + const pdscFile = `${pack.vendor}.${pack.pack}.pdsc`; + return fileFromPack(basePath, pack, pdscFile); +}; + +export const fileFromPack = (basePath: string, pack: Pack, file: string): vscode.Uri => { + if (!pack.version) { + throw new Error('CMSIS pack version is required'); + } + + const baseUri = vscode.Uri.parse(basePath); + return vscode.Uri.joinPath(baseUri, pack.vendor, pack.pack, pack.version, file); +}; diff --git a/src/cmsis-pack/pdsc.ts b/src/cmsis-pack/pdsc.ts new file mode 100644 index 0000000..85b4e74 --- /dev/null +++ b/src/cmsis-pack/pdsc.ts @@ -0,0 +1,185 @@ +/** + * Copyright (C) 2023 Arm Limited + */ + +/** + * Interface describing raw PDSC data returned from xml2js + */ +export interface PDSC { + package: { + devices?: Array<{ + family: DeviceFamily[]; + }>; + }; +} + +export interface DeviceProperties { + processor?: Array<{ + $: { + Pname: string; + Punits: string; + Dclock: string; + DcoreVersion: string; + }; + }>; + debug?: Array<{ + $?: { + __dp?: string; + __ap?: string; + __apid?: string; + address?: string; + svd?: string; + Pname?: string; + Punit?: string; + defaultResetSequence?: string; + }; + }>; +} + +export interface DeviceVariant extends DeviceProperties { + $: { + Dvariant: string; + Dname: string; + }; +} + +export interface Device extends DeviceProperties { + $: { + Dname: string; + }; + variant?: DeviceVariant[]; +} + +export interface DeviceSubFamily extends DeviceProperties { + $: { + DsubFamily: string; + }; + device?: Device[]; +} + +export interface DeviceFamily extends DeviceProperties { + $: { + Dfamily: string; + Dvendor: string; + }; + subFamily?: DeviceSubFamily[]; + device?: Device[]; +} + +// Return whether an item is an object +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const isObject = (item: any): boolean => item && typeof item === 'object' && !Array.isArray(item); + +// Merge two objects recursively, with source overwriting target when there's a conflict +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const deepMerge = (target: { [key: string]: any }, source: T): T => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const output = Object.assign({} as any, target); + + Object.keys(source).forEach(key => { + if (isObject(source[key]) && key in target) { + output[key] = deepMerge(target[key], source[key]); + } else { + Object.assign(output, { [key]: source[key] }); + } + }); + + return output; +}; + +// Recurse DeviceFamily and DeviceSubFamily to find Devices and DeviceVariants, merging them as we go +export const getDevices = (pack: PDSC): Array => { + const result: Device[] = []; + + const addDevice = (device: Device, parent: DeviceProperties = {}) => { + const entry = deepMerge(parent, device); + result.push(entry); + }; + + const walkDevices = (devices?: Device[], parent: DeviceProperties = {}) => { + if (devices) { + for (const device of devices) { + if (device.variant) { + // If there are variants, add them instead of the parent device + for (const variant of device.variant) { + // Merge in device + const variantParent = deepMerge(parent, device); + addDevice(variant, variantParent); + } + } else { + addDevice(device, parent); + } + } + } + }; + + // Walk the DeviceFamily array + if (pack.package.devices) { + for (const device of pack.package.devices) { + for (const family of device.family) { + walkDevices(family.device, family); + + // Walk the DeviceSubFamily array + if (family.subFamily) { + for (const sub of family.subFamily) { + const parent = deepMerge(family, sub); + walkDevices(sub.device, parent); + } + } + } + } + } + + return result; +}; + +/** + * Return list of processor names available for specified device + */ +export const getProcessors = (device: Device): string[] => { + const processors: string[] = []; + + if (device.processor) { + for (const processor of device.processor) { + if (processor.$ && processor.$.Pname) { + processors.push(processor.$.Pname); + } + } + } + + return processors; +}; + +/** + * Return svd path (or undefined) for specified device + * If processorName specified, matching svd file is returned, else the first one + */ +export const getSvdPath = (device: Device, processorName?: string): string | undefined => { + if (device.debug) { + const filtered = filterByProcessor(device.debug, processorName); + for (const debug of filtered) { + if (debug.$ && debug.$.svd) { + return debug.$.svd; + } + } + } + + return undefined; +}; + +const filterByProcessor = (theArray: T[], processorName: string | undefined): T[] => { + // If processorName not specified, return all items + if (!processorName) { + return theArray; + } + + return filter(theArray, item => item.$?.Pname === processorName) as T[]; +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const filter = (theArray: T[], predicate: (item: T) => boolean): T[] => { + const filtered = theArray.filter(item => item.$ && predicate(item)); + + // If no items match, return them all + return filtered.length > 0 ? filtered as T[] : theArray; +}; diff --git a/src/debug-tracker.ts b/src/debug-tracker.ts index 00f8b79..d451c73 100644 --- a/src/debug-tracker.ts +++ b/src/debug-tracker.ts @@ -1,19 +1,5 @@ -/* - * Copyright 2017-2019 Marcel Ball - * https://github.com/Marus/cortex-debug - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated - * documentation files (the "Software"), to deal in the Software without restriction, including without - * limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the - * Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED - * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF - * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. +/** + * Copyright (C) 2023 Arm Limited */ import * as vscode from 'vscode'; @@ -28,8 +14,8 @@ export class DebugTracker { private _onWillStopSession: vscode.EventEmitter = new vscode.EventEmitter(); public readonly onWillStopSession: vscode.Event = this._onWillStopSession.event; - private _onDidStopSession: vscode.EventEmitter = new vscode.EventEmitter(); - public readonly onDidStopSession: vscode.Event = this._onDidStopSession.event; + private _onDidStopDebug: vscode.EventEmitter = new vscode.EventEmitter(); + public readonly onDidStopDebug: vscode.Event = this._onDidStopDebug.event; public async activate(context: vscode.ExtensionContext): Promise { const createDebugAdapterTracker = (session: vscode.DebugSession): vscode.DebugAdapterTracker => { @@ -38,7 +24,7 @@ export class DebugTracker { onWillStopSession: () => this._onWillStopSession.fire(session), onDidSendMessage: message => { if (message.type === 'event' && message.event === 'stopped') { - this._onDidStopSession.fire(session); + this._onDidStopDebug.fire(session); } } }; diff --git a/src/desktop/extension.ts b/src/desktop/extension.ts index df00867..48cf516 100644 --- a/src/desktop/extension.ts +++ b/src/desktop/extension.ts @@ -1,13 +1,19 @@ +/** + * Copyright (C) 2023 Arm Limited + */ + import * as vscode from 'vscode'; import { PeripheralTreeProvider } from '../views/peripheral'; import { Commands } from '../commands'; import { DebugTracker } from '../debug-tracker'; import { SvdRegistry } from '../svd-registry'; +import { SvdResolver } from '../svd-resolver'; export const activate = async (context: vscode.ExtensionContext): Promise => { - const registry = new SvdRegistry(); const tracker = new DebugTracker(); - const peripheralTree = new PeripheralTreeProvider(tracker, registry); + const registry = new SvdRegistry(); + const resolver = new SvdResolver(registry); + const peripheralTree = new PeripheralTreeProvider(tracker, resolver); const commands = new Commands(peripheralTree); await tracker.activate(context); diff --git a/src/manifest.ts b/src/manifest.ts index 3b8aa90..c724934 100644 --- a/src/manifest.ts +++ b/src/manifest.ts @@ -1,25 +1,15 @@ -/* - * Copyright 2017-2019 Marcel Ball - * https://github.com/Marus/cortex-debug - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated - * documentation files (the "Software"), to deal in the Software without restriction, including without - * limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the - * Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED - * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF - * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. +/** + * Copyright (C) 2023 Arm Limited */ export const PACKAGE_NAME = 'svd-viewer'; export const CONFIG_SVD_PATH = 'svdPathConfig'; export const DEFAULT_SVD_PATH = 'svdPath'; export const CONFIG_DEVICE = 'deviceConfig'; -export const DEFAULT_DEVICE = 'device'; +export const DEFAULT_DEVICE = 'deviceName'; +export const CONFIG_PROCESSOR = 'processorConfig'; +export const DEFAULT_PROCESSOR = 'processorName'; export const CONFIG_ADDRGAP = 'svdAddrGapThreshold'; export const DEFAULT_ADDRGAP = 16; +export const CONFIG_ASSET_PATH = 'packAssetUrl'; +export const DEFAULT_ASSET_PATH = 'https://pack-asset-service.keil.arm.com'; diff --git a/src/svd-parser.ts b/src/svd-parser.ts index e81342a..b6b6802 100644 --- a/src/svd-parser.ts +++ b/src/svd-parser.ts @@ -48,11 +48,11 @@ const accessTypeFromString = (type: string): AccessType => { } }; -interface Peripheral { +export interface Peripheral { name: string[]; } -interface Device { +export interface Device { resetValue: string[]; size: string[]; access: string[]; @@ -61,7 +61,7 @@ interface Device { }[]; } -interface SvdData { +export interface SvdData { device: Device; } diff --git a/src/svd-resolver.ts b/src/svd-resolver.ts new file mode 100644 index 0000000..b833806 --- /dev/null +++ b/src/svd-resolver.ts @@ -0,0 +1,141 @@ +/** + * Copyright (C) 2023 Arm Limited + */ + +import * as vscode from 'vscode'; +import * as manifest from './manifest'; +import { isAbsolute, join, normalize } from 'path'; +import { parseStringPromise } from 'xml2js'; +import { SvdRegistry } from './svd-registry'; +import { parsePackString, pdscFromPack, fileFromPack, Pack } from './cmsis-pack/pack-utils'; +import { PDSC, Device, DeviceVariant, getDevices, getSvdPath, getProcessors } from './cmsis-pack/pdsc'; +import { readFromUrl } from './utils'; +import { getSelection } from './vscode-utils'; + +export class SvdResolver { + public constructor(protected registry: SvdRegistry) { + } + + public async resolve(session: vscode.DebugSession, wsFolderPath?: vscode.Uri): Promise { + const svdConfig = vscode.workspace.getConfiguration(manifest.PACKAGE_NAME).get(manifest.CONFIG_SVD_PATH) || manifest.DEFAULT_SVD_PATH; + let svdPath = session.configuration[svdConfig]; + + const deviceConfig = vscode.workspace.getConfiguration(manifest.PACKAGE_NAME).get(manifest.CONFIG_DEVICE) || manifest.DEFAULT_DEVICE; + const deviceName = session.configuration[deviceConfig]; + + const processorConfig = vscode.workspace.getConfiguration(manifest.PACKAGE_NAME).get(manifest.CONFIG_PROCESSOR) || manifest.DEFAULT_PROCESSOR; + const processorName = session.configuration[processorConfig]; + + if (!svdPath && !deviceName) { + return undefined; + } + + try { + if (svdPath) { + const pack = parsePackString(svdPath); + + if (pack) { + svdPath = await this.loadFromPack(pack, deviceName, processorName); + } else if (vscode.env.uiKind === vscode.UIKind.Desktop && !svdPath.startsWith('http')) { + // On desktop, ensure full path + if (!isAbsolute(svdPath) && wsFolderPath) { + svdPath = normalize(join(wsFolderPath.fsPath, svdPath)); + } + } + } else if (deviceName) { + svdPath = this.registry.getSVDFile(deviceName); + if (!svdPath) { + svdPath = await this.registry.getSVDFileFromCortexDebug(deviceName); + } + } + } catch(e) { + // eslint-disable-next-line no-console + console.warn(e); + } + + return svdPath; + } + + protected async loadFromPack(pack: Pack, deviceName: string | undefined, processorName: string | undefined): Promise { + const getDeviceName = (device: Device) => (device as DeviceVariant).$.Dvariant || device.$.Dname; + + const assetBase = vscode.workspace.getConfiguration(manifest.PACKAGE_NAME).get(manifest.CONFIG_ASSET_PATH) || manifest.DEFAULT_ASSET_PATH; + const pdscPath = pdscFromPack(assetBase, pack); + const pdscBuffer = await readFromUrl(pdscPath.toString()); + + if (!pdscBuffer) { + throw new Error(`No data loaded from ${pdscPath.toString()}`); + } + + const decoder = new TextDecoder(); + const pdscString = decoder.decode(pdscBuffer); + + const pdsc = await parseStringPromise(pdscString, { + explicitCharkey: true + }) as PDSC; + + // Load devices from pack + const devices = getDevices(pdsc); + const deviceMap = new Map(); + for (const device of devices) { + deviceMap.set(getDeviceName(device), device); + } + + // Select device + let packDevice: Device | undefined; + + if (deviceName && deviceMap.has(deviceName)) { + packDevice = deviceMap.get(deviceName); + } else if (!deviceName && devices.length == 1) { + packDevice = devices[0]; + } else { + // Ask user which device to use + const items = [...deviceMap.keys()].map(label => ({ label })); + const selected = await getSelection('Select a device', items, deviceName); + if (!selected) { + return; + } + + if (!deviceMap.has(selected)) { + throw new Error(`Device not found: ${selected}`); + } + + packDevice = deviceMap.get(selected); + } + + if (!packDevice) { + return; + } + + // Load processors for device + const processors = getProcessors(packDevice); + + // Select processor + if (processorName && processors.includes(processorName)) { + // Keep existing processor name + } else if (!processorName && processors.length == 1) { + processorName = processors[0]; + } else { + // Ask user which processor to use + const items = processors.map(label => ({ label })); + const selected = await getSelection('Select a processor', items, processorName); + if (!selected) { + return; + } + + if (!processors.includes(selected)) { + throw new Error(`Processor not found: ${selected}`); + } + + processorName = selected; + } + + const svdFile = getSvdPath(packDevice, processorName); + if (!svdFile) { + throw new Error(`Unable to load device ${getDeviceName(packDevice)}`); + } + + const svdUri = fileFromPack(assetBase, pack, svdFile); + return svdUri.toString(); + } +} diff --git a/src/utils.ts b/src/utils.ts index 45e734b..fc4106e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -118,3 +118,16 @@ export function parseDimIndex(spec: string, count: number): string[] { return []; } + +export const readFromUrl = async (url: string): Promise => { + // Download using fetch + const response = await fetch(url); + if (!response.ok) { + const body = await response.text(); + const msg = `Request to ${url} failed. Status="${response.status}". Body="${body}".`; + throw new Error(msg); + } + + const buffer = await response.arrayBuffer(); + return buffer; +}; diff --git a/src/views/peripheral.ts b/src/views/peripheral.ts index 00b469b..869c2d5 100644 --- a/src/views/peripheral.ts +++ b/src/views/peripheral.ts @@ -17,19 +17,20 @@ */ import * as vscode from 'vscode'; -import * as path from 'path'; import * as manifest from '../manifest'; import { parseStringPromise } from 'xml2js'; import { BaseNode, PeripheralBaseNode } from './nodes/basenode'; import { PeripheralNode } from './nodes/peripheralnode'; import { MessageNode } from './nodes/messagenode'; import { NodeSetting } from '../common'; -import { SVDParser } from '../svd-parser'; +import { SvdData, SVDParser } from '../svd-parser'; import { AddrRange } from '../addrranges'; import { DebugTracker } from '../debug-tracker'; -import { SvdRegistry } from '../svd-registry'; +import { SvdResolver } from '../svd-resolver'; +import { readFromUrl } from '../utils'; +import { uriExists } from '../vscode-utils'; -const STATE_FILENAME = '.svd-viewer.state.json'; +const STATE_FILENAME = '.svd-viewer.json'; const pathToUri = (path: string): vscode.Uri => { try { @@ -39,32 +40,18 @@ const pathToUri = (path: string): vscode.Uri => { } }; -const readFromUrl = async (url: string): Promise => { - // Download using fetch - const response = await fetch(url); - if (!response.ok) { - const body = await response.text(); - const msg = `Request to ${url} failed. Status="${response.status}". Body="${body}".`; - throw new Error(msg); - } - - return response; -}; - export class PeripheralTreeForSession extends PeripheralBaseNode { public myTreeItem: vscode.TreeItem; private peripherials: PeripheralNode[] = []; private loaded = false; private errMessage = 'No SVD file loaded'; - private wsFolderPath: vscode.Uri | undefined; constructor( public session: vscode.DebugSession, public state: vscode.TreeItemCollapsibleState, + private wsFolderPath: vscode.Uri | undefined, private fireCb: () => void) { super(); - // Remember the path as it may not be available when session ends - this.wsFolderPath = this.session.workspaceFolder ? this.session.workspaceFolder.uri : vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders[0].uri; this.myTreeItem = new vscode.TreeItem(this.session.name, this.state); } @@ -79,14 +66,13 @@ export class PeripheralTreeForSession extends PeripheralBaseNode { private async loadSvdState(): Promise { const stateUri = this.getSvdStateUri(); if (stateUri) { - try { + const exists = await uriExists(stateUri); + if (exists) { await vscode.workspace.fs.stat(stateUri); const data = await vscode.workspace.fs.readFile(stateUri); const decoder = new TextDecoder(); const text = decoder.decode(data); return JSON.parse(text); - } catch { - // File may not exist } } @@ -116,24 +102,16 @@ export class PeripheralTreeForSession extends PeripheralBaseNode { return state; } - private async loadSVD(svd: string, gapThreshold: number): Promise { - let svdData: string | undefined; + private async createPeripherals(svdPath: string, gapThreshold: number): Promise { + let svdData: SvdData | undefined; try { let contents: ArrayBuffer | undefined; - if (svd.startsWith('http')) { - const response = await readFromUrl(svd); - contents = await response.arrayBuffer(); + if (svdPath.startsWith('http')) { + contents = await readFromUrl(svdPath); } else { - if (vscode.env.uiKind === vscode.UIKind.Desktop) { - // On desktop, ensure full path - if (!path.isAbsolute(svd) && this.wsFolderPath) { - svd = path.normalize(path.join(this.wsFolderPath.fsPath, svd)); - } - } - - const uri = pathToUri(svd); + const uri = pathToUri(svdPath); contents = await vscode.workspace.fs.readFile(uri); } @@ -151,10 +129,10 @@ export class PeripheralTreeForSession extends PeripheralBaseNode { return; } - this.errMessage = `Loading ${svd}`; + this.errMessage = `Loading ${svdPath}`; try { - this.peripherials = await SVDParser.parseSVD(this.session, JSON.parse(svdData), gapThreshold); + this.peripherials = await SVDParser.parseSVD(this.session, svdData, gapThreshold); this.loaded = true; } catch(e) { this.peripherials = []; @@ -219,7 +197,7 @@ export class PeripheralTreeForSession extends PeripheralBaseNode { return undefined; } - public async sessionStarted(svd: string, thresh: number): Promise { // Never rejects + public async sessionStarted(svdPath: string, thresh: number): Promise { // Never rejects if (((typeof thresh) === 'number') && (thresh < 0)) { thresh = -1; // Never merge register reads even if adjacent } else { @@ -231,7 +209,7 @@ export class PeripheralTreeForSession extends PeripheralBaseNode { this.fireCb(); try { - await this.loadSVD(svd, thresh); + await this.createPeripherals(svdPath, thresh); const settings = await this.loadSvdState(); settings.forEach((s: NodeSetting) => { @@ -247,7 +225,7 @@ export class PeripheralTreeForSession extends PeripheralBaseNode { this.peripherials.sort(PeripheralNode.compare); this.fireCb(); } catch(e) { - this.errMessage = `Unable to parse SVD file ${svd}: ${(e as Error).message}`; + this.errMessage = `Unable to parse SVD file ${svdPath}: ${(e as Error).message}`; vscode.window.showErrorMessage(this.errMessage); if (vscode.debug.activeDebugConsole) { vscode.debug.activeDebugConsole.appendLine(this.errMessage); @@ -274,9 +252,10 @@ export class PeripheralTreeProvider implements vscode.TreeDataProvider(); protected oldState = new Map (); - constructor(tracker: DebugTracker, protected registry: SvdRegistry) { + constructor(tracker: DebugTracker, protected resolver: SvdResolver) { tracker.onWillStartSession(session => this.debugSessionStarted(session)); - tracker.onDidStopSession(session => this.debugSessionTerminated(session)); + tracker.onWillStopSession(session => this.debugSessionTerminated(session)); + tracker.onDidStopDebug(session => this.debugStopped(session)); } public async activate(context: vscode.ExtensionContext): Promise { @@ -319,23 +298,10 @@ export class PeripheralTreeProvider implements vscode.TreeDataProvider { - const svdConfig = vscode.workspace.getConfiguration(manifest.PACKAGE_NAME).get(manifest.CONFIG_SVD_PATH) || manifest.DEFAULT_SVD_PATH; - let svd = session.configuration[svdConfig]; - - if (!svd) { - // Try loading from device support pack - const deviceConfig = vscode.workspace.getConfiguration(manifest.PACKAGE_NAME).get(manifest.CONFIG_DEVICE) || manifest.DEFAULT_DEVICE; - const device = session.configuration[deviceConfig]; - - if (device) { - svd = this.registry.getSVDFile(device); - if (!svd) { - svd = await this.registry.getSVDFileFromCortexDebug(device); - } - } - } + const wsFolderPath = session.workspaceFolder ? session.workspaceFolder.uri : vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders[0].uri; + const svdPath = await this.resolver.resolve(session, wsFolderPath); - if (!svd) { + if (!svdPath) { return; } @@ -349,7 +315,7 @@ export class PeripheralTreeProvider implements vscode.TreeDataProvider { + const regs = new PeripheralTreeForSession(session, state, wsFolderPath, () => { this._onDidChangeTreeData.fire(undefined); }); @@ -361,7 +327,7 @@ export class PeripheralTreeProvider implements vscode.TreeDataProvider 0); } + public debugStopped(session: vscode.DebugSession): void { + const regs = this.sessionPeripheralsMap.get(session.id); + if (regs) { // We are called even before the session has started, as part of reset + regs.updateData(); + } + } + public togglePinPeripheral(node: PeripheralBaseNode): void { const session = vscode.debug.activeDebugSession; if (session) { diff --git a/src/vscode-utils.ts b/src/vscode-utils.ts new file mode 100644 index 0000000..cd44d98 --- /dev/null +++ b/src/vscode-utils.ts @@ -0,0 +1,54 @@ +/** + * Copyright (C) 2023 Arm Limited + */ + +import * as vscode from 'vscode'; + +export const uriExists = async (uri: vscode.Uri): Promise => { + try { + await vscode.workspace.fs.stat(uri); + return true; + } catch { + return false; + } +}; + +interface QuickPickItem extends vscode.QuickPickItem { + value?: string; +} + +export const getSelection = async (title: string, items: QuickPickItem[], value?: string): Promise => { + const disposables: vscode.Disposable[] = []; + try { + return await new Promise(resolve => { + const input = vscode.window.createQuickPick(); + input.title = title; + input.items = items; + if (value) { + input.value = value; + } + + for (const item of items) { + if (item.picked === true) { + input.value = item.label; + break; + } + } + + disposables.push( + input.onDidChangeSelection(items => { + const item = items[0] as QuickPickItem; + resolve(item.value || item.label); + input.hide(); + }), + input.onDidHide(() => { + resolve(undefined); + input.dispose(); + }) + ); + input.show(); + }); + } finally { + disposables.forEach(d => d.dispose()); + } +}; diff --git a/yarn.lock b/yarn.lock index 29df196..8ba0d04 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1741,6 +1741,11 @@ ignore@^5.1.8, ignore@^5.2.0: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== +immediate@~3.0.5: + version "3.0.6" + resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" + integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== + import-fresh@^3.0.0, import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" @@ -1929,6 +1934,16 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= +jszip@^3.10.1: + version "3.10.1" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2" + integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g== + dependencies: + lie "~3.3.0" + pako "~1.0.2" + readable-stream "~2.3.6" + setimmediate "^1.0.5" + keygrip@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.1.0.tgz#871b1681d5e159c62a445b0c74b615e0917e7226" @@ -2036,6 +2051,13 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +lie@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" + integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== + dependencies: + immediate "~3.0.5" + linkify-it@^3.0.1: version "3.0.3" resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.3.tgz#a98baf44ce45a550efb4d49c769d07524cc2fa2e" @@ -2375,6 +2397,11 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +pako@~1.0.2: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -2600,7 +2627,7 @@ read@^1.0.7: dependencies: mute-stream "~0.0.4" -readable-stream@^2.0.6, readable-stream@^2.3.0, readable-stream@^2.3.5: +readable-stream@^2.0.6, readable-stream@^2.3.0, readable-stream@^2.3.5, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -2800,7 +2827,7 @@ set-blocking@~2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= -setimmediate@^1.0.4: +setimmediate@^1.0.4, setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==