Skip to content

Commit 0b9b2ad

Browse files
committed
reintroduce QDL
This reverts commit b6cdc1a.
1 parent 07437e6 commit 0b9b2ad

19 files changed

+1920
-110
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
[flash.comma.ai](https://flash.comma.ai)
44

5-
This tool allows you to flash AGNOS onto your comma device. Uses a fork of [fastboot.js](https://github.com/kdrag0n/fastboot.js).
5+
This tool allows you to flash AGNOS onto your comma device.
66

77
## Development
88

bun.lockb

-795 Bytes
Binary file not shown.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
"dependencies": {
1717
"@fontsource-variable/inter": "^5.0.18",
1818
"@fontsource-variable/jetbrains-mono": "^5.0.21",
19-
"android-fastboot": "github:commaai/fastboot.js#c3ec6fe3c96a48dab46e23d0c8c861af15b2144a",
2019
"comlink": "^4.4.1",
20+
"crc-32": "^1.2.2",
2121
"jssha": "^3.3.1",
2222
"react": "^18.3.1",
2323
"react-dom": "^18.3.1",

src/QDL/firehose.js

Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
import { xmlParser } from "./xmlParser"
2+
import { concatUint8Array, containsBytes, compareStringToBytes, sleep, readBlobAsBuffer } from "./utils"
3+
import * as Sparse from "./sparse"
4+
5+
6+
class response {
7+
constructor(resp=false, data=new Uint8Array(), error="", log=[]) {
8+
this.resp = resp;
9+
this.data = data;
10+
this.error = error;
11+
this.log = log;
12+
}
13+
}
14+
15+
16+
class cfg {
17+
constructor() {
18+
this.ZLPAwareHost = 1;
19+
this.SkipStorageInit = 0;
20+
this.SkipWrite = 0;
21+
this.MaxPayloadSizeToTargetInBytes = 1048576;
22+
this.MaxPayloadSizeFromTargetInBytes = 4096;
23+
this.MaxXMLSizeInBytes = 4096;
24+
this.bit64 = true;
25+
this.SECTOR_SIZE_IN_BYTES = 4096;
26+
this.MemoryName = "UFS";
27+
this.maxlun = 6;
28+
}
29+
}
30+
31+
export class Firehose {
32+
constructor(cdc) {
33+
this.cdc = cdc;
34+
this.xml = new xmlParser();
35+
this.cfg = new cfg();
36+
this.luns = [];
37+
}
38+
39+
getStatus(resp) {
40+
if ("value" in resp) {
41+
let value = resp["value"];
42+
return (value === "ACK" || value === "true");
43+
}
44+
return true;
45+
}
46+
47+
async xmlSend(data, wait=true) {
48+
let dataToSend = new TextEncoder().encode(data).slice(0, this.cfg.MaxXMLSizeInBytes);
49+
await this.cdc?.write(dataToSend, null, wait);
50+
51+
let rData = new Uint8Array();
52+
let counter = 0;
53+
let timeout = 3;
54+
while (!(containsBytes("<response value", rData))) {
55+
let tmp = await this.cdc?.read();
56+
if (compareStringToBytes("", tmp)) {
57+
counter += 1;
58+
await sleep(50);
59+
if (counter > timeout) {
60+
break;
61+
}
62+
}
63+
rData = concatUint8Array([rData, tmp]);
64+
}
65+
66+
const resp = this.xml.getReponse(rData);
67+
const status = this.getStatus(resp);
68+
if ("rawmode" in resp) {
69+
if (resp["rawmode"] == "false") {
70+
let log = this.xml.getLog(rData);
71+
return new response(status, rData, "", log)
72+
}
73+
} else {
74+
if (status) {
75+
if (containsBytes("log value=", rData)) {
76+
let log = this.xml.getLog(rData);
77+
return new response(status, rData, "", log);
78+
}
79+
return new response(status, rData);
80+
}
81+
}
82+
return new response(true, rData);
83+
}
84+
85+
getLuns() {
86+
return Array.from({length: this.cfg.maxlun}, (x, i) => i)
87+
}
88+
89+
async configure() {
90+
const connectCmd = `<?xml version="1.0" encoding="UTF-8" ?><data>` +
91+
`<configure MemoryName="${this.cfg.MemoryName}" ` +
92+
`Verbose="0" ` +
93+
`AlwaysValidate="0" ` +
94+
`MaxDigestTableSizeInBytes="2048" ` +
95+
`MaxPayloadSizeToTargetInBytes="${this.cfg.MaxPayloadSizeToTargetInBytes}" ` +
96+
`ZLPAwareHost="${this.cfg.ZLPAwareHost}" ` +
97+
`SkipStorageInit="${this.cfg.SkipStorageInit}" ` +
98+
`SkipWrite="${this.cfg.SkipWrite}"/>` +
99+
`</data>`
100+
101+
await this.xmlSend(connectCmd, false);
102+
this.luns = this.getLuns();
103+
return true;
104+
}
105+
106+
async cmdReadBuffer(physicalPartitionNumber, startSector, numPartitionSectors) {
107+
const data = `<?xml version="1.0" ?><data><read SECTOR_SIZE_IN_BYTES="${this.cfg.SECTOR_SIZE_IN_BYTES}"` +
108+
` num_partition_sectors="${numPartitionSectors}"` +
109+
` physical_partition_number="${physicalPartitionNumber}"` +
110+
` start_sector="${startSector}"/>\n</data>`
111+
112+
let rsp = await this.xmlSend(data);
113+
let resData = new Uint8Array();
114+
if (!rsp.resp) {
115+
return rsp;
116+
} else {
117+
let bytesToRead = this.cfg.SECTOR_SIZE_IN_BYTES * numPartitionSectors;
118+
while (bytesToRead > 0) {
119+
let tmp = await this.cdc.read(Math.min(this.cdc.maxSize, bytesToRead));
120+
const size = tmp.length;
121+
bytesToRead -= size;
122+
resData = concatUint8Array([resData, tmp]);
123+
}
124+
125+
const wd = await this.waitForData();
126+
const info = this.xml.getLog(wd);
127+
rsp = this.xml.getReponse(wd);
128+
if ("value" in rsp) {
129+
if (rsp["value"] !== "ACK") {
130+
return new response(false, resData, info);
131+
} else if ("rawmode" in rsp) {
132+
if (rsp["rawmode"] === "false") {
133+
return new response(true, resData);
134+
}
135+
}
136+
} else {
137+
console.error("Failed read buffer");
138+
return new response(false, resData, rsp[2]);
139+
}
140+
}
141+
let resp = rsp["value"] === "ACK";
142+
return response(resp, resData, rsp[2]);
143+
}
144+
145+
async waitForData() {
146+
let tmp = new Uint8Array();
147+
let timeout = 0;
148+
149+
while (!containsBytes("response value", tmp)) {
150+
let res = await this.cdc.read();
151+
if (compareStringToBytes("", res)) {
152+
timeout += 1;
153+
if (timeout === 4) {
154+
break;
155+
}
156+
await sleep(20);
157+
}
158+
tmp = concatUint8Array([tmp, res]);
159+
}
160+
return tmp;
161+
}
162+
163+
async cmdProgram(physicalPartitionNumber, startSector, blob, onProgress=()=>{}) {
164+
let total = blob.size;
165+
let sparseformat = false;
166+
167+
let sparseHeader = await Sparse.parseFileHeader(blob.slice(0, Sparse.FILE_HEADER_SIZE));
168+
if (sparseHeader !== null) {
169+
sparseformat = true;
170+
total = await Sparse.getSparseRealSize(blob, sparseHeader);
171+
}
172+
173+
let numPartitionSectors = Math.floor(total / this.cfg.SECTOR_SIZE_IN_BYTES);
174+
if (total % this.cfg.SECTOR_SIZE_IN_BYTES !== 0) {
175+
numPartitionSectors += 1;
176+
}
177+
178+
const data = `<?xml version="1.0" ?><data>\n` +
179+
`<program SECTOR_SIZE_IN_BYTES="${this.cfg.SECTOR_SIZE_IN_BYTES}"` +
180+
` num_partition_sectors="${numPartitionSectors}"` +
181+
` physical_partition_number="${physicalPartitionNumber}"` +
182+
` start_sector="${startSector}" />\n</data>`;
183+
let i = 0;
184+
let bytesWritten = 0;
185+
let rsp = await this.xmlSend(data);
186+
187+
if (rsp.resp) {
188+
for await (let split of Sparse.splitBlob(blob)) {
189+
let offset = 0;
190+
let bytesToWriteSplit = split.size;
191+
192+
while (bytesToWriteSplit > 0) {
193+
const wlen = Math.min(bytesToWriteSplit, this.cfg.MaxPayloadSizeToTargetInBytes);
194+
let wdata = new Uint8Array(await readBlobAsBuffer(split.slice(offset, offset + wlen)));
195+
if (wlen % this.cfg.SECTOR_SIZE_IN_BYTES !== 0) {
196+
let fillLen = (Math.floor(wlen/this.cfg.SECTOR_SIZE_IN_BYTES) * this.cfg.SECTOR_SIZE_IN_BYTES) +
197+
this.cfg.SECTOR_SIZE_IN_BYTES;
198+
const fillArray = new Uint8Array(fillLen-wlen).fill(0x00);
199+
wdata = concatUint8Array([wdata, fillArray]);
200+
}
201+
await this.cdc.write(wdata);
202+
await this.cdc.write(new Uint8Array(0), null, true);
203+
offset += wlen;
204+
bytesWritten += wlen;
205+
bytesToWriteSplit -= wlen;
206+
207+
// Need this for sparse image when the data.length < MaxPayloadSizeToTargetInBytes
208+
// Add ~2.4s to total flash time
209+
if (sparseformat && bytesWritten < total) {
210+
await this.cdc.write(new Uint8Array(0), null, true);
211+
}
212+
213+
if (i % 10 === 0) {
214+
onProgress(bytesWritten/total);
215+
}
216+
i += 1;
217+
}
218+
}
219+
220+
const wd = await this.waitForData();
221+
const response = this.xml.getReponse(wd);
222+
if ("value" in response) {
223+
if (response["value"] !== "ACK") {
224+
return false;
225+
}
226+
} else {
227+
return false;
228+
}
229+
}
230+
231+
onProgress(1.0);
232+
return true;
233+
}
234+
235+
async cmdErase(physicalPartitionNumber, startSector, numPartitionSectors) {
236+
const data = `<?xml version="1.0" ?><data>\n` +
237+
`<program SECTOR_SIZE_IN_BYTES="${this.cfg.SECTOR_SIZE_IN_BYTES}"` +
238+
` num_partition_sectors="${numPartitionSectors}"` +
239+
` physical_partition_number="${physicalPartitionNumber}"` +
240+
` start_sector="${startSector}" />\n</data>`;
241+
let rsp = await this.xmlSend(data)
242+
let bytesToWrite = this.cfg.SECTOR_SIZE_IN_BYTES * numPartitionSectors;
243+
let empty = new Uint8Array(this.cfg.MaxPayloadSizeToTargetInBytes).fill(0);
244+
245+
if (rsp.resp) {
246+
while (bytesToWrite > 0) {
247+
let wlen = Math.min(bytesToWrite, this.cfg.MaxPayloadSizeToTargetInBytes);
248+
await this.cdc.write(empty.slice(0, wlen));
249+
bytesToWrite -= wlen;
250+
await this.cdc.write(new Uint8Array(0));
251+
}
252+
253+
const res = await this.waitForData();
254+
const response = this.xml.getReponse(res);
255+
if ("value" in response) {
256+
if (response["value"] !== "ACK") {
257+
throw "Failed to erase: NAK";
258+
}
259+
} else {
260+
throw "Failed to erase no return value";
261+
}
262+
}
263+
return true;
264+
}
265+
266+
async cmdSetBootLunId(lun) {
267+
const data = `<?xml version="1.0" ?><data>\n<setbootablestoragedrive value="${lun}" /></data>`
268+
const val = await this.xmlSend(data);
269+
if (val.resp) {
270+
console.log(`Successfully set bootID to lun ${lun}`);
271+
return true;
272+
} else {
273+
throw `Firehose - Failed to set boot lun ${lun}`;
274+
}
275+
}
276+
277+
async cmdReset() {
278+
let data = '<?xml version="1.0" ?><data><power value="reset"/></data>';
279+
let val = await this.xmlSend(data);
280+
if (val.resp) {
281+
console.log("Reset succeeded");
282+
// Drain log buffer
283+
try {
284+
await this.waitForData();
285+
} catch {
286+
// Ignore any errors
287+
}
288+
return true;
289+
} else {
290+
throw "Firehose - Reset failed";
291+
}
292+
}
293+
}

0 commit comments

Comments
 (0)