(() => { /** * (c) 2021, Micro:bit Educational Foundation and contributors * * SPDX-License-Identifier: MIT */ // https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/dap/constants.ts // https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/cortex/constants.ts // CRA's build tooling doesn't support const enums so we've converted them to prefixed constants here. // If we move this to a separate library then we can replace them. // In the meantime we should prune the list below to what we actually use. // FICR Registers const FICR = { CODEPAGESIZE: 0x10000000 | 0x10, CODESIZE: 0x10000000 | 0x14, }; const DapCmd = { DAP_INFO: 0x00, DAP_CONNECT: 0x02, DAP_DISCONNECT: 0x03, DAP_TRANSFER: 0x05, DAP_TRANSFER_BLOCK: 0x06, // Many more. }; const Csw = { CSW_SIZE: 0x00000007, CSW_SIZE32: 0x00000002, CSW_ADDRINC: 0x00000030, CSW_SADDRINC: 0x00000010, CSW_DBGSTAT: 0x00000040, CSW_HPROT: 0x02000000, CSW_MSTRDBG: 0x20000000, CSW_RESERVED: 0x01000000, CSW_VALUE: -1, // see below // Many more. }; Csw.CSW_VALUE = Csw.CSW_RESERVED | Csw.CSW_MSTRDBG | Csw.CSW_HPROT | Csw.CSW_DBGSTAT | Csw.CSW_SADDRINC; const DapVal = { AP_ACC: 1 << 0, READ: 1 << 1, WRITE: 0 << 1, // More. }; const ApReg = { CSW: 0x00, TAR: 0x04, DRW: 0x0c, // More. }; const CortexSpecialReg = { // Debug Exception and Monitor Control Register DEMCR: 0xe000edfc, // DWTENA in armv6 architecture reference manual DEMCR_VC_CORERESET: 1 << 0, // CPUID Register CPUID: 0xe000ed00, // Debug Halting Control and Status Register DHCSR: 0xe000edf0, S_RESET_ST: 1 << 25, NVIC_AIRCR: 0xe000ed0c, NVIC_AIRCR_VECTKEY: 0x5fa << 16, NVIC_AIRCR_SYSRESETREQ: 1 << 2, // Many more. }; const CoreRegister = { SP: 13, LR: 14, PC: 15, }; // Returns a representation of an Access Port Register. // Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/util.ts#L63 const apReg = (r, mode) => { const v = r | mode | DapVal.AP_ACC; return 4 + ((v & 0x0c) >> 2); }; // Returns a code representing a request to read/write a certain register. // Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/util.ts#L92 const regRequest = (regId, isWrite = false) => { let request = !isWrite ? 1 << 1 /* READ */ : 0 << 1; /* WRITE */ if (regId < 4) { request |= 0 << 0 /* DP_ACC */; } else { request |= 1 << 0 /* AP_ACC */; } request |= (regId & 3) << 2; return request; }; const bufferConcat = (bufs) => { let len = 0; for (const b of bufs) { len += b.length; } const r = new Uint8Array(len); len = 0; for (const b of bufs) { r.set(b, len); len += b.length; } return r; }; class DAPWrapper { constructor(device, logging) { this.device = device; this.logging = logging; this.initialConnectionComplete = true; this.loggedBoardSerialInfo = null; this._pageSize = 0; this._numPages = 0; this.transport = new DAPjs.WebUSB(this.device); this.daplink = new DAPjs.DAPLink(this.transport); this.cortexM = new DAPjs.CortexM(this.transport); } log(v) { //console.log(v); } /** * The page size. Throws if we've not connected. */ pageSize() { if (this._pageSize === undefined) { throw new Error("pageSize not defined until connected"); } return this._pageSize; } /** * The number of pages. Throws if we've not connected. */ numPages() { if (this._numPages === undefined) { throw new Error("numPages not defined until connected"); } return this._numPages; } boardSerialInfo() { return BoardSerialInfo.parse( this.device, this.logging.log.bind(this.logging) ); } async connectAsync() { await this.daplink.connect(); await this.daplink.setSerialBaudrate(115200); await this.cortexM.connect(); this.logging.event({ type: "WebUSB-info", message: "connected", }); const serialInfo = this.boardSerialInfo(); this.log(`Detected board ID ${serialInfo.id}`); if ( !this.loggedBoardSerialInfo || !this.loggedBoardSerialInfo.eq(this.boardSerialInfo()) ) { this.loggedBoardSerialInfo = this.boardSerialInfo(); this.logging.event({ type: "WebUSB-info", message: "board-id/" + this.boardSerialInfo().id, }); this.logging.event({ type: "WebUSB-info", message: "board-family-hic/" + this.boardSerialInfo().familyId + this.boardSerialInfo().hic, }); } this._pageSize = await this.cortexM.readMem32(FICR.CODEPAGESIZE); this._numPages = await this.cortexM.readMem32(FICR.CODESIZE); } // Drawn from https://github.com/microsoft/pxt-microbit/blob/dec5b8ce72d5c2b4b0b20aafefce7474a6f0c7b2/editor/extension.tsx#L119 async reconnectAsync() { if (this.initialConnectionComplete) { await this.disconnectAsync(); this.transport = new DAPjs.WebUSB(this.device); this.daplink = new DAPjs.DAPLink(this.transport); this.cortexM = new DAPjs.CortexM(this.transport); } else { this.initialConnectionComplete = true; } await this.daplink.connect(); await this.cortexM.connect(); this.logging.event({ type: "WebUSB-info", message: "connected", }); const serialInfo = this.boardSerialInfo(); this.log(`Detected board ID ${serialInfo.id}`); if ( !this.loggedBoardSerialInfo || !this.loggedBoardSerialInfo.eq(this.boardSerialInfo()) ) { this.loggedBoardSerialInfo = this.boardSerialInfo(); this.logging.event({ type: "WebUSB-info", message: "board-id/" + this.boardSerialInfo().id, }); this.logging.event({ type: "WebUSB-info", message: "board-family-hic/" + this.boardSerialInfo().familyId + this.boardSerialInfo().hic, }); } this._pageSize = await this.cortexM.readMem32(FICR.CODEPAGESIZE); this._numPages = await this.cortexM.readMem32(FICR.CODESIZE); } async startSerial(listener) { const currentBaud = await this.daplink.getSerialBaudrate(); if (currentBaud !== 115200) { // Changing the baud rate causes a micro:bit reset, so only do it if necessary await this.daplink.setSerialBaudrate(115200); } this.daplink.on(DAPjs.DAPLink.EVENT_SERIAL_DATA, listener); await this.daplink.startSerialRead(1); } stopSerial(listener) { this.daplink.stopSerialRead(); this.daplink.removeListener(DAPjs.DAPLink.EVENT_SERIAL_DATA, listener); } async disconnectAsync() { if (this.device.opened && this.transport.interfaceNumber !== undefined) { return this.daplink.disconnect(); } } // Send a packet to the micro:bit directly via WebUSB and return the response. // Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/transport/cmsis_dap.ts#L161 async send(packet) { const array = Uint8Array.from(packet); await this.transport.write(array.buffer); const response = await this.transport.read(); return new Uint8Array(response.buffer); } // Send a command along with relevant data to the micro:bit directly via WebUSB and handle the response. // Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/transport/cmsis_dap.ts#L74 async cmdNums( op, data ) { data.unshift(op); const buf = await this.send(data); if (buf[0] !== op) { throw new Error(`Bad response for ${op} -> ${buf[0]}`); } switch (op) { case DapCmd.DAP_CONNECT: case DapCmd.DAP_INFO: case DapCmd.DAP_TRANSFER: case DapCmd.DAP_TRANSFER_BLOCK: break; default: if (buf[1] !== 0) { throw new Error(`Bad status for ${op} -> ${buf[1]}`); } } return buf; } // Read a certain register a specified amount of times. // Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/dap/dap.ts#L117 async readRegRepeat(regId, cnt) { const request = regRequest(regId); const sendargs = [0, cnt]; for (let i = 0; i < cnt; ++i) { sendargs.push(request); } // Transfer the read requests to the micro:bit and retrieve the data read. const buf = await this.cmdNums(DapCmd.DAP_TRANSFER, sendargs); if (buf[1] !== cnt) { throw new Error("(many) Bad #trans " + buf[1]); } else if (buf[2] !== 1) { throw new Error("(many) Bad transfer status " + buf[2]); } return buf.subarray(3, 3 + cnt * 4); } // Write to a certain register a specified amount of data. // Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/dap/dap.ts#L138 async writeRegRepeat( regId, data ) { const request = regRequest(regId, true); const sendargs = [0, data.length, 0, request]; data.forEach((d) => { // separate d into bytes sendargs.push( d & 0xff, (d >> 8) & 0xff, (d >> 16) & 0xff, (d >> 24) & 0xff ); }); // Transfer the write requests to the micro:bit and retrieve the response status. const buf = await this.cmdNums(DapCmd.DAP_TRANSFER_BLOCK, sendargs); if (buf[3] !== 1) { throw new Error("(many-wr) Bad transfer status " + buf[2]); } } // Core functionality reading a block of data from micro:bit RAM at a specified address. // Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/memory/memory.ts#L181 async readBlockCore( addr, words ) { // Set up CMSIS-DAP to read/write from/to the RAM address addr using the register // ApReg.DRW to write to or read from. await this.cortexM.writeAP(ApReg.CSW, Csw.CSW_VALUE | Csw.CSW_SIZE32); await this.cortexM.writeAP(ApReg.TAR, addr); let lastSize = words % 15; if (lastSize === 0) { lastSize = 15; } const blocks = []; for (let i = 0; i < Math.ceil(words / 15); i++) { const b = await this.readRegRepeat( apReg(ApReg.DRW, DapVal.READ), i === blocks.length - 1 ? lastSize : 15 ); blocks.push(b); } return bufferConcat(blocks).subarray(0, words * 4); } // Core functionality writing a block of data to micro:bit RAM at a specified address. // Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/memory/memory.ts#L205 async writeBlockCore( addr, words ) { try { // Set up CMSIS-DAP to read/write from/to the RAM address addr using the register ApReg.DRW to write to or read from. await this.cortexM.writeAP(ApReg.CSW, Csw.CSW_VALUE | Csw.CSW_SIZE32); await this.cortexM.writeAP(ApReg.TAR, addr); await this.writeRegRepeat(apReg(ApReg.DRW, DapVal.WRITE), words); } catch (e) { if (e.dapWait) { // Retry after a delay if required. this.log(`Transfer wait, write block`); await new Promise((resolve) => setTimeout(resolve, 100)); return await this.writeBlockCore(addr, words); } else { throw e; } } } // Reads a block of data from micro:bit RAM at a specified address. // Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/memory/memory.ts#L143 async readBlockAsync(addr, words) { const bufs = []; const end = addr + words * 4; let ptr = addr; // Read a single page at a time. while (ptr < end) { let nextptr = ptr + this.pageSize(); if (ptr === addr) { nextptr &= ~(this.pageSize() - 1); } const len = Math.min(nextptr - ptr, end - ptr); bufs.push(await this.readBlockCore(ptr, len >> 2)); ptr = nextptr; } const result = bufferConcat(bufs); return result.subarray(0, words * 4); } // Writes a block of data to micro:bit RAM at a specified address. async writeBlockAsync(address, data) { let payloadSize = this.transport.packetSize - 8; if (data.buffer.byteLength > payloadSize) { let start = 0; let end = payloadSize; // Split write up into smaller writes whose data can each be held in a single packet. while (start !== end) { let temp = new Uint32Array(data.buffer.slice(start, end)); await this.writeBlockCore(address + start, temp); start = end; end = Math.min(data.buffer.byteLength, end + payloadSize); } } else { await this.writeBlockCore(address, data); } } // Execute code at a certain address with specified values in the registers. // Waits for execution to halt. async executeAsync(address, code, sp, pc, lr, registers) { if (registers.length > 12) { throw new Error(`Only 12 general purpose registers but got ${registers.length} values`); } await this.cortexM.halt(true); await this.writeBlockAsync(address, code); await this.cortexM.writeCoreRegister(CoreRegister.PC, pc); await this.cortexM.writeCoreRegister(CoreRegister.LR, lr); await this.cortexM.writeCoreRegister(CoreRegister.SP, sp); for (var i = 0; i < registers.length; ++i) { await this.cortexM.writeCoreRegister(i, registers[i]); } await this.cortexM.resume(true); return this.waitForHalt(); } // Checks whether the micro:bit has halted or timeout has been reached. // Recurses otherwise. async waitForHaltCore(halted, deadline) { if (new Date().getTime() > deadline) { throw new Error("timeout"); } if (!halted) { const isHalted = await this.cortexM.isHalted(); // NB this is a Promise so no stack risk. return this.waitForHaltCore(isHalted, deadline); } } // Initial function to call to wait for the micro:bit halt. async waitForHalt(timeToWait = 10000) { const deadline = new Date().getTime() + timeToWait; return this.waitForHaltCore(false, deadline); } // Resets the micro:bit in software by writing to NVIC_AIRCR. // Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/cortex/cortex.ts#L347 async softwareReset() { await this.cortexM.writeMem32( CortexSpecialReg.NVIC_AIRCR, CortexSpecialReg.NVIC_AIRCR_VECTKEY | CortexSpecialReg.NVIC_AIRCR_SYSRESETREQ ); // wait for the system to come out of reset let dhcsr = await this.cortexM.readMem32(CortexSpecialReg.DHCSR); while ((dhcsr & CortexSpecialReg.S_RESET_ST) !== 0) { dhcsr = await this.cortexM.readMem32(CortexSpecialReg.DHCSR); } } // Reset the micro:bit, possibly halting the core on reset. // Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/cortex/cortex.ts#L248 async reset(halt = false) { if (halt) { await this.cortexM.halt(true); // VC_CORERESET causes the core to halt on reset. const demcr = await this.cortexM.readMem32(CortexSpecialReg.DEMCR); await this.cortexM.writeMem32( CortexSpecialReg.DEMCR, CortexSpecialReg.DEMCR | CortexSpecialReg.DEMCR_VC_CORERESET ); await this.softwareReset(); await this.waitForHalt(); // Unset the VC_CORERESET bit await this.cortexM.writeMem32(CortexSpecialReg.DEMCR, demcr); } else { await this.softwareReset(); } } } window.DAPWrapper = DAPWrapper; })();