diff options
Diffstat (limited to '')
-rw-r--r-- | _ont/ont-huawei-ma5671a-ymodem.md | 206 | ||||
-rw-r--r-- | _sass/custom/custom.scss | 4 | ||||
-rw-r--r-- | assets/js/rootLantiq.js | 141 | ||||
-rw-r--r-- | assets/js/xymini.js | 147 |
4 files changed, 490 insertions, 8 deletions
diff --git a/_ont/ont-huawei-ma5671a-ymodem.md b/_ont/ont-huawei-ma5671a-ymodem.md new file mode 100644 index 0000000..9756dd0 --- /dev/null +++ b/_ont/ont-huawei-ma5671a-ymodem.md @@ -0,0 +1,206 @@ +--- +title: Root Procedure for Huawei MA5671A (flash firmware) +has_children: false +parent: Huawei MA5671A +layout: default +--- + +<div class="modal" data-jtd-modal="flash-modal" data-jtd-modal-backdrop="static" id="flash-modal"> + <div class="modal-content"> + <div class="modal-header"> + <span class="close">×</span> + <h2>Flash firmware</h2> + </div> + <form id="flash-form" class="p-4" novalidate> + <div class="form-floating mb-3"> + <input type="file" class="form-control" placeholder="Flash MTD" name="flash-mtd" id="flash-mtd" required> + <label for="flash-mtd">Flash MTD</label> + </div> + <div class="mb-3 form-check"> + <input type="checkbox" class="form-check-input" id="baud-rate-oc" name="baud-rate-oc"> + <label class="form-check-label" for="baud-rate-oc">230400 baud rate, do not enable unless told to do so</label> + </div> + <div class="form-check"> + <input class="form-check-input" type="radio" name="image" id="image0" value="image0"> + <label class="form-check-label" for="image0"> + Image 0 + </label> + </div> + <div class="form-check"> + <input class="form-check-input" type="radio" name="image" id="image1" value="image1"> + <label class="form-check-label" for="image1"> + Image 1 + </label> + </div> + <div class="mb-3"> + <input type="submit" class="btn btn-primary" value="Flash!"> + </div> + <progress id="flash-progress" value="0" max="100"></progress> + <p id="flash-text-step"></p> + </form> + </div> +</div> + +<button id="flash-start-button" class="btn btn-blue" data-jtd-toogle="modal" data-jtd-target="#flash-modal">Start flash!</button> + +<script type="text/javascript" src="/assets/js/xymini.js"></script> +<script type="text/javascript" src="/assets/js/rootLantiq.js"></script> +<script type="text/javascript" src="/assets/js/serialUtil.js"></script> +<script> + const acontroller = new AbortController(); + const cs = acontroller.signal; + let flashModal = document.getElementById("flash-modal"); + let flashForm = document.getElementById("flash-form"); + let flashProgress = document.getElementById("flash-progress"); + let flashTextStep = document.getElementById("flash-text-step"); + flashModal.addEventListener('modal-jtd-close', async function(event) { + acontroller.abort(); + }); + flashModal.addEventListener('modal-jtd-open', async function(event) { + flash({signal: cs}); + }); + function initTextStep() { + flashTextStep.textContent = ""; + flashTextStep.classList.remove('success'); + flashTextStep.classList.remove('error'); + } + function pause(message) { + flashTextStep.textContent = message; + } + function loading(message) { + flashTextStep.textContent = message; + } + function showError(message) { + flashTextStep.textContent = message; + flashTextStep.classList.add('error'); + flashTextStep.classList.remove('success'); + } + function showSuccess(message) { + flashTextStep.textContent = message; + flashTextStep.classList.add('success'); + flashTextStep.classList.remove('error'); + } + async function flash({ signal } = {}) { + initTextStep(); + let port; + try { + port = await navigator.serial.requestPort(); + } catch (err) { + showError(`Error: ${err.message}`); + console.log(`Error: ${err.message}\n`); + return; + } + if (!port) { + showError('Error: port not open'); + console.log('Error: port not open\n'); + return; + } + flashForm.addEventListener('submit', async function(event) { + [...flashForm.elements].map(function(e){return e.parentNode}).forEach(function(e){e.classList.toggle('was-validated', true)}); + if (!flashForm.checkValidity()) { + event.preventDefault(); + } else { + event.preventDefault(); + var fomrdata = new FormData(flashForm); + var file = fomrdata.get('flash-mtd'); + var image = fomrdata.get('image'); + var data = new Uint8Array(await file.arrayBuffer()); + console.log(data); + + /* Unlock U-Boot if needed and stop booting */ + let result = await lantiqRootUboot(port, "Huawei MA5671A", + (msg) => { + loading(msg); + console.log(msg); + }, + (err) => { + showError(err); + console.log(err); + } + ); + + if (!result) { + return; + } + + let baudrate = 115200; + if(fomrdata.has('baud-rate-oc')) { + let newBaudrate = 230400; + loading(`Changing baudrate to: ${newBaudrate}`); + + result = await changeBaudrate(port, newBaudrate, baudrate, + (err) => { + showError(err); + console.log(err); + } + ); + + if (result) { + baudrate = newBaudrate; + } else { + return; + } + } + + loading("Start sending image to the SFP..."); + result = await sendImageMtd(port, data, baudrate, + (err) => { + showError(err); + console.log(err); + }, + (byteTransfered) => { + const perc = (byteTransfered/data.length) * 100; + const percTrunc = Math.trunc(perc*100)/100; /* Two decimal trunc */ + flashProgress.value = perc; + loading(`Image transfer: ${percTrunc}% complete`) + } + ); + + if (!result) { + return; + } + + result = await waitEndImageLoad(port, baudrate, + (err) => { + showError(err); + console.log(err); + } + ); + + if (!result) { + return; + } + + if(fomrdata.has('baud-rate-oc')) { + let newBaudrate = 115200; + loading(`Restore baudrate to: ${newBaudrate}`); + + result = await changeBaudrate(port, newBaudrate, baudrate, + (err) => { + showError(err); + console.log(err); + } + ); + + if (result) { + baudrate = newBaudrate; + } else { + return; + } + } + + loading("Transfer complete, image flash in progress. DO NOT REMOVE the SFP!"); + result = await flashImageMtd(port, image, baudrate, + (err) => { + showError(err); + console.log(err); + } + ); + + if (result) { + showSuccess("Flash completed, now you can remove SFP"); + } + } + }); + }; +</script> diff --git a/_sass/custom/custom.scss b/_sass/custom/custom.scss index 9651d11..f8bf607 100644 --- a/_sass/custom/custom.scss +++ b/_sass/custom/custom.scss @@ -231,4 +231,8 @@ figure { stroke-dashoffset: 0; } } +} + +progress { + width: 100%; }
\ No newline at end of file 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 <mail@ernestocastellotti.it> + * 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++; + } + } +} |