pxt-calliope/editor/flash.ts

401 lines
15 KiB
TypeScript
Raw Normal View History

const imul = (Math as any).imul;
const timeoutMessage = "timeout"
const membase = 0x20000000
const loadAddr = membase
const dataAddr = 0x20002000
const stackAddr = 0x20001000
const flashPageBIN = new Uint32Array([
0xbe00be00, // bkpt - LR is set to this
0x2502b5f0, 0x4c204b1f, 0xf3bf511d, 0xf3bf8f6f, 0x25808f4f, 0x002e00ed,
0x2f00595f, 0x25a1d0fc, 0x515800ed, 0x2d00599d, 0x2500d0fc, 0xf3bf511d,
0xf3bf8f6f, 0x25808f4f, 0x002e00ed, 0x2f00595f, 0x2501d0fc, 0xf3bf511d,
0xf3bf8f6f, 0x599d8f4f, 0xd0fc2d00, 0x25002680, 0x00f60092, 0xd1094295,
0x511a2200, 0x8f6ff3bf, 0x8f4ff3bf, 0x2a00599a, 0xbdf0d0fc, 0x5147594f,
0x2f00599f, 0x3504d0fc, 0x46c0e7ec, 0x4001e000, 0x00000504,
])
// void computeHashes(uint32_t *dst, uint8_t *ptr, uint32_t pageSize, uint32_t numPages)
const computeChecksums2 = new Uint32Array([
0x4c27b5f0, 0x44a52680, 0x22009201, 0x91004f25, 0x00769303, 0x24080013,
0x25010019, 0x40eb4029, 0xd0002900, 0x3c01407b, 0xd1f52c00, 0x468c0091,
0xa9044665, 0x506b3201, 0xd1eb42b2, 0x089b9b01, 0x23139302, 0x9b03469c,
0xd104429c, 0x2000be2a, 0x449d4b15, 0x9f00bdf0, 0x4d149e02, 0x49154a14,
0x3e01cf08, 0x2111434b, 0x491341cb, 0x405a434b, 0x4663405d, 0x230541da,
0x4b10435a, 0x466318d2, 0x230541dd, 0x4b0d435d, 0x2e0018ed, 0x6002d1e7,
0x9a009b01, 0x18d36045, 0x93003008, 0xe7d23401, 0xfffffbec, 0xedb88320,
0x00000414, 0x1ec3a6c8, 0x2f9be6cc, 0xcc9e2d51, 0x1b873593, 0xe6546b64,
])
let startTime = 0
function log(msg: string) {
let now = Date.now()
if (!startTime) startTime = now
now -= startTime
let ts = ("00000" + now).slice(-5)
pxt.debug(`dap ${ts}: ${msg}`)
}
function murmur3_core(data: Uint8Array) {
let h0 = 0x2F9BE6CC;
let h1 = 0x1EC3A6C8;
for (let i = 0; i < data.length; i += 4) {
let k = pxt.HF2.read32(data, i) >>> 0
k = imul(k, 0xcc9e2d51);
k = (k << 15) | (k >>> 17);
k = imul(k, 0x1b873593);
h0 ^= k;
h1 ^= k;
h0 = (h0 << 13) | (h0 >>> 19);
h1 = (h1 << 13) | (h1 >>> 19);
h0 = (imul(h0, 5) + 0xe6546b64) >>> 0;
h1 = (imul(h1, 5) + 0xe6546b64) >>> 0;
}
return [h0, h1]
}
class DAPWrapper implements pxt.packetio.PacketIOWrapper {
familyID: number;
private dap: DapJS.DAP;
private cortexM: DapJS.CortexM
private cmsisdap: any;
private flashing = false;
private readSerialId = 0;
private pbuf = new pxt.U.PromiseBuffer<Uint8Array>();
private pageSize = 1024;
private numPages = 256;
private binName = pxtc.BINARY_HEX;
constructor(public readonly io: pxt.packetio.PacketIO) {
this.familyID = 0x1366; // this is the microbit vendor id, not quite UF2 family id
this.io.onDeviceConnectionChanged = (connect) =>
this.disconnectAsync()
.then(() => connect && this.reconnectAsync());
this.io.onData = buf => {
// console.log("RD: " + pxt.Util.toHex(buf))
this.pbuf.push(buf);
}
this.allocDAP();
}
icon = "usb";
private startReadSerial() {
log(`start read serial`)
const rid = this.readSerialId;
const readSerial = () => {
if (rid != this.readSerialId) {
log(`stopped read serial ${rid}`)
return;
}
if (this.flashing) {
setTimeout(readSerial, 500);
return;
}
// done
this.cmsisdap.cmdNums(0x83, [])
.then((r: number[]) => {
const len = r[1]
let str = ""
for (let i = 2; i < len + 2; ++i) {
str += String.fromCharCode(r[i])
}
if (str.length > 0) {
pxt.U.nextTick(readSerial)
if (this.onSerial) {
const utf8Str = pxt.U.toUTF8(str);
this.onSerial(pxt.U.stringToUint8Array(utf8Str), false)
}
} else
setTimeout(readSerial, 50)
}, (err: any) => {
log(`read error: ` + err.message);
this.disconnectAsync(); // force disconnect
});
}
readSerial();
}
private stopSerialAsync() {
log(`stopping serial reader`)
this.readSerialId++;
return Promise.delay(200);
}
onSerial: (buf: Uint8Array, isStderr: boolean) => void;
private allocDAP() {
log(`alloc dap`);
this.dap = new DapJS.DAP({
write: writeAsync,
close: this.disconnectAsync,
read: readAsync,
//sendMany: sendMany
});
this.cmsisdap = (this.dap as any).dap;
this.cortexM = new DapJS.CortexM(this.dap);
const h = this.io;
const pbuf = this.pbuf;
function writeAsync(data: ArrayBuffer) {
//console.log("WR: " + pxt.Util.toHex(new Uint8Array(data)));
return h.sendPacketAsync(new Uint8Array(data));
}
function readAsync() {
return pbuf.shiftAsync();
}
}
reconnectAsync(): Promise<void> {
log(`reconnect`)
// configure serial at 115200
return this.stopSerialAsync()
.then(() => this.io.reconnectAsync())
.then(() => this.cortexM.init())
.then(() => this.cmsisdap.cmdNums(0x80, []))
.then(r => {
this.binName = (r[2] == 57 && r[3] == 57 && r[5] >= 51 ? "mbcodal-" : "") + pxtc.BINARY_HEX
})
.then(() => this.cortexM.memory.readBlock(0x10000010, 2, this.pageSize))
.then(res => {
this.pageSize = pxt.HF2.read32(res, 0)
this.numPages = pxt.HF2.read32(res, 4)
})
.then(() => this.cmsisdap.cmdNums(0x82, [0x00, 0xC2, 0x01, 0x00]))
.then(() => this.startReadSerial());
}
disconnectAsync() {
log(`disconnect`)
return this.stopSerialAsync()
.then(() => this.io.disconnectAsync());
}
reflashAsync(resp: pxtc.CompileResult): Promise<void> {
log("reflash")
startTime = 0
pxt.tickEvent("hid.flash.start");
this.flashing = true;
return (this.io.isConnected() ? Promise.resolve() : this.io.reconnectAsync())
.then(() => this.cortexM.init())
.then(() => this.cortexM.reset(true))
.then(() => this.cortexM.memory.readBlock(0x10001014, 1, this.pageSize))
.then(v => {
if ((pxt.HF2.read32(v, 0) & 0xff) != 0) {
pxt.tickEvent("hid.flash.uicrfail");
return this.fullVendorCommandFlashAsync(resp);
}
return this.quickHidFlashAsync(resp);
})
.finally(() => { this.flashing = false })
.then(() => Promise.delay(100))
.then(() => this.disconnectAsync())
}
private fullVendorCommandFlashAsync(resp: pxtc.CompileResult): Promise<void> {
log("full flash")
const chunkSize = 62;
let aborted = false;
return Promise.resolve()
.then(() => {
return this.cmsisdap.cmdNums(0x8A /* DAPLinkFlash.OPEN */, [1]);
})
.then((res) => {
const hexUint8 = pxt.U.stringToUint8Array(resp.outfiles[this.binName]);
const hexArray: number[] = Array.prototype.slice.call(hexUint8);
const sendPages = (offset: number = 0): Promise<void> => {
const end = Math.min(hexArray.length, offset + chunkSize);
const nextPage = hexArray.slice(offset, end);
nextPage.unshift(nextPage.length);
return this.cmsisdap.cmdNums(0x8C /* DAPLinkFlash.WRITE */, nextPage)
.then(() => {
if (!aborted && end < hexArray.length) {
return sendPages(end);
}
return Promise.resolve();
});
}
return sendPages();
})
.then((res) => {
return this.cmsisdap.cmdNums(0x8B /* DAPLinkFlash.CLOSE */, []);
})
.timeout(60000, timeoutMessage)
.catch((e) => {
aborted = true;
return this.cmsisdap.cmdNums(0x89 /* DAPLinkFlash.RESET */, [])
.catch((e2: any) => {
// Best effort reset, no-op if there's an error
})
.then(() => {
return Promise.reject(e);
});
});
}
private quickHidFlashAsync(resp: pxtc.CompileResult): Promise<void> {
log("quick flash")
let logV = (msg: string) => { }
//let logV = log
let aborted = false;
const runFlash = (b: ts.pxtc.UF2.Block, dataAddr: number) => {
const cmd = this.cortexM.prepareCommand();
cmd.halt();
cmd.writeCoreRegister(DapJS.CortexReg.PC, loadAddr + 4 + 1);
cmd.writeCoreRegister(DapJS.CortexReg.LR, loadAddr + 1);
cmd.writeCoreRegister(DapJS.CortexReg.SP, stackAddr);
cmd.writeCoreRegister(0, b.targetAddr);
cmd.writeCoreRegister(1, dataAddr);
cmd.writeCoreRegister(2, this.pageSize >> 2);
return Promise.resolve()
.then(() => {
logV("setregs")
return cmd.go()
})
.then(() => {
logV("dbg en")
// starts the program
return this.cortexM.debug.enable()
})
}
let checksums: Uint8Array
return this.getFlashChecksumsAsync()
.then(buf => {
checksums = buf;
log("write code");
return this.cortexM.memory.writeBlock(loadAddr, flashPageBIN);
})
.then(() => {
log("convert");
// TODO this is seriously inefficient (130ms on a fast machine)
let uf2 = ts.pxtc.UF2.newBlockFile();
ts.pxtc.UF2.writeHex(uf2, resp.outfiles[this.binName].split(/\r?\n/));
let bytes = pxt.U.stringToUint8Array(ts.pxtc.UF2.serializeFile(uf2));
let parsed = ts.pxtc.UF2.parseFile(bytes);
let aligned = DAPWrapper.pageAlignBlocks(parsed, this.pageSize);
log(`initial: ${aligned.length} pages`);
aligned = DAPWrapper.onlyChanged(aligned, checksums, this.pageSize);
log(`incremental: ${aligned.length} pages`);
return Promise.mapSeries(pxt.U.range(aligned.length),
i => {
if (aborted) return Promise.resolve();
let b = aligned[i];
if (b.targetAddr >= 0x10000000)
return Promise.resolve();
logV("about to write at 0x" + b.targetAddr.toString(16));
let writeBl = Promise.resolve();
let thisAddr = (i & 1) ? dataAddr : dataAddr + this.pageSize;
let nextAddr = (i & 1) ? dataAddr + this.pageSize : dataAddr;
if (i == 0) {
let u32data = new Uint32Array(b.data.length / 4);
for (let i = 0; i < b.data.length; i += 4)
u32data[i >> 2] = pxt.HF2.read32(b.data, i);
writeBl = this.cortexM.memory.writeBlock(thisAddr, u32data);
}
return writeBl
.then(() => runFlash(b, thisAddr))
.then(() => {
let next = aligned[i + 1];
if (!next)
return Promise.resolve();
logV("write next");
let buf = new Uint32Array(next.data.buffer);
return this.cortexM.memory.writeBlock(nextAddr, buf);
})
.then(() => {
logV("wait");
return this.cortexM.waitForHalt(500);
})
.then(() => {
logV("done block");
});
})
.then(() => {
log("flash done");
pxt.tickEvent("hid.flash.done");
return this.cortexM.reset(false);
});
})
.timeout(25000, timeoutMessage)
.catch((e) => {
aborted = true;
return Promise.reject(e);
});
}
private getFlashChecksumsAsync() {
log("flash checksums")
let pages = this.numPages
return this.cortexM.runCode(computeChecksums2, loadAddr, loadAddr + 1, 0xffffffff, stackAddr, true,
dataAddr, 0, this.pageSize, pages)
.then(() => this.cortexM.memory.readBlock(dataAddr, pages * 2, this.pageSize))
}
static onlyChanged(blocks: ts.pxtc.UF2.Block[], checksums: Uint8Array, pageSize: number) {
return blocks.filter(b => {
let idx = b.targetAddr / pageSize
pxt.U.assert((idx | 0) == idx)
pxt.U.assert(b.data.length == pageSize)
if (idx * 8 + 8 > checksums.length)
return true // out of range?
let c0 = pxt.HF2.read32(checksums, idx * 8)
let c1 = pxt.HF2.read32(checksums, idx * 8 + 4)
let ch = murmur3_core(b.data)
if (c0 == ch[0] && c1 == ch[1])
return false
return true
})
}
static pageAlignBlocks(blocks: ts.pxtc.UF2.Block[], pageSize: number) {
pxt.U.assert(pageSize % 256 == 0)
let res: ts.pxtc.UF2.Block[] = []
for (let i = 0; i < blocks.length;) {
let b0 = blocks[i]
let newbuf = new Uint8Array(pageSize)
for (let i = 0; i < newbuf.length; ++i)
newbuf[i] = 0xff
let startPad = b0.targetAddr & (pageSize - 1)
let newAddr = b0.targetAddr - startPad
for (; i < blocks.length; ++i) {
let b = blocks[i]
if (b.targetAddr + b.payloadSize > newAddr + pageSize)
break
pxt.U.memcpy(newbuf, b.targetAddr - newAddr, b.data, 0, b.payloadSize)
}
let bb = pxt.U.flatClone(b0)
bb.data = newbuf
bb.targetAddr = newAddr
bb.payloadSize = pageSize
res.push(bb)
}
return res
}
}
export function mkDAPLinkPacketIOWrapper(io: pxt.packetio.PacketIO): pxt.packetio.PacketIOWrapper {
pxt.log(`packetio: mk wrapper dap wrapper`)
return new DAPWrapper(io);
}