From ed42ac7a996ef49249b67594635d800aae7dc711 Mon Sep 17 00:00:00 2001 From: Simone <26844016+simonebortolin@users.noreply.github.com> Date: Mon, 16 Jan 2023 21:44:44 +0100 Subject: Add serial-ymodem flash web-app (#77) (#96) * add gui form ymodem * Remove LineBreakTransformer and add async * Add function to start ymodem in uboot * Add XYMini implementation * Remove delay from startYmodemLoad * update gui * Fix broken script code * Split waitUbootStop * Add changeBaudrate * Fix changeBaudrate * Add sendImageMtd * Add waitEndImageLoad * Add flashImageMtd * Implement flash firmware * fix js code Co-authored-by: Ernesto Castellotti --- assets/js/rootLantiq.js | 141 +++++++++++++++++++++++++++++++++++++++++++--- assets/js/xymini.js | 147 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 280 insertions(+), 8 deletions(-) create mode 100644 assets/js/xymini.js (limited to 'assets') diff --git a/assets/js/rootLantiq.js b/assets/js/rootLantiq.js index ff0f478..570133c 100644 --- a/assets/js/rootLantiq.js +++ b/assets/js/rootLantiq.js @@ -1,20 +1,28 @@ -async function waitUbootStop(writer, reader, sfpModel, outputMsgCallback) { - const interval = setInterval(function() { - writer.write(String.fromCharCode(3)); - }, 10); +const LOAD_ADDR = "80800000" +const IMAGE0_ADDR = "C0000 740000"; +const IMAGE1_ADDR = "800000 800000"; +async function detectUboot(reader) { while (true) { const { value, done } = await reader.read(); if (value.startsWith('U-Boot')) { - outputMsgCallback(`Root in progress: Trigger characters received. DO NOT TOUCH THE ${sfpModel} UNTIL THE PROCEDURE IS COMPLETED!`); - await delay(5000); - clearInterval(interval); - break; + return; } } } +async function waitUbootStop(writer, reader, sfpModel, outputMsgCallback) { + const interval = setInterval(function() { + writer.write(String.fromCharCode(3)); + }, 10); + + await detectUboot(reader); + outputMsgCallback(`Root in progress: Trigger characters received. DO NOT TOUCH THE ${sfpModel} UNTIL THE PROCEDURE IS COMPLETED!`); + await delay(5000); + clearInterval(interval); +} + async function checkUbootUnlocked(reader) { while (true) { try { @@ -131,3 +139,120 @@ async function unlockHuaweiShell(port, outputMsgCallback, outputErrorCallback, b return false; } } + +async function changeBaudrate(port, newBaudrate, currBaudrate, outputErrorCallback) { + let reader,writer, readableStreamClosed, writerStreamClosed; + + try { + ({ reader, writer, readableStreamClosed, writerStreamClosed } = await openPortLineBreak(port, currBaudrate)); + await writer.write(`setenv baudrate ${newBaudrate}\n`); + await delay(1000); + await closePortLineBreak(port, reader, writer, readableStreamClosed, writerStreamClosed); + ({ reader, writer, readableStreamClosed, writerStreamClosed } = await openPortLineBreak(port, newBaudrate)); + + const interval = setInterval(function() { + writer.write(String.fromCharCode(13)); + }, 10); + + while (true) { + const { value, done } = await reader.read(); + + if (value.startsWith('FALCON')) { + clearInterval(interval); + break; + } + } + + await closePortLineBreak(port, reader, writer, readableStreamClosed, writerStreamClosed); + return true; + } catch (err) { + outputErrorCallback(`Error: ${err.message}`); + await closePortLineBreak(port, reader, writer, readableStreamClosed, writerStreamClosed); + return false; + } +} + +async function sendImageMtd(port, data, baudRate, outputErrorCallback, progressCallback) { + let reader,writer, readableStreamClosed, writerStreamClosed; + + try { + ({ reader, writer, readableStreamClosed, writerStreamClosed } = await openPortLineBreak(port, baudRate)); + await writer.write(`loady 0x${LOAD_ADDR}\n`); + await delay(1000); + await closePortLineBreak(port, reader, writer, readableStreamClosed, writerStreamClosed); /* XYMini needs reopen the port */ + } catch (err) { + outputErrorCallback(`Error: ${err.message}`); + await closePortLineBreak(port, reader, writer, readableStreamClosed, writerStreamClosed); + return false; + } + + try { + await port.open({ baudRate: baudRate }); + reader = port.readable.getReader(); + writer = port.writable.getWriter(); + + await sendXYMini(reader, writer, data, baudRate, + (byteTransfered) => { + progressCallback(byteTransfered); + } + ); + await reader.cancel(); + await writer.close(); + await port.close(); + return true; + } catch (err) { + await reader.cancel(); + await writer.close(); + await port.close(); + outputErrorCallback(`Error: ${err.message}`); + return false; + } +} + +async function waitEndImageLoad(port, baudRate, outputErrorCallback) { + let reader, writer, readableStreamClosed, writerStreamClosed; + + try { + ({ reader, writer, readableStreamClosed, writerStreamClosed } = await openPortLineBreak(port, baudRate)); + + while (true) { + const { value, done } = await reader.read(); + + if (value.includes('Total Size')) { + break; + } + } + + await(1000); + await closePortLineBreak(port, reader, writer, readableStreamClosed, writerStreamClosed); + return true; + } catch (err) { + outputErrorCallback(`Error: ${err.message}`); + await closePortLineBreak(port, reader, writer, readableStreamClosed, writerStreamClosed); + return false; + } +} + +async function flashImageMtd(port, image, baudRate, outputErrorCallback) { + let reader, writer, readableStreamClosed, writerStreamClosed; + + try { + ({ reader, writer, readableStreamClosed, writerStreamClosed } = await openPortLineBreak(port, baudRate)); + if (image == "image0") { + await writer.write(`sf probe 0 && sf erase ${IMAGE0_ADDR} && sf write ${LOAD_ADDR} ${IMAGE0_ADDR} && setenv committed_image 0 && setenv image0_is_valid 1 && saveenv && reset\n`); + } else { + await writer.write(`sf probe 0 && sf erase ${IMAGE1_ADDR} && sf write ${LOAD_ADDR} ${IMAGE1_ADDR} && setenv committed_image 1 && setenv image1_is_valid 1 && saveenv && reset\n`); + } + + await delay(1000); + + /* Wait to avoid the user from disconnecting the SFP while the image is being flashed */ + await detectUboot(reader); + await closePortLineBreak(port, reader, writer, readableStreamClosed, writerStreamClosed); + return true; + } catch (err) { + outputErrorCallback(`Error: ${err.message}`); + await closePortLineBreak(port, reader, writer, readableStreamClosed, writerStreamClosed); + return false; + } +} diff --git a/assets/js/xymini.js b/assets/js/xymini.js new file mode 100644 index 0000000..a1fd1ca --- /dev/null +++ b/assets/js/xymini.js @@ -0,0 +1,147 @@ +/* XYMini Sender - Minimal implementation of file transfer through serial + * Copyright (C) Ernesto Castellotti + * SPDX-License-Identifier: MPL-2.0-no-copyleft-exception + * + * Warning: This does not comply with XMODEM and YMODEM standards +*/ + +const STX = 0x02; +const ACK = 0x06; +const NAK = 0x15; +const EOF = 0x04; +const XYMINI_1K_MAGIC = 0x43; +const PAYLOAD_LEN = 1024; +const BLOCK_LEN = PAYLOAD_LEN + 5; +const CRC_POLY = 0x1021; + +function uint16 (n) { + return n & 0xFFFF; +} + +function updateCrc(crcIn, incr) { + const xor = uint16(crcIn >> 15); + let result = uint16(crcIn << 1); + + if (incr) { + result = uint16(result + 1); + } + + if (xor) { + result = uint16(result ^= CRC_POLY); + } + + return result; +} + +function crc16(data) { + let crc; + + for (let i = 0; i < data.length; i++) { + for (let j = 0x80; j; j >>= 1) { + crc = updateCrc(crc, data[i] & j); + } + } + + for (let n = 0; n < 16; n++) { + crc = updateCrc(crc, 0); + } + + return crc; +} + +async function detectXYMini(reader) { + const textDecoder = new TextDecoder(); + + while (true) { + const { value, done } = await reader.read(); + + if (value[0] == XYMINI_1K_MAGIC) { + console.log("XYMini: detected"); + break; + } + } +} + +function generateXYMiniBlock(blockId, payload) { + let buf = new Uint8Array(BLOCK_LEN); + let i = 0; + + buf[i++] = STX; + buf[i++] = blockId; + buf[i++] = 0xFF - blockId; + + if (payload.length > PAYLOAD_LEN) { + throw new Error("Payload too large to be transmitted in one block"); + } + + for (let j = 0; j < payload.length; j++) { + buf[i++] = payload[j]; + } + + while (i < BLOCK_LEN - 2) { + buf[i++] = 0xFF; + } + + let crcBuf = buf.slice(3, PAYLOAD_LEN + 3) + let crc = crc16(crcBuf); + + buf[i++] = (crc >> 8) & 0xFF; + buf[i++] = crc & 0xFF; + + return buf; +} + +async function sendXYMini(portReader, portWriter, data, baudRate = 115200, progressCallback) { + let blockId = 1; + let size = data.length; + let i = 0; + let nakN = 0; + let wrongCharN = 0; + + await detectXYMini(portReader); + + while(true) { + const payloadSize = Math.min(PAYLOAD_LEN, size); + + if (size) { + const payload = data.slice(i, payloadSize + i); + + const block = generateXYMiniBlock(blockId, payload); + await portWriter.write(block); + } else { + portWriter.write(new Uint8Array([EOF])); + } + + const { value, done } = await portReader.read(); + + if (value[0] == ACK) { + if (!size) { + console.log("XYMini: End of transmission"); + + return; + } + + blockId++; + size -= payloadSize; + i += payloadSize; + nakN = 0; + wrongCharN = 0; + progressCallback(data.length - size); + } else if (value[0] == NAK) { + if (nakN >= 10) { + throw new Error("Received 10 NAK, receiver is rejecting file transmission"); + } + + console.log("XYMini: NAK"); + nakN++; + } else { + if (wrongCharN >= 30) { + throw new Error("Received 30 wrong characters, the receiver is rejecting the transmission or the connection is too noisy"); + } + + console.log("XYMini: wrong character"); + console.log(value); + wrongCharN++; + } + } +} -- cgit v1.2.3