UNPKG

bossa-web

Version:

Port of BOSSA to TypeScript with support for WebSerial API

245 lines (244 loc) 9.76 kB
import { Flash } from './flash'; // CMDEX field should be 0xA5 to allow execution of any command. const CMDEX_KEY = 0xa500; const NVM_REG_BASE = 0x41004000; const NVM_REG_CTRLA = 0x00; const NVM_REG_CTRLB = 0x04; const NVM_REG_INTFLAG = 0x10; const NVM_REG_STATUS = 0x12; const NVM_REG_ADDR = 0x14; const NVM_REG_RUNLOCK = 0x18; const NVM_CMD_EP = 0x00; const NVM_CMD_EB = 0x01; const NVM_CMD_WP = 0x03; const NVM_CMD_WQW = 0x04; const NVM_CMD_LR = 0x11; const NVM_CMD_UR = 0x12; const NVM_CMD_SSB = 0x16; const NVM_CMD_PBC = 0x15; const ERASE_BLOCK_PAGES = 16; // pages // NVM User Row const NVM_UP_ADDR = 0x804000; const NVM_UP_BOD33_DISABLE_OFFSET = 0x0; const NVM_UP_BOD33_DISABLE_MASK = 0x1; const NVM_UP_BOD33_RESET_OFFSET = 0x1; const NVM_UP_BOD33_RESET_MASK = 0x2; const NVM_UP_NVM_LOCK_OFFSET = 0x8; export class FlashEraseError extends Error { constructor(msg = undefined) { super(msg); } } export class FlashCmdError extends Error { constructor(msg = undefined) { super(msg); } } export class FlashPageError extends Error { constructor(msg = undefined) { super(msg); } } export class D5xNvmFlash extends Flash { constructor(samba, name, pages, size, user, stack) { super(samba, name, 0, pages, size, 1, 32, user, stack); this._eraseAuto = true; } get NVM_UP_SIZE() { return this._size; } async erase(offset, size) { let eraseSize = this._size * ERASE_BLOCK_PAGES; // Offset must be a multiple of the erase size if (offset % eraseSize) throw new FlashEraseError(); // Offset and size must be in range if (offset + size > this.totalSize) throw new FlashEraseError(); let eraseEnd = (offset + size + eraseSize - 1) / eraseSize; // Erase each erase size set of pages for (var eraseNum = offset / eraseSize; eraseNum < eraseEnd; eraseNum++) { // Issue erase command let wordAddr = (eraseNum * eraseSize); await this.writeRegU32(NVM_REG_ADDR, wordAddr); await this.command(NVM_CMD_EB); } } async waitReady() { while ((await this.readRegU16(NVM_REG_STATUS) & 0x1) == 0) ; } async eraseAll(offset) { // Use the extended Samba command if available if (this._samba.canChipErase) { await this._samba.chipErase(offset); } else { await this.erase(offset, this.totalSize - offset); } } get eraseAuto() { return this._eraseAuto; } set eraseAuto(enable) { this._eraseAuto = enable; } async getLockRegions() { var lockBits = 0; let addr = NVM_UP_ADDR + NVM_UP_NVM_LOCK_OFFSET; var regions = new Array(this._lockRegions); for (var region = 0; region < this._lockRegions; region++) { if (region % 8 == 0) lockBits = await this._samba.readByte(addr++); regions[region] = (lockBits & (1 << (region % 8))) == 0; } return regions; } async getSecurity() { // There doesn't seem to be a way to read this return false; } async getBod() { let byte = await this._samba.readByte(NVM_UP_ADDR + NVM_UP_BOD33_DISABLE_OFFSET); return (byte & NVM_UP_BOD33_DISABLE_MASK) == 0; } canBod() { return true; } async getBor() { let byte = await this._samba.readByte(NVM_UP_ADDR + NVM_UP_BOD33_RESET_OFFSET); return (byte & NVM_UP_BOD33_RESET_MASK) != 0; } canBor() { return true; } getBootFlash() { return true; } canBootFlash() { return false; } async readUserPage(userRow) { if (userRow.length != this.NVM_UP_SIZE) throw new Error('Invalid row buffer size'); await this._samba.read(NVM_UP_ADDR, userRow, this.NVM_UP_SIZE); } async writeOptions() { var userPage = new Uint8Array(this.NVM_UP_SIZE); if (this.canBor() && this._bor.isDirty() && this._bor.get() != await this.getBor()) { await this.readUserPage(userPage); if (this._bor.get()) userPage[NVM_UP_BOD33_RESET_OFFSET] |= NVM_UP_BOD33_RESET_MASK; else userPage[NVM_UP_BOD33_RESET_OFFSET] &= ~NVM_UP_BOD33_RESET_MASK; } if (this.canBod() && this._bod.isDirty() && this._bod.get() != await this.getBod()) { await this.readUserPage(userPage); if (this._bod.get()) userPage[NVM_UP_BOD33_DISABLE_OFFSET] &= ~NVM_UP_BOD33_DISABLE_MASK; else userPage[NVM_UP_BOD33_DISABLE_OFFSET] |= NVM_UP_BOD33_DISABLE_MASK; } if (this._regions.isDirty()) { // Check if any lock bits are different from the current set var current = await this.getLockRegions(); var regions = this._regions.get(); var equal = true; for (var i = 0; i < regions.length && equal; i++) { equal && (equal = regions[i] == current[i]); } if (!equal) { await this.readUserPage(userPage); for (var region = 0; region < this._regions.get().length; region++) { if (this._regions.get()[region]) userPage[NVM_UP_NVM_LOCK_OFFSET + region / 8] &= ~(1 << (region % 8)); else userPage[NVM_UP_NVM_LOCK_OFFSET + region / 8] |= (1 << (region % 8)); } } } // Erase and write the user row if modified if (userPage) { // Disable cache and configure manual page write // Configure manual page write and disable caches await this.writeRegU16(NVM_REG_CTRLA, (await this.readRegU16(NVM_REG_CTRLA) | (0x3 << 14)) & 0xffcf); // Erase user row await this.writeRegU32(NVM_REG_ADDR, NVM_UP_ADDR); await this.command(NVM_CMD_EP); // Write user page in quad-word chunks for (var offset = 0; offset < this.NVM_UP_SIZE; offset += this._size) { // Load the buffer with the quad word await this.loadBuffer(userPage, offset, 16); // Clear page buffer await this.command(NVM_CMD_PBC); // Copy quad word to page buffer await this.prepareApplet(); await this._wordCopy.setDstAddr(NVM_UP_ADDR + offset); await this._wordCopy.setSrcAddr(this._onBufferA ? this._pageBufferA : this._pageBufferB); await this._wordCopy.setWords(4); this._onBufferA = !this._onBufferA; await this.waitReady(); await this._wordCopy.runv(); // Write the quad word await this.writeRegU32(NVM_REG_ADDR, (NVM_UP_ADDR + offset)); await this.command(NVM_CMD_WQW); } } // Always do security last if (this._security.isDirty() && this._security.get() == true && this._security.get() != await this.getSecurity()) { await this.command(NVM_CMD_SSB); } } async writePage(page) { if (page >= this._pages) { throw new FlashPageError(); } // Disable cache and configure manual page write await this.writeRegU16(NVM_REG_CTRLA, (await this.readRegU16(NVM_REG_CTRLA) | (0x3 << 14)) & 0xffcf); // Auto-erase if writing at the start of the erase page if (this.eraseAuto && page % ERASE_BLOCK_PAGES == 0) await this.erase(page * this._size, ERASE_BLOCK_PAGES * this._size); // Clear page buffer await this.command(NVM_CMD_PBC); // Compute the start address. let addr = this._addr + (page * this._size); await this.prepareApplet(); await this._wordCopy.setDstAddr(addr); await this._wordCopy.setSrcAddr(this._onBufferA ? this._pageBufferA : this._pageBufferB); await this._wordCopy.setWords(this._size / 4); // sizeof(uint32_t) this._onBufferA = !this._onBufferA; await this.waitReady(); await this._wordCopy.runv(); await this.writeRegU32(NVM_REG_ADDR, addr / 2); await this.command(NVM_CMD_WP); } async readPage(page, buf) { if (page >= this._pages) { throw new FlashPageError(); } await this._samba.read(this._addr + (page * this._size), buf, this._size); } async readRegU16(reg) { return await this._samba.readByte(NVM_REG_BASE + reg) | (await this._samba.readByte(NVM_REG_BASE + reg + 1) << 8); } async writeRegU16(reg, value) { await this._samba.writeByte(NVM_REG_BASE + reg, value & 0xff); await this._samba.writeByte(NVM_REG_BASE + reg, (value >> 8) & 0xff); } async readRegU32(reg) { return await this._samba.readWord(NVM_REG_BASE + reg); } async writeRegU32(reg, value) { await this._samba.writeWord(NVM_REG_BASE + reg, value); } async command(cmd) { await this.waitReady(); await this.writeRegU32(NVM_REG_CTRLB, CMDEX_KEY | cmd); await this.waitReady(); if ((await this.readRegU16(NVM_REG_INTFLAG)) & 0xce) { // Clear the error bit await this.writeRegU16(NVM_REG_INTFLAG, 0xce); throw new FlashCmdError(); } } async writeBuffer(dst_addr, size) { // Auto-erase if enabled if (this.eraseAuto && ((dst_addr / this._size) % ERASE_BLOCK_PAGES == 0)) await this.erase(dst_addr, size); // Call the base class method await super.writeBuffer(dst_addr, size); } }