Skip to content

Commit e9e075f

Browse files
committed
ev3: Fix issues with flashing (some?) EV3s.
Flash the EV3 all in one go rather than sector-by-sector. This appears to resolve issues we had with flashing (some?) EV3s, and mirrors what we do in pybricksdev.
1 parent 4516cc8 commit e9e075f

File tree

1 file changed

+102
-59
lines changed

1 file changed

+102
-59
lines changed

src/firmware/sagas.ts

Lines changed: 102 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,20 @@ function* loadFirmware(
351351
'Expected metadata to be v2.x',
352352
);
353353

354-
const firmware = new Uint8Array(firmwareBase.length + 4);
354+
const [checksumFunc, checksumSize] = (function () {
355+
switch (metadata['checksum-type']) {
356+
case 'sum':
357+
return [sumComplement32, 4];
358+
case 'crc32':
359+
return [crc32, 4];
360+
case 'none':
361+
return [null, 0];
362+
default:
363+
return [undefined, 0];
364+
}
365+
})();
366+
367+
const firmware = new Uint8Array(firmwareBase.length + checksumSize);
355368
const firmwareView = new DataView(firmware.buffer);
356369

357370
firmware.set(firmwareBase);
@@ -361,22 +374,7 @@ function* loadFirmware(
361374
firmware.set(encodeHubName(hubName, metadata), metadata['hub-name-offset']);
362375
}
363376

364-
const checksum = (function () {
365-
switch (metadata['checksum-type']) {
366-
case 'sum':
367-
return sumComplement32(
368-
firmwareIterator(firmwareView, metadata['checksum-size']),
369-
);
370-
case 'crc32':
371-
return crc32(firmwareIterator(firmwareView, metadata['checksum-size']));
372-
case 'none':
373-
return null;
374-
default:
375-
return undefined;
376-
}
377-
})();
378-
379-
if (checksum === undefined) {
377+
if (checksumFunc === undefined) {
380378
// FIXME: we should return error/throw instead
381379
yield* put(
382380
didFailToFinish(
@@ -391,8 +389,12 @@ function* loadFirmware(
391389
throw new Error('unreachable');
392390
}
393391

394-
if (checksum !== null) {
395-
firmwareView.setUint32(firmwareBase.length, checksum, true);
392+
if (checksumFunc !== null) {
393+
firmwareView.setUint32(
394+
firmwareBase.length,
395+
checksumFunc(firmwareIterator(firmwareView, metadata['checksum-size'])),
396+
true,
397+
);
396398
}
397399

398400
return { firmware, deviceId: metadata['device-id'] };
@@ -1219,73 +1221,114 @@ function* handleFlashEV3(action: ReturnType<typeof firmwareFlashEV3>): Generator
12191221

12201222
defined(version);
12211223

1222-
console.debug(
1223-
`EV3 bootloader version: ${version.getUint32(
1224-
0,
1225-
true,
1226-
)}, HW version: ${version.getUint32(4, true)}`,
1227-
);
1224+
// For reasons that we do not currently understand, some EV3s do not return
1225+
// anything to our GetVersion command. We don't actually use the version
1226+
// for anything so we will just ignore this error.
1227+
try {
1228+
console.debug(
1229+
`EV3 bootloader version: ${version.getUint32(
1230+
0,
1231+
true,
1232+
)}, HW version: ${version.getUint32(4, true)}`,
1233+
);
1234+
} catch (err) {
1235+
console.error(`Failed to parse ev3 version response: ${ensureError(err)}`);
1236+
}
12281237

12291238
// FIXME: should be called much earlier.
12301239
yield* put(didStart());
12311240

12321241
const sectorSize = 64 * 1024; // flash memory sector size
12331242
const maxPayloadSize = 1018; // maximum payload size for EV3 commands
12341243

1235-
for (let i = 0; i < action.firmware.byteLength; i += sectorSize) {
1236-
const sectorData = action.firmware.slice(i, i + sectorSize);
1237-
assert(sectorData.byteLength <= sectorSize, 'sector data too large');
1244+
console.info(`Firmware size: ${action.firmware.byteLength} bytes`);
1245+
1246+
// Apparently, erasing a span of the flash creates some sort of record in
1247+
// the EV3, and we can only write within a given erase span. Writes that
1248+
// cross the boundary will hang. To avoid this, we erase the whole firmware
1249+
// range at once.
1250+
const numSectors = Math.floor(action.firmware.byteLength / sectorSize);
1251+
assert(
1252+
action.firmware.byteLength === numSectors * sectorSize,
1253+
'Firmware size is required to be a round multiple of sector size.',
1254+
);
1255+
1256+
const erasePayload = new DataView(new ArrayBuffer(8));
1257+
erasePayload.setUint32(0, 0, true); // start address
1258+
erasePayload.setUint32(4, numSectors * sectorSize, true); // size
1259+
console.debug(
1260+
`Erasing bytes [0x${(0).toString(16)}, 0x${(numSectors * sectorSize).toString(
1261+
16,
1262+
)})`,
1263+
);
1264+
1265+
yield* put(
1266+
alertsShowAlert(
1267+
'firmware',
1268+
'flashProgress',
1269+
{
1270+
action: 'erase',
1271+
progress: undefined,
1272+
},
1273+
firmwareBleProgressToastId,
1274+
true,
1275+
),
1276+
);
1277+
1278+
const [, eraseError] = yield* sendCommand(
1279+
0xf0,
1280+
new Uint8Array(erasePayload.buffer),
1281+
);
1282+
1283+
if (eraseError) {
1284+
yield* put(
1285+
alertsShowAlert('alerts', 'unexpectedError', {
1286+
error: eraseError,
1287+
}),
1288+
);
1289+
// FIXME: should have a better error reason
1290+
yield* put(didFailToFinish(FailToFinishReasonType.Unknown, eraseError));
1291+
yield* put(firmwareDidFailToFlashEV3());
1292+
yield* cleanup();
1293+
return;
1294+
}
12381295

1239-
const erasePayload = new DataView(new ArrayBuffer(8));
1240-
erasePayload.setUint32(0, i, true);
1241-
erasePayload.setUint32(4, sectorData.byteLength, true);
1242-
const [, eraseError] = yield* sendCommand(
1243-
0xf0,
1244-
new Uint8Array(erasePayload.buffer),
1296+
// If we don't write an exact multiple of the sector size, the flash process
1297+
// will hang on the last write we send.
1298+
const firmware = action.firmware;
1299+
for (let i = 0; i < firmware.byteLength; i += maxPayloadSize) {
1300+
const writeLength = Math.min(maxPayloadSize, firmware.byteLength - i);
1301+
const payload = firmware.slice(i, i + writeLength);
1302+
console.debug(
1303+
`Programming bytes [0x${i.toString(16)}, 0x${(i + writeLength).toString(
1304+
16,
1305+
)})`,
12451306
);
12461307

1247-
if (eraseError) {
1308+
const [, sendError] = yield* sendCommand(0xf2, new Uint8Array(payload));
1309+
if (sendError) {
12481310
yield* put(
12491311
alertsShowAlert('alerts', 'unexpectedError', {
1250-
error: eraseError,
1312+
error: sendError,
12511313
}),
12521314
);
12531315
// FIXME: should have a better error reason
1254-
yield* put(didFailToFinish(FailToFinishReasonType.Unknown, eraseError));
1316+
yield* put(didFailToFinish(FailToFinishReasonType.Unknown, sendError));
12551317
yield* put(firmwareDidFailToFlashEV3());
12561318
yield* cleanup();
12571319
return;
12581320
}
12591321

1260-
for (let j = 0; j < sectorData.byteLength; j += maxPayloadSize) {
1261-
const payload = sectorData.slice(j, j + maxPayloadSize);
1262-
1263-
const [, sendError] = yield* sendCommand(0xf2, new Uint8Array(payload));
1264-
if (sendError) {
1265-
yield* put(
1266-
alertsShowAlert('alerts', 'unexpectedError', {
1267-
error: sendError,
1268-
}),
1269-
);
1270-
// FIXME: should have a better error reason
1271-
yield* put(didFailToFinish(FailToFinishReasonType.Unknown, sendError));
1272-
yield* put(firmwareDidFailToFlashEV3());
1273-
yield* cleanup();
1274-
return;
1275-
}
1276-
}
1277-
1278-
yield* put(
1279-
didProgress((i + sectorData.byteLength) / action.firmware.byteLength),
1280-
);
1322+
const progress = (i + writeLength) / firmware.byteLength;
1323+
yield* put(didProgress(progress));
12811324

12821325
yield* put(
12831326
alertsShowAlert(
12841327
'firmware',
12851328
'flashProgress',
12861329
{
12871330
action: 'flash',
1288-
progress: (i + sectorData.byteLength) / action.firmware.byteLength,
1331+
progress: progress,
12891332
},
12901333
firmwareBleProgressToastId,
12911334
true,

0 commit comments

Comments
 (0)