jetson-flash
Version:
This tool allows users to flash BalenaOS on specific Jetson devices using Nvidia flashing tools.
455 lines (423 loc) • 15.7 kB
JavaScript
/*
* Copyright 2018 - 2020 Balena.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
const _ = require('lodash');
const Bluebird = require('bluebird');
const convert = require('xml-js');
const str = require('string-to-stream');
const fs = Bluebird.promisifyAll(require('fs'));
const mkdirp = require('mkdirp');
const { copy } = require('fs-extra');
const { tmpdir } = require('os');
const { join } = require('path');
const { applyPatch } = require('apply-patch');
const { spawn } = require('child_process');
const {
unwrapResinImageFlasher,
} = require('../scripts/resin-image-flasher-unwrap.js');
const utils = require('./utils.js');
const path = require('path');
module.exports = class ResinJetsonFlash {
constructor(deviceType, image, odmdata, assetDir, output, cache) {
this.deviceType = deviceType;
this.image = image;
this.odmdata = odmdata;
this.assetDir = assetDir;
this.output = output;
this.cache = cache;
this.dictionary = {
['jetson-tx2']: {
url:
'https://developer.nvidia.com/downloads/remksjetpack-463r32releasev73t186jetsonlinur3273aarch64tbz2',
partitions: {
kernel: { path: null },
kernel_b: { path: null },
'bootloader-dtb': { path: null },
'bootloader-dtb_b': { path: null },
'kernel-dtb': { path: null },
'kernel-dtb_b': { path: null },
'resin-boot': { path: null },
'resin-rootA': { path: null },
'resin-rootB': { path: null },
'resin-state': { path: null },
'resin-data': { path: null },
},
medium: 'sdmmc_user',
},
['jetson-xavier-nx-devkit-tx2-nx']: {
url:
'https://developer.nvidia.com/downloads/remksjetpack-463r32releasev73t186jetsonlinur3273aarch64tbz2',
partitions: {
kernel: { path: null },
kernel_b: { path: null },
'bootloader-dtb': { path: null },
'bootloader-dtb_b': { path: null },
'kernel-dtb': { path: null },
'kernel-dtb_b': { path: null },
'resin-boot': { path: null },
'resin-rootA': { path: null },
'resin-rootB': { path: null },
'resin-state': { path: null },
'resin-data': { path: null },
},
medium: 'sdmmc_user',
},
['jetson-nano-emmc']: {
url:
'https://developer.nvidia.com/downloads/remetpack-463r32releasev73t210jetson-210linur3273aarch64tbz2',
partitions: {
LNX: { path: null },
DTB: { path: null },
RP1: { path: null },
BMP: { path: null },
'resin-boot': { path: null },
'resin-rootA': { path: null },
'resin-rootB': { path: null },
'resin-state': { path: null },
'resin-data': { path: null },
},
medium: 'sdmmc',
},
['jetson-nano-2gb-devkit']: {
url:
'https://developer.nvidia.com/embedded/l4t/r32_release_v7.1/t210/jetson-210_linux_r32.7.1_aarch64.tbz2',
partitions: {
LNX: { path: null },
BMP: { path: null },
'resin-boot': { path: null },
'resin-rootA': { path: null },
'resin-rootB': { path: null },
'resin-state': { path: null },
'resin-data': { path: null },
},
medium: ['sdcard', 'spi']
},
['jetson-nano-qspi-sd']: {
url:
'https://developer.nvidia.com/downloads/remetpack-463r32releasev73t210jetson-210linur3273aarch64tbz2',
partitions: {
LNX: { path: null },
DTB: { path: null },
RP1: { path: null },
BMP: { path: null },
'resin-boot': { path: null },
'resin-rootA': { path: null },
'resin-rootB': { path: null },
'resin-state': { path: null },
'resin-data': { path: null },
},
medium: ['sdcard', 'spi']
},
['jetson-xavier']: {
url:
'https://developer.nvidia.com/downloads/remksjetpack-463r32releasev73t186jetsonlinur3273aarch64tbz2',
partitions: {
'bootloader-dtb': { path: null },
'bootloader-dtb_b': { path: null },
kernel: { path: null },
kernel_b: { path: null },
'kernel-dtb': { path: null },
'kernel-dtb_b': { path: null },
BMP: { path: null },
BMP_b: { path: null },
'bpmp-fw-dtb': { path: null },
'bpmp-fw-dtb_b': { path: null },
'resin-boot': { path: null },
'resin-rootA': { path: null },
'resin-rootB': { path: null },
'resin-state': { path: null },
'resin-data': { path: null },
},
medium: 'sdmmc_user',
},
['jetson-agx-orin-devkit']: {
url:
'https://developer.nvidia.com/downloads/embedded/l4t/r36_release_v4.3/release/Jetson_Linux_r36.4.3_aarch64.tbz2',
partitions: {
'resin-boot': { path: null },
'resin-rootA': { path: null },
'resin-rootB': { path: null },
'resin-state': { path: null },
'resin-data': { path: null },
},
medium: 'sdmmc_user',
},
['jetson-xavier-nx-devkit-emmc']: {
url:
'https://developer.nvidia.com/downloads/remksjetpack-463r32releasev73t186jetsonlinur3273aarch64tbz2',
partitions: {
kernel: { path: null },
kernel_b: { path: null },
'kernel-dtb': { path: null },
'kernel-dtb_b': { path: null },
'resin-boot': { path: null },
'resin-rootA': { path: null },
'resin-rootB': { path: null },
'resin-state': { path: null },
'resin-data': { path: null },
},
medium: 'sdmmc_user',
},
['jetson-xavier-nx-devkit']: {
url:
'https://developer.nvidia.com/downloads/remksjetpack-463r32releasev73t186jetsonlinur3273aarch64tbz2',
partitions: {
kernel: { path: null },
kernel_b: { path: null },
'kernel-dtb': { path: null },
'kernel-dtb_b': { path: null },
'resin-boot': { path: null },
'resin-rootA': { path: null },
'resin-rootB': { path: null },
'resin-state': { path: null },
'resin-data': { path: null },
},
medium: 'sdcard',
},
};
}
spawnFlash() {
return new Bluebird(async (resolve, reject) => {
const workPath = join(tmpdir(), 'Linux_for_Tegra');
await copy(join(this.output, 'Linux_for_Tegra'), workPath);
process.chdir(workPath);
/* Create an empty extlinux.conf so that flash.sh won't fail */
const extlinuxDir = mkdirp.sync(workPath + '/rootfs/boot/extlinux');
fs.closeSync(fs.openSync(workPath + '/rootfs/boot/extlinux/extlinux.conf', 'w'));
fs.mkdirSync(workPath + '/bootloader/', { recursive: true });
fs.mkdirSync(workPath + '/tools/ota_tools/version_upgrade/', { recursive: true });
fs.closeSync(fs.openSync(workPath + '/bootloader/recovery.img', 'w'));
fs.writeFileSync(workPath + '/tools/ota_tools/version_upgrade/recovery_copy_binlist.txt', '');
fs.writeFileSync(workPath + '/tools/ota_tools/version_upgrade/ota_make_recovery_img_dtb.sh', '');
if ("jetson-agx-orin-devkit" == this.deviceType.toString()) {
fs.copyFileSync(path.resolve(__dirname, '../AGX_Orin/bootloader/uefi_jetson.bin'), workPath + '/bootloader/uefi_jetson.bin');
const flashfile = join(workPath, '/flash.sh');
console.log('Fixing flash.sh for AGX Orin Devkit');
try {
var data = fs.readFileSync(flashfile, 'utf8');
var result = data.replace(/limit_boot_chains \|/g, ' ');
var result2 = result.replace(/limit_qspi_device \|/g,' ');
console.log("Fixed flash");
fs.writeFileSync(flashfile, result2, 'utf8');
} catch (err) {
console.error(err);
}
var dtsi = join(workPath, '/bootloader/tegra234-firewall-config-base.dtsi');
var dtsiData = fs.readFileSync(dtsi, 'utf8').toString().split("\n");
var replaceStr = " exclusion-info = <0>;"
const replaceArr = [2766, 2771, 2776, 2781, 2786, 2791, 2796, 2801, 2806, 2811, 2816, 2821, 2826, 2831, 25712, 25717, 25722, 25727, 25732, 25737, 25742, 25747, 25752, 25757, 25762, 25767];
for (let i=0; i < replaceArr.length; i++) {
/* L4T 35.4.1 added one extra line at the start of the dtsi */
dtsiData.splice((replaceArr[i] - 1), 1, replaceStr);
}
var replacedContents = dtsiData.join("\n");
fs.writeFileSync(workPath + '/bootloader/tegra234-firewall-config-base.dtsi', replacedContents, 'utf8');
dtsi = join(workPath, '/bootloader/tegra234-mb2-bct-common.dtsi');
dtsiData = fs.readFileSync(dtsi, 'utf8');
replacedContents = dtsiData.replace(/cvb_eeprom_read_size = <0x100>;/g, 'cvb_eeprom_read_size = <0x0>;');
fs.writeFileSync(workPath + '/bootloader/tegra234-mb2-bct-common.dtsi', replacedContents, 'utf8');
}
if ("default" != this.odmdata.toString()) {
if ("jetson-tx2" == this.deviceType.toString()) {
/* flash.sh from the BSP has a bug which causes the -o argument
* to be ignored. Let's edit the configuration file to overcome this
*/
const odmfile = join(workPath, '/p2771-0000.conf.common');
console.log('Using ODMDATA value: ' + this.odmdata.toString());
try {
var data = fs.readFileSync(odmfile, 'utf8');
// Default ODMDATA configuration option in the BSP archive is #2 - 0x1090000
var result = data.replace(/ODMDATA=0x1090000/g, 'ODMDATA=' + this.odmdata.toString());
console.log("Replaced ODMDATA in " + odmfile);
fs.writeFileSync(odmfile, result, 'utf8');
} catch (err) {
console.error(err);
}
}
} else {
console.log("ODMDATA not specified, using default value set by BSP archive")
}
if (this.deviceType.toString().startsWith("jetson-xavier")) {
try {
fs.copyFileSync(this.assetDir + '/../cbo/cbo.dtb', workPath + '/bootloader/cbo.dtb');
} catch (err) {
console.error(err);
}
}
const child = spawn(
'sudo',
[
'bash',
'./flash.sh',
'-c',
join(this.output, 'resin', 'flash.xml'),
this.deviceType,
'mmcblk0p12',
],
{
stdio: 'inherit',
},
);
child.on('error', () => {
utils.sudoRm(workPath);
reject();
});
child.on('exit', () => {
utils.sudoRm(workPath);
resolve();
});
});
}
async injectFilepathInFlashXML(options) {
const xml = await fs.readFileAsync(options.file);
const result = convert.xml2js(xml);
const stream = fs.createWriteStream(options.output);
result.elements
.find(value => {
return value.name === 'partition_layout';
})
.elements.filter(value => {
return ( this.deviceType === 'jetson-nano-qspi-sd' || this.deviceType === 'jetson-nano-2gb-devkit' ?
value.attributes.type === this.dictionary[this.deviceType].medium[0] || value.attributes.type === this.dictionary[this.deviceType].medium[1] :
value.attributes.type === this.dictionary[this.deviceType].medium
);
})
.forEach(value => {
value.elements
.filter(value => {
return (
value.name === 'partition' &&
Object.keys(options.partitions).includes(value.attributes.name)
);
})
.forEach(value => {
value.elements
.filter(element => {
return element.name === 'filename';
})
.forEach(element => {
element.elements.forEach(filename => {
filename.text =
options.partitions[value.attributes.name].path;
});
});
});
/* Data partition size will differ according to the preloaded image size,
* let's update the xml with the actual size in bytes. This is available
* only for the data partition.
*/
value.elements.
filter(value => {
return (
value.name === 'partition' &&
Object.keys(options.partitions).includes(value.attributes.name) &&
value.attributes.name === 'resin-data'
);
})
.forEach(value => {
value.elements
.filter(element => {
return element.name === 'size';
})
.forEach(element => {
element.elements.forEach(size => {
size.text =
options.partitions['resin-data'].size;
});
});
});
});
return new Bluebird((resolve, reject) => {
const read = str(
convert.js2xml(result, {
spaces: 4,
}),
);
read.pipe(stream);
read.on('end', () => {
resolve(options.output);
});
read.on('error', reject);
});
}
async generateArtifacts() {
const nvidiaToolPath = join(this.output, 'Linux_for_Tegra');
if ((!(await utils.checkConsistency(nvidiaToolPath)) && !this.cache)) {
await utils.decompressTbz2FromUrl(
this.dictionary[this.deviceType].url,
this.output,
);
await utils.generateStamp(nvidiaToolPath);
} else {
console.log('Using cached Linux_for_Tegra');
}
// TODO: Patching the XML is currently disabled as it needs a fix to be used in Autokit
/*
if (this.deviceType === 'jetson-agx-orin-devkit') {
fs.copyFileSync(join(nvidiaToolPath + '/bootloader/generic/cfg/flash_t234_qspi_sdmmc.xml'), join(this.assetDir, 'flash_t234_qspi_sdmmc.xml'));
console.log("Copied " + join(nvidiaToolPath + '/bootloader/generic/cfg/flash_t234_qspi_sdmmc.xml') + ' to ' + join(this.assetDir, 'flash_t234_qspi_sdmmc.xml'));
// Diff generated with `diff -u flash_t234_qspi_sdmmc.xml resinOS-flash.xml > resinOS.patch`
var patchPath = join(this.assetDir, '/resinOS.patch');
var patchContents = fs.readFileSync(join(this.assetDir, 'resinOS.patch'), 'utf8');
var replacedContents = patchContents.replace(/deviceAssetsPath/g, join(this.assetDir));
fs.writeFileSync(patchPath, replacedContents, 'utf8');
applyPatch(join(this.assetDir, 'resinOS.patch'));
}
*/
const resinImagePath = join(this.output, 'resin');
if (!(await utils.cached(resinImagePath, this.image))) {
const unwrappedImage = await unwrapResinImageFlasher(
this.image,
join(resinImagePath, 'img'),
);
for (const partition of Object.keys(
this.dictionary[this.deviceType].partitions,
)) {
if (
this.dictionary[this.deviceType].partitions[partition].path != null
) {
continue;
}
this.dictionary[this.deviceType].partitions[
partition
].path = await utils.extractPartition(
unwrappedImage,
partition,
resinImagePath,
);
}
const dataPartition = (await utils.getPartitionTableWithNames(
unwrappedImage,
)).partitions.find(part => {
return part.name === 'resin-data';
});
console.log(`Data partition size in bytes: ${dataPartition.size}`);
this.dictionary[this.deviceType].partitions['resin-data'].size = dataPartition.size;
await this.injectFilepathInFlashXML({
file: join(this.assetDir, 'resinOS-flash.xml'),
output: join(resinImagePath, 'flash.xml'),
partitions: this.dictionary[this.deviceType].partitions,
});
await utils.generateCache(resinImagePath, this.image);
} else {
console.log('Using cached resin image');
}
}
async run() {
await this.generateArtifacts();
await this.spawnFlash();
}
};